Skip to content

OVNChassis CRD ​

ovnchassis.ovn.openstack.c5c3.io/v1alpha1, kind OVNChassis. The CRD is generated from operators/ovn/api/v1alpha1/ovnchassis_types.go; the defaulting and validating webhooks live in operators/ovn/api/v1alpha1/ovnchassis_webhook.go.

One CR describes one class of node. It puts the Open vSwitch and ovn-controller DaemonSets onto the nodes spec.nodeSelector matches and registers each of those nodes as a chassis with the OVNCentral it names. Everything below the selector applies uniformly to every node matched: the bridge mappings, the encapsulation, the rollout pace. A deployment whose gateway nodes carry different wiring, or an availability zone with its own provider networks, gets a second CR of its own; widening the selector would apply one CR's wiring to nodes that do not have it.

The link to the control plane is one field. spec.centralRef names an OVNCentral in the same namespace; the chassis wait for its published Southbound address and for status.clientSecretName, and mount that Secret. A central that projects onto another cluster than the chassis is reached at the addresses it publishes on node ports, and the chassis mounts a copy of its Secret, {name}-ovn-client, which the operator writes on the chassis's own cluster. Both refs are immutable, because repointing a live chassis strands its registration in the old Southbound database.

kubectl get ovnchassis prints Ready (.status.conditions[?(@.type=='Ready')].status), Desired (.status.desiredNumberScheduled), Ready pods (.status.numberReady), and Age.

Spec ​

FieldTypeRequiredDefaultDescription
image*commonv1.ImageSpecnooperator-resolved ghcr.io/c5c3/ovn:26.03.2The image that runs Open vSwitch and ovn-controller. Both ship in the one OVN image, so one reference covers them. The operator resolves it at reconcile time, so an unset field keeps tracking its tested version across upgrades (image.go). An unset image names no pullPolicy of its own, so its containers take the operator default (--default-image-pull-policy) or the rule: Always for the tag (see pull policy resolution)
centralRefOVNCentralRefyesnoneThe OVNCentral whose Southbound database these chassis connect to and whose client Secret they mount. Immutable, enforced by a CEL transition rule and by the webhook
nodeSelectormap[string]string (MinProperties=1)yesnoneThe nodes both DaemonSets land on. At least one label is required: an empty selector matches every node in the cluster, which would start ovn-controller on the control-plane nodes and on whatever joins later
tolerations[]corev1.TolerationnononeLets the DaemonSet pods run on tainted nodes. Networking nodes are commonly tainted to keep ordinary workloads off them, and the chassis pods are what has to run there
jobs*commonv1.JobBaseSpecnonilSizes and prioritizes the maintenance Jobs: apply, evacuate and chassis-del. It carries resources and priorityClassName only. Unset resources default to a 70m CPU request and 368Mi memory as request and limit, and an unset priority class renders none; there is no fallback Deployment. The Jobs take spec.tolerations. The block has no placement fields: the apply Job is pinned to its node, and a node selector that node does not match fails the pinned pod's kubelet admission
gateway*OVNGatewaySpecnonilMarks the subset of the selected nodes that announce enable-chassis-as-gw, the flag that makes a chassis eligible to host a distributed router's gateway port. When nil no node in this CR is a gateway
bridgeMappings[]OVNBridgeMappingnononeMaps each OpenStack physical network onto the local OVS bridge that reaches it. List-map keyed by physicalNetwork. Every node this CR selects gets the same mapping
encapTypestring (Enum geneve, vxlan)nogeneveThe tunnel protocol between chassis. Geneve carries the variable-length option header OVN uses for its logical metadata; VXLAN has no room for it and so caps the logical topology. VXLAN exists for hardware that cannot terminate Geneve
updateStrategyOVNChassisUpdateStrategyno{}Paces the DaemonSet rollout. Restarting ovn-controller interrupts the dataplane programming on that node, so the pace is a per-deployment tradeoff
remoteProbeIntervalMsint32 (Minimum=0)no60000How long ovn-controller lets its Southbound connection sit idle before probing it. Zero disables the probe, which is what a chassis behind a connection-tracking middlebox needs when the probe is what tears the connection down
ovs*OVNChassisOVSSpecnonilTunes the ovs-vswitchd container: its resources and its revalidator thread count. When nil the operator renders no requests or limits for the container and pins 2 revalidator threads. The local ovsdb-server container beside it takes no resources from any field
controller*OVNChassisContainerSpecnonilTunes the ovn-controller container
verticalAutoscaling*VerticalAutoscalingSpecnonilOpts both chassis DaemonSets, {name}-ovs and {name}-ovn-controller, one VPA each, into a VerticalPodAutoscaler that controls the requests of its containers; see VerticalAutoscalingSpec. On a cluster without the VPA, VPAReady turns False with reason VPANotInstalled.
targetClusterRef*commonv1.TargetClusterRefSpecnonil (the local cluster)The registered target cluster the DaemonSets are created on. The CR itself, its status and its finalizer stay on the management cluster. Immutable, enforced by two CEL transition rules and by the webhook. It may name another cluster than the OVNCentral's, which then has to publish both databases outside its cluster (see Conditions). See Target Clusters

