diff --git a/README.md b/README.md index eec6b96e45..ad87d60f85 100644 --- a/README.md +++ b/README.md @@ -157,18 +157,6 @@ OVNKubernetes supports the following configuration options, all of which are opt * `egressIPConfig`: holds the configuration for EgressIP options. * `reachabilityTotalTimeoutSeconds`: Set EgressIP node reachability total timeout in seconds, 0 means disable reachability check and the default is 1 second. -#### DPU Host Mode Support - -OVN-Kubernetes supports specialized hardware deployments such as DPU (Data Processing Unit) hosts through the `OVN_NODE_MODE` environment variable. In `dpu-host` mode, certain features are automatically disabled on those nodes regardless of cluster-wide configuration: - -- Egress IP and related features (egress firewall, egress QoS, egress service) -- Multicast support -- Multi-external gateway support -- Multi-network policies and admin network policies -- Network segmentation features - -This per-node feature enforcement is implemented through conditional logic in the startup scripts, allowing the same cluster configuration to work across heterogeneous node types. For detailed information about node modes and the technical implementation, see `docs/ovn_node_mode.md`. - These configuration flags are only in the Operator configuration object. Example from the `manifests/cluster-network-03-config.yml` file: diff --git a/bindata/network/ovn-kubernetes/common/008-script-lib.yaml b/bindata/network/ovn-kubernetes/common/008-script-lib.yaml index 37b7c45bbb..096d3b9552 100644 --- a/bindata/network/ovn-kubernetes/common/008-script-lib.yaml +++ b/bindata/network/ovn-kubernetes/common/008-script-lib.yaml @@ -569,16 +569,8 @@ data: echo "I$(date "+%m%d %H:%M:%S.%N") - starting ovnkube-node" - # enable egress ip, egress firewall, egress qos, egress service - egress_features_enable_flag="--enable-egress-ip=true --enable-egress-firewall=true --enable-egress-qos=true --enable-egress-service=true" init_ovnkube_controller="--init-ovnkube-controller ${K8S_NODE}" - multi_external_gateway_enable_flag="--enable-multi-external-gateway=true" - gateway_interface=br-ex - - # enable multicast - enable_multicast_flag="--enable-multicast" - - # Use OVN_NODE_MODE environment variable, default to "full" if not set + gateway_interface="br-ex" OVN_NODE_MODE=${OVN_NODE_MODE:-full} # We check only dpu-host mode and not smart-nic mode here as currently we do not support it yet # Once we support it, we will need to check for it here and add relevant code. @@ -587,17 +579,10 @@ data: # https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5327/files gateway_interface="derive-from-mgmt-port" ovnkube_node_mode="--ovnkube-node-mode dpu-host" - # disable egress ip for dpu-host mode as it is not supported - egress_features_enable_flag="" - - # disable multicast for dpu-host mode as it is not supported - enable_multicast_flag="" # disable init-ovnkube-controller for dpu-host mode as it is not supported init_ovnkube_controller="" - # disable multi-external-gateway for dpu-host mode as it is not supported - multi_external_gateway_enable_flag="" fi if [ "{{.OVN_GATEWAY_MODE}}" == "shared" ]; then @@ -642,17 +627,6 @@ data: node_mgmt_port_netdev_flags="$node_mgmt_port_netdev_flags --ovnkube-node-mgmt-port-dp-resource-name ${OVNKUBE_NODE_MGMT_PORT_DP_RESOURCE_NAME}" fi - multi_network_enabled_flag= - if [[ "{{.OVN_MULTI_NETWORK_ENABLE}}" == "true" && "${OVN_NODE_MODE}" != "dpu-host" ]]; then - multi_network_enabled_flag="--enable-multi-network" - fi - - network_segmentation_enabled_flag= - if [[ "${OVN_NODE_MODE}" != "dpu-host" ]]; then - multi_network_enabled_flag="--enable-multi-network" - network_segmentation_enabled_flag="--enable-network-segmentation" - fi - route_advertisements_enable_flag= if [[ "{{.OVN_ROUTE_ADVERTISEMENTS_ENABLE}}" == "true" ]]; then route_advertisements_enable_flag="--enable-route-advertisements" @@ -662,8 +636,6 @@ data: if [[ "{{.OVN_EVPN_ENABLE}}" == "true" ]]; then evpn_enable_flag="--enable-evpn" fi - - preconfigured_udn_addresses_enable_flag="--enable-preconfigured-udn-addresses" network_connect_enabled_flag= if [[ "{{.OVN_NETWORK_CONNECT_ENABLE}}" == "true" ]]; then @@ -675,21 +647,6 @@ data: network_observability_enabled_flag="--enable-observability" fi - multi_network_policy_enabled_flag= - if [[ "{{.OVN_MULTI_NETWORK_POLICY_ENABLE}}" == "true"&& "${OVN_NODE_MODE}" != "dpu-host" ]]; then - multi_network_policy_enabled_flag="--enable-multi-networkpolicy" - fi - - admin_network_policy_enabled_flag= - if [[ "${OVN_NODE_MODE}" != "dpu-host" ]]; then - admin_network_policy_enabled_flag="--enable-admin-network-policy" - fi - - dns_name_resolver_enabled_flag= - if [[ "{{.DNS_NAME_RESOLVER_ENABLE}}" == "true" ]]; then - dns_name_resolver_enabled_flag="--enable-dns-name-resolver" - fi - # If IP Forwarding mode is global set it in the host here. IPv6 IP Forwarding shuld be # enabled for all interfaces at all times if cluster is configured as single stack IPv6 # or dual stack. This will be taken care by ovn-kubernetes(ovn-org/ovn-kubernetes#4376). @@ -770,19 +727,12 @@ data: --export-ovs-metrics \ --disable-snat-multiple-gws \ ${export_network_flows_flags} \ - ${multi_network_enabled_flag} \ - ${network_segmentation_enabled_flag} \ - ${network_connect_enabled_flag} \ ${route_advertisements_enable_flag} \ ${evpn_enable_flag} \ - ${preconfigured_udn_addresses_enable_flag} \ - ${multi_network_policy_enabled_flag} \ - ${admin_network_policy_enabled_flag} \ - ${dns_name_resolver_enabled_flag} \ ${network_observability_enabled_flag} \ - ${enable_multicast_flag} \ --zone ${K8S_NODE} \ --enable-interconnect \ + --enable-multicast \ --acl-logging-rate-limit "{{.OVNPolicyAuditRateLimit}}" \ ${gw_interface_flag} \ ${ip_forwarding_flag} \ @@ -795,7 +745,5 @@ data: ${ovn_v4_masquerade_subnet_opt} \ ${ovn_v6_masquerade_subnet_opt} \ ${ovn_v4_transit_switch_subnet_opt} \ - ${ovn_v6_transit_switch_subnet_opt} \ - ${egress_features_enable_flag} \ - ${multi_external_gateway_enable_flag} + ${ovn_v6_transit_switch_subnet_opt} } diff --git a/bindata/network/ovn-kubernetes/managed/004-config.yaml b/bindata/network/ovn-kubernetes/managed/004-config.yaml index d0a737950b..5c790c9c6e 100644 --- a/bindata/network/ovn-kubernetes/managed/004-config.yaml +++ b/bindata/network/ovn-kubernetes/managed/004-config.yaml @@ -33,14 +33,22 @@ data: dns-service-name="dns-default" [ovnkubernetesfeature] + enable-egress-ip=true + enable-egress-firewall=true + enable-egress-qos=true + enable-egress-service=true {{- if .ReachabilityNodePort }} egressip-node-healthcheck-port={{.ReachabilityNodePort}} {{- end }} - {{- if not .OVN_MULTI_NETWORK_ENABLE }} enable-multi-network=true - {{- end }} enable-network-segmentation=true enable-preconfigured-udn-addresses=true + +{{- if .OVN_MULTI_NETWORK_POLICY_ENABLE }} + enable-multi-networkpolicy=true +{{- end }} + enable-admin-network-policy=true + enable-multi-external-gateway=true {{- if .DNS_NAME_RESOLVER_ENABLE }} enable-dns-name-resolver=true {{- end }} @@ -120,14 +128,14 @@ data: {{- if .ReachabilityNodePort }} egressip-node-healthcheck-port={{.ReachabilityNodePort}} {{- end }} -{{- if .OVN_MULTI_NETWORK_ENABLE }} - enable-multi-network=true -{{- end }} - {{- if not .OVN_MULTI_NETWORK_ENABLE }} enable-multi-network=true - {{- end }} enable-network-segmentation=true enable-preconfigured-udn-addresses=true +{{- if .OVN_MULTI_NETWORK_POLICY_ENABLE }} + enable-multi-networkpolicy=true +{{- end }} + enable-admin-network-policy=true + enable-multi-external-gateway=true {{- if .DNS_NAME_RESOLVER_ENABLE }} enable-dns-name-resolver=true {{- end }} diff --git a/bindata/network/ovn-kubernetes/managed/ovnkube-control-plane.yaml b/bindata/network/ovn-kubernetes/managed/ovnkube-control-plane.yaml index 567dc365c1..4c166f28b2 100644 --- a/bindata/network/ovn-kubernetes/managed/ovnkube-control-plane.yaml +++ b/bindata/network/ovn-kubernetes/managed/ovnkube-control-plane.yaml @@ -183,19 +183,8 @@ spec: ovn_v6_masquerade_subnet_opt="--gateway-v6-masquerade-subnet {{.V6MasqueradeSubnet}}" fi - dns_name_resolver_enabled_flag= - if [[ "{{.DNS_NAME_RESOLVER_ENABLE}}" == "true" ]]; then - dns_name_resolver_enabled_flag="--enable-dns-name-resolver" - fi - persistent_ips_enabled_flag="--enable-persistent-ips" - # This is needed so that converting clusters from GA to TP - # will rollout control plane pods as well - network_segmentation_enabled_flag= - multi_network_enabled_flag="--enable-multi-network" - network_segmentation_enabled_flag="--enable-network-segmentation" - route_advertisements_enable_flag= if [[ "{{.OVN_ROUTE_ADVERTISEMENTS_ENABLE}}" == "true" ]]; then route_advertisements_enable_flag="--enable-route-advertisements" @@ -206,25 +195,10 @@ spec: evpn_enable_flag="--enable-evpn" fi - preconfigured_udn_addresses_enable_flag="--enable-preconfigured-udn-addresses" - - # Enable multi-network policy if configured (control-plane always full mode) - multi_network_policy_enabled_flag= - if [[ "{{.OVN_MULTI_NETWORK_POLICY_ENABLE}}" == "true" ]]; then - multi_network_policy_enabled_flag="--enable-multi-networkpolicy" - fi - - # Enable admin network policy if configured (control-plane always full mode) - admin_network_policy_enabled_flag="--enable-admin-network-policy" - - network_connect_enabled_flag= - if [[ "{{.OVN_NETWORK_CONNECT_ENABLE}}" == "true" ]]; then - network_connect_enabled_flag="--enable-network-connect" - fi - echo "I$(date "+%m%d %H:%M:%S.%N") - ovnkube-control-plane - start ovnkube --init-cluster-manager ${K8S_NODE}" exec /usr/bin/ovnkube \ --enable-interconnect \ + --enable-multicast \ --init-cluster-manager "${K8S_NODE}" \ --config-file=/run/ovnkube-config/ovnkube.conf \ --k8s-token-file=/var/run/secrets/hosted_cluster/token \ @@ -240,22 +214,9 @@ spec: ${ovn_v6_transit_switch_subnet_opt} \ ${ovn_v4_masquerade_subnet_opt} \ ${ovn_v6_masquerade_subnet_opt} \ - ${dns_name_resolver_enabled_flag} \ ${persistent_ips_enabled_flag} \ - ${multi_network_enabled_flag} \ - ${network_segmentation_enabled_flag} \ - ${network_connect_enabled_flag} \ ${route_advertisements_enable_flag} \ - ${evpn_enable_flag} \ - ${preconfigured_udn_addresses_enable_flag} \ - --enable-egress-ip=true \ - --enable-egress-firewall=true \ - --enable-egress-qos=true \ - --enable-egress-service=true \ - --enable-multicast \ - --enable-multi-external-gateway=true \ - ${multi_network_policy_enabled_flag} \ - ${admin_network_policy_enabled_flag} + ${evpn_enable_flag} volumeMounts: - mountPath: /run/ovnkube-config/ name: ovnkube-config diff --git a/bindata/network/ovn-kubernetes/self-hosted/004-config.yaml b/bindata/network/ovn-kubernetes/self-hosted/004-config.yaml index 44a4cbbc69..6c1944f7b8 100644 --- a/bindata/network/ovn-kubernetes/self-hosted/004-config.yaml +++ b/bindata/network/ovn-kubernetes/self-hosted/004-config.yaml @@ -36,21 +36,24 @@ data: dns-service-name="dns-default" [ovnkubernetesfeature] - + enable-egress-ip=true + enable-egress-firewall=true + enable-egress-qos=true + enable-egress-service=true {{- if .ReachabilityTotalTimeoutSeconds }} egressip-reachability-total-timeout={{.ReachabilityTotalTimeoutSeconds}} {{- end }} {{- if .ReachabilityNodePort }} egressip-node-healthcheck-port={{.ReachabilityNodePort}} {{- end }} - {{- if not .OVN_MULTI_NETWORK_ENABLE }} enable-multi-network=true - {{- end }} enable-network-segmentation=true enable-preconfigured-udn-addresses=true {{- if .OVN_MULTI_NETWORK_POLICY_ENABLE }} enable-multi-networkpolicy=true {{- end }} + enable-admin-network-policy=true + enable-multi-external-gateway=true {{- if .DNS_NAME_RESOLVER_ENABLE }} enable-dns-name-resolver=true {{- end }} diff --git a/bindata/network/ovn-kubernetes/self-hosted/ovnkube-control-plane.yaml b/bindata/network/ovn-kubernetes/self-hosted/ovnkube-control-plane.yaml index 1795b662b4..178ef3239e 100644 --- a/bindata/network/ovn-kubernetes/self-hosted/ovnkube-control-plane.yaml +++ b/bindata/network/ovn-kubernetes/self-hosted/ovnkube-control-plane.yaml @@ -130,19 +130,8 @@ spec: ovn_v6_masquerade_subnet_opt="--gateway-v6-masquerade-subnet {{.V6MasqueradeSubnet}}" fi - dns_name_resolver_enabled_flag= - if [[ "{{.DNS_NAME_RESOLVER_ENABLE}}" == "true" ]]; then - dns_name_resolver_enabled_flag="--enable-dns-name-resolver" - fi - persistent_ips_enabled_flag="--enable-persistent-ips" - # This is needed so that converting clusters from GA to TP - # will rollout control plane pods as well - network_segmentation_enabled_flag= - multi_network_enabled_flag="--enable-multi-network" - network_segmentation_enabled_flag="--enable-network-segmentation" - route_advertisements_enable_flag= if [[ "{{.OVN_ROUTE_ADVERTISEMENTS_ENABLE}}" == "true" ]]; then route_advertisements_enable_flag="--enable-route-advertisements" @@ -152,22 +141,6 @@ spec: if [[ "{{.OVN_EVPN_ENABLE}}" == "true" ]]; then evpn_enable_flag="--enable-evpn" fi - - preconfigured_udn_addresses_enable_flag="--enable-preconfigured-udn-addresses" - - # Enable multi-network policy if configured (control-plane always full mode) - multi_network_policy_enabled_flag= - if [[ "{{.OVN_MULTI_NETWORK_POLICY_ENABLE}}" == "true" ]]; then - multi_network_policy_enabled_flag="--enable-multi-networkpolicy" - fi - - # Enable admin network policy if configured (control-plane always full mode) - admin_network_policy_enabled_flag="--enable-admin-network-policy" - - network_connect_enabled_flag= - if [[ "{{.OVN_NETWORK_CONNECT_ENABLE}}" == "true" ]]; then - network_connect_enabled_flag="--enable-network-connect" - fi if [ "{{.OVN_GATEWAY_MODE}}" == "shared" ]; then gateway_mode_flags="--gateway-mode shared" @@ -181,6 +154,7 @@ spec: echo "I$(date "+%m%d %H:%M:%S.%N") - ovnkube-control-plane - start ovnkube --init-cluster-manager ${K8S_NODE}" exec /usr/bin/ovnkube \ --enable-interconnect \ + --enable-multicast \ --init-cluster-manager "${K8S_NODE}" \ --config-file=/run/ovnkube-config/ovnkube.conf \ --loglevel "${OVN_KUBE_LOG_LEVEL}" \ @@ -193,23 +167,11 @@ spec: ${ovn_v6_transit_switch_subnet_opt} \ ${ovn_v4_masquerade_subnet_opt} \ ${ovn_v6_masquerade_subnet_opt} \ - ${dns_name_resolver_enabled_flag} \ ${persistent_ips_enabled_flag} \ - ${multi_network_enabled_flag} \ - ${network_segmentation_enabled_flag} \ ${gateway_mode_flags} \ ${network_connect_enabled_flag} \ ${route_advertisements_enable_flag} \ - ${evpn_enable_flag} \ - ${preconfigured_udn_addresses_enable_flag} \ - --enable-egress-ip=true \ - --enable-egress-firewall=true \ - --enable-egress-qos=true \ - --enable-egress-service=true \ - --enable-multicast \ - --enable-multi-external-gateway=true \ - ${multi_network_policy_enabled_flag} \ - ${admin_network_policy_enabled_flag} + ${evpn_enable_flag} volumeMounts: - mountPath: /run/ovnkube-config/ name: ovnkube-config diff --git a/docs/architecture.md b/docs/architecture.md index 6a2339eba8..289e06a3c0 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -141,12 +141,6 @@ The Network operator needs to make sure that the input configuration doesn't cha The persisted configuration must **make all defaults explicit**. This protects against inadvertent code changes that could destabilize an existing cluster. -### Per-Node Configuration - -For certain specialized deployments (e.g., DPU host nodes), some features need to be disabled on a per-node basis even when enabled cluster-wide. Since ConfigMap values cannot be reliably overridden per-node, the CNO implements per-node feature enforcement through conditional logic in the startup scripts. - -The `OVN_NODE_MODE` environment variable is injected into `ovnkube-node` pods and consumed by the startup script (`008-script-lib.yaml`) to conditionally enable or disable features based on the node's operational mode. This ensures that unsupported features are deterministically disabled on specialized hardware regardless of cluster-wide configuration. - ## Egress Router **Input:** `EgressRouter.network.operator.openshift.io` diff --git a/docs/operands.md b/docs/operands.md index a64a00bd0d..64016fff6b 100644 --- a/docs/operands.md +++ b/docs/operands.md @@ -91,26 +91,6 @@ configuration object (which in turn is copied there from the configuration) is "`OVNKubernetes`". If the specified network type is not "`OVNKubernetes`", the CNO will not render any network plugin. -### OVN-Kubernetes Node Modes - -OVN-Kubernetes supports different node operational modes through the `OVN_NODE_MODE` -environment variable. This allows per-node feature enforcement, particularly for -specialized hardware like DPU (Data Processing Unit) hosts where certain features -must be disabled. - -The startup script (`008-script-lib.yaml`) contains conditional logic that adjusts -feature enablement based on the node mode: - -- **`full` mode (default)**: All features enabled as configured -- **`dpu-host` mode**: Certain features like egress IP, multicast, multi-network - policies, and admin network policies are automatically disabled regardless of - cluster-wide configuration - -This approach was necessary because ConfigMap values (`004-config.yaml`) cannot be -reliably overridden on a per-node basis, but startup script logic can be conditional. - -For detailed information, see `docs/ovn_node_mode.md`. - ## Multus Multus is deployed as long as `.spec.disableMultiNetwork` is not set. diff --git a/docs/ovn_node_mode.md b/docs/ovn_node_mode.md index d64861b10b..b511eea598 100644 --- a/docs/ovn_node_mode.md +++ b/docs/ovn_node_mode.md @@ -1,100 +1,18 @@ -## OVN node modes and per-node feature enforcement +## OVN node modes -This change introduces `OVN_NODE_MODE` as an environment variable injected into the `ovnkube-node` Pod. The value is consumed by the startup script rendered from `bindata/network/ovn-kubernetes/common/008-script-lib.yaml` to tailor behavior per node mode at runtime. - -### Why move flags from the config map into the script? - -- The INI-based config (`004-config.yaml`) is rendered cluster-wide. Those values are not reliably overridable on a per-node or per-mode basis. -- In DPU host mode, some features are not supported and must be deterministically disabled on those nodes even if the cluster-wide config enables them. -- Moving the enablement logic to the entrypoint script allows per-node enforcement using `OVN_NODE_MODE`, preventing unsupported features from being turned on by cluster defaults. +The `OVN_NODE_MODE` environment variable is injected into the `ovnkube-node` Pod to identify the node's operational mode. It is consumed by the startup script rendered from `bindata/network/ovn-kubernetes/common/008-script-lib.yaml`. ### Behavior by mode - `full` (default): - - `gateway_interface=br-ex` + - `gateway_interface="br-ex"` - `init_ovnkube_controller="--init-ovnkube-controller ${K8S_NODE}"` - - `enable_multicast_flag="--enable-multicast"` - - `egress_features_enable_flag="--enable-egress-ip=true --enable-egress-firewall=true --enable-egress-qos=true --enable-egress-service=true"` - - `multi_external_gateway_enable_flag="--enable-multi-external-gateway=true"` - `dpu-host`: - - `gateway_interface="derive-from-mgmt-port"` + - `gateway_interface="derive-from-mgmt-port"` ([ovn-kubernetes#5327](https://github.com/ovn-kubernetes/ovn-kubernetes/pull/5327)) - `ovnkube_node_mode="--ovnkube-node-mode dpu-host"` - `init_ovnkube_controller=""` (disabled) - - `enable_multicast_flag=""` (disabled) - - `egress_features_enable_flag=""` (egress IP and related features disabled) - - `multi_external_gateway_enable_flag=""` (multi-external gateway disabled) - - Multi-network, network segmentation, and multi-network policy/admin network policy are gated and not enabled in this mode. - -### Manifests - -- `ovnkube-node.yaml` (managed and self-hosted) now inject `OVN_NODE_MODE` into the Pod env so the script can apply mode-aware logic. -- `ovnkube-control-plane.yaml` (managed and self-hosted) have feature flags moved from ConfigMap to inline script logic. -- `004-config.yaml` drops hard-coded feature enables that conflict with per-node enforcement. - -**Note**: Control-plane components always run in "full" mode since they don't run on DPU hosts and need all features enabled for cluster coordination. Always-enabled features (egress, multicast, multi-external-gateway) are added directly to the command line, while conditional features use script variables. - -### Implementation Details - -#### Environment Variable Injection - -The `OVN_NODE_MODE` environment variable is injected into `ovnkube-node` pods through the DaemonSet specification in both managed and self-hosted variants: - -- `bindata/network/ovn-kubernetes/managed/ovnkube-node.yaml` -- `bindata/network/ovn-kubernetes/self-hosted/ovnkube-node.yaml` - -The value is typically derived from node labels or annotations that identify the node's hardware type. - -#### Script Logic Flow - -The startup script (`008-script-lib.yaml`) implements the following conditional logic: - -```bash -if [[ "${OVN_NODE_MODE}" != "dpu-host" ]]; then - # Enable features for full mode - egress_ip_enable_flag="--enable-egress-ip=true --enable-egress-firewall=true --enable-egress-qos=true --enable-egress-service=true" - enable_multicast_flag="--enable-multicast" - # ... other feature flags -else - # DPU host mode - disable features - egress_ip_enable_flag="" - enable_multicast_flag="" - gateway_interface="derive-from-mgmt-port" - ovnkube_node_mode="--ovnkube-node-mode dpu-host" -fi -``` - -#### Feature Flag Mapping - -The following table shows how cluster-wide configuration translates to per-node enforcement: - -| Feature | ConfigMap (004-config.yaml) | Script Variable | DPU Host Behavior | -|---------|----------------------------|-----------------|-------------------| -| Egress IP | `enable-egress-ip=true` | `egress_features_enable_flag` | Force disabled | -| Multicast | `enable-multicast=true` | `enable_multicast_flag` | Force disabled | -| Multi External Gateway | `enable-multi-external-gateway=true` | `multi_external_gateway_enable_flag` | Force disabled | -| Multi-network | `enable-multi-network=true` | `multi_network_enabled_flag` | Conditionally disabled | -| Admin Network Policy | `enable-admin-network-policy=true` | `admin_network_policy_enabled_flag` | Conditionally disabled | -| Network Segmentation | `enable-network-segmentation=true` | `network_segmentation_enabled_flag` | Conditionally disabled | - -### Testing - -- Unit tests assert that the rendered script contains the correct assignments for `gateway_interface`, `init_ovnkube_controller`, `enable_multicast_flag`, `egress_features_enable_flag`, and `ovnkube_node_mode` across modes. -- The comprehensive test `TestOVNKubernetesScriptLibCombined` validates all conditional logic paths and feature flag assignments for node scripts. -- The test `TestOVNKubernetesControlPlaneFlags` validates that control-plane scripts have: - - Always-enabled features added directly to the command line (egress, multicast, multi-external-gateway) - - Conditional features handled via script variables (multi-network, network policies, etc.) - - Correct multi-network enablement logic (OVN_MULTI_NETWORK_ENABLE) -- Tests verify both positive cases (features enabled in full mode) and negative cases (features disabled in DPU host mode). - -### Migration Notes - -When upgrading clusters that previously relied on ConfigMap-based feature control: - -1. Existing ConfigMap values in `004-config.yaml` have been removed for features that require per-node control -2. The startup scripts (both node and control-plane) now contain the authoritative feature enablement logic -3. Control-plane components automatically enable all features (always run in "full" mode) -4. DPU host nodes will automatically have incompatible features disabled regardless of previous ConfigMap settings -5. No manual intervention is required - the migration is handled automatically during the upgrade process +### Feature configuration +Feature enablement (egress IP, multicast, multi-network, network segmentation, admin network policy, etc.) is managed through the cluster-wide ConfigMap (`004-config.yaml`) which is passed to ovnkube via `--config-file`. These features are not gated per node mode. diff --git a/pkg/network/ovn_kubernetes_test.go b/pkg/network/ovn_kubernetes_test.go index 98a1689b03..d8e1e6fbfc 100644 --- a/pkg/network/ovn_kubernetes_test.go +++ b/pkg/network/ovn_kubernetes_test.go @@ -296,9 +296,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -335,9 +342,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=local @@ -387,10 +401,17 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-reachability-total-timeout=3 egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=local @@ -442,10 +463,17 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-reachability-total-timeout=0 egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=local @@ -497,9 +525,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=local @@ -551,9 +586,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -594,9 +636,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -640,9 +689,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -679,10 +735,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -720,10 +782,17 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true enable-multi-networkpolicy=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -762,9 +831,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -801,10 +877,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -843,9 +925,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true enable-dns-name-resolver=true [gateway] @@ -883,9 +972,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true [gateway] mode=shared @@ -922,9 +1018,16 @@ dns-service-namespace="openshift-dns" dns-service-name="dns-default" [ovnkubernetesfeature] +enable-egress-ip=true +enable-egress-firewall=true +enable-egress-qos=true +enable-egress-service=true egressip-node-healthcheck-port=9107 +enable-multi-network=true enable-network-segmentation=true enable-preconfigured-udn-addresses=true +enable-admin-network-policy=true +enable-multi-external-gateway=true enable-network-connect=true [gateway] @@ -4008,92 +4111,6 @@ func extractOVNScriptLib(g *WithT, objs []*uns.Unstructured) string { return "" } -// renderControlPlaneWithOverrides renders using the full render path and returns -// the embedded startup script from the control-plane container. -func renderControlPlaneWithOverrides(t *testing.T, variant string, overrides map[string]interface{}) string { - g := NewGomegaWithT(t) - crd := OVNKubernetesConfig.DeepCopy() - config := &crd.Spec - fillDefaults(config, nil) - - bs := fakeBootstrapResult() - bs.OVN = bootstrap.OVNBootstrapResult{ - ControlPlaneReplicaCount: 1, - OVNKubernetesConfig: &bootstrap.OVNConfigBoostrapResult{ - DpuHostModeLabel: OVN_NODE_SELECTOR_DEFAULT_DPU_HOST, - DpuModeLabel: OVN_NODE_SELECTOR_DEFAULT_DPU, - SmartNicModeLabel: OVN_NODE_SELECTOR_DEFAULT_SMART_NIC, - HyperShiftConfig: &bootstrap.OVNHyperShiftBootstrapResult{Enabled: false}, - ConfigOverrides: toStringMap(overrides), - }, - } - - featureGatesCNO := getDefaultFeatureGates() - fakeClient := cnofake.NewFakeClient() - objs, _, err := renderOVNKubernetes(config, bs, manifestDirOvn, fakeClient, featureGatesCNO) - g.Expect(err).NotTo(HaveOccurred()) - - var script string - for _, obj := range objs { - if obj.GetKind() == "Deployment" && obj.GetName() == "ovnkube-control-plane" && obj.GetNamespace() == "openshift-ovn-kubernetes" { - containers, found, err := uns.NestedSlice(obj.Object, "spec", "template", "spec", "containers") - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(found).To(BeTrue()) - for _, c := range containers { - cm := c.(map[string]interface{}) - if name, ok := cm["name"]; ok && (name == "ovnkube-cluster-manager" || name == "ovnkube-control-plane") { - command, found, err := uns.NestedSlice(cm, "command") - g.Expect(err).NotTo(HaveOccurred()) - g.Expect(found).To(BeTrue()) - g.Expect(len(command)).To(BeNumerically(">", 2)) - script = command[2].(string) - break - } - } - } - } - g.Expect(script).NotTo(BeEmpty()) - return script -} - -// renderScriptLibWithOverrides renders using the full render path and returns -// the ovnkube script-lib content. -func renderScriptLibWithOverrides(t *testing.T, overrides map[string]interface{}) string { - g := NewGomegaWithT(t) - crd := OVNKubernetesConfig.DeepCopy() - config := &crd.Spec - fillDefaults(config, nil) - - bs := fakeBootstrapResult() - bs.OVN = bootstrap.OVNBootstrapResult{ - ControlPlaneReplicaCount: 1, - OVNKubernetesConfig: &bootstrap.OVNConfigBoostrapResult{ - DpuHostModeLabel: OVN_NODE_SELECTOR_DEFAULT_DPU_HOST, - DpuModeLabel: OVN_NODE_SELECTOR_DEFAULT_DPU, - SmartNicModeLabel: OVN_NODE_SELECTOR_DEFAULT_SMART_NIC, - HyperShiftConfig: &bootstrap.OVNHyperShiftBootstrapResult{Enabled: false}, - ConfigOverrides: toStringMap(overrides), - }, - } - featureGatesCNO := getDefaultFeatureGates() - fakeClient := cnofake.NewFakeClient() - objs, _, err := renderOVNKubernetes(config, bs, manifestDirOvn, fakeClient, featureGatesCNO) - g.Expect(err).NotTo(HaveOccurred()) - return extractOVNScriptLib(g, objs) -} - -// helper to convert map[string]interface{} -> map[string]string for overrides -func toStringMap(in map[string]interface{}) map[string]string { - if in == nil { - return nil - } - out := make(map[string]string, len(in)) - for k, v := range in { - out[k] = fmt.Sprintf("%v", v) - } - return out -} - // checkDaemonsetAnnotation check that all the daemonset have the annotation with the // same key and value func checkDaemonsetAnnotation(g *WithT, objs []*uns.Unstructured, key, value string) bool { @@ -4450,220 +4467,6 @@ func TestRenderOVNKubernetes_AllowICMPNetworkPolicyOverride(t *testing.T) { }) } -func TestOVNKubernetesControlPlaneFlags(t *testing.T) { - g := NewGomegaWithT(t) - - testCases := []struct { - name string - variant string - overrides map[string]interface{} - mustContain []string - mustNotContain []string - }{ - { - name: "self-hosted control-plane: always-enabled features", - variant: "self-hosted", - overrides: map[string]interface{}{ - "OVN_OBSERVABILITY_ENABLE": "false", - "OVN_MULTI_NETWORK_POLICY_ENABLE": "false", - }, - mustContain: []string{ - "--enable-egress-ip=true", - "--enable-egress-firewall=true", - "--enable-egress-qos=true", - "--enable-egress-service=true", - "--enable-multicast", - "--enable-multi-external-gateway=true", - }, - mustNotContain: []string{ - "egress_features_enable_flag=", - "enable_multicast_flag=", - "multi_external_gateway_enable_flag=", - }, - }, - - { - name: "self-hosted control-plane: conditional features enabled", - variant: "self-hosted", - overrides: map[string]interface{}{ - "OVN_MULTI_NETWORK_POLICY_ENABLE": "true", - }, - mustContain: []string{ - "--enable-egress-ip=true", - "--enable-multicast", - "--enable-multi-external-gateway=true", - "multi_network_policy_enabled_flag=\"--enable-multi-networkpolicy\"", - "admin_network_policy_enabled_flag=\"--enable-admin-network-policy\"", - }, - mustNotContain: []string{ - "network_observability_enabled_flag=", - }, - }, - { - name: "self-hosted control-plane: multi-network enabled", - variant: "self-hosted", - overrides: map[string]interface{}{ - "OVN_MULTI_NETWORK_ENABLE": "true", - }, - mustContain: []string{ - "--enable-egress-ip=true", - "--enable-multicast", - "--enable-multi-external-gateway=true", - "multi_network_enabled_flag=\"--enable-multi-network\"", - }, - mustNotContain: []string{}, - }, - { - name: "self-hosted control-plane: network segmentation enabled (auto-enables multi-network)", - variant: "self-hosted", - overrides: map[string]interface{}{ - "OVN_MULTI_NETWORK_ENABLE": "false", - }, - mustContain: []string{ - "--enable-egress-ip=true", - "--enable-multicast", - "--enable-multi-external-gateway=true", - "multi_network_enabled_flag=\"--enable-multi-network\"", - "network_segmentation_enabled_flag=\"--enable-network-segmentation\"", - }, - mustNotContain: []string{}, - }, - { - name: "self-hosted control-plane: both multi-network and segmentation enabled", - variant: "self-hosted", - overrides: map[string]interface{}{ - "OVN_MULTI_NETWORK_ENABLE": "true", - }, - mustContain: []string{ - "--enable-egress-ip=true", - "--enable-multicast", - "--enable-multi-external-gateway=true", - "multi_network_enabled_flag=\"--enable-multi-network\"", - "network_segmentation_enabled_flag=\"--enable-network-segmentation\"", - }, - mustNotContain: []string{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - script := renderControlPlaneWithOverrides(t, tc.variant, tc.overrides) - for _, s := range tc.mustContain { - g.Expect(script).To(ContainSubstring(s), "Expected to find: %s", s) - } - for _, s := range tc.mustNotContain { - g.Expect(script).NotTo(ContainSubstring(s), "Expected NOT to find: %s", s) - } - }) - } -} - -func TestOVNKubernetesScriptLibCombined(t *testing.T) { - g := NewGomegaWithT(t) - - renderScript := func(overrides map[string]interface{}) string { - return renderScriptLibWithOverrides(t, overrides) - } - - testCases := []struct { - name string - overrides map[string]interface{} - mustContain []string - mustNotContain []string - }{ - { - name: "dpu-host gating and egress/policy disable", - overrides: map[string]interface{}{ - "OVN_NODE_MODE": "dpu-host", - "OVN_MULTI_NETWORK_ENABLE": "true", - "OVN_MULTI_NETWORK_POLICY_ENABLE": "true", - }, - mustContain: []string{ - "gateway_interface=\"derive-from-mgmt-port\"", - "init_ovnkube_controller=\"\"", - "enable_multicast_flag=\"\"", - "egress_features_enable_flag=\"\"", - "multi_external_gateway_enable_flag=\"\"", - "ovnkube_node_mode=\"--ovnkube-node-mode dpu-host\"", - "multi_network_enabled_flag=", - "network_segmentation_enabled_flag=", - "multi_network_policy_enabled_flag=", - "admin_network_policy_enabled_flag=", - }, - mustNotContain: []string{}, - }, - { - name: "full mode with multi-network features enabled", - overrides: map[string]interface{}{ - "OVN_NODE_MODE": "full", - "OVN_MULTI_NETWORK_ENABLE": "true", - "OVN_MULTI_NETWORK_POLICY_ENABLE": "true", - }, - mustContain: []string{ - "gateway_interface=br-ex", - "init_ovnkube_controller=\"--init-ovnkube-controller ${K8S_NODE}\"", - "enable_multicast_flag=\"--enable-multicast\"", - "egress_features_enable_flag=\"--enable-egress-ip=true --enable-egress-firewall=true --enable-egress-qos=true --enable-egress-service=true\"", - "multi_external_gateway_enable_flag=\"--enable-multi-external-gateway=true\"", - "multi_network_enabled_flag=\"--enable-multi-network\"", - "network_segmentation_enabled_flag=\"--enable-network-segmentation\"", - "multi_network_policy_enabled_flag=\"--enable-multi-networkpolicy\"", - "admin_network_policy_enabled_flag=\"--enable-admin-network-policy\"", - }, - mustNotContain: []string{}, - }, - { - name: "non-mode-gated features enabled", - overrides: map[string]interface{}{ - "OVN_NODE_MODE": "full", - "OVN_ROUTE_ADVERTISEMENTS_ENABLE": "true", - "OVN_PRE_CONF_UDN_ADDR_ENABLE": "true", - "OVN_OBSERVABILITY_ENABLE": "true", - "DNS_NAME_RESOLVER_ENABLE": "true", - "NETWORK_NODE_IDENTITY_ENABLE": "true", - }, - mustContain: []string{ - "route_advertisements_enable_flag=\"--enable-route-advertisements\"", - "preconfigured_udn_addresses_enable_flag=\"--enable-preconfigured-udn-addresses\"", - "network_observability_enabled_flag=\"--enable-observability\"", - "dns_name_resolver_enabled_flag=\"--enable-dns-name-resolver\"", - "ip_forwarding_flag=\"--disable-forwarding\"", - "--bootstrap-kubeconfig=/var/lib/kubelet/kubeconfig", - }, - mustNotContain: []string{}, - }, - { - name: "full mode: multi-network features disabled", - overrides: map[string]interface{}{ - "OVN_NODE_MODE": "full", - "OVN_MULTI_NETWORK_ENABLE": "false", - "OVN_MULTI_NETWORK_POLICY_ENABLE": "false", - }, - mustContain: []string{ - "multi_network_enabled_flag=", - "network_segmentation_enabled_flag=", - "multi_network_policy_enabled_flag=", - "admin_network_policy_enabled_flag=", - }, - mustNotContain: []string{}, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - script := renderScript(tc.overrides) - for _, s := range tc.mustContain { - g.Expect(script).To(ContainSubstring(s)) - } - for _, s := range tc.mustNotContain { - g.Expect(script).NotTo(ContainSubstring(s)) - } - // Ensure gateway flags use the variable rather than a hardcoded iface - g.Expect(script).To(ContainSubstring("--gateway-interface ${gateway_interface}")) - }) - } -} - // TestDaemonSetProgressing verifies daemonSetProgressing returns the correct // result for a variety of DaemonSet status scenarios, including the zero-worker // HyperShift case where DesiredNumberScheduled==0 must not be treated as progressing.