From ee528097103084135b775a1716d5316a519e8a41 Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Tue, 11 Aug 2026 19:59:25 -0400 Subject: [PATCH 1/2] feat(api): add NetworkGateway and NetworkRule types Adds the CRD types for the edge NAT+LB gateway engine's control plane: NetworkGateway (one per dedicated gateway-role node, spec.targetRef names the node) and NetworkRule (namespaced, tenant-writable, carries an ingress VIP/protocol/port and backend targets for a vpc/vpcAttachment). NetworkGatewayStatus carries sRv6Address rather than a tunnel endpoint address: the consuming galactic-router design pivoted away from an earlier Geneve-overlay approach to pushing SRv6 uSID headers directly from an XDP program, so there is no tunnel endpoint to publish. This address is the gateway node's own SRv6-reachable address, used as the Full-NAT SNAT source for every ingress flow the node translates, and advertised into BGP the same way any workload prefix is (a BGPAdvertisement naming it at the reserved Argument 0, which PR #740 forbids ever registering into a tenant VRF, so it can never collide with real tenant state). NetworkRuleStatus.primaryNode implements the active-active BGP model: assigned once at creation (hash(vpcRef) % gateway node count) and never recomputed, so a later reconcile can't silently flip which node is preferred for a live VIP. Regenerated deepcopy methods and CRD manifests via controller-gen. --- api/v1alpha1/gateway_types.go | 86 +++++++ api/v1alpha1/gateway_types_test.go | 128 ++++++++++ api/v1alpha1/rule_types.go | 161 ++++++++++++ api/v1alpha1/rule_types_test.go | 160 ++++++++++++ api/v1alpha1/zz_generated.deepcopy.go | 218 ++++++++++++++++ ...network.datumapis.com_networkgateways.yaml | 182 ++++++++++++++ .../network.datumapis.com_networkrules.yaml | 238 ++++++++++++++++++ ...apis.com_v1alpha1_networkgateway_node.yaml | 9 + ...atumapis.com_v1alpha1_networkrule_tcp.yaml | 17 ++ docs/api/bgp.md | 169 +++++++++++++ 10 files changed, 1368 insertions(+) create mode 100644 api/v1alpha1/gateway_types.go create mode 100644 api/v1alpha1/gateway_types_test.go create mode 100644 api/v1alpha1/rule_types.go create mode 100644 api/v1alpha1/rule_types_test.go create mode 100644 config/crd/network.datumapis.com_networkgateways.yaml create mode 100644 config/crd/network.datumapis.com_networkrules.yaml create mode 100644 config/samples/network.datumapis.com_v1alpha1_networkgateway_node.yaml create mode 100644 config/samples/network.datumapis.com_v1alpha1_networkrule_tcp.yaml diff --git a/api/v1alpha1/gateway_types.go b/api/v1alpha1/gateway_types.go new file mode 100644 index 0000000..ca95e21 --- /dev/null +++ b/api/v1alpha1/gateway_types.go @@ -0,0 +1,86 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NetworkGateway defines an XDP ingress NAT+LB gateway engine instance bound +// to a single dedicated gateway-role node. Exactly one NetworkGateway exists +// per gateway node (spec.targetRef.name is the Kubernetes node name), +// mirroring the BGPRouter node-scoped root object pattern. NetworkRule +// resources are assigned to a NetworkGateway via status.primaryNode. +// +// There is no tunnel overlay in this design (an earlier Geneve-based +// approach was superseded before this type shipped): the gateway's XDP +// program does Full-NAT (DNAT the VIP to a backend Pod's address, SNAT the +// client's source to status.sRv6Address) and pushes an SRv6 uSID outer +// header addressed to the backend's worker node directly, so return traffic +// (addressed to status.sRv6Address) arrives back at this same gateway node +// over the ordinary SRv6 fabric — no compute-node encap agent, no tunnel +// endpoint to publish. status.sRv6Address is advertised into BGP the same +// way any workload prefix is (a BGPAdvertisement naming it, /128, Argument +// 0 — the value PR #740 reserves and forbids registering into any tenant +// VRF, guaranteeing it never collides with a real tenant's Argument), so +// every other node learns a real kernel SEG6 route to it for free through +// the existing EVPN pipeline. +// +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced,shortName=netgw +// +kubebuilder:printcolumn:name="TARGET",type="string",JSONPath=".spec.targetRef.name" +// +kubebuilder:printcolumn:name="SRV6-ADDRESS",type="string",JSONPath=".status.sRv6Address" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +type NetworkGateway struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NetworkGatewaySpec `json:"spec,omitempty"` + Status NetworkGatewayStatus `json:"status,omitempty"` +} + +// NetworkGatewaySpec defines the desired state of a NetworkGateway. +type NetworkGatewaySpec struct { + // TargetRef identifies the Node this gateway engine executes on. + // +kubebuilder:validation:Required + TargetRef TargetRef `json:"targetRef"` +} + +// NetworkGatewayStatus defines the observed state of a NetworkGateway. +type NetworkGatewayStatus struct { + // ObservedGeneration is the .metadata.generation this status was computed from. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // SRv6Address is this gateway node's own SRv6-reachable IPv6 address, + // used as the Full-NAT SNAT source for every ingress flow this node + // translates. Backend Pods' replies are naturally routed back to it + // over the ordinary SRv6 fabric (the same mechanism that routes any + // other node's traffic), where this node's XDP program decapsulates + // and un-NATs them using its own conn_table — there is no separate + // tunnel endpoint or overlay device to publish. Populated by the + // engine once it has computed the address (a uFMT 48+16 uSID over this + // node's own BGPRouter locator/node-ID, at the reserved Argument 0) + // and advertised it into BGP. + // +optional + // +kubebuilder:validation:XValidation:rule="self == '' || isIP(self)",message="sRv6Address must be a valid IPv6 address" + SRv6Address string `json:"sRv6Address,omitempty"` + + // Conditions contains the standard conditions for this resource. + // + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// NetworkGatewayList is a list of NetworkGateway resources. +// +kubebuilder:object:root=true +type NetworkGatewayList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NetworkGateway `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NetworkGateway{}, &NetworkGatewayList{}) +} diff --git a/api/v1alpha1/gateway_types_test.go b/api/v1alpha1/gateway_types_test.go new file mode 100644 index 0000000..6c7624f --- /dev/null +++ b/api/v1alpha1/gateway_types_test.go @@ -0,0 +1,128 @@ +package v1alpha1 + +import ( + "encoding/json" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func newTestGateway() *NetworkGateway { + return &NetworkGateway{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "network.datumapis.com/v1alpha1", + Kind: "NetworkGateway", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-gateway"}, + Spec: NetworkGatewaySpec{ + TargetRef: TargetRef{Kind: "Node", Name: "gw-node-a"}, + }, + Status: NetworkGatewayStatus{ + SRv6Address: "2001:db8:1::1", + }, + } +} + +// TestNetworkGatewayDeepCopy verifies that DeepCopy produces an independent +// copy: mutations to the copy must not affect the original. +func TestNetworkGatewayDeepCopy(t *testing.T) { + orig := newTestGateway() + dup := orig.DeepCopy() + + dup.Spec.TargetRef.Name = "gw-node-b" + dup.Status.SRv6Address = "2001:db8:1::2" + dup.Status.Conditions = append(dup.Status.Conditions, metav1.Condition{Type: ConditionTypeReady}) + + if orig.Spec.TargetRef.Name != "gw-node-a" { + t.Errorf("TargetRef.Name mutated: got %q", orig.Spec.TargetRef.Name) + } + if orig.Status.SRv6Address != "2001:db8:1::1" { + t.Errorf("SRv6Address mutated: got %q", orig.Status.SRv6Address) + } + if len(orig.Status.Conditions) != 0 { + t.Errorf("Conditions mutated: got %v", orig.Status.Conditions) + } +} + +// TestNetworkGatewayDeepCopyNil verifies DeepCopy on a nil pointer returns nil. +func TestNetworkGatewayDeepCopyNil(t *testing.T) { + var g *NetworkGateway + if g.DeepCopy() != nil { + t.Error("DeepCopy on nil pointer should return nil") + } +} + +// TestNetworkGatewayJSONRoundTrip verifies that the struct serialises and +// deserialises through JSON without data loss. +func TestNetworkGatewayJSONRoundTrip(t *testing.T) { + orig := newTestGateway() + orig.Status.Conditions = []metav1.Condition{ + {Type: ConditionTypeReady, Status: metav1.ConditionTrue, Reason: "Ready", Message: "ok"}, + } + + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var got NetworkGateway + if err := json.Unmarshal(data, &got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + if got.Spec.TargetRef != orig.Spec.TargetRef { + t.Errorf("TargetRef: got %+v, want %+v", got.Spec.TargetRef, orig.Spec.TargetRef) + } + if got.Status.SRv6Address != orig.Status.SRv6Address { + t.Errorf("SRv6Address: got %q, want %q", got.Status.SRv6Address, orig.Status.SRv6Address) + } + if len(got.Status.Conditions) != 1 { + t.Fatalf("Conditions len: got %d, want 1", len(got.Status.Conditions)) + } +} + +// TestNetworkGatewayListDeepCopy verifies that NetworkGatewayList.DeepCopy +// produces independent copies of each item. +func TestNetworkGatewayListDeepCopy(t *testing.T) { + list := &NetworkGatewayList{ + Items: []NetworkGateway{*newTestGateway()}, + } + copied := list.DeepCopy() + copied.Items[0].Spec.TargetRef.Name = "other-node" + + if list.Items[0].Spec.TargetRef.Name != "gw-node-a" { + t.Errorf("original list item mutated via copy") + } +} + +// TestNetworkGatewayFieldNames verifies the JSON keys for spec/status fields +// match the CRD schema. +func TestNetworkGatewayFieldNames(t *testing.T) { + orig := newTestGateway() + + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + spec, ok := m["spec"].(map[string]any) + if !ok { + t.Fatalf("spec not found or wrong type: %v", m["spec"]) + } + if _, ok := spec["targetRef"]; !ok { + t.Errorf("expected spec.targetRef field, got %v", spec) + } + + status, ok := m["status"].(map[string]any) + if !ok { + t.Fatalf("status not found or wrong type: %v", m["status"]) + } + if v, ok := status["sRv6Address"]; !ok || v != "2001:db8:1::1" { + t.Errorf("expected status.sRv6Address=%q, got %v", "2001:db8:1::1", status["sRv6Address"]) + } +} diff --git a/api/v1alpha1/rule_types.go b/api/v1alpha1/rule_types.go new file mode 100644 index 0000000..8c048bd --- /dev/null +++ b/api/v1alpha1/rule_types.go @@ -0,0 +1,161 @@ +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// NetworkRuleProtocol is the transport protocol matched by a NetworkRule's +// ingress VIP. +// +// +kubebuilder:validation:Enum=tcp;udp +type NetworkRuleProtocol string + +const ( + // NetworkRuleProtocolTCP matches TCP traffic. + NetworkRuleProtocolTCP NetworkRuleProtocol = "tcp" + + // NetworkRuleProtocolUDP matches UDP traffic. + NetworkRuleProtocolUDP NetworkRuleProtocol = "udp" +) + +// Accepted reasons — used as Accepted.Reason on NetworkRule, set by the +// admission webhook that verifies the requester is authorized for the +// vpcRef/vpcAttachmentRef named in the rule. +const ( + // AcceptedReasonOwnershipVerified indicates admission verified the + // requester is authorized for the target VPC/VPCAttachment. + AcceptedReasonOwnershipVerified string = "OwnershipVerified" + + // AcceptedReasonOwnershipDenied indicates admission rejected the rule + // because the requester is not authorized for the target + // VPC/VPCAttachment named in vpcRef/vpcAttachmentRef. + AcceptedReasonOwnershipDenied string = "OwnershipDenied" +) + +// NetworkRuleBackend is a single backend endpoint that ingress traffic +// matching a NetworkRule's VIP addresses is load-balanced to. +type NetworkRuleBackend struct { + // Address is the backend's IPv4 or IPv6 address. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MaxLength=45 + // +kubebuilder:validation:XValidation:rule="isIP(self)",message="address must be a valid IPv4 or IPv6 address" + Address string `json:"address"` + + // Port is the backend's destination port. + // +kubebuilder:validation:Required + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` +} + +// NetworkRule defines ingress load-balancing and NAT for a single tenant +// VPC/VPCAttachment, served by the shared hyperconverged gateway engine. +// It is namespaced (deployed to galactic-system) and tenant-writable; the +// vpcRef/vpcAttachmentRef fields are opaque string identifiers because the +// VPC API is owned by a separate companion operator, not this repo. An +// admission webhook (implemented by the consuming controller) must verify +// the requester is authorized for vpcRef/vpcAttachmentRef before a rule is +// accepted — see the Accepted condition. +// +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Namespaced,shortName=netrule +// +kubebuilder:printcolumn:name="VPC",type="string",JSONPath=".spec.vpcRef" +// +kubebuilder:printcolumn:name="PROTOCOL",type="string",JSONPath=".spec.protocol" +// +kubebuilder:printcolumn:name="PORT",type="integer",JSONPath=".spec.port" +// +kubebuilder:printcolumn:name="PRIMARY-NODE",type="string",JSONPath=".status.primaryNode" +// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp" +type NetworkRule struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec NetworkRuleSpec `json:"spec,omitempty"` + Status NetworkRuleStatus `json:"status,omitempty"` +} + +// NetworkRuleSpec defines the desired ingress load-balancing state for a +// tenant VPC/VPCAttachment. +type NetworkRuleSpec struct { + // VPCRef is the opaque identifier of the target VPC this rule applies + // to. This repo does not own the VPC API and does not validate the + // identifier beyond non-emptiness; the admission webhook of the + // consuming controller is responsible for verifying the requester is + // authorized for this VPC before the rule is accepted. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + VPCRef string `json:"vpcRef"` + + // VPCAttachmentRef is the opaque identifier of the target + // VPCAttachment this rule applies to. Like VPCRef, this is an opaque + // string reference validated by the admission webhook, not by this API. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinLength=1 + VPCAttachmentRef string `json:"vpcAttachmentRef"` + + // VIPAddresses is the list of ingress VIP addresses (IPv4 and/or IPv6) + // this rule provisions on the assigned gateway node(s). + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=8 + // +kubebuilder:validation:items:MaxLength=45 + // +kubebuilder:validation:XValidation:rule="self.all(v, isIP(v))",message="vipAddresses must all be valid IPv4 or IPv6 addresses" + // +listType=set + VIPAddresses []string `json:"vipAddresses"` + + // Protocol is the transport protocol matched by VIPAddresses/Port. + // +kubebuilder:validation:Required + Protocol NetworkRuleProtocol `json:"protocol"` + + // Port is the ingress port on VIPAddresses that this rule load-balances. + // +kubebuilder:validation:Required + // +kubebuilder:validation:Minimum=1 + // +kubebuilder:validation:Maximum=65535 + Port int32 `json:"port"` + + // Backends is the list of backend address:port targets that ingress + // traffic matching VIPAddresses/Protocol/Port is load-balanced to. + // +kubebuilder:validation:Required + // +kubebuilder:validation:MinItems=1 + // +kubebuilder:validation:MaxItems=64 + Backends []NetworkRuleBackend `json:"backends"` +} + +// NetworkRuleStatus defines the observed state of a NetworkRule. +type NetworkRuleStatus struct { + // ObservedGeneration is the .metadata.generation this status was computed from. + // +optional + ObservedGeneration int64 `json:"observedGeneration,omitempty"` + + // PrimaryNode is the name of the NetworkGateway-backed gateway node + // assigned to advertise this rule's VIPAddresses at the preferred BGP + // local-preference, per the active-active model: primary_node = + // hash(vpcRef) % . The controller consuming this + // CRD sets this field exactly once, at creation. + // + // This value must never be silently recomputed by a reconciler once + // set. Recomputing it on a later reconcile can flip which node is + // primary for a live VIP and cause an avoidable traffic flap; a + // reconciler that observes a stale or removed node here must surface + // that via a condition instead of overwriting the value. + // +optional + PrimaryNode string `json:"primaryNode,omitempty"` + + // Conditions contains the standard conditions for this resource. + // + // +listType=map + // +listMapKey=type + // +optional + Conditions []metav1.Condition `json:"conditions,omitempty"` +} + +// NetworkRuleList is a list of NetworkRule resources. +// +kubebuilder:object:root=true +type NetworkRuleList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []NetworkRule `json:"items"` +} + +func init() { + SchemeBuilder.Register(&NetworkRule{}, &NetworkRuleList{}) +} diff --git a/api/v1alpha1/rule_types_test.go b/api/v1alpha1/rule_types_test.go new file mode 100644 index 0000000..a48d2cc --- /dev/null +++ b/api/v1alpha1/rule_types_test.go @@ -0,0 +1,160 @@ +package v1alpha1 + +import ( + "encoding/json" + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func newTestRule() *NetworkRule { + return &NetworkRule{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "network.datumapis.com/v1alpha1", + Kind: "NetworkRule", + }, + ObjectMeta: metav1.ObjectMeta{Name: "test-rule", Namespace: "galactic-system"}, + Spec: NetworkRuleSpec{ + VPCRef: "vpc-a", + VPCAttachmentRef: "vpcattachment-a", + VIPAddresses: []string{"2001:db8:1::10"}, + Protocol: NetworkRuleProtocolTCP, + Port: 443, + Backends: []NetworkRuleBackend{ + {Address: "fd00:10:1::1", Port: 8443}, + }, + }, + Status: NetworkRuleStatus{ + PrimaryNode: "gw-node-a", + }, + } +} + +// TestNetworkRuleDeepCopy verifies that DeepCopy produces an independent +// copy: mutations to slices in the copy must not affect the original. +func TestNetworkRuleDeepCopy(t *testing.T) { + orig := newTestRule() + dup := orig.DeepCopy() + + dup.Spec.VIPAddresses[0] = "2001:db8:1::20" + dup.Spec.Backends[0].Address = "fd00:10:1::2" + dup.Status.PrimaryNode = "gw-node-b" + + if orig.Spec.VIPAddresses[0] != "2001:db8:1::10" { + t.Errorf("VIPAddresses[0] mutated: got %q", orig.Spec.VIPAddresses[0]) + } + if orig.Spec.Backends[0].Address != "fd00:10:1::1" { + t.Errorf("Backends[0].Address mutated: got %q", orig.Spec.Backends[0].Address) + } + if orig.Status.PrimaryNode != "gw-node-a" { + t.Errorf("PrimaryNode mutated: got %q", orig.Status.PrimaryNode) + } +} + +// TestNetworkRuleDeepCopyNil verifies DeepCopy on a nil pointer returns nil. +func TestNetworkRuleDeepCopyNil(t *testing.T) { + var r *NetworkRule + if r.DeepCopy() != nil { + t.Error("DeepCopy on nil pointer should return nil") + } +} + +// TestNetworkRuleJSONRoundTrip verifies that the struct serialises and +// deserialises through JSON without data loss. +func TestNetworkRuleJSONRoundTrip(t *testing.T) { + orig := newTestRule() + orig.Spec.Backends = append(orig.Spec.Backends, NetworkRuleBackend{Address: "fd00:10:1::3", Port: 8444}) + orig.Status.Conditions = []metav1.Condition{ + {Type: ConditionTypeAccepted, Status: metav1.ConditionTrue, Reason: AcceptedReasonOwnershipVerified}, + } + + data, err := json.Marshal(orig) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var got NetworkRule + if err := json.Unmarshal(data, &got); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + if got.Spec.VPCRef != orig.Spec.VPCRef { + t.Errorf("VPCRef: got %q, want %q", got.Spec.VPCRef, orig.Spec.VPCRef) + } + if got.Spec.VPCAttachmentRef != orig.Spec.VPCAttachmentRef { + t.Errorf("VPCAttachmentRef: got %q, want %q", got.Spec.VPCAttachmentRef, orig.Spec.VPCAttachmentRef) + } + if len(got.Spec.Backends) != 2 { + t.Errorf("Backends len: got %d, want 2", len(got.Spec.Backends)) + } + if got.Status.PrimaryNode != orig.Status.PrimaryNode { + t.Errorf("PrimaryNode: got %q, want %q", got.Status.PrimaryNode, orig.Status.PrimaryNode) + } + if len(got.Status.Conditions) != 1 || got.Status.Conditions[0].Reason != AcceptedReasonOwnershipVerified { + t.Errorf("Conditions: got %v", got.Status.Conditions) + } +} + +// TestNetworkRuleListDeepCopy verifies that NetworkRuleList.DeepCopy +// produces independent copies of each item. +func TestNetworkRuleListDeepCopy(t *testing.T) { + list := &NetworkRuleList{ + Items: []NetworkRule{*newTestRule()}, + } + copied := list.DeepCopy() + copied.Items[0].Status.PrimaryNode = "gw-node-b" + + if list.Items[0].Status.PrimaryNode != "gw-node-a" { + t.Errorf("original list item mutated via copy") + } +} + +// TestNetworkRulePrimaryNodeFieldName verifies the JSON key is "primaryNode". +func TestNetworkRulePrimaryNodeFieldName(t *testing.T) { + orig := newTestRule() + + data, err := json.Marshal(orig.Status) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + + if v, ok := m["primaryNode"]; !ok || v != "gw-node-a" { + t.Errorf("unexpected primaryNode value: %v", m["primaryNode"]) + } +} + +// TestNetworkRuleBackendFieldNames verifies the JSON keys for +// NetworkRuleBackend match the CRD schema ("address", "port"). +func TestNetworkRuleBackendFieldNames(t *testing.T) { + b := NetworkRuleBackend{Address: "fd00:10:1::1", Port: 8443} + data, err := json.Marshal(b) + if err != nil { + t.Fatalf("Marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Unmarshal: %v", err) + } + if v, ok := m["address"]; !ok || v != "fd00:10:1::1" { + t.Errorf("expected JSON key \"address\"=%q, got %v", "fd00:10:1::1", m) + } + if v, ok := m["port"]; !ok || v != float64(8443) { + t.Errorf("expected JSON key \"port\"=8443, got %v", m) + } +} + +// TestNetworkRuleProtocolValues is a regression test pinning the accepted +// NetworkRuleProtocol enum values. +func TestNetworkRuleProtocolValues(t *testing.T) { + if NetworkRuleProtocolTCP != "tcp" { + t.Errorf("NetworkRuleProtocolTCP: got %q, want %q", NetworkRuleProtocolTCP, "tcp") + } + if NetworkRuleProtocolUDP != "udp" { + t.Errorf("NetworkRuleProtocolUDP: got %q, want %q", NetworkRuleProtocolUDP, "udp") + } +} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 6211ab2..525e865 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1306,6 +1306,224 @@ func (in *LocalSecretRef) DeepCopy() *LocalSecretRef { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkGateway) DeepCopyInto(out *NetworkGateway) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkGateway. +func (in *NetworkGateway) DeepCopy() *NetworkGateway { + if in == nil { + return nil + } + out := new(NetworkGateway) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkGateway) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkGatewayList) DeepCopyInto(out *NetworkGatewayList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NetworkGateway, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkGatewayList. +func (in *NetworkGatewayList) DeepCopy() *NetworkGatewayList { + if in == nil { + return nil + } + out := new(NetworkGatewayList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkGatewayList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkGatewaySpec) DeepCopyInto(out *NetworkGatewaySpec) { + *out = *in + out.TargetRef = in.TargetRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkGatewaySpec. +func (in *NetworkGatewaySpec) DeepCopy() *NetworkGatewaySpec { + if in == nil { + return nil + } + out := new(NetworkGatewaySpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkGatewayStatus) DeepCopyInto(out *NetworkGatewayStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkGatewayStatus. +func (in *NetworkGatewayStatus) DeepCopy() *NetworkGatewayStatus { + if in == nil { + return nil + } + out := new(NetworkGatewayStatus) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkRule) DeepCopyInto(out *NetworkRule) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkRule. +func (in *NetworkRule) DeepCopy() *NetworkRule { + if in == nil { + return nil + } + out := new(NetworkRule) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkRule) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkRuleBackend) DeepCopyInto(out *NetworkRuleBackend) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkRuleBackend. +func (in *NetworkRuleBackend) DeepCopy() *NetworkRuleBackend { + if in == nil { + return nil + } + out := new(NetworkRuleBackend) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkRuleList) DeepCopyInto(out *NetworkRuleList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]NetworkRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkRuleList. +func (in *NetworkRuleList) DeepCopy() *NetworkRuleList { + if in == nil { + return nil + } + out := new(NetworkRuleList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *NetworkRuleList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkRuleSpec) DeepCopyInto(out *NetworkRuleSpec) { + *out = *in + if in.VIPAddresses != nil { + in, out := &in.VIPAddresses, &out.VIPAddresses + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Backends != nil { + in, out := &in.Backends, &out.Backends + *out = make([]NetworkRuleBackend, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkRuleSpec. +func (in *NetworkRuleSpec) DeepCopy() *NetworkRuleSpec { + if in == nil { + return nil + } + out := new(NetworkRuleSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkRuleStatus) DeepCopyInto(out *NetworkRuleStatus) { + *out = *in + if in.Conditions != nil { + in, out := &in.Conditions, &out.Conditions + *out = make([]v1.Condition, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkRuleStatus. +func (in *NetworkRuleStatus) DeepCopy() *NetworkRuleStatus { + if in == nil { + return nil + } + out := new(NetworkRuleStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *NextHopSet) DeepCopyInto(out *NextHopSet) { *out = *in diff --git a/config/crd/network.datumapis.com_networkgateways.yaml b/config/crd/network.datumapis.com_networkgateways.yaml new file mode 100644 index 0000000..746702b --- /dev/null +++ b/config/crd/network.datumapis.com_networkgateways.yaml @@ -0,0 +1,182 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networkgateways.network.datumapis.com +spec: + group: network.datumapis.com + names: + kind: NetworkGateway + listKind: NetworkGatewayList + plural: networkgateways + shortNames: + - netgw + singular: networkgateway + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.targetRef.name + name: TARGET + type: string + - jsonPath: .status.sRv6Address + name: SRV6-ADDRESS + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + NetworkGateway defines an XDP ingress NAT+LB gateway engine instance bound + to a single dedicated gateway-role node. Exactly one NetworkGateway exists + per gateway node (spec.targetRef.name is the Kubernetes node name), + mirroring the BGPRouter node-scoped root object pattern. NetworkRule + resources are assigned to a NetworkGateway via status.primaryNode. + + There is no tunnel overlay in this design (an earlier Geneve-based + approach was superseded before this type shipped): the gateway's XDP + program does Full-NAT (DNAT the VIP to a backend Pod's address, SNAT the + client's source to status.sRv6Address) and pushes an SRv6 uSID outer + header addressed to the backend's worker node directly, so return traffic + (addressed to status.sRv6Address) arrives back at this same gateway node + over the ordinary SRv6 fabric — no compute-node encap agent, no tunnel + endpoint to publish. status.sRv6Address is advertised into BGP the same + way any workload prefix is (a BGPAdvertisement naming it, /128, Argument + 0 — the value PR #740 reserves and forbids registering into any tenant + VRF, guaranteeing it never collides with a real tenant's Argument), so + every other node learns a real kernel SEG6 route to it for free through + the existing EVPN pipeline. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: NetworkGatewaySpec defines the desired state of a NetworkGateway. + properties: + targetRef: + description: TargetRef identifies the Node this gateway engine executes + on. + properties: + kind: + description: Kind is the target resource kind (e.g. Node). + minLength: 1 + type: string + name: + description: Name is the name of the target resource. + minLength: 1 + type: string + required: + - kind + - name + type: object + required: + - targetRef + type: object + status: + description: NetworkGatewayStatus defines the observed state of a NetworkGateway. + properties: + conditions: + description: Conditions contains the standard conditions for this + resource. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + description: ObservedGeneration is the .metadata.generation this status + was computed from. + format: int64 + type: integer + sRv6Address: + description: |- + SRv6Address is this gateway node's own SRv6-reachable IPv6 address, + used as the Full-NAT SNAT source for every ingress flow this node + translates. Backend Pods' replies are naturally routed back to it + over the ordinary SRv6 fabric (the same mechanism that routes any + other node's traffic), where this node's XDP program decapsulates + and un-NATs them using its own conn_table — there is no separate + tunnel endpoint or overlay device to publish. Populated by the + engine once it has computed the address (a uFMT 48+16 uSID over this + node's own BGPRouter locator/node-ID, at the reserved Argument 0) + and advertised it into BGP. + type: string + x-kubernetes-validations: + - message: sRv6Address must be a valid IPv6 address + rule: self == '' || isIP(self) + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/network.datumapis.com_networkrules.yaml b/config/crd/network.datumapis.com_networkrules.yaml new file mode 100644 index 0000000..e302fee --- /dev/null +++ b/config/crd/network.datumapis.com_networkrules.yaml @@ -0,0 +1,238 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.18.0 + name: networkrules.network.datumapis.com +spec: + group: network.datumapis.com + names: + kind: NetworkRule + listKind: NetworkRuleList + plural: networkrules + shortNames: + - netrule + singular: networkrule + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .spec.vpcRef + name: VPC + type: string + - jsonPath: .spec.protocol + name: PROTOCOL + type: string + - jsonPath: .spec.port + name: PORT + type: integer + - jsonPath: .status.primaryNode + name: PRIMARY-NODE + type: string + - jsonPath: .metadata.creationTimestamp + name: AGE + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: |- + NetworkRule defines ingress load-balancing and NAT for a single tenant + VPC/VPCAttachment, served by the shared hyperconverged gateway engine. + It is namespaced (deployed to galactic-system) and tenant-writable; the + vpcRef/vpcAttachmentRef fields are opaque string identifiers because the + VPC API is owned by a separate companion operator, not this repo. An + admission webhook (implemented by the consuming controller) must verify + the requester is authorized for vpcRef/vpcAttachmentRef before a rule is + accepted — see the Accepted condition. + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: |- + NetworkRuleSpec defines the desired ingress load-balancing state for a + tenant VPC/VPCAttachment. + properties: + backends: + description: |- + Backends is the list of backend address:port targets that ingress + traffic matching VIPAddresses/Protocol/Port is load-balanced to. + items: + description: |- + NetworkRuleBackend is a single backend endpoint that ingress traffic + matching a NetworkRule's VIP addresses is load-balanced to. + properties: + address: + description: Address is the backend's IPv4 or IPv6 address. + maxLength: 45 + type: string + x-kubernetes-validations: + - message: address must be a valid IPv4 or IPv6 address + rule: isIP(self) + port: + description: Port is the backend's destination port. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + required: + - address + - port + type: object + maxItems: 64 + minItems: 1 + type: array + port: + description: Port is the ingress port on VIPAddresses that this rule + load-balances. + format: int32 + maximum: 65535 + minimum: 1 + type: integer + protocol: + description: Protocol is the transport protocol matched by VIPAddresses/Port. + enum: + - tcp + - udp + type: string + vipAddresses: + description: |- + VIPAddresses is the list of ingress VIP addresses (IPv4 and/or IPv6) + this rule provisions on the assigned gateway node(s). + items: + maxLength: 45 + type: string + maxItems: 8 + minItems: 1 + type: array + x-kubernetes-list-type: set + x-kubernetes-validations: + - message: vipAddresses must all be valid IPv4 or IPv6 addresses + rule: self.all(v, isIP(v)) + vpcAttachmentRef: + description: |- + VPCAttachmentRef is the opaque identifier of the target + VPCAttachment this rule applies to. Like VPCRef, this is an opaque + string reference validated by the admission webhook, not by this API. + minLength: 1 + type: string + vpcRef: + description: |- + VPCRef is the opaque identifier of the target VPC this rule applies + to. This repo does not own the VPC API and does not validate the + identifier beyond non-emptiness; the admission webhook of the + consuming controller is responsible for verifying the requester is + authorized for this VPC before the rule is accepted. + minLength: 1 + type: string + required: + - backends + - port + - protocol + - vipAddresses + - vpcAttachmentRef + - vpcRef + type: object + status: + description: NetworkRuleStatus defines the observed state of a NetworkRule. + properties: + conditions: + description: Conditions contains the standard conditions for this + resource. + items: + description: Condition contains details for one aspect of the current + state of this API Resource. + properties: + lastTransitionTime: + description: |- + lastTransitionTime is the last time the condition transitioned from one status to another. + This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable. + format: date-time + type: string + message: + description: |- + message is a human readable message indicating details about the transition. + This may be an empty string. + maxLength: 32768 + type: string + observedGeneration: + description: |- + observedGeneration represents the .metadata.generation that the condition was set based upon. + For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date + with respect to the current state of the instance. + format: int64 + minimum: 0 + type: integer + reason: + description: |- + reason contains a programmatic identifier indicating the reason for the condition's last transition. + Producers of specific condition types may define expected values and meanings for this field, + and whether the values are considered a guaranteed API. + The value should be a CamelCase string. + This field may not be empty. + maxLength: 1024 + minLength: 1 + pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$ + type: string + status: + description: status of the condition, one of True, False, Unknown. + enum: + - "True" + - "False" + - Unknown + type: string + type: + description: type of condition in CamelCase or in foo.example.com/CamelCase. + maxLength: 316 + pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$ + type: string + required: + - lastTransitionTime + - message + - reason + - status + - type + type: object + type: array + x-kubernetes-list-map-keys: + - type + x-kubernetes-list-type: map + observedGeneration: + description: ObservedGeneration is the .metadata.generation this status + was computed from. + format: int64 + type: integer + primaryNode: + description: |- + PrimaryNode is the name of the NetworkGateway-backed gateway node + assigned to advertise this rule's VIPAddresses at the preferred BGP + local-preference, per the active-active model: primary_node = + hash(vpcRef) % . The controller consuming this + CRD sets this field exactly once, at creation. + + This value must never be silently recomputed by a reconciler once + set. Recomputing it on a later reconcile can flip which node is + primary for a live VIP and cause an avoidable traffic flap; a + reconciler that observes a stale or removed node here must surface + that via a condition instead of overwriting the value. + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/samples/network.datumapis.com_v1alpha1_networkgateway_node.yaml b/config/samples/network.datumapis.com_v1alpha1_networkgateway_node.yaml new file mode 100644 index 0000000..fecad53 --- /dev/null +++ b/config/samples/network.datumapis.com_v1alpha1_networkgateway_node.yaml @@ -0,0 +1,9 @@ +apiVersion: network.datumapis.com/v1alpha1 +kind: NetworkGateway +metadata: + name: iad-gw-a + namespace: galactic-system +spec: + targetRef: + kind: Node + name: iad-gw-a diff --git a/config/samples/network.datumapis.com_v1alpha1_networkrule_tcp.yaml b/config/samples/network.datumapis.com_v1alpha1_networkrule_tcp.yaml new file mode 100644 index 0000000..a76824a --- /dev/null +++ b/config/samples/network.datumapis.com_v1alpha1_networkrule_tcp.yaml @@ -0,0 +1,17 @@ +apiVersion: network.datumapis.com/v1alpha1 +kind: NetworkRule +metadata: + name: vpc-a-https + namespace: galactic-system +spec: + vpcRef: vpc-a + vpcAttachmentRef: vpc-a-iad + vipAddresses: + - 2001:db8:1::10 + protocol: tcp + port: 443 + backends: + - address: fd00:10:1::1 + port: 8443 + - address: fd00:10:1::2 + port: 8443 diff --git a/docs/api/bgp.md b/docs/api/bgp.md index 11b33bb..a16c5b1 100644 --- a/docs/api/bgp.md +++ b/docs/api/bgp.md @@ -15,6 +15,8 @@ Package v1alpha1 contains API Schema definitions for the network.datumapis.com/v - [BGPPolicy](#bgppolicy) - [BGPRouter](#bgprouter) - [BGPVRFInstance](#bgpvrfinstance) +- [NetworkGateway](#networkgateway) +- [NetworkRule](#networkrule) @@ -969,6 +971,172 @@ _Appears in:_ | `shutdown` | MaxPrefixShutdownActionShutdown tears down the BGP session when the limit is exceeded.
| +#### NetworkGateway + + + +NetworkGateway defines a hyperconverged gateway engine instance bound to a +single gateway-role node. Exactly one NetworkGateway exists per gateway +node (spec.targetRef.name is the Kubernetes node name), mirroring the +BGPRouter node-scoped root object pattern. NetworkRule resources are +assigned to a NetworkGateway via status.primaryNode; compute-node Geneve +encap agents read the assigned NetworkGateway's status.tunnelEndpointAddress +to learn where to send that tenant's traffic. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `network.datumapis.com/v1alpha1` | | | +| `kind` _string_ | `NetworkGateway` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[NetworkGatewaySpec](#networkgatewayspec)_ | | | | +| `status` _[NetworkGatewayStatus](#networkgatewaystatus)_ | | | | + + +#### NetworkGatewaySpec + + + +NetworkGatewaySpec defines the desired state of a NetworkGateway. + + + +_Appears in:_ +- [NetworkGateway](#networkgateway) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `targetRef` _[TargetRef](#targetref)_ | TargetRef identifies the Node this gateway engine executes on. | | Required: \{\}
| + + +#### NetworkGatewayStatus + + + +NetworkGatewayStatus defines the observed state of a NetworkGateway. + + + +_Appears in:_ +- [NetworkGateway](#networkgateway) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | | +| `tunnelEndpointAddress` _string_ | TunnelEndpointAddress is the Geneve tunnel endpoint IPv4 or IPv6
address for this gateway node. Compute-node encap agents resolve a
tenant's assigned NetworkGateway (via NetworkRule.status.primaryNode)
and read this field to learn where to send that tenant's Geneve-encapsulated
traffic. Populated by the engine once its Geneve decap endpoint is programmed. | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains the standard conditions for this resource. | | | + + +#### NetworkRule + + + +NetworkRule defines ingress load-balancing and NAT for a single tenant +VPC/VPCAttachment, served by the shared hyperconverged gateway engine. +It is namespaced (deployed to galactic-system) and tenant-writable; the +vpcRef/vpcAttachmentRef fields are opaque string identifiers because the +VPC API is owned by a separate companion operator, not this repo. An +admission webhook (implemented by the consuming controller) must verify +the requester is authorized for vpcRef/vpcAttachmentRef before a rule is +accepted — see the Accepted condition. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `network.datumapis.com/v1alpha1` | | | +| `kind` _string_ | `NetworkRule` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[NetworkRuleSpec](#networkrulespec)_ | | | | +| `status` _[NetworkRuleStatus](#networkrulestatus)_ | | | | + + +#### NetworkRuleBackend + + + +NetworkRuleBackend is a single backend endpoint that ingress traffic +matching a NetworkRule's VIP addresses is load-balanced to. + + + +_Appears in:_ +- [NetworkRuleSpec](#networkrulespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `address` _string_ | Address is the backend's IPv4 or IPv6 address. | | MaxLength: 45
Required: \{\}
| +| `port` _integer_ | Port is the backend's destination port. | | Maximum: 65535
Minimum: 1
Required: \{\}
| + + +#### NetworkRuleProtocol + +_Underlying type:_ _string_ + +NetworkRuleProtocol is the transport protocol matched by a NetworkRule's +ingress VIP. + +_Validation:_ +- Enum: [tcp udp] + +_Appears in:_ +- [NetworkRuleSpec](#networkrulespec) + +| Field | Description | +| --- | --- | +| `tcp` | NetworkRuleProtocolTCP matches TCP traffic.
| +| `udp` | NetworkRuleProtocolUDP matches UDP traffic.
| + + +#### NetworkRuleSpec + + + +NetworkRuleSpec defines the desired ingress load-balancing state for a +tenant VPC/VPCAttachment. + + + +_Appears in:_ +- [NetworkRule](#networkrule) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `vpcRef` _string_ | VPCRef is the opaque identifier of the target VPC this rule applies
to. This repo does not own the VPC API and does not validate the
identifier beyond non-emptiness; the admission webhook of the
consuming controller is responsible for verifying the requester is
authorized for this VPC before the rule is accepted. | | MinLength: 1
Required: \{\}
| +| `vpcAttachmentRef` _string_ | VPCAttachmentRef is the opaque identifier of the target
VPCAttachment this rule applies to. Like VPCRef, this is an opaque
string reference validated by the admission webhook, not by this API. | | MinLength: 1
Required: \{\}
| +| `vipAddresses` _string array_ | VIPAddresses is the list of ingress VIP addresses (IPv4 and/or IPv6)
this rule provisions on the assigned gateway node(s). | | MaxItems: 8
MinItems: 1
Required: \{\}
items:MaxLength: 45
| +| `protocol` _[NetworkRuleProtocol](#networkruleprotocol)_ | Protocol is the transport protocol matched by VIPAddresses/Port. | | Enum: [tcp udp]
Required: \{\}
| +| `port` _integer_ | Port is the ingress port on VIPAddresses that this rule load-balances. | | Maximum: 65535
Minimum: 1
Required: \{\}
| +| `backends` _[NetworkRuleBackend](#networkrulebackend) array_ | Backends is the list of backend address:port targets that ingress
traffic matching VIPAddresses/Protocol/Port is load-balanced to. | | MaxItems: 64
MinItems: 1
Required: \{\}
| + + +#### NetworkRuleStatus + + + +NetworkRuleStatus defines the observed state of a NetworkRule. + + + +_Appears in:_ +- [NetworkRule](#networkrule) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | | +| `primaryNode` _string_ | PrimaryNode is the name of the NetworkGateway-backed gateway node
assigned to advertise this rule's VIPAddresses at the preferred BGP
local-preference, per the active-active model: primary_node =
hash(vpcRef) % . The controller consuming this
CRD sets this field exactly once, at creation.
This value must never be silently recomputed by a reconciler once
set. Recomputing it on a later reconcile can flip which node is
primary for a live VIP and cause an avoidable traffic flap; a
reconciler that observes a stale or removed node here must surface
that via a condition instead of overwriting the value. | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains the standard conditions for this resource. | | | + + #### NextHopSet @@ -1240,6 +1408,7 @@ Supported values for kind: Node. _Appears in:_ - [BGPRouterSpec](#bgprouterspec) +- [NetworkGatewaySpec](#networkgatewayspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | From 6d3b85eb9dcaac4d9b5dde66afe8a7ab910405ce Mon Sep 17 00:00:00 2001 From: Peter Sprygada Date: Wed, 12 Aug 2026 14:25:50 -0400 Subject: [PATCH 2/2] chore: split gateway docs into separate file and update tooling NetworkGateway and NetworkRule docs are now generated to docs/api/gateway.md via a dedicated .crd-ref-docs-gateway.yaml config, keeping bgp.md focused on the BGP API group. Added docs/api/index.md as a landing page linking both references. Updated Taskfile generate:docs target, AGENTS.md conventions, and the BGP crd-ref-docs config to ignore the gateway types. Co-authored-by: Qwen-Coder --- .crd-ref-docs-gateway.yaml | 76 +++++++++++++ .crd-ref-docs.yaml | 8 ++ AGENTS.md | 18 ++-- Taskfile.yaml | 2 + docs/api/bgp.md | 169 ----------------------------- docs/api/gateway.md | 212 +++++++++++++++++++++++++++++++++++++ docs/api/index.md | 18 ++++ templates/api-index.md | 18 ++++ 8 files changed, 344 insertions(+), 177 deletions(-) create mode 100644 .crd-ref-docs-gateway.yaml create mode 100644 docs/api/gateway.md create mode 100644 docs/api/index.md create mode 100644 templates/api-index.md diff --git a/.crd-ref-docs-gateway.yaml b/.crd-ref-docs-gateway.yaml new file mode 100644 index 0000000..daa762f --- /dev/null +++ b/.crd-ref-docs-gateway.yaml @@ -0,0 +1,76 @@ +processor: + docPaths: [] + ignoreTypes: + - ".*List$" + - "AFI" + - "SAFI" + - "AddressFamily" + - "SRv6Function" + - "RouterRole" + - "OriginType" + - "RouterRef" + - "RouterSelector" + - "RouterTarget" + - "LocalSecretRef" + - "RouterStatus" + - "ResolvedRouterConfig" + - "BGPPeerBFD" + - "BGPPeerGracefulRestart" + - "BGPPeerAuthentication" + - "Community" + - "Prefix" + - "RedistributeSource" + - "AdvertisementOriginateType" + - "AdvertisementOriginateFrom" + - "AdvertisementPolicyRef" + - "BGPAdvertisement" + - "BGPAdvertisementSpec" + - "BGPAdvertisementStatus" + - "BGPCommunitySet" + - "BGPCommunitySetType" + - "BGPCommunitySetSpec" + - "BGPCommunitySetStatus" + - "BGPPeerState" + - "SendCommunityType" + - "MaxPrefixShutdownAction" + - "BGPMaximumPrefix" + - "BGPPeer" + - "BGPPeerSpec" + - "BGPPeerStatus" + - "BGPRouterPhase" + - "BGPRouter" + - "BGPRouterSpec" + - "BGPRouterStatus" + - "BGPRouterPeerSummary" + - "BGPVRFInstance" + - "BGPVRFInstanceSpec" + - "BGPVRFInstanceStatus" + - "RouteTarget" + - "BGPPolicyDirection" + - "BGPPolicyAction" + - "BGPPolicy" + - "BGPPolicySpec" + - "BGPPolicyTerm" + - "BGPPolicyMatch" + - "BGPPolicySetActions" + - "BGPPolicyStatus" + - "ASPathMatchType" + - "ASPathFilter" + - "EVPNRouteType" + - "BGPOrigin" + - "AsPathSet" + - "NextHopSet" + - "CommunitySet" + - "ExtendedCommunitySet" + - "BGPPrefixList" + - "BGPPrefixListEntry" + - "BGPPrefixListSpec" + - "BGPPrefixListStatus" + ignoreFields: [] + +render: + kubernetes: {} + markdown: + mediaWidth: 80 + anchorLink: true + tableHideEmpty: true diff --git a/.crd-ref-docs.yaml b/.crd-ref-docs.yaml index 3361ffb..2056c5d 100644 --- a/.crd-ref-docs.yaml +++ b/.crd-ref-docs.yaml @@ -2,6 +2,14 @@ processor: docPaths: [] ignoreTypes: - ".*List$" + - "NetworkGateway" + - "NetworkGatewaySpec" + - "NetworkGatewayStatus" + - "NetworkRule" + - "NetworkRuleBackend" + - "NetworkRuleProtocol" + - "NetworkRuleSpec" + - "NetworkRuleStatus" ignoreFields: [] render: diff --git a/AGENTS.md b/AGENTS.md index 3c5457f..b743183 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,7 +22,7 @@ task lint-fix # Same with auto-fix applied task generate # Run all generators (methods, manifests, docs) task generate:methods # Regenerate zz_generated.deepcopy.go files task generate:manifests # Regenerate CRD YAML in config/crd/ from Go types -task generate:docs # Regenerate docs/api/bgp.md from Go types +task generate:docs # Regenerate docs/api/bgp.md and docs/api/gateway.md from Go types task ci # Full local pipeline: build → lint → test:unit → test:e2e task clean # Remove ./bin/ and cover.out ``` @@ -38,9 +38,9 @@ All dev tools (golangci-lint, controller-gen, chainsaw, yamlfmt) are installed l ### API groups -| Group | Version | Resources | -|--------------------|------------|-----------------------------------------------------------------| -| `network.datumapis.com` | `v1alpha1` | BGPRouter, BGPPeer, BGPAdvertisement, BGPPolicy, BGPVRFInstance | +| Group | Version | Resources | +|--------------------|------------|---------------------------------------------------------------------------| +| `network.datumapis.com` | `v1alpha1` | BGPRouter, BGPPeer, BGPAdvertisement, BGPPolicy, BGPVRFInstance, NetworkGateway, NetworkRule | Source lives in `api/v1alpha1/`. Each resource has its own `*_types.go` file; shared types (RouterTarget, AddressFamily, etc.) live in `shared_types.go`. @@ -60,7 +60,7 @@ The `RouterTarget` struct (in `shared_types.go`) is embedded by resources that s - **Kubernetes 1.28+ required** — CEL functions `isIP()` and `isCIDR()` are used for field validation. - **Status conditions** follow `metav1.Condition` conventions. Condition type constants (e.g., `ConditionTypeReady`, `ConditionTypeAccepted`) are defined alongside the resource type they belong to. - **YAML files must use `.yaml` extension**, never `.yml` — the lint task enforces this. -- **Never hand-edit generated files** — `docs/api/bgp.md`, `config/crd/*.yaml`, and `zz_generated.deepcopy.go` are all generated. Always regenerate via `task generate` (or the individual `generate:methods`, `generate:manifests`, `generate:docs` targets). Editing them directly will be overwritten and drifts from source of truth. +- **Never hand-edit generated files** — `docs/api/bgp.md`, `docs/api/gateway.md`, `config/crd/*.yaml`, and `zz_generated.deepcopy.go` are all generated. Always regenerate via `task generate` (or the individual `generate:methods`, `generate:manifests`, `generate:docs` targets). Editing them directly will be overwritten and drifts from source of truth. ### Code generation @@ -68,9 +68,9 @@ After changing kubebuilder markers (`// +kubebuilder:...`) or adding new types: 1. `task generate:methods` — regenerates `zz_generated.deepcopy.go` 2. `task generate:manifests` — regenerates CRDs in `config/crd/` -3. `task generate:docs` — regenerates `docs/api/bgp.md` from Go types (config in `.crd-ref-docs.yaml`) +3. `task generate:docs` — regenerates `docs/api/bgp.md` and `docs/api/gateway.md` from Go types (configs in `.crd-ref-docs.yaml` and `.crd-ref-docs-gateway.yaml`) -Or run `task generate` to execute all three in order. All three are generated; never edit `zz_generated.deepcopy.go`, CRD YAML, or `docs/api/bgp.md` directly. +Or run `task generate` to execute all three in order. All three are generated; never edit `zz_generated.deepcopy.go`, CRD YAML, or `docs/api/*.md` directly. ### Testing @@ -86,7 +86,9 @@ See [CONVENTIONS.md](docs/agents/CONVENTIONS.md) for coding standards, naming ru ## Docs -- `docs/api/bgp.md` — full BGP CRD field reference +- `docs/api/index.md` — API docs index (links to bgp.md and gateway.md) +- `docs/api/bgp.md` — BGP CRD field reference (generated) +- `docs/api/gateway.md` — Gateway CRD field reference (generated) - `docs/getting-started.md` — install and first resources - `docs/enhancements/` — design proposals ## GitHub PR / Issue / Comment Conventions diff --git a/Taskfile.yaml b/Taskfile.yaml index 344d0e3..9b6c6db 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -115,6 +115,8 @@ tasks: cmds: - rm -rf docs/api && mkdir -p docs/api - '{{.CRD_REF_DOCS}} --renderer=markdown --source-path=api/v1alpha1 --output-path=docs/api/bgp.md --config=.crd-ref-docs.yaml' + - '{{.CRD_REF_DOCS}} --renderer=markdown --source-path=api/v1alpha1 --output-path=docs/api/gateway.md --config=.crd-ref-docs-gateway.yaml' + - cp templates/api-index.md docs/api/index.md ## ## Testing diff --git a/docs/api/bgp.md b/docs/api/bgp.md index a16c5b1..11b33bb 100644 --- a/docs/api/bgp.md +++ b/docs/api/bgp.md @@ -15,8 +15,6 @@ Package v1alpha1 contains API Schema definitions for the network.datumapis.com/v - [BGPPolicy](#bgppolicy) - [BGPRouter](#bgprouter) - [BGPVRFInstance](#bgpvrfinstance) -- [NetworkGateway](#networkgateway) -- [NetworkRule](#networkrule) @@ -971,172 +969,6 @@ _Appears in:_ | `shutdown` | MaxPrefixShutdownActionShutdown tears down the BGP session when the limit is exceeded.
| -#### NetworkGateway - - - -NetworkGateway defines a hyperconverged gateway engine instance bound to a -single gateway-role node. Exactly one NetworkGateway exists per gateway -node (spec.targetRef.name is the Kubernetes node name), mirroring the -BGPRouter node-scoped root object pattern. NetworkRule resources are -assigned to a NetworkGateway via status.primaryNode; compute-node Geneve -encap agents read the assigned NetworkGateway's status.tunnelEndpointAddress -to learn where to send that tenant's traffic. - - - - - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `apiVersion` _string_ | `network.datumapis.com/v1alpha1` | | | -| `kind` _string_ | `NetworkGateway` | | | -| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | -| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[NetworkGatewaySpec](#networkgatewayspec)_ | | | | -| `status` _[NetworkGatewayStatus](#networkgatewaystatus)_ | | | | - - -#### NetworkGatewaySpec - - - -NetworkGatewaySpec defines the desired state of a NetworkGateway. - - - -_Appears in:_ -- [NetworkGateway](#networkgateway) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `targetRef` _[TargetRef](#targetref)_ | TargetRef identifies the Node this gateway engine executes on. | | Required: \{\}
| - - -#### NetworkGatewayStatus - - - -NetworkGatewayStatus defines the observed state of a NetworkGateway. - - - -_Appears in:_ -- [NetworkGateway](#networkgateway) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | | -| `tunnelEndpointAddress` _string_ | TunnelEndpointAddress is the Geneve tunnel endpoint IPv4 or IPv6
address for this gateway node. Compute-node encap agents resolve a
tenant's assigned NetworkGateway (via NetworkRule.status.primaryNode)
and read this field to learn where to send that tenant's Geneve-encapsulated
traffic. Populated by the engine once its Geneve decap endpoint is programmed. | | | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains the standard conditions for this resource. | | | - - -#### NetworkRule - - - -NetworkRule defines ingress load-balancing and NAT for a single tenant -VPC/VPCAttachment, served by the shared hyperconverged gateway engine. -It is namespaced (deployed to galactic-system) and tenant-writable; the -vpcRef/vpcAttachmentRef fields are opaque string identifiers because the -VPC API is owned by a separate companion operator, not this repo. An -admission webhook (implemented by the consuming controller) must verify -the requester is authorized for vpcRef/vpcAttachmentRef before a rule is -accepted — see the Accepted condition. - - - - - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `apiVersion` _string_ | `network.datumapis.com/v1alpha1` | | | -| `kind` _string_ | `NetworkRule` | | | -| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | -| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | -| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | -| `spec` _[NetworkRuleSpec](#networkrulespec)_ | | | | -| `status` _[NetworkRuleStatus](#networkrulestatus)_ | | | | - - -#### NetworkRuleBackend - - - -NetworkRuleBackend is a single backend endpoint that ingress traffic -matching a NetworkRule's VIP addresses is load-balanced to. - - - -_Appears in:_ -- [NetworkRuleSpec](#networkrulespec) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `address` _string_ | Address is the backend's IPv4 or IPv6 address. | | MaxLength: 45
Required: \{\}
| -| `port` _integer_ | Port is the backend's destination port. | | Maximum: 65535
Minimum: 1
Required: \{\}
| - - -#### NetworkRuleProtocol - -_Underlying type:_ _string_ - -NetworkRuleProtocol is the transport protocol matched by a NetworkRule's -ingress VIP. - -_Validation:_ -- Enum: [tcp udp] - -_Appears in:_ -- [NetworkRuleSpec](#networkrulespec) - -| Field | Description | -| --- | --- | -| `tcp` | NetworkRuleProtocolTCP matches TCP traffic.
| -| `udp` | NetworkRuleProtocolUDP matches UDP traffic.
| - - -#### NetworkRuleSpec - - - -NetworkRuleSpec defines the desired ingress load-balancing state for a -tenant VPC/VPCAttachment. - - - -_Appears in:_ -- [NetworkRule](#networkrule) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `vpcRef` _string_ | VPCRef is the opaque identifier of the target VPC this rule applies
to. This repo does not own the VPC API and does not validate the
identifier beyond non-emptiness; the admission webhook of the
consuming controller is responsible for verifying the requester is
authorized for this VPC before the rule is accepted. | | MinLength: 1
Required: \{\}
| -| `vpcAttachmentRef` _string_ | VPCAttachmentRef is the opaque identifier of the target
VPCAttachment this rule applies to. Like VPCRef, this is an opaque
string reference validated by the admission webhook, not by this API. | | MinLength: 1
Required: \{\}
| -| `vipAddresses` _string array_ | VIPAddresses is the list of ingress VIP addresses (IPv4 and/or IPv6)
this rule provisions on the assigned gateway node(s). | | MaxItems: 8
MinItems: 1
Required: \{\}
items:MaxLength: 45
| -| `protocol` _[NetworkRuleProtocol](#networkruleprotocol)_ | Protocol is the transport protocol matched by VIPAddresses/Port. | | Enum: [tcp udp]
Required: \{\}
| -| `port` _integer_ | Port is the ingress port on VIPAddresses that this rule load-balances. | | Maximum: 65535
Minimum: 1
Required: \{\}
| -| `backends` _[NetworkRuleBackend](#networkrulebackend) array_ | Backends is the list of backend address:port targets that ingress
traffic matching VIPAddresses/Protocol/Port is load-balanced to. | | MaxItems: 64
MinItems: 1
Required: \{\}
| - - -#### NetworkRuleStatus - - - -NetworkRuleStatus defines the observed state of a NetworkRule. - - - -_Appears in:_ -- [NetworkRule](#networkrule) - -| Field | Description | Default | Validation | -| --- | --- | --- | --- | -| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | | -| `primaryNode` _string_ | PrimaryNode is the name of the NetworkGateway-backed gateway node
assigned to advertise this rule's VIPAddresses at the preferred BGP
local-preference, per the active-active model: primary_node =
hash(vpcRef) % . The controller consuming this
CRD sets this field exactly once, at creation.
This value must never be silently recomputed by a reconciler once
set. Recomputing it on a later reconcile can flip which node is
primary for a live VIP and cause an avoidable traffic flap; a
reconciler that observes a stale or removed node here must surface
that via a condition instead of overwriting the value. | | | -| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains the standard conditions for this resource. | | | - - #### NextHopSet @@ -1408,7 +1240,6 @@ Supported values for kind: Node. _Appears in:_ - [BGPRouterSpec](#bgprouterspec) -- [NetworkGatewaySpec](#networkgatewayspec) | Field | Description | Default | Validation | | --- | --- | --- | --- | diff --git a/docs/api/gateway.md b/docs/api/gateway.md new file mode 100644 index 0000000..f75dca8 --- /dev/null +++ b/docs/api/gateway.md @@ -0,0 +1,212 @@ +# API Reference + +## Packages +- [network.datumapis.com/v1alpha1](#networkdatumapiscomv1alpha1) + + +## network.datumapis.com/v1alpha1 + +Package v1alpha1 contains API Schema definitions for the network.datumapis.com/v1alpha1 API group. + +### Resource Types +- [NetworkGateway](#networkgateway) +- [NetworkRule](#networkrule) + + + +#### NetworkGateway + + + +NetworkGateway defines an XDP ingress NAT+LB gateway engine instance bound +to a single dedicated gateway-role node. Exactly one NetworkGateway exists +per gateway node (spec.targetRef.name is the Kubernetes node name), +mirroring the BGPRouter node-scoped root object pattern. NetworkRule +resources are assigned to a NetworkGateway via status.primaryNode. + +There is no tunnel overlay in this design (an earlier Geneve-based +approach was superseded before this type shipped): the gateway's XDP +program does Full-NAT (DNAT the VIP to a backend Pod's address, SNAT the +client's source to status.sRv6Address) and pushes an SRv6 uSID outer +header addressed to the backend's worker node directly, so return traffic +(addressed to status.sRv6Address) arrives back at this same gateway node +over the ordinary SRv6 fabric — no compute-node encap agent, no tunnel +endpoint to publish. status.sRv6Address is advertised into BGP the same +way any workload prefix is (a BGPAdvertisement naming it, /128, Argument +0 — the value PR #740 reserves and forbids registering into any tenant +VRF, guaranteeing it never collides with a real tenant's Argument), so +every other node learns a real kernel SEG6 route to it for free through +the existing EVPN pipeline. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `network.datumapis.com/v1alpha1` | | | +| `kind` _string_ | `NetworkGateway` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[NetworkGatewaySpec](#networkgatewayspec)_ | | | | +| `status` _[NetworkGatewayStatus](#networkgatewaystatus)_ | | | | + + +#### NetworkGatewaySpec + + + +NetworkGatewaySpec defines the desired state of a NetworkGateway. + + + +_Appears in:_ +- [NetworkGateway](#networkgateway) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `targetRef` _[TargetRef](#targetref)_ | TargetRef identifies the Node this gateway engine executes on. | | Required: \{\}
| + + +#### NetworkGatewayStatus + + + +NetworkGatewayStatus defines the observed state of a NetworkGateway. + + + +_Appears in:_ +- [NetworkGateway](#networkgateway) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | | +| `sRv6Address` _string_ | SRv6Address is this gateway node's own SRv6-reachable IPv6 address,
used as the Full-NAT SNAT source for every ingress flow this node
translates. Backend Pods' replies are naturally routed back to it
over the ordinary SRv6 fabric (the same mechanism that routes any
other node's traffic), where this node's XDP program decapsulates
and un-NATs them using its own conn_table — there is no separate
tunnel endpoint or overlay device to publish. Populated by the
engine once it has computed the address (a uFMT 48+16 uSID over this
node's own BGPRouter locator/node-ID, at the reserved Argument 0)
and advertised it into BGP. | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains the standard conditions for this resource. | | | + + +#### NetworkRule + + + +NetworkRule defines ingress load-balancing and NAT for a single tenant +VPC/VPCAttachment, served by the shared hyperconverged gateway engine. +It is namespaced (deployed to galactic-system) and tenant-writable; the +vpcRef/vpcAttachmentRef fields are opaque string identifiers because the +VPC API is owned by a separate companion operator, not this repo. An +admission webhook (implemented by the consuming controller) must verify +the requester is authorized for vpcRef/vpcAttachmentRef before a rule is +accepted — see the Accepted condition. + + + + + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `apiVersion` _string_ | `network.datumapis.com/v1alpha1` | | | +| `kind` _string_ | `NetworkRule` | | | +| `kind` _string_ | Kind is a string value representing the REST resource this object represents.
Servers may infer this from the endpoint the client submits requests to.
Cannot be updated.
In CamelCase.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds | | | +| `apiVersion` _string_ | APIVersion defines the versioned schema of this representation of an object.
Servers should convert recognized schemas to the latest internal value, and
may reject unrecognized values.
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources | | | +| `metadata` _[ObjectMeta](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#objectmeta-v1-meta)_ | Refer to Kubernetes API documentation for fields of `metadata`. | | | +| `spec` _[NetworkRuleSpec](#networkrulespec)_ | | | | +| `status` _[NetworkRuleStatus](#networkrulestatus)_ | | | | + + +#### NetworkRuleBackend + + + +NetworkRuleBackend is a single backend endpoint that ingress traffic +matching a NetworkRule's VIP addresses is load-balanced to. + + + +_Appears in:_ +- [NetworkRuleSpec](#networkrulespec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `address` _string_ | Address is the backend's IPv4 or IPv6 address. | | MaxLength: 45
Required: \{\}
| +| `port` _integer_ | Port is the backend's destination port. | | Maximum: 65535
Minimum: 1
Required: \{\}
| + + +#### NetworkRuleProtocol + +_Underlying type:_ _string_ + +NetworkRuleProtocol is the transport protocol matched by a NetworkRule's +ingress VIP. + +_Validation:_ +- Enum: [tcp udp] + +_Appears in:_ +- [NetworkRuleSpec](#networkrulespec) + +| Field | Description | +| --- | --- | +| `tcp` | NetworkRuleProtocolTCP matches TCP traffic.
| +| `udp` | NetworkRuleProtocolUDP matches UDP traffic.
| + + +#### NetworkRuleSpec + + + +NetworkRuleSpec defines the desired ingress load-balancing state for a +tenant VPC/VPCAttachment. + + + +_Appears in:_ +- [NetworkRule](#networkrule) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `vpcRef` _string_ | VPCRef is the opaque identifier of the target VPC this rule applies
to. This repo does not own the VPC API and does not validate the
identifier beyond non-emptiness; the admission webhook of the
consuming controller is responsible for verifying the requester is
authorized for this VPC before the rule is accepted. | | MinLength: 1
Required: \{\}
| +| `vpcAttachmentRef` _string_ | VPCAttachmentRef is the opaque identifier of the target
VPCAttachment this rule applies to. Like VPCRef, this is an opaque
string reference validated by the admission webhook, not by this API. | | MinLength: 1
Required: \{\}
| +| `vipAddresses` _string array_ | VIPAddresses is the list of ingress VIP addresses (IPv4 and/or IPv6)
this rule provisions on the assigned gateway node(s). | | MaxItems: 8
MinItems: 1
Required: \{\}
items:MaxLength: 45
| +| `protocol` _[NetworkRuleProtocol](#networkruleprotocol)_ | Protocol is the transport protocol matched by VIPAddresses/Port. | | Enum: [tcp udp]
Required: \{\}
| +| `port` _integer_ | Port is the ingress port on VIPAddresses that this rule load-balances. | | Maximum: 65535
Minimum: 1
Required: \{\}
| +| `backends` _[NetworkRuleBackend](#networkrulebackend) array_ | Backends is the list of backend address:port targets that ingress
traffic matching VIPAddresses/Protocol/Port is load-balanced to. | | MaxItems: 64
MinItems: 1
Required: \{\}
| + + +#### NetworkRuleStatus + + + +NetworkRuleStatus defines the observed state of a NetworkRule. + + + +_Appears in:_ +- [NetworkRule](#networkrule) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `observedGeneration` _integer_ | ObservedGeneration is the .metadata.generation this status was computed from. | | | +| `primaryNode` _string_ | PrimaryNode is the name of the NetworkGateway-backed gateway node
assigned to advertise this rule's VIPAddresses at the preferred BGP
local-preference, per the active-active model: primary_node =
hash(vpcRef) % . The controller consuming this
CRD sets this field exactly once, at creation.
This value must never be silently recomputed by a reconciler once
set. Recomputing it on a later reconcile can flip which node is
primary for a live VIP and cause an avoidable traffic flap; a
reconciler that observes a stale or removed node here must surface
that via a condition instead of overwriting the value. | | | +| `conditions` _[Condition](https://kubernetes.io/docs/reference/generated/kubernetes-api/v/#condition-v1-meta) array_ | Conditions contains the standard conditions for this resource. | | | + + +#### TargetRef + + + +TargetRef identifies the execution target for a BGPRouter. +Supported values for kind: Node. + + + +_Appears in:_ +- [NetworkGatewaySpec](#networkgatewayspec) + +| Field | Description | Default | Validation | +| --- | --- | --- | --- | +| `kind` _string_ | Kind is the target resource kind (e.g. Node). | | MinLength: 1
| +| `name` _string_ | Name is the name of the target resource. | | MinLength: 1
| + + diff --git a/docs/api/index.md b/docs/api/index.md new file mode 100644 index 0000000..d423052 --- /dev/null +++ b/docs/api/index.md @@ -0,0 +1,18 @@ +# API Reference + +API types for the `network.datumapis.com` group, version `v1alpha1`. + +## BGP + +BGP routing resources for defining routers, peers, policies, advertisements, +and VRF instances. + +- [BGP API Reference](bgp.md) — BGPRouter, BGPPeer, BGPPolicy, BGPAdvertisement, + BGPVRFInstance, BGPCommunitySet, BGPPrefixList, and shared types + +## Gateway + +Hyperconverged gateway and ingress load-balancing resources. + +- [Gateway API Reference](gateway.md) — NetworkGateway, NetworkRule, and + associated types diff --git a/templates/api-index.md b/templates/api-index.md new file mode 100644 index 0000000..d423052 --- /dev/null +++ b/templates/api-index.md @@ -0,0 +1,18 @@ +# API Reference + +API types for the `network.datumapis.com` group, version `v1alpha1`. + +## BGP + +BGP routing resources for defining routers, peers, policies, advertisements, +and VRF instances. + +- [BGP API Reference](bgp.md) — BGPRouter, BGPPeer, BGPPolicy, BGPAdvertisement, + BGPVRFInstance, BGPCommunitySet, BGPPrefixList, and shared types + +## Gateway + +Hyperconverged gateway and ingress load-balancing resources. + +- [Gateway API Reference](gateway.md) — NetworkGateway, NetworkRule, and + associated types