OVNCentralRef ​

FieldTypeRequiredDefaultDescription
namestring (MinLength=1)yesnoneThe OVNCentral's name. The reference is namespace-local: the chassis mount the client Secret it publishes, and a Secret cannot be mounted across namespaces. Across clusters the chassis mounts the copy in the same namespace on its own cluster

OVNGatewaySpec ​

FieldTypeRequiredDefaultDescription
nodeSelectormap[string]string (MinProperties=1)yesnonePicks the gateway nodes out of the set spec.nodeSelector already matched, so it can only narrow that set. At least one label is required: an empty selector would promote every selected node to a gateway, spreading external connectivity across nodes with no uplink to carry it

OVNBridgeMapping ​

Ties one OpenStack physical network to one local OVS bridge.

FieldTypeRequiredDefaultDescription
physicalNetworkstring (Pattern ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$, MaxLength=63)yesnoneThe provider-network name Neutron knows the segment by. The grammar is the DNS-1123 label the Neutron network name is bounded to
bridgestring (Pattern ^[a-zA-Z0-9_.-]{1,15}$)yesnoneThe local OVS bridge name, bounded to 15 characters: the bridge appears as a Linux interface and the kernel's IFNAMSIZ leaves 15 usable bytes

OVNChassisUpdateStrategy ​

A narrowed DaemonSetUpdateStrategy. There is no maxSurge counterpart, because a second ovn-controller on the same node would contend with the first one over the local Open vSwitch database.

FieldTypeRequiredDefaultDescription
typestring (Enum RollingUpdate, OnDelete)noRollingUpdateThe rollout mode. OnDelete hands the pace to whoever drains the nodes, which is what a deployment with an external maintenance workflow wants
maxUnavailable*intstr.IntOrStringnooperator-resolved 1How many selected nodes may lose their dataplane programming at once. It applies to RollingUpdate only, and the webhook rejects it alongside OnDelete so it cannot read as effective

OVNChassisOVSSpec ​

The ovs block carries the fields of OVNChassisContainerSpec inline, so spec.ovs.resources keeps its place, and adds one field.

FieldTypeRequiredDefaultDescription
resources*corev1.ResourceRequirementsnononeAs in OVNChassisContainerSpec, for ovs-vswitchd and host-prepare
revalidatorThreads*int32 (Minimum=1)nooperator-resolved 2The number of revalidator threads ovs-vswitchd runs, the threads that dump the datapath flows and expire or revalidate them. The operator resolves the default at render time and never writes it into the CR

The count reaches the ovs-vswitchd container as the environment variable OVS_REVALIDATOR_THREADS, and run-vswitchd.sh writes it as other_config:n-revalidator-threads into the node's Open_vSwitch table before it starts the daemon. A pod created before the operator upgrade has no such variable, but the kubelet still refreshes the script inside it. When that pod's ovs-vswitchd container restarts, the script skips the write and starts the daemon with the OVS default count, which lasts until the DaemonSet replaces the pod.

Unpinned, OVS 3.7 starts the handler count divided by four, plus one, revalidators. The handler count follows the CPU cores in the process's CPU affinity, so the default would be 2 revalidators on a 4-CPU node and 5 on a 16-CPU node, and a chassis pod's footprint would depend on the node it lands on. The handler threads cannot be pinned the same way. Under the kernel datapath's per-CPU upcall dispatch, which Linux 5.14 and later provide, OVS starts one handler per core in its CPU affinity and overrides other_config:n-handler-threads. A CPU limit does not change that affinity; only a cpuset bounds the handlers.

ovs-vswitchd logs the applied counts at INFO when it starts its threads:

text
Overriding n-handler-threads to <n>, setting n-revalidator-threads to <m>

kubectl logs <pod> -c ovs-vswitchd shows the line, and kubectl exec <pod> -c ovs-vswitchd -- ovs-vsctl get open . other_config:n-revalidator-threads prints the pinned value. The value lives in the node's conf.db, which outlives the pod. Every start of ovs-vswitchd writes it again, so changing revalidatorThreads takes effect when the DaemonSet rolls the pod. An operator downgrade to a release without the field leaves the last value in place; remove it with ovs-vsctl remove open . other_config n-revalidator-threads.

OVNChassisContainerSpec ​

FieldTypeRequiredDefaultDescription
resources*corev1.ResourceRequirementsnononeRequests and limits for the container and for the init container that prepares it: host-prepare in the OVS DaemonSet for spec.ovs, apply-node in the controller DaemonSet for spec.controller. An init container never runs beside the main containers, so this adds nothing to the pod's footprint. When nil the operator renders none on either, unless a LimitRange in the namespace fills them in: what a datapath needs depends on the traffic the node carries, so no default fits most hardware

Defaulting and validation ​

The mutating webhook leaves the object untouched. Every default is either a +kubebuilder:default the API server applies from the CRD schema, or a value the operator resolves at reconcile time: the image and the maxUnavailable of 1. Resolving those two late keeps an unset field tracking the operator default across upgrades instead of freezing today's value into the stored CR. The webhook stays registered so a default that has to be materialized later can be added without changing the deployed webhook configuration.

Schema-layer rules ​

These hold even when the webhook is down.

MessageWhere it comes from
targetClusterRef is immutableTwo CEL transition rules on OVNChassisSpec, one for adding or removing the ref and one for renaming it
centralRef is immutableA third transition rule on OVNChassisSpec. Repointing a live chassis leaves its registration behind in the old Southbound database, where it keeps claiming the ports of workloads that have moved on
exactly one of image.tag or image.digest must be setInherited from commonv1.ImageSpec on spec.image

The three transition rules are evaluated on UPDATE only. Beside them the schema carries the ordinary field markers: MinProperties=1 on both selectors, the geneve/vxlan enum on spec.encapType, the RollingUpdate/OnDelete enum on spec.updateStrategy.type, Minimum=0 on spec.remoteProbeIntervalMs, Minimum=1 on spec.ovs.revalidatorThreads, the two patterns on a bridge mapping, and MinLength=1 on spec.centralRef.name. spec.bridgeMappings is a list-map keyed by physicalNetwork, so the API server already refuses a repeated physical network.

Webhook rules ​

The validating webhook accumulates every violation into one admission response.

MessageTrigger
name must be at most %d characters: the per-node Jobs append up to 21 characters and Kubernetes caps object names at 63metadata.name longer than MaxOVNChassisNameLength. The argument is the bound, 42
centralRef.name must be setspec.centralRef.name is empty
centralRef is immutableAn update renames spec.centralRef.name, the webhook-layer twin of the CEL rule
nodeSelector must carry at least one labelspec.nodeSelector is empty or absent, mirroring the MinProperties=1 marker
gateway.nodeSelector must carry at least one labelspec.gateway is set with an empty selector, mirroring the second MinProperties=1 marker
maxUnavailable applies to RollingUpdate onlyspec.updateStrategy.maxUnavailable is set alongside type: OnDelete, where nothing reads it
maxUnavailable must be an integer or a percentage such as "25%"The value is neither, so intstr.GetScaledValueFromIntOrPercent refuses it
maxUnavailable must resolve to at least 1 for RollingUpdateThe value scales to zero, which is a rollout that can never move a node
Duplicate value on spec.bridgeMappings[i].bridgeTwo mappings name the same local bridge. The rendered ovn-bridge-mappings string would have its second entry shadow the first, and the list-map key covers the physical-network side alone
Duplicate value on spec.bridgeMappings[i].physicalNetworkTwo mappings name the same physical network. The +listMapKey already refuses this at the schema layer; the webhook repeats it so the invariant holds where schema validation is bypassed
repository must be setspec.image.repository is empty
exactly one of image.tag or image.digest must be setBoth or neither are set on spec.image, re-checked outside the schema
target cluster name must be setspec.targetClusterRef is present with an empty name
targetClusterRef is immutable (adding or removing it after creation is not permitted)An update adds or drops the ref. Both strand the children already created on the previously selected cluster
targetClusterRef is immutable (the children already exist on the previously named cluster)An update renames the ref
%s request must not exceed limit (%s)A request in spec.jobs.resources above its own limit
field.NotFound on spec.jobs.priorityClassNameThe named PriorityClass does not exist. Skipped when no lookup client is injected, and for ""
revalidatorThreads must be at least 1spec.ovs.revalidatorThreads is set below 1, mirroring the Minimum=1 marker

The percentage in maxUnavailable is scaled against 100, not against the node count, and rounded up the way the DaemonSet controller rounds it. The nodes a DaemonSet lands on are unknown at admission time, and a percentage that rounds to zero on a smaller cluster wedges the rollout the same way. 1% is therefore admitted and behaves as one node.

MaxOVNChassisNameLength is 42. The child with the tightest name budget is the per-node chassis-deletion Job: {name}-chassis-del-{8 hex} adds 21 characters, and Kubernetes caps an object name at 63. The bound is enforced on create only. metadata.name is immutable, so on update the rule could only fire against an object a pre-upgrade operator already admitted, and the validating webhook also sees the finalizer-removal update that completes a deletion: rejecting that one would wedge the CR in Terminating with no field left to edit.

One cross-CR constraint is outside the webhook's reach. spec.centralRef may name an OVNCentral that does not exist at admission time, so the controller checks it. When the two CRs project onto different clusters, the central has to set spec.northbound.externallyReachable and spec.southbound.externallyReachable: the chassis dials the Southbound database, and the gateway-evacuation Job writes the Northbound one. A central that publishes either one alone leaves the chassis at CentralReady=False with reason CentralNotExternallyReachable, and the message names both fields.

Status ​

FieldTypeDescription
conditions[]metav1.ConditionList-map keyed by type; see Conditions
observedGenerationint64The .metadata.generation the controller last reconciled
installedImagestringThe image reference the running DaemonSets were projected from, recorded once the ovn-controller DaemonSet reports every node ready. It tells a rollout that has not reached the nodes from one that has
clientSecretNamestringThe Secret in this CR's namespace, on the cluster this CR projects onto, that the chassis pods mount: the OVNCentral's status.clientSecretName when both CRs project onto the same cluster, the copy {name}-ovn-client when they do not. A NeutronMetadataAgent attached to this chassis mounts the same Secret
desiredNumberScheduledint32How many nodes the DaemonSets should run on, mirrored from the ovn-controller DaemonSet
numberReadyint32How many of those nodes have a ready ovn-controller pod
nodes[]OVNChassisNodeStatusThe per-node registration state, list-map keyed by name

The two counters are read from the ovn-controller DaemonSet, not from the Open vSwitch one: ovn-controller is what makes a node a chassis, and a node running Open vSwitch alone carries no logical flows.

OVNChassisNodeStatus ​

status.nodes is what the controller works from; it re-derives nothing from the Southbound database on a normal pass. A node that stops being selected disappears from the node list while its chassis registration survives, so the record of it has to outlive the selection.

FieldTypeDescription
namestringThe node's name. Also the key of that node's entry in the {name}-nodes ConfigMap and the file the pod on it reads
systemIDstringThe chassis identity ovn-controller registered in the Southbound database, a UUID. A chassis-deletion Job addresses it, so it has to be recorded before the node goes away
gatewayboolWhether the node currently announces enable-chassis-as-gw. It reports what the node last applied, so the flip to false is held back until the evacuation has landed
configHashstringEight hex characters of the SHA-256 of the entry the node last applied. Comparing it against the rendered entry tells a configuration change from a node that has not reported back yet
gatewayEvacuatedboolThe evacuation Job moved the gateway ports off this node and succeeded. It resets when the node takes the gateway role back, so a re-promoted node is not treated as still drained
leavingboolThe node is no longer selected, or no longer in the cluster, and its chassis registration has still to be deleted. Until that deletion lands the stale chassis keeps claiming the ports of workloads that have moved elsewhere

Conditions ​

Seven sub-reconcilers set six condition types: the client-Secret step reports under CentralReady, the condition of the central step before it. The aggregate Ready is True only when all six are. For the pipeline that sets them see Reconciler Architecture.

TypeStatusReasonMeaning
CentralReadyTrueCentralResolvedThe OVNCentral published the addresses that apply and its client Secret, and the Secret the pods mount is in place. The message names the address the chassis dial: the relay or the Southbound database inside one cluster, the relay's or the Southbound database's node address across clusters
CentralReadyFalseCentralNotFoundNo OVNCentral of that name in the namespace. An OVNChassis applied before its OVNCentral is an ordinary ordering of two objects in one manifest, so this polls
CentralReadyFalseCentralReadErrorReading the OVNCentral failed
CentralReadyFalseCentralNotExternallyReachableThe two CRs project onto different clusters and the central does not set both spec.northbound.externallyReachable and spec.southbound.externallyReachable. The message names both clusters and both fields. No requeue: the fix is an edit to the central, which the central watch delivers
CentralReadyFalseCentralNotReadyThe OVNCentral has not published the Southbound address that applies (outside its cluster, across a boundary) or its client Secret yet, or it publishes its relay outside its cluster and has no relay node address yet
CentralReadyFalseClientSecretPendingThe central's client Secret does not exist yet on the central's cluster, so there is nothing to copy. Polls
CentralReadyFalseClientSecretIncompleteThe central's client Secret lacks tls.crt, tls.key or ca.crt, or carries one empty. The message names the key. Polls
CentralReadyFalseClientSecretReadErrorReading the central's client Secret failed
CentralReadyFalseClientSecretCopyFailedWriting the copy {name}-ovn-client failed, or a Secret of that name exists that this chassis does not own, which the operator refuses to overwrite
CentralReadyFalseCentralUpgradingThe central's status.installedImage differs from the image it resolves, so its own rollout is still in flight. OVN's supported upgrade order is central first, hypervisors second: ovn-controller reads the Southbound schema the central owns
CentralReadyFalseTargetClusterUnavailablespec.targetClusterRef names a cluster that does not resolve, or the cluster the central projects onto does not resolve when the client Secret has to be copied from it. This is the pipeline's first gate, so the failure lands on the condition the rest of the graph waits behind
NodesReadyTrueNodesRenderedEvery selected node has an entry in the {name}-nodes ConfigMap. The message counts the entries and how many of them are leaving
NodesReadyFalseNoMatchingNodesNo node carries spec.nodeSelector. The message repeats the selector. Both ConfigMaps are still applied, because a pod whose ConfigMap volume does not exist never starts
NodesReadyFalseNodeListErrorListing the nodes of the target cluster failed
NodesReadyFalseNodesErrorReading the live nodes ConfigMap or applying one of the two ConfigMaps failed
OVSReady, ControllerReadyTrueDaemonSetReadyThat DaemonSet runs a ready pod on every node it selects. A DaemonSet selecting no node is ready on zero nodes: its rollout has nothing left to do
OVSReady, ControllerReadyFalseDaemonSetProgressingFewer nodes run a ready pod than the DaemonSet schedules. The message counts both
OVSReady, ControllerReadyFalseDaemonSetErrorThe DaemonSet could not be applied or read
MaintenanceReadyTrueMaintenanceIdleNo node has maintenance outstanding
MaintenanceReadyTrueMaintenanceRunningPer-node Jobs are in flight. The message names the nodes. It stays True: a Job that runs finishes on its own, and holding the aggregate Ready down for it would report a rolling drain as an outage
MaintenanceReadyTrueMaintenanceDeferredA node needs an address the OVNCentral has not published. It outranks MaintenanceRunning in the message, because this one waits on another CR
MaintenanceReadyFalseMaintenanceJobFailedA per-node Job reached a terminal failure. The message names the kind, the Job and the node. No error is returned: a rerun under an unchanged key produces the same failure
MaintenanceReadyFalseMaintenanceErrorRunning a Job, or dropping a deregistered node from the ConfigMap, failed
ReadyTrueAllReadyAll six sub-conditions are True
ReadyFalseNotAllReadyAt least one is not
VPAReadyTrueVPAReadyThe VerticalPodAutoscaler of every opted-in workload is applied; the message names them. See VerticalAutoscalingSpec
VPAReadyTrueVPANotRequiredNo workload opts in (spec.verticalAutoscaling unset); a VPA the CR created before is deleted
VPAReadyFalseVPANotInstalledA workload opts in, but the cluster the children land on does not serve autoscaling.k8s.io/v1 VerticalPodAutoscaler. Nothing is created, and the other conditions still converge
VPAReadyFalseCapabilityProbeFailedThe target cluster the CR names could not be probed for the kind
VPAReadyFalseVPAErrorListing, applying or deleting a VPA failed; the message carries the error

