Skip to content

feat(api)!: collapse typed CIDR fields into flat allowedNetworks#21

Merged
Andrei Kvapil (kvaps) merged 1 commit into
mainfrom
feat/flat-allowed-networks
Jun 19, 2026
Merged

feat(api)!: collapse typed CIDR fields into flat allowedNetworks#21
Andrei Kvapil (kvaps) merged 1 commit into
mainfrom
feat/flat-allowed-networks

Conversation

@kvaps

@kvaps Andrei Kvapil (kvaps) commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Collapses the four typed CIDR fields on ClusterEntry (podCIDRs, wireguardCIDR, serviceCIDR, additionalCIDRs) into a single flat allowedNetworks []string.

The typed split carried no real semantic value: serviceCIDR and additionalCIDRs were handled identically (folded into the anchor Peer), and podCIDRs/wireguardCIDR served only as per-node validation bounds — the values actually advertised come from each node's own Node.Spec.PodCIDRs / wireguard-ip. A flat list does everything the typed fields did, keeps the "nothing outside allowedNetworks can appear in a Peer" bound, and removes the two-ways-to-express-the-same-CIDR footgun. Aligns with the upstream ClusterMesh design direction.

Changes

  • ClusterEntry: typed CIDR fields -> allowedNetworks (MinItems=1); wireguardPort, persistentKeepalive, name, local, kubeconfigSecretRef unchanged; AllCIDRs() returns allowedNetworks.
  • Validation: a node is eligible when its PodCIDR is a subset of some allowedNetworks entry and its wireguard-ip host falls within some entry.
  • Anchor Peer is now node-aware: CollectAnchorCIDRs(entry, nodes) returns only the entries no node already represents (service CIDR, host-network ranges); pod aggregates and the WG CIDR are carried by per-node Peers and no longer double-advertised.
  • Regenerated CRD (both config/crd/bases and the embedded internal/crd copy, now in sync) and deepcopy.
  • README examples + descriptions updated.

Tests

  • All fixtures migrated to allowedNetworks.
  • Added TestCollectAnchorCIDRs (service/host-net -> anchor; pod/WG aggregates -> not).
  • Added tests for the discovered-endpoint enrichment + per-cluster keepalive that merged earlier without unit coverage (DiscoveredEndpointsByKey, parseDiscoveredEndpoint, enrichEndpointsFromDiscovered).
  • go build, go vet, lint, and unit tests green; integration tests compile (need envtest to run).

Breaking change - no conversion

Intentional breaking CRD change with no conversion webhook / v1alpha2. Existing ClusterMesh resources must be re-written to allowedNetworks. The downstream kubernetes-switchcloud chart (which emits the typed fields) must be updated in lockstep.

Summary by CodeRabbit

Release Notes

  • Breaking Changes

    • Simplified ClusterEntry configuration: podCIDRs, wireguardCIDR, serviceCIDR, and additionalCIDRs fields replaced with single allowedNetworks field containing all network ranges.
  • New Features

    • Added persistentKeepalive field to control WireGuard keepalive behavior (0-65535 seconds).
  • Documentation

    • Updated README and example resources to reflect new allowedNetworks semantics and configuration format.

BREAKING CHANGE: the ClusterEntry typed CIDR fields (podCIDRs,
wireguardCIDR, serviceCIDR, additionalCIDRs) are replaced by a single
flat allowedNetworks []string. Validation and Peer construction treat
every entry uniformly: a node is eligible when its PodCIDR is a subset
of some entry and its wireguard-ip host IP falls within some entry.

The anchor now advertises only the residual allowedNetworks entries
that no per-node Peer already carries (CollectAnchorCIDRs became
node-aware): pod aggregates and the WG CIDR are dropped because the
per-node Peers announce them, while service CIDRs and host-network
ranges survive into the anchor.

No conversion/migration is provided. Both CRD copies (config/crd/bases
and the embedded internal/crd) are regenerated and synced.

Tests: existing ClusterEntry constructions migrated to allowedNetworks;
added unit tests for the node-aware CollectAnchorCIDRs and for the
discovered-endpoint logic (kilonode.DiscoveredEndpointsByKey,
parseDiscoveredEndpoint, enrichEndpointsFromDiscovered).

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9a19616d-05cb-4474-95d1-fa7f0f18a0a8

📥 Commits

Reviewing files that changed from the base of the PR and between 6f3a3ac and 20d2ca6.

