From 1bdb5ed4ee7513b26c12ddac3b84f827830180dd Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Mon, 15 Dec 2025 19:44:16 +0000 Subject: [PATCH 1/6] Adds option to disable registration of networks Useful if you are using OpenTofu to manage these. Change-Id: Ib18cf699d2b477fa2d4272e3fe690d17dbe17173 Signed-off-by: Will Szumski --- ansible/inventory/group_vars/all/network | 11 ++++ ansible/provision-net.yml | 52 ++++++++++--------- .../configuration/reference/network.rst | 27 ++++++++++ etc/kayobe/networks.yml | 11 ++++ ...registration-enabled-2450171db1d2b63a.yaml | 6 +++ 5 files changed, 82 insertions(+), 25 deletions(-) create mode 100644 releasenotes/notes/adds-openstack-network-registration-enabled-2450171db1d2b63a.yaml diff --git a/ansible/inventory/group_vars/all/network b/ansible/inventory/group_vars/all/network index dbb263712..49acbd3fb 100644 --- a/ansible/inventory/group_vars/all/network +++ b/ansible/inventory/group_vars/all/network @@ -80,6 +80,17 @@ network_patch_suffix_phy: '-phy' # OVS bridge. network_patch_suffix_ovs: '-ovs' +############################################################################### +# OpenStack Network configuration + +# Whether to register networks in OpenStack. The following networks are +# registered: +# - Ironic provisioning network (defined by provision_wl_net_name) +# - Ironic inspection network (defined by inspection_net_name) +# - Ironic cleaning network (defined by cleaning_net_name) +# Default is true. +openstack_network_registration_enabled: true + ############################################################################### # Network routing table configuration. diff --git a/ansible/provision-net.yml b/ansible/provision-net.yml index 94a0d1f60..f6c8b8ccb 100644 --- a/ansible/provision-net.yml +++ b/ansible/provision-net.yml @@ -68,29 +68,31 @@ - cleaning-net - inspection-net tasks: - - name: Validate OpenStack password authentication parameters - fail: - msg: > - Required OpenStack authentication parameter {{ item }} is - {% if item in openstack_auth %}empty{% else %}not present{% endif %} - in openstack_auth. Have you sourced the environment file? - when: - - openstack_auth_type == 'password' - - item not in openstack_auth or not openstack_auth[item] - with_items: "{{ openstack_auth_password_required_params }}" - tags: - - config-validation + - block: + - name: Validate OpenStack password authentication parameters + fail: + msg: > + Required OpenStack authentication parameter {{ item }} is + {% if item in openstack_auth %}empty{% else %}not present{% endif %} + in openstack_auth. Have you sourced the environment file? + when: + - openstack_auth_type == 'password' + - item not in openstack_auth or not openstack_auth[item] + with_items: "{{ openstack_auth_password_required_params }}" + tags: + - config-validation - - import_role: - name: stackhpc.openstack.os_networks - vars: - os_openstacksdk_install_epel: "{{ dnf_install_epel }}" - os_openstacksdk_state: "latest" - os_networks_upper_constraints_file: "{{ openstacksdk_upper_constraints_file }}" - os_networks_venv: "{{ venv }}" - os_networks_auth_type: "{{ openstack_auth_type }}" - os_networks_auth: "{{ openstack_auth }}" - os_networks_cacert: "{{ openstack_cacert | default(omit, true) }}" - os_networks_interface: "{{ openstack_interface | default(omit, true) }}" - # Network configuration. - os_networks: "{{ network_registrations }}" + - import_role: + name: stackhpc.openstack.os_networks + vars: + os_openstacksdk_install_epel: "{{ dnf_install_epel }}" + os_openstacksdk_state: "latest" + os_networks_upper_constraints_file: "{{ openstacksdk_upper_constraints_file }}" + os_networks_venv: "{{ venv }}" + os_networks_auth_type: "{{ openstack_auth_type }}" + os_networks_auth: "{{ openstack_auth }}" + os_networks_cacert: "{{ openstack_cacert | default(omit, true) }}" + os_networks_interface: "{{ openstack_interface | default(omit, true) }}" + # Network configuration. + os_networks: "{{ network_registrations }}" + when: openstack_network_registration_enabled | bool diff --git a/doc/source/configuration/reference/network.rst b/doc/source/configuration/reference/network.rst index 42c802688..36e5d53be 100644 --- a/doc/source/configuration/reference/network.rst +++ b/doc/source/configuration/reference/network.rst @@ -746,6 +746,27 @@ consideration: lookup the interface for the cloud-init network configuration that occurs during bifrost provisioning of the overcloud. + +Registration of networks in OpenStack +-------------------------------------- + +By default, Kayobe will register the following networks: + +* :ref:`workload-cleaning-network` +* :ref:`workload-provisioning-network` +* :ref:`workload-inspection-network` + +as part of ``kayobe overcloud post configure``. You can change this behaviour +for all networks with the ``openstack_network_registration_enabled`` variable: + +.. code-block:: yaml + :caption: ``networks.yml`` + + # Disabling registration of OpenStack networks + openstack_network_registration_enabled: false + +This can be useful if you define you networks by some other means e.g OpenTofu. + Overcloud Provisioning Network ------------------------------ @@ -771,6 +792,8 @@ To configure a network called ``example`` with an inspection allocation pool: This pool should not overlap with a kayobe allocation pool on the same network. +.. _workload-cleaning-network: + Workload Cleaning Network ------------------------- @@ -800,6 +823,8 @@ allocation pool: This pool should not overlap with a kayobe or inspection allocation pool on the same network. +.. _workload-provisioning-network: + Workload Provisioning Network ----------------------------- @@ -827,6 +852,8 @@ allocation pool: This pool should not overlap with a kayobe or inspection allocation pool on the same network. +.. _workload-inspection-network: + Workload Inspection Network --------------------------- diff --git a/etc/kayobe/networks.yml b/etc/kayobe/networks.yml index 17c9028c4..047951c12 100644 --- a/etc/kayobe/networks.yml +++ b/etc/kayobe/networks.yml @@ -96,6 +96,17 @@ # OVS bridge. #network_patch_suffix_ovs: +############################################################################### +# OpenStack Network configuration + +# Whether to register networks in OpenStack. The following networks are +# registered: +# - Ironic provisioning network (defined by provision_wl_net_name) +# - Ironic inspection network (defined by inspection_net_name) +# - Ironic cleaning network (defined by cleaning_net_name) +# Default is true. +#openstack_network_registration_enabled: + ############################################################################### # Network routing table configuration. diff --git a/releasenotes/notes/adds-openstack-network-registration-enabled-2450171db1d2b63a.yaml b/releasenotes/notes/adds-openstack-network-registration-enabled-2450171db1d2b63a.yaml new file mode 100644 index 000000000..5497423d3 --- /dev/null +++ b/releasenotes/notes/adds-openstack-network-registration-enabled-2450171db1d2b63a.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds a new feature flag, ``openstack_network_registration_enabled``, + to control whether or not networks defined in Kayobe are registered + as Neutron networks in OpenStack. From 66e4916344f8111a45681a53977c9d0ffb0d6fa9 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Fri, 24 Apr 2026 17:14:55 +0100 Subject: [PATCH 2/6] fixup! Skip external connectivity check when behind a proxy Change-Id: I3f087d2cbfcb7b73284896d224841657ac80ab51 Signed-off-by: Will Szumski --- ansible/network-connectivity.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/network-connectivity.yml b/ansible/network-connectivity.yml index 8259f898a..327522c67 100644 --- a/ansible/network-connectivity.yml +++ b/ansible/network-connectivity.yml @@ -7,7 +7,7 @@ default(100) }} vars: # Skip external connectivity check when behind a proxy. - nc_skip_external_net: "{{ http_proxy | truthy }}" + nc_skip_external_net: "{{ http_proxy is truthy }}" # Set this to an external IP address to check. nc_external_ip: 8.8.8.8 # Set this to an external hostname to check. From 40fca0f7c415247c06f5f05954297b78143bb156 Mon Sep 17 00:00:00 2001 From: Grzegorz Koper Date: Wed, 22 Apr 2026 17:58:18 +0200 Subject: [PATCH 3/6] Add nmstate VLAN QoS map support Render VLAN ingress and egress QoS maps for the nmstate network engine. Assisted-by: Codex(GPT5.4) and Opus4.7 as an initial reviewer Change-Id: Ie749959774b9c0d25cc9b47250539b5dd75c53e4 Signed-off-by: Grzegorz Koper --- .../configuration/reference/network.rst | 43 +++++++++ kayobe/plugins/filter/nmstate.py | 87 +++++++++++++++++++ .../tests/unit/plugins/filter/test_nmstate.py | 55 ++++++++++++ .../overrides.yml.j2 | 12 +++ .../run.yml | 1 + .../tests/test_overcloud_host_configure.py | 28 ++++++ ...nmstate-vlan-qos-map-7c4f0f7f57128e7a.yaml | 7 ++ 7 files changed, 233 insertions(+) create mode 100644 releasenotes/notes/nmstate-vlan-qos-map-7c4f0f7f57128e7a.yaml diff --git a/doc/source/configuration/reference/network.rst b/doc/source/configuration/reference/network.rst index 97f0e493a..495d9fe91 100644 --- a/doc/source/configuration/reference/network.rst +++ b/doc/source/configuration/reference/network.rst @@ -609,6 +609,26 @@ The following attributes are supported: bond and bridge interfaces, settings apply to underlying interfaces. This should be a string of arguments passed to the ``ethtool`` utility, for example ``"-G ${DEVICE} rx 8192 tx 8192"``. +``ingress_qos_map`` + .. note:: + + ``ingress_qos_map`` is only supported with + ``network_engine: nmstate`` on VLAN interfaces. + + VLAN ingress QoS map configuration. This maps VLAN header Priority Code + Point (PCP) to Linux internal packet priority for incoming packets. + + - Structured list: ``[{from: 7, to: 254}, {from: 3, to: 12}]`` +``egress_qos_map`` + .. note:: + + ``egress_qos_map`` is only supported with + ``network_engine: nmstate`` on VLAN interfaces. + + VLAN egress QoS map configuration. This maps Linux internal packet + priority to VLAN header Priority Code Point (PCP) for outgoing packets. + + - Structured list: ``[{from: 129, to: 7}, {from: 130, to: 6}]`` ``zone`` .. note:: ``zone`` is not currently supported on Ubuntu. @@ -758,6 +778,29 @@ this case, a ``parent`` attribute must specify the underlying interface: Ethernet interfaces, bridges, and bond master interfaces may all be parents to a VLAN interface. +VLAN QoS Mapping (nmstate) +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When using ``network_engine: nmstate``, VLAN interfaces can define native +nmstate QoS maps via ``ingress_qos_map`` and ``egress_qos_map``. These map +between Linux internal packet priority and VLAN header Priority Code Point +(PCP). Use the structured list-of-maps form. + +.. code-block:: yaml + :caption: ``inventory/group_vars//network-interfaces`` + + example_interface: "eth2.{{ example_vlan }}" + example_ingress_qos_map: + - from: 7 + to: 254 + example_egress_qos_map: + - from: 129 + to: 7 + +Undefined QoS map values do not render an nmstate QoS map key. Use an +explicit empty list (``[]``) to render an empty map through nmstate, for +example when clearing an existing map. + Bridges and VLANs ^^^^^^^^^^^^^^^^^ diff --git a/kayobe/plugins/filter/nmstate.py b/kayobe/plugins/filter/nmstate.py index 54e4c8155..7fae395f1 100644 --- a/kayobe/plugins/filter/nmstate.py +++ b/kayobe/plugins/filter/nmstate.py @@ -65,6 +65,9 @@ def _get_ip_config(context, name, inventory_hostname, defroute=None): 'rx', 'tx', 'rx-max', 'tx-max', 'rx-jumbo', 'rx-mini' } +MAX_U32 = 2**32 - 1 +MAX_VLAN_PRIORITY = 7 + def _resolve_ethtool_feature_aliases(features): """Convert ethtool feature aliases to canonical names. @@ -290,6 +293,79 @@ def _get_bond_options(context, name, inventory_hostname): return bond_options +def _validate_vlan_qos_map_int(value, network_name, direction, field): + if isinstance(value, bool) or not isinstance(value, int): + raise ValueError( + f"Network '{network_name}' has invalid {direction} QoS map " + f"'{field}' value '{value}'. Expected an integer.") + + if value < 0 or value > MAX_U32: + raise ValueError( + f"Network '{network_name}' has invalid {direction} QoS map " + f"'{field}' value '{value}'. Expected a value in range " + f"0..{MAX_U32}.") + + if direction == "ingress" and field == "from" \ + and value > MAX_VLAN_PRIORITY: + raise ValueError( + f"Network '{network_name}' has invalid ingress QoS map " + f"'from' value '{value}'. Maximum VLAN priority is " + f"{MAX_VLAN_PRIORITY}.") + + if direction == "egress" and field == "to" \ + and value > MAX_VLAN_PRIORITY: + raise ValueError( + f"Network '{network_name}' has invalid egress QoS map " + f"'to' value '{value}'. Maximum VLAN priority is " + f"{MAX_VLAN_PRIORITY}.") + + return value + + +def _validate_vlan_qos_map_entry(entry, network_name, direction): + if not isinstance(entry, dict): + raise ValueError( + f"Network '{network_name}' has invalid {direction} QoS map " + "entry format. Expected a dict with keys 'from' and 'to'.") + + if "from" not in entry or "to" not in entry: + raise ValueError( + f"Network '{network_name}' has invalid {direction} QoS map " + "entry. Required keys are 'from' and 'to'.") + + return { + "from": _validate_vlan_qos_map_int( + entry["from"], network_name, direction, "from"), + "to": _validate_vlan_qos_map_int( + entry["to"], network_name, direction, "to") + } + + +def _validate_and_sort_vlan_qos_map(raw_map, network_name, direction): + if raw_map is None: + return None + + if not isinstance(raw_map, list): + raise ValueError( + f"Network '{network_name}' has invalid {direction} QoS map " + f"format '{type(raw_map).__name__}'. Expected a list of dicts " + "with keys 'from' and 'to'.") + + normalized = [] + for entry in raw_map: + normalized.append(_validate_vlan_qos_map_entry( + entry, network_name, direction)) + + normalized.sort(key=lambda item: (item["from"], item["to"])) + return normalized + + +def _get_vlan_qos_map(context, name, inventory_hostname, direction): + qos_map = networks.net_attr( + context, name, f"{direction}_qos_map", inventory_hostname) + return _validate_and_sort_vlan_qos_map(qos_map, name, direction) + + @jinja2.pass_context def nmstate_config(context, names, inventory_hostname=None): interfaces = {} @@ -514,6 +590,17 @@ def get_iface(name): "base-iface": parent, "id": int(vlan_id) } + + ingress_qos_map = _get_vlan_qos_map( + context, name, inventory_hostname, "ingress") + if ingress_qos_map is not None: + iface["vlan"]["ingress-qos-map"] = ingress_qos_map + + egress_qos_map = _get_vlan_qos_map( + context, name, inventory_hostname, "egress") + if egress_qos_map is not None: + iface["vlan"]["egress-qos-map"] = egress_qos_map + # Ensure parent is initialized get_iface(parent) diff --git a/kayobe/tests/unit/plugins/filter/test_nmstate.py b/kayobe/tests/unit/plugins/filter/test_nmstate.py index 720200ec8..91c5966c8 100644 --- a/kayobe/tests/unit/plugins/filter/test_nmstate.py +++ b/kayobe/tests/unit/plugins/filter/test_nmstate.py @@ -494,6 +494,61 @@ def test_vlan_interface_parent_derivation(self): if i["name"] == "bond0") self.assertEqual(bond_iface["state"], "up") + def test_vlan_interface_qos_map_structured(self): + context = self._make_context( + { + "inventory_hostname": "test-host", + "ansible_facts": {"os_family": "RedHat"}, + "vlan_interface": "eth0.123", + "vlan_ingress_qos_map": [ + {"from": 7, "to": 254}, + {"from": 3, "to": 12}, + ], + "vlan_egress_qos_map": [ + {"from": 130, "to": 6}, + {"from": 129, "to": 7}, + ], + } + ) + + result = nmstate.nmstate_config(context, ["vlan"]) + vlan_iface = next( + iface for iface in result["interfaces"] + if iface["name"] == "eth0.123" + ) + self.assertEqual( + vlan_iface["vlan"]["ingress-qos-map"], + [{"from": 3, "to": 12}, {"from": 7, "to": 254}], + ) + self.assertEqual( + vlan_iface["vlan"]["egress-qos-map"], + [{"from": 129, "to": 7}, {"from": 130, "to": 6}], + ) + + def test_vlan_interface_qos_map_invalid_input(self): + test_cases = [ + ("non-list input", {"vlan_ingress_qos_map": ""}), + ("missing required key", {"vlan_ingress_qos_map": [{"from": 1}]}), + ("wrong entry type", {"vlan_ingress_qos_map": ["1:2"]}), + ( + "invalid numeric bound", + {"vlan_egress_qos_map": [{"from": 129, "to": 8}]}, + ), + ] + + for test_case, qos_map in test_cases: + variables = { + "inventory_hostname": "test-host", + "ansible_facts": {"os_family": "RedHat"}, + "vlan_interface": "eth0.123", + } + variables.update(qos_map) + context = self._make_context(variables) + + with self.subTest(test_case=test_case): + with self.assertRaises(ValueError): + nmstate.nmstate_config(context, ["vlan"]) + def test_bridge_stp_unset(self): """Test bridge with unset bridge_stp does not configure STP.""" variables = { diff --git a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 index b3fd5fcd8..d2bdd1def 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 +++ b/playbooks/kayobe-overcloud-host-configure-base/overrides.yml.j2 @@ -67,6 +67,18 @@ test_net_eth_vlan_rules: table: kayobe-test-route-table {% endif %} test_net_eth_vlan_zone: test-zone1 +{% if host_configure_network_engine == "nmstate" %} +test_net_eth_vlan_ingress_qos_map: + - from: 3 + to: 12 + - from: 7 + to: 254 +test_net_eth_vlan_egress_qos_map: + - from: 129 + to: 7 + - from: 130 + to: 6 +{% endif %} # br0: bridge with ports dummy3, dummy4. test_net_bridge_cidr: 192.168.36.0/24 diff --git a/playbooks/kayobe-overcloud-host-configure-base/run.yml b/playbooks/kayobe-overcloud-host-configure-base/run.yml index 688166c32..1863cc753 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/run.yml +++ b/playbooks/kayobe-overcloud-host-configure-base/run.yml @@ -35,6 +35,7 @@ environment: SITE_MIRROR_FQDN: "{{ zuul_site_mirror_fqdn }}" FAIL2BAN_ENABLED: "{{ fail2ban_enabled | default(false) }}" + CI_NETWORK_ENGINE: "{{ ci_network_engine | default('default') }}" - name: Test bouncing interfaces shell: diff --git a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py index 1a7a083d5..0f620472f 100644 --- a/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py +++ b/playbooks/kayobe-overcloud-host-configure-base/tests/test_overcloud_host_configure.py @@ -4,6 +4,7 @@ # Uses py.test and TestInfra. import ipaddress +import json import os import distro @@ -37,6 +38,24 @@ def _is_fail2ban_enabled(): return os.environ.get('FAIL2BAN_ENABLED', 'false').lower() == 'true' +def _is_nmstate_enabled(): + return os.environ.get('CI_NETWORK_ENGINE', 'default').lower() == 'nmstate' + + +def _get_vlan_info_data(host, interface_name): + output = host.check_output( + '/sbin/ip -d -j link show dev %s' % interface_name) + link_data = json.loads(output) + assert len(link_data) == 1 + linkinfo = link_data[0].get('linkinfo', {}) + assert linkinfo.get('info_kind') == 'vlan' + return linkinfo.get('info_data', {}) + + +def _normalize_qos_map(qos_map): + return {(entry['from'], entry['to']) for entry in qos_map} + + def test_network_ethernet(host): interface = host.interface('dummy2') assert interface.exists @@ -59,6 +78,15 @@ def test_network_ethernet_vlan(host): expected_to = 'to 192.168.35.0/24 lookup kayobe-test-route-table' assert expected_from in rules assert expected_to in rules + vlan_info_data = _get_vlan_info_data(host, 'dummy2.42') + assert vlan_info_data['id'] == 42 + if _is_nmstate_enabled(): + ingress_qos = _normalize_qos_map(vlan_info_data['ingress_qos']) + egress_qos = _normalize_qos_map(vlan_info_data['egress_qos']) + assert (3, 12) in ingress_qos + assert (7, 254) in ingress_qos + assert (129, 7) in egress_qos + assert (130, 6) in egress_qos def test_network_bridge(host): diff --git a/releasenotes/notes/nmstate-vlan-qos-map-7c4f0f7f57128e7a.yaml b/releasenotes/notes/nmstate-vlan-qos-map-7c4f0f7f57128e7a.yaml new file mode 100644 index 000000000..c09326b9f --- /dev/null +++ b/releasenotes/notes/nmstate-vlan-qos-map-7c4f0f7f57128e7a.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Adds native nmstate support for structured VLAN QoS mapping in Kayobe via + ``_ingress_qos_map`` and ``_egress_qos_map``. + These are rendered to nmstate VLAN keys + ``ingress-qos-map`` and ``egress-qos-map``. From 38ac2d96cb8c89343b6b1f06fdb096cba12d9f7c Mon Sep 17 00:00:00 2001 From: Matt Crees Date: Tue, 28 Apr 2026 10:03:31 +0100 Subject: [PATCH 4/6] Split out lint reqs + stop using upper constraints This fixes CI, as a UC bump of pathspec is giving us conflicts: ansible-lint 26.4.0 depends on pathspec<1.1.0 and >=1.0.3 The user requested (constraint) pathspec===1.1.0 Change-Id: Ie250257e7f55228a610f54f3d15bed8613a807b7 Signed-off-by: Matt Crees --- lint-requirements.txt | 6 ++++++ test-requirements.txt | 10 ---------- tox.ini | 6 ++---- 3 files changed, 8 insertions(+), 14 deletions(-) create mode 100644 lint-requirements.txt diff --git a/lint-requirements.txt b/lint-requirements.txt new file mode 100644 index 000000000..222ad5319 --- /dev/null +++ b/lint-requirements.txt @@ -0,0 +1,6 @@ +ansible-lint>=26.0.0,<27.0.0 # MIT +bandit>=1.1.0 # Apache-2.0 +bashate>=0.2 # Apache-2.0 +doc8 # Apache-2.0 +hacking>=7.0.0,<7.1.0 # Apache-2.0 +yamllint # GPLv3 diff --git a/test-requirements.txt b/test-requirements.txt index 0f57abc8e..41948fe06 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,13 +1,3 @@ -# The order of packages is significant, because pip processes them in the order -# of appearance. Changing the order has an impact on the overall integration -# process, which may cause wedges in the gate later. - -ansible-lint>=26.0.0,<27.0.0 # MIT -bandit>=1.1.0 # Apache-2.0 -bashate>=0.2 # Apache-2.0 coverage>=4.0 # Apache-2.0 -doc8 # Apache-2.0 -hacking>=7.0.0,<7.1.0 # Apache-2.0 oslotest>=1.10.0 # Apache-2.0 stestr # Apache-2.0 -yamllint # GPLv3 diff --git a/tox.ini b/tox.ini index 71ce94a0d..d284e89ae 100644 --- a/tox.ini +++ b/tox.ini @@ -27,10 +27,9 @@ commands = stestr run {posargs} [testenv:pep8] # sphinx8 needs the sphinx package which is required via doc/requirements.txt deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt - -r{toxinidir}/test-requirements.txt + -r{toxinidir}/lint-requirements.txt commands = bash {toxinidir}/tools/run-bashate.sh flake8 {posargs} kayobe @@ -79,9 +78,8 @@ setenv = ANSIBLE_ROLES_PATH = {toxinidir}/ansible/roles deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} -r{toxinidir}/requirements.txt - -r{toxinidir}/test-requirements.txt + -r{toxinidir}/lint-requirements.txt commands = {[testenv:ansible-lint]commands} From 87d1546f30e776b56bc58e2f56d0342cd91d5cb3 Mon Sep 17 00:00:00 2001 From: Leonie Chamberlin-Medd Date: Wed, 8 Apr 2026 12:40:54 +0000 Subject: [PATCH 5/6] Add skeleton code for Infra VM Service Destroy Adds no-op class for future infra VM service destroy functionality Change-Id: I40b7852a60e5bb69384472003a6def9622f1a879 Signed-off-by: Leonie Chamberlin-Medd --- kayobe/cli/commands.py | 14 +++++++++++++- ...-service-destroy-skeleton-6e49240e720e2593.yaml | 5 +++++ setup.cfg | 3 +++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 releasenotes/notes/add-infra-vm-service-destroy-skeleton-6e49240e720e2593.yaml diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index ec9275e1a..8be86b214 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -1202,12 +1202,24 @@ def take_action(self, parsed_args): class InfraVMServiceDeploy(KayobeAnsibleMixin, VaultMixin, Command): - """Run hooks for infra structure services.""" + """Run hooks for infra VM services.""" def take_action(self, parsed_args): self.app.LOG.debug("Running no-op Infra VM service deploy") +class InfraVMServiceDestroy(KayobeAnsibleMixin, VaultMixin, + Command): + """Destroy the infra VM services. + + Permanently destroy the infra VM containers, container images, and + container volumes. + """ + + def take_action(self, parsed_args): + self.app.LOG.debug("Running no-op Infra VM service destroy") + + class OvercloudInventoryDiscover(KayobeAnsibleMixin, VaultMixin, Command): """Discover the overcloud inventory from the seed's Ironic service. diff --git a/releasenotes/notes/add-infra-vm-service-destroy-skeleton-6e49240e720e2593.yaml b/releasenotes/notes/add-infra-vm-service-destroy-skeleton-6e49240e720e2593.yaml new file mode 100644 index 000000000..d57ea8dc2 --- /dev/null +++ b/releasenotes/notes/add-infra-vm-service-destroy-skeleton-6e49240e720e2593.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Adds new command ``kayobe infra vm service destroy``. This is currently a + no-op, but can be used for hooks. diff --git a/setup.cfg b/setup.cfg index 61c698637..4f4d9f9e3 100644 --- a/setup.cfg +++ b/setup.cfg @@ -110,6 +110,7 @@ kayobe.cli= infra_vm_host_command_run = kayobe.cli.commands:InfraVMHostCommandRun infra_vm_host_package_update = kayobe.cli.commands:InfraVMHostPackageUpdate infra_vm_service_deploy = kayobe.cli.commands:InfraVMServiceDeploy + infra_vm_service_destroy = kayobe.cli.commands:InfraVMServiceDestroy kayobe.cli.baremetal_compute_register = hooks = kayobe.cli.commands:HookDispatcher @@ -255,3 +256,5 @@ kayobe.cli.infra_vm_host_package_update = hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.infra_vm_service_deploy = hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.infra_vm_service_destroy = + hooks = kayobe.cli.commands:HookDispatcher From 3955ae9330c9f1ef5258f9a3ae9f43b07d8ce6b2 Mon Sep 17 00:00:00 2001 From: Leonie Chamberlin-Medd Date: Wed, 8 Apr 2026 13:43:00 +0000 Subject: [PATCH 6/6] Add control host service deploy/destroy skeleton Adds no-op classes for future control host functionality Change-Id: I2cb422998f8dc2585c67a3e7f0b4218bdeacf1be Signed-off-by: Leonie Chamberlin-Medd --- kayobe/cli/commands.py | 14 ++++++++++++++ ...e-destroy-deploy-skeleton-1297979c046e298e.yaml | 6 ++++++ setup.cfg | 6 ++++++ 3 files changed, 26 insertions(+) create mode 100644 releasenotes/notes/add-control-host-service-destroy-deploy-skeleton-1297979c046e298e.yaml diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index 8be86b214..ee97c5d3b 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -462,6 +462,20 @@ def take_action(self, parsed_args): ignore_limit=True, check=False) +class ControlHostServiceDeploy(KayobeAnsibleMixin, VaultMixin, Command): + """Deploy the Ansible control host services.""" + + def take_action(self, parsed_args): + self.app.LOG.debug("Running no-op control host service deploy") + + +class ControlHostServiceDestroy(KayobeAnsibleMixin, VaultMixin, Command): + """Destroy the Ansible control host services.""" + + def take_action(self, parsed_args): + self.app.LOG.debug("Running no-op control host service destroy") + + class ConfigurationDump(KayobeAnsibleMixin, VaultMixin, Command): """Dump Kayobe configuration. diff --git a/releasenotes/notes/add-control-host-service-destroy-deploy-skeleton-1297979c046e298e.yaml b/releasenotes/notes/add-control-host-service-destroy-deploy-skeleton-1297979c046e298e.yaml new file mode 100644 index 000000000..023be8180 --- /dev/null +++ b/releasenotes/notes/add-control-host-service-destroy-deploy-skeleton-1297979c046e298e.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds two new commands, ``kayobe control host service deploy`` and + ``kayobe control host service destroy``. This is currently a + no-op, but can be used for hooks. diff --git a/setup.cfg b/setup.cfg index 4f4d9f9e3..7f385d1e4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -51,6 +51,8 @@ kayobe.cli= control_host_configure = kayobe.cli.commands:ControlHostConfigure control_host_package_update = kayobe.cli.commands:ControlHostPackageUpdate control_host_upgrade = kayobe.cli.commands:ControlHostUpgrade + control_host_service_deploy = kayobe.cli.commands:ControlHostServiceDeploy + control_host_service_destroy = kayobe.cli.commands:ControlHostServiceDestroy configuration_dump = kayobe.cli.commands:ConfigurationDump environment_create = kayobe.cli.commands:EnvironmentCreate inventory= kayobe.cli.commands:Inventory @@ -140,6 +142,10 @@ kayobe.cli.control_host_package_update = hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.control_host_upgrade = hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.control_host_service_deploy = + hooks = kayobe.cli.commands:HookDispatcher +kayobe.cli.control_host_service_destroy = + hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.configuration_dump = hooks = kayobe.cli.commands:HookDispatcher kayobe.cli.environment_create =