Sub-Resource Naming Convention ​

Every child takes the CR name plus a component suffix. For an OVNChassis named {name}:

ResourceNameNotes
DaemonSet{name}-ovsovsdb-server and ovs-vswitchd on the selected nodes, in the node's network namespace
DaemonSet{name}-ovn-controllerovn-controller, connected to the Southbound address the central published
ConfigMap{name}-nodesOne key per node this CR is responsible for, carrying that node's values. One object serves every node: the pod on a node reads the file named after it and ignores the rest
ConfigMap{name}-chassis-scriptsThe scripts both DaemonSets and the three Jobs run. The suffix names the kind, so a chassis and a central of the same name keep separate scripts ConfigMaps in one namespace
Secret{name}-ovn-clientOnly while the central projects onto another cluster. The copy of the central's client identity (tls.crt, tls.key, ca.crt) the pods mount, kept equal to the source across renewals. Swept with the DaemonSets on a placed chassis, garbage-collected through its controller reference on a local one
Job{name}-apply-<8 hex>Re-applies one node's values after they changed. Pinned to that node and in its network namespace
Job{name}-evacuate-<8 hex>Moves the gateway duties off one node, against the Northbound database
Job{name}-chassis-del-<8 hex>Deletes one node's Southbound Chassis row, against the Southbound database

The eight hex characters are the first four bytes of the SHA-256 of the node name. A node name is a DNS subdomain of up to 253 characters and a Job name has 63, so the name is hashed into the suffix; four bytes tell any two node names of one cluster apart. Each Job runs with backoffLimit: 0, an active deadline of 300 seconds and a TTL of 86400 seconds after it finishes.

The longest of the three suffixes is what bounds metadata.name at 42 characters. See Webhook rules.

Node contract ​

spec.nodeSelector is required and is copied onto both DaemonSets, so the pods land where the CR says and nowhere else. spec.gateway.nodeSelector is evaluated on top of it: a node is a gateway when it matches both. A nil spec.gateway matches no node.

The figure shows the two chassis pods on a node, the pods of a metadata agent and of a compute pool that wait for them, and what a gateway node adds.

One hypervisor node with the four pods three resources put on it. An OVNChassis creates two DaemonSets: the pod {chassis}-ovs with the init container host-prepare and the containers ovsdb-server and ovs-vswitchd, and the pod {chassis}-ovn-controller with the init container apply-node and the container ovn-controller. A NeutronMetadataAgent creates the pod {agent}-metadata-agent, a NovaCompute the pod {pool}-nova-compute. All four run in the network namespace of the node, beside a libvirtd that no operator runs. Three gates order the start. The operator creates the ovn-controller DaemonSet only once every OVS pod is Ready. The init container wait-for-chassis of nova-compute waits until apply-node has written a system-id into the local Open vSwitch database. The init container wait-for-chassis of the metadata agent waits until ovn-controller has registered the chassis in the Southbound database. The pods share host paths: /run/openvswitch is mounted by all four, /run/ovn by the two chassis pods, /run/netns by the metadata agent, /run/libvirt and /var/lib/nova by nova-compute and libvirtd. A gateway node has no further pod: it also matches spec.gateway.nodeSelector, and apply-node sets ovn-cms-options=enable-chassis-as-gw.