📒 Files selected for processing (20)
  • README.md
  • api/v1alpha1/clustermesh_types.go
  • api/v1alpha1/clustermesh_types_test.go
  • api/v1alpha1/zz_generated.deepcopy.go
  • cmd/main_test.go
  • config/crd/bases/kilo.squat.ai_clustermeshes.yaml
  • internal/controller/clustermesh_controller.go
  • internal/controller/discovered_endpoint_test.go
  • internal/crd/clustermeshes.yaml
  • internal/kilonode/discovered_test.go
  • internal/peer/builder.go
  • internal/peer/builder_test.go
  • internal/restart/watcher_test.go
  • internal/validation/mesh_test.go
  • internal/validation/node.go
  • internal/validation/node_test.go
  • test/integration/cleanup_test.go
  • test/integration/helpers_test.go
  • test/integration/labels_test.go
  • test/integration/validation_test.go

📝 Walkthrough

Walkthrough

Replaces the multiple per-cluster CIDR fields (podCIDRs, wireguardCIDR, serviceCIDR, additionalCIDRs) on ClusterEntry with a single flat allowedNetworks []string. Validation, peer-building anchor logic (CollectAnchorCIDRs), CRD schemas, deepcopy, and all tests are updated accordingly. Two new test files add coverage for discovered-endpoint parsing and reconciler enrichment.

Changes

AllowedNetworks API migration

Layer / File(s) Summary
ClusterEntry API contract and CRD schema
api/v1alpha1/clustermesh_types.go, api/v1alpha1/zz_generated.deepcopy.go, config/crd/bases/kilo.squat.ai_clustermeshes.yaml, internal/crd/clustermeshes.yaml, README.md
ClusterEntry replaces four typed CIDR fields with AllowedNetworks []string; AllCIDRs() returns it directly; deepcopy is regenerated; both CRD YAML files gain allowedNetworks (with minItems: 1) and persistentKeepalive, update required lists, and drop the old CIDR fields. README example and feature description updated.
Node validation against AllowedNetworks
internal/validation/node.go, internal/validation/node_test.go, internal/validation/mesh_test.go
validatePodCIDR and validateWireguardIP iterate AllowedNetworks instead of individual CIDR fields. Test fixtures and skip-reason messages updated to reference allowedNetworks.
Node-aware CollectAnchorCIDRs and controller wiring
internal/peer/builder.go, internal/controller/clustermesh_controller.go
CollectAnchorCIDRs gains a nodes parameter and computes residual CIDRs by excluding those already covered by any node's PodCIDR subset or WireGuard host IP. New helpers networkCoveredByAnyNode and nodeCoversNetwork implement coverage detection. buildDesiredPeers passes the node list through.
Peer builder tests and TestCollectAnchorCIDRs
internal/peer/builder_test.go
Existing anchor/CIDR tests updated to use AllowedNetworks with explicit node slices. New TestCollectAnchorCIDRs table-driven test covers residual/coverage scenarios.
API type unit tests
api/v1alpha1/clustermesh_types_test.go
JSON round-trip, deep-copy, and AllCIDRs test fixtures rewritten for AllowedNetworks.
Remaining fixture updates
cmd/main_test.go, internal/restart/watcher_test.go, test/integration/...
All remaining test fixtures across packages switch ClusterEntry construction from per-field CIDRs to AllowedNetworks.

Discovered-endpoint test coverage

Layer / File(s) Summary
DiscoveredEndpointsByKey tests
internal/kilonode/discovered_test.go
New table-driven test validates DiscoveredEndpointsByKey parsing, loopback/link-local/empty filtering, missing-annotation handling, and multi-node deduplication.
enrichEndpointsFromDiscovered tests
internal/controller/discovered_endpoint_test.go
New test file covers parseDiscoveredEndpoint parsing and four enrichEndpointsFromDiscovered scenarios: override-when-different, keep-when-same, no annotation, and no matching key.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Arsolitt
  • sircthulhu

