Skip to content

Commit 7bc4d04

Browse files
authored
Merge pull request #559 from stackhpc/upstream/master-2026-05-04
Synchronise master with upstream
2 parents a3653f9 + 3af169b commit 7bc4d04

19 files changed

Lines changed: 371 additions & 41 deletions

ansible/inventory/group_vars/all/network

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ network_patch_suffix_phy: '-phy'
8080
# OVS bridge.
8181
network_patch_suffix_ovs: '-ovs'
8282

83+
###############################################################################
84+
# OpenStack Network configuration
85+
86+
# Whether to register networks in OpenStack. The following networks are
87+
# registered:
88+
# - Ironic provisioning network (defined by provision_wl_net_name)
89+
# - Ironic inspection network (defined by inspection_net_name)
90+
# - Ironic cleaning network (defined by cleaning_net_name)
91+
# Default is true.
92+
openstack_network_registration_enabled: true
93+
8394
###############################################################################
8495
# Network routing table configuration.
8596

ansible/network-connectivity.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
default(100) }}
88
vars:
99
# Skip external connectivity check when behind a proxy.
10-
nc_skip_external_net: "{{ http_proxy | truthy }}"
10+
nc_skip_external_net: "{{ http_proxy is truthy }}"
1111
# Set this to an external IP address to check.
1212
nc_external_ip: 8.8.8.8
1313
# Set this to an external hostname to check.

ansible/provision-net.yml

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -68,29 +68,31 @@
6868
- cleaning-net
6969
- inspection-net
7070
tasks:
71-
- name: Validate OpenStack password authentication parameters
72-
fail:
73-
msg: >
74-
Required OpenStack authentication parameter {{ item }} is
75-
{% if item in openstack_auth %}empty{% else %}not present{% endif %}
76-
in openstack_auth. Have you sourced the environment file?
77-
when:
78-
- openstack_auth_type == 'password'
79-
- item not in openstack_auth or not openstack_auth[item]
80-
with_items: "{{ openstack_auth_password_required_params }}"
81-
tags:
82-
- config-validation
71+
- block:
72+
- name: Validate OpenStack password authentication parameters
73+
fail:
74+
msg: >
75+
Required OpenStack authentication parameter {{ item }} is
76+
{% if item in openstack_auth %}empty{% else %}not present{% endif %}
77+
in openstack_auth. Have you sourced the environment file?
78+
when:
79+
- openstack_auth_type == 'password'
80+
- item not in openstack_auth or not openstack_auth[item]
81+
with_items: "{{ openstack_auth_password_required_params }}"
82+
tags:
83+
- config-validation
8384

84-
- import_role:
85-
name: stackhpc.openstack.os_networks
86-
vars:
87-
os_openstacksdk_install_epel: "{{ dnf_install_epel }}"
88-
os_openstacksdk_state: "latest"
89-
os_networks_upper_constraints_file: "{{ openstacksdk_upper_constraints_file }}"
90-
os_networks_venv: "{{ venv }}"
91-
os_networks_auth_type: "{{ openstack_auth_type }}"
92-
os_networks_auth: "{{ openstack_auth }}"
93-
os_networks_cacert: "{{ openstack_cacert | default(omit, true) }}"
94-
os_networks_interface: "{{ openstack_interface | default(omit, true) }}"
95-
# Network configuration.
96-
os_networks: "{{ network_registrations }}"
85+
- import_role:
86+
name: stackhpc.openstack.os_networks
87+
vars:
88+
os_openstacksdk_install_epel: "{{ dnf_install_epel }}"
89+
os_openstacksdk_state: "latest"
90+
os_networks_upper_constraints_file: "{{ openstacksdk_upper_constraints_file }}"
91+
os_networks_venv: "{{ venv }}"
92+
os_networks_auth_type: "{{ openstack_auth_type }}"
93+
os_networks_auth: "{{ openstack_auth }}"
94+
os_networks_cacert: "{{ openstack_cacert | default(omit, true) }}"
95+
os_networks_interface: "{{ openstack_interface | default(omit, true) }}"
96+
# Network configuration.
97+
os_networks: "{{ network_registrations }}"
98+
when: openstack_network_registration_enabled | bool

doc/source/configuration/reference/network.rst

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,26 @@ The following attributes are supported:
616616
bond and bridge interfaces, settings apply to underlying interfaces. This
617617
should be a string of arguments passed to the ``ethtool`` utility, for
618618
example ``"-G ${DEVICE} rx 8192 tx 8192"``.
619+
``ingress_qos_map``
620+
.. note::
621+
622+
``ingress_qos_map`` is only supported with
623+
``network_engine: nmstate`` on VLAN interfaces.
624+
625+
VLAN ingress QoS map configuration. This maps VLAN header Priority Code
626+
Point (PCP) to Linux internal packet priority for incoming packets.
627+
628+
- Structured list: ``[{from: 7, to: 254}, {from: 3, to: 12}]``
629+
``egress_qos_map``
630+
.. note::
631+
632+
``egress_qos_map`` is only supported with
633+
``network_engine: nmstate`` on VLAN interfaces.
634+
635+
VLAN egress QoS map configuration. This maps Linux internal packet
636+
priority to VLAN header Priority Code Point (PCP) for outgoing packets.
637+
638+
- Structured list: ``[{from: 129, to: 7}, {from: 130, to: 6}]``
619639
``zone``
620640
.. note:: ``zone`` is not currently supported on Ubuntu.
621641

@@ -765,6 +785,29 @@ this case, a ``parent`` attribute must specify the underlying interface:
765785
Ethernet interfaces, bridges, and bond master interfaces may all be parents to
766786
a VLAN interface.
767787

788+
VLAN QoS Mapping (nmstate)
789+
^^^^^^^^^^^^^^^^^^^^^^^^^^
790+
791+
When using ``network_engine: nmstate``, VLAN interfaces can define native
792+
nmstate QoS maps via ``ingress_qos_map`` and ``egress_qos_map``. These map
793+
between Linux internal packet priority and VLAN header Priority Code Point
794+
(PCP). Use the structured list-of-maps form.
795+
796+
.. code-block:: yaml
797+
:caption: ``inventory/group_vars/<group>/network-interfaces``
798+
799+
example_interface: "eth2.{{ example_vlan }}"
800+
example_ingress_qos_map:
801+
- from: 7
802+
to: 254
803+
example_egress_qos_map:
804+
- from: 129
805+
to: 7
806+
807+
Undefined QoS map values do not render an nmstate QoS map key. Use an
808+
explicit empty list (``[]``) to render an empty map through nmstate, for
809+
example when clearing an existing map.
810+
768811
Bridges and VLANs
769812
^^^^^^^^^^^^^^^^^
770813

@@ -926,6 +969,27 @@ consideration:
926969
lookup the interface for the cloud-init network configuration that occurs
927970
during bifrost provisioning of the overcloud.
928971

972+
973+
Registration of networks in OpenStack
974+
--------------------------------------
975+
976+
By default, Kayobe will register the following networks:
977+
978+
* :ref:`workload-cleaning-network`
979+
* :ref:`workload-provisioning-network`
980+
* :ref:`workload-inspection-network`
981+
982+
as part of ``kayobe overcloud post configure``. You can change this behaviour
983+
for all networks with the ``openstack_network_registration_enabled`` variable:
984+
985+
.. code-block:: yaml
986+
:caption: ``networks.yml``
987+
988+
# Disabling registration of OpenStack networks
989+
openstack_network_registration_enabled: false
990+
991+
This can be useful if you define you networks by some other means e.g OpenTofu.
992+
929993
Overcloud Provisioning Network
930994
------------------------------
931995

@@ -951,6 +1015,8 @@ To configure a network called ``example`` with an inspection allocation pool:
9511015
This pool should not overlap with a kayobe allocation pool on the same
9521016
network.
9531017

1018+
.. _workload-cleaning-network:
1019+
9541020
Workload Cleaning Network
9551021
-------------------------
9561022

@@ -980,6 +1046,8 @@ allocation pool:
9801046
This pool should not overlap with a kayobe or inspection allocation pool on
9811047
the same network.
9821048

1049+
.. _workload-provisioning-network:
1050+
9831051
Workload Provisioning Network
9841052
-----------------------------
9851053

@@ -1007,6 +1075,8 @@ allocation pool:
10071075
This pool should not overlap with a kayobe or inspection allocation pool on
10081076
the same network.
10091077

1078+
.. _workload-inspection-network:
1079+
10101080
Workload Inspection Network
10111081
---------------------------
10121082

etc/kayobe/networks.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@
9696
# OVS bridge.
9797
#network_patch_suffix_ovs:
9898

99+
###############################################################################
100+
# OpenStack Network configuration
101+
102+
# Whether to register networks in OpenStack. The following networks are
103+
# registered:
104+
# - Ironic provisioning network (defined by provision_wl_net_name)
105+
# - Ironic inspection network (defined by inspection_net_name)
106+
# - Ironic cleaning network (defined by cleaning_net_name)
107+
# Default is true.
108+
#openstack_network_registration_enabled:
109+
99110
###############################################################################
100111
# Network routing table configuration.
101112

kayobe/cli/commands.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,20 @@ def take_action(self, parsed_args):
462462
ignore_limit=True, check=False)
463463

464464

465+
class ControlHostServiceDeploy(KayobeAnsibleMixin, VaultMixin, Command):
466+
"""Deploy the Ansible control host services."""
467+
468+
def take_action(self, parsed_args):
469+
self.app.LOG.debug("Running no-op control host service deploy")
470+
471+
472+
class ControlHostServiceDestroy(KayobeAnsibleMixin, VaultMixin, Command):
473+
"""Destroy the Ansible control host services."""
474+
475+
def take_action(self, parsed_args):
476+
self.app.LOG.debug("Running no-op control host service destroy")
477+
478+
465479
class ConfigurationDump(KayobeAnsibleMixin, VaultMixin, Command):
466480
"""Dump Kayobe configuration.
467481
@@ -1202,12 +1216,24 @@ def take_action(self, parsed_args):
12021216

12031217
class InfraVMServiceDeploy(KayobeAnsibleMixin, VaultMixin,
12041218
Command):
1205-
"""Run hooks for infra structure services."""
1219+
"""Run hooks for infra VM services."""
12061220

12071221
def take_action(self, parsed_args):
12081222
self.app.LOG.debug("Running no-op Infra VM service deploy")
12091223

12101224

1225+
class InfraVMServiceDestroy(KayobeAnsibleMixin, VaultMixin,
1226+
Command):
1227+
"""Destroy the infra VM services.
1228+
1229+
Permanently destroy the infra VM containers, container images, and
1230+
container volumes.
1231+
"""
1232+
1233+
def take_action(self, parsed_args):
1234+
self.app.LOG.debug("Running no-op Infra VM service destroy")
1235+
1236+
12111237
class OvercloudInventoryDiscover(KayobeAnsibleMixin, VaultMixin, Command):
12121238
"""Discover the overcloud inventory from the seed's Ironic service.
12131239

kayobe/plugins/filter/nmstate.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ def _get_ip_config(context, name, inventory_hostname, defroute=None):
6565
'rx', 'tx', 'rx-max', 'tx-max', 'rx-jumbo', 'rx-mini'
6666
}
6767

68+
MAX_U32 = 2**32 - 1
69+
MAX_VLAN_PRIORITY = 7
70+
6871

6972
def _resolve_ethtool_feature_aliases(features):
7073
"""Convert ethtool feature aliases to canonical names.
@@ -290,6 +293,79 @@ def _get_bond_options(context, name, inventory_hostname):
290293
return bond_options
291294

292295

296+
def _validate_vlan_qos_map_int(value, network_name, direction, field):
297+
if isinstance(value, bool) or not isinstance(value, int):
298+
raise ValueError(
299+
f"Network '{network_name}' has invalid {direction} QoS map "
300+
f"'{field}' value '{value}'. Expected an integer.")
301+
302+
if value < 0 or value > MAX_U32:
303+
raise ValueError(
304+
f"Network '{network_name}' has invalid {direction} QoS map "
305+
f"'{field}' value '{value}'. Expected a value in range "
306+
f"0..{MAX_U32}.")
307+
308+
if direction == "ingress" and field == "from" \
309+
and value > MAX_VLAN_PRIORITY:
310+
raise ValueError(
311+
f"Network '{network_name}' has invalid ingress QoS map "
312+
f"'from' value '{value}'. Maximum VLAN priority is "
313+
f"{MAX_VLAN_PRIORITY}.")
314+
315+
if direction == "egress" and field == "to" \
316+
and value > MAX_VLAN_PRIORITY:
317+
raise ValueError(
318+
f"Network '{network_name}' has invalid egress QoS map "
319+
f"'to' value '{value}'. Maximum VLAN priority is "
320+
f"{MAX_VLAN_PRIORITY}.")
321+
322+
return value
323+
324+
325+
def _validate_vlan_qos_map_entry(entry, network_name, direction):
326+
if not isinstance(entry, dict):
327+
raise ValueError(
328+
f"Network '{network_name}' has invalid {direction} QoS map "
329+
"entry format. Expected a dict with keys 'from' and 'to'.")
330+
331+
if "from" not in entry or "to" not in entry:
332+
raise ValueError(
333+
f"Network '{network_name}' has invalid {direction} QoS map "
334+
"entry. Required keys are 'from' and 'to'.")
335+
336+
return {
337+
"from": _validate_vlan_qos_map_int(
338+
entry["from"], network_name, direction, "from"),
339+
"to": _validate_vlan_qos_map_int(
340+
entry["to"], network_name, direction, "to")
341+
}
342+
343+
344+
def _validate_and_sort_vlan_qos_map(raw_map, network_name, direction):
345+
if raw_map is None:
346+
return None
347+
348+
if not isinstance(raw_map, list):
349+
raise ValueError(
350+
f"Network '{network_name}' has invalid {direction} QoS map "
351+
f"format '{type(raw_map).__name__}'. Expected a list of dicts "
352+
"with keys 'from' and 'to'.")
353+
354+
normalized = []
355+
for entry in raw_map:
356+
normalized.append(_validate_vlan_qos_map_entry(
357+
entry, network_name, direction))
358+
359+
normalized.sort(key=lambda item: (item["from"], item["to"]))
360+
return normalized
361+
362+
363+
def _get_vlan_qos_map(context, name, inventory_hostname, direction):
364+
qos_map = networks.net_attr(
365+
context, name, f"{direction}_qos_map", inventory_hostname)
366+
return _validate_and_sort_vlan_qos_map(qos_map, name, direction)
367+
368+
293369
@jinja2.pass_context
294370
def nmstate_config(context, names, inventory_hostname=None):
295371
interfaces = {}
@@ -514,6 +590,17 @@ def get_iface(name):
514590
"base-iface": parent,
515591
"id": int(vlan_id)
516592
}
593+
594+
ingress_qos_map = _get_vlan_qos_map(
595+
context, name, inventory_hostname, "ingress")
596+
if ingress_qos_map is not None:
597+
iface["vlan"]["ingress-qos-map"] = ingress_qos_map
598+
599+
egress_qos_map = _get_vlan_qos_map(
600+
context, name, inventory_hostname, "egress")
601+
if egress_qos_map is not None:
602+
iface["vlan"]["egress-qos-map"] = egress_qos_map
603+
517604
# Ensure parent is initialized
518605
get_iface(parent)
519606

0 commit comments

Comments
 (0)