The operator never writes a Node. Its RBAC on nodes is get, list and watch, and the labels a node carries are an input. Whoever provisions the cluster owns them.

Both DaemonSets run with hostNetwork: true. The tunnels between chassis terminate on the node's own address, and the bridges the datapath attaches to are the node's interfaces. Three host paths are mounted:

PathMounted byWhy it is a host path
/run/openvswitchboth DaemonSets, the apply JobThe local Open vSwitch database, the sockets the containers reach each other through, and the pid files. The kernel datapath outlives the pod: a restarted pod that found an empty directory would build a second database and leave the flows of the first one behind
/run/ovnboth DaemonSetsovn-controller's socket and pid file, for the same reason
/lib/modulesthe ovs DaemonSet, read-onlyThe node's kernel module tree, which the init container loads the datapath modules from. It is mounted as it is, with no DirectoryOrCreate, so a node that has no module tree fails on the mount instead of on a modprobe

The one privileged container is the host-prepare init of the {name}-ovs DaemonSet. It runs modprobe openvswitch && modprobe geneve, creates the two run directories group-writable under uid and gid 42424, creates the local database when the node carries none, and converts an existing one to the schema of the image that is now running. Everything else runs unprivileged or with two named capabilities: NET_ADMIN and SYS_NICE on ovs-vswitchd, NET_ADMIN on ovn-controller. SYS_NICE is what lets ovs-vswitchd raise the priority of its own polling threads; without it every start logs the failure and the daemon runs at ordinary priority.

The apply-node init container of the {name}-ovn-controller pod is what writes the node's identity into the local database, and the apply Job runs the same script. It waits for its own key in the {name}-nodes ConfigMap, then sets these external_ids on the Open_vSwitch table:

KeyValue
system-idThe UUID recorded as status.nodes[].systemID
hostnameThe node name, from the downward API
ovn-encap-typespec.encapType
ovn-encap-ipThe node's own address, from the downward API
ovn-remoteThe Southbound relay when the central runs one the chassis can reach: on another cluster, only a relay published with spec.relay.externallyReachable. The Southbound database itself otherwise
ovn-remote-probe-intervalspec.remoteProbeIntervalMs
ovn-bridge-mappingsThe physnet:bridge list rendered from spec.bridgeMappings, in spec order. Removed when there are no mappings, never set to the empty string: ovs-vsctl refuses a value-less assignment and would fail the init container under set -eu
ovn-cms-optionsenable-chassis-as-gw on a gateway node, removed on every other one

Every mapped bridge is then created with ovs-vsctl --may-exist add-br. ovn-controller attaches patch ports to a bridge but never creates one, so a mapping pointing at a bridge that does not exist would silently drop every packet on that physical network.

The ovs-vswitchd container writes one other_config key on the same table itself, before its daemon starts: n-revalidator-threads, from spec.ovs.revalidatorThreads.

The CRD fixes no label keys. The suites and the guides use openstack.c5c3.io/chassis=true for the nodes an OVNChassis selects and openstack.c5c3.io/gateway=true for the gateway subset, and following that convention keeps a deployment readable next to the e2e fixtures.

Example ​

yaml
apiVersion: ovn.openstack.c5c3.io/v1alpha1
kind: OVNChassis
metadata:
  name: ovn-chassis
  namespace: openstack
spec:
  centralRef:
    name: ovn-chassis-central
  nodeSelector:
    openstack.c5c3.io/chassis: "true"

This is the fixture the chassis-single-node suite applies (tests/e2e/ovn/chassis-single-node/02-ovnchassis-cr.yaml). It carries no bridge mappings and no gateway: a kind node has no provider network to map, and a gateway chassis without an uplink would announce connectivity it does not have. spec.image is left unset so the operator resolves the same OVN image the control plane runs.