🐇 One field to rule them all, the CIDRs combined,
allowedNetworks flattens what was once misaligned.
Pod routes, WireGuard hops, and services in a list—
The anchor peer checks nodes so no network is missed.
A tidy flat slice, the rabbit hops with glee! 🌿

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/flat-allowed-networks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@kvaps Andrei Kvapil (kvaps) merged commit 5e7cdf8 into main Jun 19, 2026
7 of 8 checks passed
@kvaps Andrei Kvapil (kvaps) deleted the feat/flat-allowed-networks branch June 19, 2026 15:57

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request simplifies the ClusterMesh configuration by replacing several specific network fields (podCIDRs, wireguardCIDR, serviceCIDR, and additionalCIDRs) in ClusterEntry with a single flat list called allowedNetworks. This change unifies validation and peer construction, with any networks not covered by individual nodes being dynamically folded into the anchor peer. Feedback on the changes suggests optimizing the CollectAnchorCIDRs function by pre-parsing node CIDRs and IPs to avoid redundant string parsing operations inside nested loops.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread internal/peer/builder.go
Comment on lines +122 to 182
func CollectAnchorCIDRs(entry *v1alpha1.ClusterEntry, nodes []*corev1.Node) []string {
var residual []string

for _, networkStr := range entry.AllowedNetworks {
network, err := netutil.ParseCIDR(networkStr)
if err != nil {
// Keep unparseable entries verbatim: validation never trusted
// these for announcement either, and dropping them would
// silently discard operator intent.
residual = append(residual, networkStr)

continue
}

if !networkCoveredByAnyNode(network, nodes) {
residual = append(residual, networkStr)
}
}

return residual
}

// networkCoveredByAnyNode reports whether some node already advertises network
// via its per-node Peer: either the node's first PodCIDR is a subset of
// network, or the host IP of its kilo.squat.ai/wireguard-ip annotation falls
// within network. Nodes with missing/invalid PodCIDR or wireguard-ip do not
// cover anything.
func networkCoveredByAnyNode(network *net.IPNet, nodes []*corev1.Node) bool {
for _, node := range nodes {
if nodeCoversNetwork(network, node) {
return true
}
}

return false
}

if entry.ServiceCIDR != "" {
cidrs = append(cidrs, entry.ServiceCIDR)
// nodeCoversNetwork reports whether a single node already advertises network,
// either through its first PodCIDR (subset of network) or through the host IP
// of its kilo.squat.ai/wireguard-ip annotation (contained in network). A
// missing or unparseable PodCIDR / wireguard-ip simply does not cover.
func nodeCoversNetwork(network *net.IPNet, node *corev1.Node) bool {
if len(node.Spec.PodCIDRs) > 0 {
nodeCIDR, err := netutil.ParseCIDR(node.Spec.PodCIDRs[0])
if err == nil && netutil.CIDRContains(network, nodeCIDR) {
return true
}
}

cidrs = append(cidrs, entry.AdditionalCIDRs...)
wgIP := node.Annotations[kilonode.AnnotationWireguardIP]
if wgIP == "" {
return false
}

hostIP, _, err := netutil.ParseHostInCIDR(wgIP)
if err != nil {
return false
}

return cidrs
return network.Contains(hostIP)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation of CollectAnchorCIDRs loops over all AllowedNetworks and, for each network, iterates through all nodes to check if the network is covered. Inside nodeCoversNetwork, it repeatedly parses the node's PodCIDR and wireguard-ip from strings. This results in $O(N \times M)$ string parsing operations on every reconciliation cycle, which can become a performance bottleneck as the number of nodes ($M$) scales.

We can optimize this to $O(M)$ string parsing operations by pre-parsing the nodes' CIDRs and IPs once before checking the networks.

type parsedNode struct {
	podCIDR *net.IPNet
	wgIP    net.IP
}

func CollectAnchorCIDRs(entry *v1alpha1.ClusterEntry, nodes []*corev1.Node) []string {
	var residual []string

	parsedNodes := make([]parsedNode, 0, len(nodes))
	for _, node := range nodes {
		pn := parsedNode{}
		if len(node.Spec.PodCIDRs) > 0 {
			if cidr, err := netutil.ParseCIDR(node.Spec.PodCIDRs[0]); err == nil {
				pn.podCIDR = cidr
			}
		}
		if wgIP := node.Annotations[kilonode.AnnotationWireguardIP]; wgIP != "" {
			if hostIP, _, err := netutil.ParseHostInCIDR(wgIP); err == nil {
				pn.wgIP = hostIP
			}
		}
		if pn.podCIDR != nil || pn.wgIP != nil {
			parsedNodes = append(parsedNodes, pn)
		}
	}

	for _, networkStr := range entry.AllowedNetworks {
		network, err := netutil.ParseCIDR(networkStr)
		if err != nil {
			residual = append(residual, networkStr)
			continue
		}

		covered := false
		for _, pn := range parsedNodes {
			if pn.podCIDR != nil && netutil.CIDRContains(network, pn.podCIDR) {
				covered = true
				break
			}
			if pn.wgIP != nil && network.Contains(pn.wgIP) {
				covered = true
				break
			}
		}

		if !covered {
			residual = append(residual, networkStr)
		}
	}

	return residual
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant