From dae8cd6a9868cd903d2dcd3ad7d2b0681f672ad8 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Tue, 7 Apr 2026 18:08:36 +0100 Subject: [PATCH 1/4] Add real world networking example People often complain that kayobe networking is complex to configure. This example adds configuration patterns found in a real deployment. The hope is that the same patterns can be applied to new deployments. Change-Id: I74dd61b4f521fa29eb3cf307863a48a2e9172b22 Signed-off-by: Will Szumski --- bindep.txt | 4 + .../_static/network-topology-nwdiag.svg | 189 ++++ doc/source/conf.py | 1 + doc/source/configuration/examples/index.rst | 10 + ...configuration-a-virtualised-case-study.rst | 963 ++++++++++++++++++ doc/source/configuration/index.rst | 1 + .../configuration/reference/network.rst | 12 + 7 files changed, 1180 insertions(+) create mode 100644 doc/source/_static/network-topology-nwdiag.svg create mode 100644 doc/source/configuration/examples/index.rst create mode 100644 doc/source/configuration/examples/real-world-bridges-and-bonds-configuration-a-virtualised-case-study.rst diff --git a/bindep.txt b/bindep.txt index d99bfd217..ab5f0867b 100644 --- a/bindep.txt +++ b/bindep.txt @@ -2,5 +2,9 @@ libpcre2-dev [platform:dpkg test] pcre2-devel [platform:rpm test] +# Sphinx graphviz extension requires the dot binary. +graphviz [platform:dpkg test doc] +graphviz [platform:rpm test doc] + # PDF Docs package dependencies tex-gyre [platform:dpkg doc] diff --git a/doc/source/_static/network-topology-nwdiag.svg b/doc/source/_static/network-topology-nwdiag.svg new file mode 100644 index 000000000..ac839d555 --- /dev/null +++ b/doc/source/_static/network-topology-nwdiag.svg @@ -0,0 +1,189 @@ + + + + + + + + + blockdiag + nwdiag { + // Node labels keep names concise while preserving readable host group + // names in the rendered diagram. + seed_hv [label = "Seed Hypervisor"]; + seed [label = "Seed"]; + controllers [label = "Controllers"]; + computes [label = "Computes"]; + storage [label = "Storage"]; + + network external_admin_net { + address = "203.0.113.0/24"; + color = "#93c5fd"; + seed_hv; + seed; + } + + network power_mgmt_net { + address = "10.0.1.0/24"; + color = "#fca5a5"; + seed_hv; + seed; + } + + network provision_ctl_net { + address = "10.0.2.0/24"; + color = "#86efac"; + seed_hv; + seed; + controllers; + computes; + storage; + } + + network internal_net { + address = "172.20.10.0/26"; + color = "#fcd34d"; + controllers; + computes; + storage; + } + + network storage_net { + address = "172.20.20.0/25"; + color = "#c4b5fd"; + controllers; + computes; + storage; + } + + network storage_mgmt_net { + address = "172.20.10.64/26"; + color = "#fdba74"; + storage; + } + + network tenant_tunnel_net { + address = "10.0.4.0/25"; + color = "#67e8f9"; + controllers; + computes; + } + + network external_user_net { + address = "172.31.50.0/24"; + color = "#f9a8d4"; + controllers; + } +} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + external_admin_net + 203.0.113.0/24 + power_mgmt_net + 10.0.1.0/24 + provision_ctl_net + 10.0.2.0/24 + internal_net + 172.20.10.0/26 + storage_net + 172.20.20.0/25 + storage_mgmt_net + 172.20.10.64/26 + tenant_tunnel_net + 10.0.4.0/25 + external_user_net + 172.31.50.0/24 + + + + + + + Seed Hypervisor + + + + + + + Seed + + + + + + + + + + + + + + + + + + + Controllers + + + + + + + + + + + + Computes + + + + + + + + + + + + Storage + diff --git a/doc/source/conf.py b/doc/source/conf.py index 3c150fec6..19cca2bb6 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -56,6 +56,7 @@ 'openstackdocstheme', #'sphinx.ext.autodoc', #'sphinx.ext.intersphinx', + 'sphinx.ext.graphviz', 'sphinxcontrib.rsvgconverter', ] diff --git a/doc/source/configuration/examples/index.rst b/doc/source/configuration/examples/index.rst new file mode 100644 index 000000000..641faeb9b --- /dev/null +++ b/doc/source/configuration/examples/index.rst @@ -0,0 +1,10 @@ +====================== +Configuration Examples +====================== + +This section provides real-world configuration examples. + +.. toctree:: + :maxdepth: 2 + + real-world-bridges-and-bonds-configuration-a-virtualised-case-study diff --git a/doc/source/configuration/examples/real-world-bridges-and-bonds-configuration-a-virtualised-case-study.rst b/doc/source/configuration/examples/real-world-bridges-and-bonds-configuration-a-virtualised-case-study.rst new file mode 100644 index 000000000..3efbbff23 --- /dev/null +++ b/doc/source/configuration/examples/real-world-bridges-and-bonds-configuration-a-virtualised-case-study.rst @@ -0,0 +1,963 @@ +=========================================================================== +Real-World Bridges and Bonds Configuration: A virtualised case study +=========================================================================== + +This section details a real-world configuration to illustrate how to configure +bonds and bridges using Kayobe. The deployment runs +virtualised compute workloads with no bare metal resources managed through +Ironic. + +This scenario walks through configuring host networking for each host group, +starting from a known network topology. + +Network Topology +================ + +The following diagram shows how networks are mapped to each of the hosts groups. + +.. figure:: ../../_static/network-topology-nwdiag.svg + :alt: Network assignment to host groups + + Network assignment to host groups + +.. + Original nwdiag source used to generate the SVG above: + + nwdiag { + // Node labels keep names concise while preserving readable host group + // names in the rendered diagram. + seed_hv [label = "Seed Hypervisor"]; + seed [label = "Seed"]; + controllers [label = "Controllers"]; + computes [label = "Computes"]; + storage [label = "Storage"]; + + network external_admin_net { + address = "203.0.113.0/24"; + color = "#93c5fd"; + seed_hv; + seed; + } + + network power_mgmt_net { + address = "10.0.1.0/24"; + color = "#fca5a5"; + seed_hv; + seed; + } + + network provision_ctl_net { + address = "10.0.2.0/24"; + color = "#86efac"; + seed_hv; + seed; + controllers; + computes; + storage; + } + + network internal_net { + address = "172.20.10.0/26"; + color = "#fcd34d"; + controllers; + computes; + storage; + } + + network storage_net { + address = "172.20.20.0/25"; + color = "#c4b5fd"; + controllers; + computes; + storage; + } + + network storage_mgmt_net { + address = "172.20.10.64/26"; + color = "#fdba74"; + storage; + } + + network tenant_tunnel_net { + address = "10.0.4.0/25"; + color = "#67e8f9"; + controllers; + computes; + } + + network external_user_net { + address = "172.31.50.0/24"; + color = "#f9a8d4"; + controllers; + } + } + +.. |color_sky_blue| raw:: html + + sky blue + +.. |color_rose| raw:: html + + rose + +.. |color_green| raw:: html + + green + +.. |color_amber| raw:: html + + amber + +.. |color_violet| raw:: html + + violet + +.. |color_orange| raw:: html + + orange + +.. |color_cyan| raw:: html + + cyan + +.. |color_pink| raw:: html + + pink + +.. note:: + + It is important to use non-routable (RFC 1918 private) address ranges for the + power management and provisioning/control networks. This prevents accidental + exposure of the infrastructure management and provisioning interfaces to + external networks, limiting access to authorized internal networks only. + + +.. list-table:: VLAN key and colour groups + :header-rows: 1 + + * - Network + - VLAN + - Group + * - external_admin_net + - 2270 + - Admin/management (|color_sky_blue|) + * - power_mgmt_net + - 2802 + - Power management (|color_rose|) + * - provision_ctl_net + - 3 + - Provision/control (|color_green|) + * - internal_net + - 4 + - Internal API (|color_amber|) + * - storage_net + - 5 + - Storage (|color_violet|) + * - storage_mgmt_net + - 6 + - Storage management (|color_orange|) + * - tenant_tunnel_net + - 7 + - Tenant tunnel (|color_cyan|) + * - external_user_net + - 2280 + - Public API (|color_pink|) + +Define Networks in networks.yml +=============================== + +Kayobe uses well-known logical network role variables (such as +``provision_oc_net_name`` and ``tunnel_net_name``). These can optionally be +customised to give more descriptive names. The mappings are defined in +``etc/kayobe/networks.yml``. + +.. code-block:: yaml + :caption: ``etc/kayobe/networks.yml`` + + --- + # Kayobe network configuration. + + ############################################################################### + # Network role to network name mappings. + + # Network used by the seed to manage bare metal overcloud hosts via their + # out-of-band management controllers. + oob_oc_net_name: "power_mgmt_net" + + # Network used by the seed to provision bare metal overcloud hosts. + provision_oc_net_name: "provision_ctl_net" + + # Network used to expose the internal OpenStack API endpoints. + internal_net_name: "internal_net" + + # Network used to expose the public OpenStack API endpoints. + public_net_name: "external_user_net" + + # Network used by Neutron to carry tenant overlay network traffic. + tunnel_net_name: "tenant_tunnel_net" + +With the names established, populate the CIDRs, VLAN IDs, MTU values, and +allocation pools for each named network in ``etc/kayobe/environments/production/networks.yml``: + +.. code-block:: yaml + :caption: ``etc/kayobe/environments/production/networks.yml`` + + --- + # Kayobe network configuration. + + ############################################################################### + # Network definitions. + + hs_bond_mtu: 9150 + + # Power/Management network IP information. + power_mgmt_net_cidr: "10.0.1.0/24" + power_mgmt_net_allocation_pool_start: "10.0.1.10" + power_mgmt_net_allocation_pool_end: "10.0.1.250" + power_mgmt_net_vlan: 2802 + power_mgmt_net_mtu: 1500 + + # Provisioning/Control network IP information. + provision_ctl_net_cidr: "10.0.2.0/24" + provision_ctl_net_allocation_pool_start: "10.0.2.10" + provision_ctl_net_allocation_pool_end: "10.0.2.127" + provision_ctl_net_inspection_allocation_pool_start: "10.0.2.128" + provision_ctl_net_inspection_allocation_pool_end: "10.0.2.250" + provision_ctl_net_switch_vlan: 3 + provision_ctl_net_mtu: 1500 + + # Internal API network IP information. + internal_net_vip_address: "172.20.10.2" + internal_net_cidr: "172.20.10.0/26" + internal_net_allocation_pool_start: "172.20.10.7" + internal_net_allocation_pool_end: "172.20.10.62" + internal_net_vlan: 4 + internal_net_mtu: 9000 + + # Storage network IP information. + storage_net_cidr: "172.20.20.0/25" + storage_net_allocation_pool_start: "172.20.20.10" + storage_net_allocation_pool_end: "172.20.20.126" + storage_net_vlan: 5 + storage_net_mtu: "{{ hs_bond_mtu }}" + + # Storage Management network IP information. + storage_mgmt_net_cidr: "172.20.10.64/26" + storage_mgmt_net_allocation_pool_start: "172.20.10.74" + storage_mgmt_net_allocation_pool_end: "172.20.10.124" + storage_mgmt_net_vlan: 6 + storage_mgmt_net_mtu: 9000 + + # Tenant/Tunnel network IP information. + tenant_tunnel_net_cidr: "10.0.4.0/25" + tenant_tunnel_net_allocation_pool_start: "10.0.4.10" + tenant_tunnel_net_allocation_pool_end: "10.0.4.127" + tenant_tunnel_net_vlan: 7 + tenant_tunnel_net_mtu: "{{ hs_bond_mtu }}" + + # External/Admin network IP information. + # This network is used for management access. + external_admin_net_cidr: "203.0.113.0/24" + external_admin_net_allocation_pool_start: "203.0.113.5" + external_admin_net_allocation_pool_end: "203.0.113.9" + external_admin_net_vlan: 2270 + external_admin_net_mtu: 1500 + external_admin_net_gateway: "203.0.113.250" + + # External User network + external_user_net_cidr: "172.31.50.0/24" + external_user_net_vip_address: "172.31.50.4" + external_user_net_gateway: "172.31.50.250" + external_user_net_allocation_pool_start: "172.31.50.10" + external_user_net_allocation_pool_end: "172.31.50.249" + external_user_net_vlan: 2280 + external_user_net_mtu: 1500 + + # FQDN address for public API endpoints, registered with the external DNS + # server to resolve to the public VIP address. + external_user_net_fqdn: "api.example.org" + + ############################################################################### + # Dummy variable to allow Ansible to accept this file. + workaround_ansible_issue_8743: yes + +Common Configuration +==================== + +Shared overcloud interface mappings should be defined in +``inventory/group_vars/overcloud/network-interfaces``. +This common configuration is inherited by +all hosts in the overcloud group, including controllers, storage, and compute. + +The aim is to avoid repetition of common configuration. You can still override +values for specific host groups where required. + +.. code-block:: yaml + :caption: ``inventory/group_vars/overcloud/network-interfaces`` + + --- + # High speed bond config + hs_bond_interface: "bond0" + hs_bond_bond_mode: "802.3ad" + hs_bond_bond_xmit_hash_policy: "layer3+4" + hs_bond_physical_network: "physnet2" + + # 25G Control bond config + control_25g_interface: "bond1" + control_25g_bond_mode: "802.3ad" + control_25g_bond_xmit_hash_policy: "layer3+4" + control_25g_physical_network: "physnet3" + + # Provision / Control maps to a bridge on the 25G bond port + provision_ctl_net_interface: "br{{ control_25g_interface }}" + provision_ctl_net_bridge_ports: + - "{{ control_25g_interface }}" + provision_ctl_net_physical_network: "physnet3" + + # List of names of networks used to provide external network access via Neutron. + # This value is overridden for controllers to add the external_10g network. + external_net_names: + - "hs_bond" + - "provision_ctl_net" + +Seed Hypervisor +=============== + +.. warning:: + + Network reconfiguration can potentially break networking and render hosts + inaccessible. This is particularly risky when moving the admin interface + onto a bridge or bond. It is recommended that you: + + - Add a password to a local user account that can be accessed via the BMC + (either web UI or serial console) to recover should configuration fail. + This password should be removed after successful initial deployment. + + - Configure one host per group initially before applying the configuration + across the entire group. If a host becomes misconfigured, it can simply be + reprovisioned. + +.. graphviz:: + :caption: Seed hypervisor interface layering + + digraph seed_hypervisor_interfaces { + rankdir=LR; + nodesep=0.35; + ranksep=0.6; + fontname="Helvetica"; + node [shape=box, style="rounded,filled", color="#334155", fillcolor="#f8fafc", fontname="Helvetica", fontsize=10]; + edge [color="#475569", arrowsize=0.7, penwidth=1.1]; + + sh_m1 [label="enp65s0f0np0", fillcolor="#e2e8f0"]; + sh_m2 [label="enp65s0f1np1", fillcolor="#e2e8f0"]; + sh_m3 [label="enp2s0f0np0", fillcolor="#e2e8f0"]; + sh_m4 [label="enp2s0f1np1", fillcolor="#e2e8f0"]; + + sh_b0 [label="bond0", fillcolor="#dbeafe", color="#1d4ed8"]; + sh_b1 [label="bond1", fillcolor="#dbeafe", color="#1d4ed8"]; + + sh_v_admin [label="bond0.2270", fillcolor="#fef3c7", color="#b45309"]; + sh_v_power [label="bond0.2802", fillcolor="#fef3c7", color="#b45309"]; + + sh_br_admin [label="brbond0_admin", fillcolor="#dcfce7", color="#15803d"]; + sh_br_power [label="brbond0_power", fillcolor="#dcfce7", color="#15803d"]; + sh_br_ctl [label="brbond1", fillcolor="#dcfce7", color="#15803d"]; + + sh_n_admin [label="external_admin_net", fillcolor="#fee2e2", color="#b91c1c"]; + sh_n_power [label="power_mgmt_net", fillcolor="#fee2e2", color="#b91c1c"]; + sh_n_ctl [label="provision_ctl_net", fillcolor="#fee2e2", color="#b91c1c"]; + + sh_n_admin -> sh_br_admin -> sh_v_admin -> sh_b0; + sh_n_power -> sh_br_power -> sh_v_power -> sh_b0; + sh_n_ctl -> sh_br_ctl -> sh_b1; + + sh_b0 -> sh_m1; + sh_b0 -> sh_m2; + sh_b1 -> sh_m3; + sh_b1 -> sh_m4; + } + +.. note:: + + Any network interface that the seed VM needs to be connected to must be + backed by a Linux bridge on the hypervisor. Kayobe provisions the seed using + libvirt, which attaches virtual machine interfaces to host networks via + bridges — a plain bond or VLAN sub-interface cannot be used directly. + +In ``etc/kayobe/seed-hypervisor.yml`` define extra networks that are not +mapped to this host by default: + +.. code-block:: yaml + + seed_hypervisor_extra_network_interfaces: + - mgmt_bond_ext + - mgmt_vlan_admin + - mgmt_vlan_power + - mgmt_bond_int + - external_admin_net + - power_mgmt_net + +In ``inventory/group_vars/seed-hypervisor/network-interfaces`` define the +network interface mappings and properties. + +To see which interface variables require definitions, run +``kayobe configuration dump --var-name network_interfaces --limit seed-hypervisor``. +Before creating this file, also run ``ip a`` on the seed +hypervisor and use the reported physical interface names in +``*_bond_slaves`` variables. + +.. code-block:: yaml + + mgmt_bond_ext_interface: bond0 + mgmt_bond_ext_bond_mode: 802.3ad + mgmt_bond_ext_bond_xmit_hash_policy: layer3+4 + mgmt_bond_ext_bond_slaves: + - enp65s0f0np0 + - enp65s0f1np1 + + mgmt_vlan_admin_interface: "{{ mgmt_bond_ext_interface }}.{{ external_admin_net_vlan }}" + external_admin_net_interface: "br{{ mgmt_bond_ext_interface }}_admin" + external_admin_net_bridge_ports: + - "{{ mgmt_vlan_admin_interface }}" + + mgmt_vlan_power_interface: "{{ mgmt_bond_ext_interface }}.{{ power_mgmt_net_vlan }}" + power_mgmt_net_interface: "br{{ mgmt_bond_ext_interface }}_power" + power_mgmt_net_bridge_ports: + - "{{ mgmt_vlan_power_interface }}" + + mgmt_bond_int_interface: bond1 + mgmt_bond_int_bond_mode: 802.3ad + mgmt_bond_int_bond_xmit_hash_policy: layer3+4 + mgmt_bond_int_bond_slaves: + - enp2s0f0np0 + - enp2s0f1np1 + + provision_ctl_net_interface: "br{{ mgmt_bond_int_interface }}" + provision_ctl_net_bridge_ports: + - "{{ mgmt_bond_int_interface }}" + +Seed +==== + +.. warning:: + + Network reconfiguration can potentially break networking and render hosts + inaccessible. This is particularly risky when moving the admin interface + onto a bridge or bond. It is recommended that you: + + - Add a password to a local user account that can be accessed via the BMC + (either web UI or serial console) to recover should configuration fail. + This password should be removed after successful initial deployment. + + - Configure one host per group initially before applying the configuration + across the entire group. If a host becomes misconfigured, it can simply be + reprovisioned. + +.. note:: + + The seed's network interfaces must be defined before running + ``kayobe seed host provision``. Provisioning creates the seed VM and + attaches it to the bridges defined on the hypervisor, so the interface + configuration must be in place for Kayobe to know which networks to connect. + + The interface order on the seed VM is derived from + ``seed_hypervisor_libvirt_networks``. To inspect the resolved value, run: + + .. code-block:: console + + kayobe configuration dump --var-name seed_hypervisor_libvirt_networks --limit seed-hypervisor + +The seed is configured to use a separate network card for each logical network. + +.. graphviz:: + :caption: Seed host interface mapping + + digraph seed_host_interfaces { + rankdir=LR; + nodesep=0.6; + ranksep=0.7; + fontname="Helvetica"; + node [shape=box, style="rounded,filled", color="#334155", fillcolor="#f8fafc", fontname="Helvetica", fontsize=10]; + edge [color="#475569", arrowsize=0.7, penwidth=1.1]; + + s_if2 [label="ens2", fillcolor="#e2e8f0"]; + s_if3 [label="ens3", fillcolor="#e2e8f0"]; + s_if4 [label="ens4", fillcolor="#e2e8f0"]; + + s_n_admin [label="external_admin_net", fillcolor="#fee2e2", color="#b91c1c"]; + s_n_ctl [label="provision_ctl_net", fillcolor="#fee2e2", color="#b91c1c"]; + s_n_power [label="power_mgmt_net", fillcolor="#fee2e2", color="#b91c1c"]; + + s_n_admin -> s_if2; + s_n_ctl -> s_if3; + s_n_power -> s_if4; + } + +In ``etc/kayobe/seed.yml`` define extra networks that are not mapped to this +host by default: + +.. code-block:: yaml + + seed_extra_network_interfaces: + - external_admin_net + +In ``inventory/group_vars/seed/network-interfaces`` map networks directly to +host interfaces. + +To see which interface variables require definitions, run +``kayobe configuration dump --var-name network_interfaces --limit seed`` + +.. code-block:: yaml + + external_admin_net_interface: ens2 + provision_ctl_net_interface: ens3 + power_mgmt_net_interface: ens4 + +Controllers +=========== + +.. warning:: + + Network reconfiguration can potentially break networking and render hosts + inaccessible. This is particularly risky when moving the admin interface + onto a bridge or bond. It is recommended that you: + + - Add a password to a local user account that can be accessed via the BMC + (either web UI or serial console) to recover should configuration fail. + This password should be removed after successful initial deployment. + + - Configure one host per group initially before applying the configuration + across the entire group. If a host becomes misconfigured, it can simply be + reprovisioned. + +The diagram below shows the network interface configuration for the +controllers: + +Controllers use a combination of bond aggregation and Linux bridges. + +.. graphviz:: + :caption: Controller interface layering + + digraph controller_interfaces { + rankdir=LR; + nodesep=0.35; + ranksep=0.6; + fontname="Helvetica"; + node [shape=box, style="rounded,filled", color="#334155", fillcolor="#f8fafc", fontname="Helvetica", fontsize=10]; + edge [color="#475569", arrowsize=0.7, penwidth=1.1]; + + c_m1 [label="eno33np0", fillcolor="#e2e8f0"]; + c_m2 [label="eno34np1", fillcolor="#e2e8f0"]; + c_m3 [label="ens3f0np0", fillcolor="#e2e8f0"]; + c_m4 [label="ens3f1np1", fillcolor="#e2e8f0"]; + c_m5 [label="eno35np2", fillcolor="#e2e8f0"]; + c_m6 [label="eno36np3", fillcolor="#e2e8f0"]; + + c_b1 [label="bond1", fillcolor="#dbeafe", color="#1d4ed8"]; + c_b0 [label="bond0", fillcolor="#dbeafe", color="#1d4ed8"]; + c_b2 [label="bond2", fillcolor="#dbeafe", color="#1d4ed8"]; + + c_v_int [label="bond0.4", fillcolor="#fef3c7", color="#b45309"]; + c_v_sto [label="bond0.5", fillcolor="#fef3c7", color="#b45309"]; + c_v_tun [label="bond0.7", fillcolor="#fef3c7", color="#b45309"]; + c_v_ext [label="brbond2.2280", fillcolor="#fef3c7", color="#b45309"]; + + c_br_ctl [label="brbond1", fillcolor="#dcfce7", color="#15803d"]; + c_br_ext [label="brbond2", fillcolor="#dcfce7", color="#15803d"]; + + c_n_ctl [label="provision_ctl_net", fillcolor="#fee2e2", color="#b91c1c"]; + c_n_int [label="internal_net", fillcolor="#fee2e2", color="#b91c1c"]; + c_n_sto [label="storage_net", fillcolor="#fee2e2", color="#b91c1c"]; + c_n_tun [label="tenant_tunnel_net", fillcolor="#fee2e2", color="#b91c1c"]; + c_n_ext [label="external_user_net", fillcolor="#fee2e2", color="#b91c1c"]; + + c_n_ctl -> c_br_ctl -> c_b1; + c_n_int -> c_v_int -> c_b0; + c_n_sto -> c_v_sto -> c_b0; + c_n_tun -> c_v_tun -> c_b0; + c_n_ext -> c_v_ext -> c_br_ext -> c_b2; + + c_b1 -> c_m1; + c_b1 -> c_m2; + c_b0 -> c_m3; + c_b0 -> c_m4; + c_b2 -> c_m5; + c_b2 -> c_m6; + } + +Controllers integrate the physical bond interfaces with OpenStack Neutron to +provide tenant network connectivity. + +.. graphviz:: + :caption: Wiring networks into Neutron (controllers) + + digraph neutron_wiring_controllers { + rankdir=LR; + nodesep=0.35; + ranksep=0.7; + fontname="Helvetica"; + node [shape=box, style="rounded,filled", color="#334155", fillcolor="#f8fafc", fontname="Helvetica", fontsize=10]; + edge [color="#475569", arrowsize=0.7, penwidth=1.1]; + + { rank=same; c_net_ext; c_net_hs; c_net_ctl; } + + c_net_ext [label="external_bridge\nphysnet1\n(network maps to Linux bridge)", fillcolor="#fee2e2", color="#b91c1c"]; + c_net_hs [label="hs_bond\nphysnet2\n(network maps directly)", fillcolor="#fee2e2", color="#b91c1c"]; + c_net_ctl [label="provision_ctl_net\nphysnet3\n(network maps to Linux bridge)", fillcolor="#fee2e2", color="#b91c1c"]; + + c_lx_ext [label="Linux bridge\nbrbond2", fillcolor="#dcfce7", color="#15803d"]; + c_lx_ctl [label="Linux bridge\nbrbond1", fillcolor="#dcfce7", color="#15803d"]; + c_bond0 [label="bond0", fillcolor="#fef3c7", color="#b45309"]; + + c_ovs_ext [label="OVS bridge\nbrbond2-ovs", fillcolor="#dbeafe", color="#1d4ed8"]; + c_ovs_hs [label="OVS bridge\nbond0-ovs", fillcolor="#dbeafe", color="#1d4ed8"]; + c_ovs_ctl [label="OVS bridge\nbrbond1-ovs", fillcolor="#dbeafe", color="#1d4ed8"]; + c_neutron [label="Neutron\nprovider networks", fillcolor="#e0f2fe", color="#0284c7"]; + + subgraph cluster_c_pp_brbond2 { + label="patch link"; + style="dashed"; + color="#6d28d9"; + fillcolor="#faf5ff"; + c_pp_brbond2_phy [label="p-brbond2-phy", fillcolor="#ede9fe", color="#6d28d9"]; + c_pp_brbond2_ovs [label="p-brbond2-ovs", fillcolor="#ede9fe", color="#6d28d9"]; + } + + subgraph cluster_c_pp_brbond1 { + label="patch link"; + style="dashed"; + color="#6d28d9"; + fillcolor="#faf5ff"; + c_pp_brbond1_phy [label="p-brbond1-phy", fillcolor="#ede9fe", color="#6d28d9"]; + c_pp_brbond1_ovs [label="p-brbond1-ovs", fillcolor="#ede9fe", color="#6d28d9"]; + } + + c_net_ext -> c_lx_ext; + c_lx_ext -> c_pp_brbond2_phy; + c_pp_brbond2_ovs -> c_ovs_ext; + c_net_ctl -> c_lx_ctl; + c_lx_ctl -> c_pp_brbond1_phy; + c_pp_brbond1_ovs -> c_ovs_ctl; + c_net_hs -> c_bond0; + c_bond0 -> c_ovs_hs [label="direct interface"]; + + c_ovs_ext -> c_neutron; + c_ovs_hs -> c_neutron; + c_ovs_ctl -> c_neutron; + } + +This diagram shows how host networks are wired into Neutron through Open +vSwitch bridges. When a network maps to a Linux bridge, that bridge connects to +OVS via a patch link. When a network does not map to a Linux bridge, it is +connected directly into the OVS bridge. Where possible, operators should +prefer wiring a bond directly into OVS, since this can enable hardware +offloading. Linux bridges are required when a service running on the host must +attach directly to that network. + +Controllers usually combine bridge-on-bond for external/API traffic with VLAN +subinterfaces for API and tunnel networks. + +In ``etc/kayobe/controllers.yml`` ensure the host network list includes the +expected external and control networks: + +.. code-block:: yaml + + controller_network_interfaces: "{{ storage_network_interfaces + controller_extra_network_interfaces }}" + + controller_extra_network_interfaces: + - external_10g + - external_bridge + - control_25g + - external_user_net + - tenant_tunnel_net + +In ``inventory/group_vars/controllers/network-interfaces`` define the bond and +bridge pattern. + +To see which interface variables require definitions, run +``kayobe configuration dump --var-name network_interfaces --limit controllers[0]``. Before creating this file, also run ``ip a`` on +controller hosts and capture the physical NIC names used in bond slave lists. + +.. code-block:: yaml + + external_net_names: + - external_bridge + - hs_bond + - provision_ctl_net + + external_10g_interface: bond2 + external_10g_bond_mode: 802.3ad + external_10g_bond_xmit_hash_policy: layer3+4 + external_10g_bond_slaves: + - eno35np2 + - eno36np3 + + control_25g_bond_slaves: + - eno33np0 + - eno34np1 + + hs_bond_bond_slaves: + - ens3f0np0 + - ens3f1np1 + + external_bridge_interface: "br{{ external_10g_interface }}" + external_bridge_bridge_ports: + - "{{ external_10g_interface }}" + + external_user_net_interface: "{{ external_bridge_interface }}.{{ external_user_net_vlan }}" + tenant_tunnel_net_interface: "{{ hs_bond_interface }}.{{ tenant_tunnel_net_vlan }}" + +Compute +======= + +.. warning:: + + Network reconfiguration can potentially break networking and render hosts + inaccessible. This is particularly risky when moving the admin interface + onto a bridge or bond. It is recommended that you: + + - Add a password to a local user account that can be accessed via the BMC + (either web UI or serial console) to recover should configuration fail. + This password should be removed after successful initial deployment. + + - Configure one host per group initially before applying the configuration + across the entire group. If a host becomes misconfigured, it can simply be + reprovisioned. + +The diagram below shows the network interface configuration for the compute +nodes: + +Compute hosts use a high-speed bond for internal OpenStack networks and a +separate bond for management connectivity. + +.. graphviz:: + :caption: Compute interface layering + + digraph compute_interfaces { + rankdir=LR; + nodesep=0.35; + ranksep=0.6; + fontname="Helvetica"; + node [shape=box, style="rounded,filled", color="#334155", fillcolor="#f8fafc", fontname="Helvetica", fontsize=10]; + edge [color="#475569", arrowsize=0.7, penwidth=1.1]; + + k_m1 [label="eno12399np0", fillcolor="#e2e8f0"]; + k_m2 [label="eno12409np1", fillcolor="#e2e8f0"]; + k_m3 [label="ens3f0np0", fillcolor="#e2e8f0"]; + k_m4 [label="ens3f1np1", fillcolor="#e2e8f0"]; + + k_b1 [label="bond1", fillcolor="#dbeafe", color="#1d4ed8"]; + k_b0 [label="bond0", fillcolor="#dbeafe", color="#1d4ed8"]; + + k_v_int [label="bond0.4", fillcolor="#fef3c7", color="#b45309"]; + k_v_sto [label="bond0.5", fillcolor="#fef3c7", color="#b45309"]; + k_v_tun [label="bond0.7", fillcolor="#fef3c7", color="#b45309"]; + + k_br_ctl [label="brbond1", fillcolor="#dcfce7", color="#15803d"]; + + k_n_ctl [label="provision_ctl_net", fillcolor="#fee2e2", color="#b91c1c"]; + k_n_int [label="internal_net", fillcolor="#fee2e2", color="#b91c1c"]; + k_n_sto [label="storage_net", fillcolor="#fee2e2", color="#b91c1c"]; + k_n_tun [label="tenant_tunnel_net", fillcolor="#fee2e2", color="#b91c1c"]; + + k_n_ctl -> k_br_ctl -> k_b1; + k_n_int -> k_v_int -> k_b0; + k_n_sto -> k_v_sto -> k_b0; + k_n_tun -> k_v_tun -> k_b0; + + k_b1 -> k_m1; + k_b1 -> k_m2; + k_b0 -> k_m3; + k_b0 -> k_m4; + } + +Compute hosts also integrate with Neutron through Open vSwitch bridges, using +a similar bridge-on-bond model for control network traffic. + +.. graphviz:: + :caption: Wiring networks into Neutron (compute) + + digraph neutron_wiring_compute { + rankdir=LR; + nodesep=0.35; + ranksep=0.7; + fontname="Helvetica"; + node [shape=box, style="rounded,filled", color="#334155", fillcolor="#f8fafc", fontname="Helvetica", fontsize=10]; + edge [color="#475569", arrowsize=0.7, penwidth=1.1]; + + { rank=same; k_net_hs; k_net_ctl; } + + k_net_hs [label="hs_bond\nphysnet2\n(network maps directly)", fillcolor="#fee2e2", color="#b91c1c"]; + k_net_ctl [label="provision_ctl_net\nphysnet3\n(network maps to Linux bridge)", fillcolor="#fee2e2", color="#b91c1c"]; + + k_lx_ctl [label="Linux bridge\nbrbond1", fillcolor="#dcfce7", color="#15803d"]; + k_bond0 [label="bond0", fillcolor="#fef3c7", color="#b45309"]; + k_ovs_hs [label="OVS bridge\nbond0-ovs", fillcolor="#dbeafe", color="#1d4ed8"]; + k_ovs_ctl [label="OVS bridge\nbrbond1-ovs", fillcolor="#dbeafe", color="#1d4ed8"]; + k_neutron [label="Neutron\nprovider networks", fillcolor="#e0f2fe", color="#0284c7"]; + + subgraph cluster_k_pp_brbond1 { + label="patch link"; + style="dashed"; + color="#6d28d9"; + fillcolor="#faf5ff"; + k_pp_brbond1_phy [label="p-brbond1-phy", fillcolor="#ede9fe", color="#6d28d9"]; + k_pp_brbond1_ovs [label="p-brbond1-ovs", fillcolor="#ede9fe", color="#6d28d9"]; + } + + k_net_ctl -> k_lx_ctl; + k_lx_ctl -> k_pp_brbond1_phy; + k_pp_brbond1_ovs -> k_ovs_ctl; + k_net_hs -> k_bond0; + k_bond0 -> k_ovs_hs [label="direct interface"]; + + k_ovs_hs -> k_neutron; + k_ovs_ctl -> k_neutron; + } + +.. note:: + + Operators should prefer directly connecting interfaces into Open vSwitch + bridges to allow for hardware offloading. + +Compute hosts in this configuration use a high-speed bond and a separate +control bond, with internal, storage, and tunnel VLANs carried on the +high-speed bond. + +In ``etc/kayobe/compute.yml`` include the required network list: + +.. code-block:: yaml + + compute_extra_network_interfaces: + - provision_ctl_net + - control_25g + - hs_bond + +In ``inventory/group_vars/compute/network-interfaces`` define bond members and +VLAN subinterfaces. + +To see which interface variables require definitions, run +``kayobe configuration dump --var-name network_interfaces --limit compute[0]``. +Before creating this file, also run ``ip a`` on compute hosts +and use the detected physical interface names for bond members. + +.. code-block:: yaml + + control_25g_bond_slaves: + - eno12399np0 + - eno12409np1 + + hs_bond_bond_slaves: + - ens3f0np0 + - ens3f1np1 + + internal_net_interface: "{{ hs_bond_interface }}.{{ internal_net_vlan }}" + storage_net_interface: "{{ hs_bond_interface }}.{{ storage_net_vlan }}" + tenant_tunnel_net_interface: "{{ hs_bond_interface }}.{{ tenant_tunnel_net_vlan }}" + +Storage +======= + +.. warning:: + + Network reconfiguration can potentially break networking and render hosts + inaccessible. This is particularly risky when moving the admin interface + onto a bridge or bond. It is recommended that you: + + - Add a password to a local user account that can be accessed via the BMC + (either web UI or serial console) to recover should configuration fail. + This password should be removed after successful initial deployment. + + - Configure one host per group initially before applying the configuration + across the entire group. If a host becomes misconfigured, it can simply be + reprovisioned. + +Storage hosts follow the same control-bond plus high-speed-bond model as +compute hosts, with additional networks for storage and storage management. + +.. graphviz:: + :caption: Storage interface layering + + digraph storage_interfaces { + rankdir=LR; + nodesep=0.35; + ranksep=0.6; + fontname="Helvetica"; + node [shape=box, style="rounded,filled", color="#334155", fillcolor="#f8fafc", fontname="Helvetica", fontsize=10]; + edge [color="#475569", arrowsize=0.7, penwidth=1.1]; + + s_m1 [label="eno33np0", fillcolor="#e2e8f0"]; + s_m2 [label="eno34np1", fillcolor="#e2e8f0"]; + s_m3 [label="ens2f0np0", fillcolor="#e2e8f0"]; + s_m4 [label="ens2f1np1", fillcolor="#e2e8f0"]; + + s_b1 [label="bond1", fillcolor="#dbeafe", color="#1d4ed8"]; + s_b0 [label="bond0", fillcolor="#dbeafe", color="#1d4ed8"]; + + s_v_int [label="bond0.4", fillcolor="#fef3c7", color="#b45309"]; + s_v_sto [label="bond0.5", fillcolor="#fef3c7", color="#b45309"]; + s_v_sto_mgmt [label="bond0.6", fillcolor="#fef3c7", color="#b45309"]; + + s_br_ctl [label="brbond1", fillcolor="#dcfce7", color="#15803d"]; + + s_n_ctl [label="provision_ctl_net", fillcolor="#fee2e2", color="#b91c1c"]; + s_n_int [label="internal_net", fillcolor="#fee2e2", color="#b91c1c"]; + s_n_sto [label="storage_net", fillcolor="#fee2e2", color="#b91c1c"]; + s_n_sto_mgmt [label="storage_mgmt_net", fillcolor="#fee2e2", color="#b91c1c"]; + + s_n_ctl -> s_br_ctl -> s_b1; + s_n_int -> s_v_int -> s_b0; + s_n_sto -> s_v_sto -> s_b0; + s_n_sto_mgmt -> s_v_sto_mgmt -> s_b0; + + s_b1 -> s_m1; + s_b1 -> s_m2; + s_b0 -> s_m3; + s_b0 -> s_m4; + } + +Storage hosts in this configuration follow the same control-bond plus +high-speed-bond model, with storage and storage management networks carried as +VLAN subinterfaces on the high-speed bond. + +In ``etc/kayobe/storage.yml`` include the required network list: + +.. code-block:: yaml + + storage_extra_network_interfaces: + - provision_ctl_net + - control_25g + - hs_bond + - storage_mgmt_net + +In ``inventory/group_vars/storage/network-interfaces`` define bond members and +network VLAN mappings. + +To see which interface variables require definitions, run +``kayobe configuration dump --var-name network_interfaces --limit storage[0]`` against the +storage group. Before creating this file, also run ``ip a`` on storage hosts +and use the detected physical interface names for bond members. + +.. code-block:: yaml + + control_25g_bond_slaves: + - eno33np0 + - eno34np1 + + hs_bond_bond_slaves: + - ens2f0np0 + - ens2f1np1 + + internal_net_interface: "{{ hs_bond_interface }}.{{ internal_net_vlan }}" + storage_net_interface: "{{ hs_bond_interface }}.{{ storage_net_vlan }}" + storage_mgmt_net_interface: "{{ hs_bond_interface }}.{{ storage_mgmt_net_vlan }}" + +Validation +========== + +After applying the configuration, verify network connectivity across all host +groups: + +.. code-block:: console + + kayobe network connectivity check diff --git a/doc/source/configuration/index.rst b/doc/source/configuration/index.rst index 3dc8c7a43..f567656d0 100644 --- a/doc/source/configuration/index.rst +++ b/doc/source/configuration/index.rst @@ -11,4 +11,5 @@ Kayobe's configuration options. :maxdepth: 2 scenarios/index + examples/index reference/index diff --git a/doc/source/configuration/reference/network.rst b/doc/source/configuration/reference/network.rst index 721ee6aee..e75771266 100644 --- a/doc/source/configuration/reference/network.rst +++ b/doc/source/configuration/reference/network.rst @@ -10,6 +10,13 @@ that define the network's attributes. For example, to configure the ``cidr`` attribute of a network named ``arpanet``, we would use a variable named ``arpanet_cidr``. +Real World Examples +=================== + +* :doc:`../examples/real-world-bridges-and-bonds-configuration-a-virtualised-case-study`: + End-to-end example covering bridges, bonds, and seed hypervisor network + layout. + .. _configuration-network-global: Global Network Configuration @@ -1204,6 +1211,11 @@ are attached may be defined in a host or group variables file. See Complete Example ================ +.. note:: + + For a more comprehensive real-world example, see + :doc:`../examples/real-world-bridges-and-bonds-configuration-a-virtualised-case-study`. + The following example combines the complete network configuration into a single system configuration. In our example cloud we have three networks: ``management``, ``cloud`` and ``external``: From 83bd18487b1bf75263775ea3c01cc5fae4352b90 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Mon, 23 Feb 2026 19:38:38 +0000 Subject: [PATCH 2/4] Adds kayobe inventory command This is a thin wrapper around ansible inventory that takes into account environments and kayobe extra vars. Change-Id: I48930f1ffbd407406070ba7aef1392bbe9c9b045 Signed-off-by: Will Szumski --- doc/source/administration/general.rst | 14 ++ kayobe/ansible.py | 227 ++++++++++++++++++ kayobe/cli/commands.py | 14 ++ kayobe/tests/unit/test_ansible.py | 86 +++++++ ...le-inventory-wrapper-3c3b88c0c0b33d16.yaml | 5 + setup.cfg | 1 + 6 files changed, 347 insertions(+) create mode 100644 releasenotes/notes/adds-ansible-inventory-wrapper-3c3b88c0c0b33d16.yaml diff --git a/doc/source/administration/general.rst b/doc/source/administration/general.rst index e33e4bc75..2c8e2fa4e 100644 --- a/doc/source/administration/general.rst +++ b/doc/source/administration/general.rst @@ -58,6 +58,20 @@ We can use the ``--var-name`` argument to inspect a particular variable or the ``--host`` or ``--hosts`` arguments to view a variable or variables for a specific host or set of hosts. +Viewing the Kayobe inventory +============================ + +In some cases you may want to examine the full inventory that Kayobe will +pass to Ansible. The ``kayobe inventory`` command wraps +``ansible-inventory`` and automatically assembles the list of inventory +paths (shared config plus any environment-specific directories). It can +be used exactly like ``ansible-inventory``; for example:: + + (kayobe) $ kayobe inventory --graph + +Any other flags supported by ``ansible-inventory`` are also accepted. + + Checking Network Connectivity ============================= diff --git a/kayobe/ansible.py b/kayobe/ansible.py index caa6df7b1..066668aaa 100644 --- a/kayobe/ansible.py +++ b/kayobe/ansible.py @@ -83,6 +83,169 @@ def add_args(parser): "specific playbooks. \"all\" skips all playbooks") +def add_inventory_args(parser): + """Add arguments required for running Ansible playbooks to a parser.""" + + # Kayobe configuration options + default_config_path = os.getenv(CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH) + default_environment = os.getenv(ENVIRONMENT_ENV) + parser.add_argument( + "--config-path", + default=default_config_path, + help="path to Kayobe configuration. (default=$%s or %s)" + % (CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH), + ) + parser.add_argument( + "--environment", + default=default_environment, + help=("specify environment name " + "(default=$%s or None)" % ENVIRONMENT_ENV), + ) + parser.add_argument( + "-i", + "--inventory", + metavar="INVENTORY", + action="append", + help=( + "specify inventory host path " + "(default=$%s/inventory or %s/inventory) " + ) + % (CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH), + ) + # Positional argument + parser.add_argument( + "group", + nargs="?", + help=( + "The name of a group in the inventory, relevant when using --graph" + ), + ) + + # Standard options + parser.add_argument( + "-v", + "--verbose", + action="count", + help=( + "Causes Ansible to print more debug messages. Adding multiple -v" + " will increase the verbosity, the builtin plugins currently" + " evaluate up to -vvvvvv. A reasonable level to start is -vvv," + " connection debugging might require -vvvv." + ), + ) + parser.add_argument( + "-l", + "--limit", + metavar="SUBSET", + help="further limit selected hosts to an additional pattern", + ) + parser.add_argument( + "--flush-cache", + action="store_true", + help="clear the fact cache for every host in inventory", + ) + parser.add_argument( + "--vault-id", + metavar="VAULT_IDS", + action="append", + help=( + "the vault identity to use. This argument may be specified " + "multiple times." + ), + ) + parser.add_argument( + "--playbook-dir", + metavar="BASEDIR", + help=( + "Since this tool does not use playbooks, use this as a substitute" + "playbook directory. This sets the relative path for many " + "features including roles/ group_vars/ etc." + ), + ) + parser.add_argument( + "-e", + "--extra-vars", + metavar="EXTRA_VARS", + action="append", + dest="extra_vars", + help=( + "set additional variables as key=value or YAML/JSON, if filename" + " prepend with @. This argument may be specified multiple times." + ), + ) + + # Format options + parser.add_argument( + "-y", + "--yaml", + action="store_true", + help="Use YAML format instead of default JSON, ignored for --graph", + ) + parser.add_argument( + "--toml", + action="store_true", + help="Use TOML format instead of default JSON, ignored for --graph", + ) + + # Other options + parser.add_argument( + "--export", + action="store_true", + help=( + "When doing --list, represent in a way that is optimized for" + " export,not as an accurate representation of how Ansible has" + " processed it" + ), + ) + parser.add_argument( + "--output", + metavar="OUTPUT_FILE", + help=( + "When doing --list, send the inventory to a file instead of to the" + " screen" + ), + ) + parser.add_argument( + "--vars", + action="store_true", + help="Add vars to graph display, ignored unless used with --graph", + ) + parser.add_argument( + "--version", + action="version", + version="%(prog)s 2.15.0 (example version)", + help=( + "show program's version number, config file location, configured" + "module search path, module location, executable location " + "and exit" + ), + ) + + # Actions: mutually exclusive + action_group = parser.add_mutually_exclusive_group(required=True) + action_group.add_argument( + "--list", + action="store_true", + help="Output all hosts info, works as inventory script", + ) + action_group.add_argument( + "--host", + metavar="HOST", + help=( + "Output specific host info, works as inventory script. It will" + " ignore limit" + ), + ) + action_group.add_argument( + "--graph", + action="store_true", + help=( + "create inventory graph, if supplying pattern it must be a valid" + " group name. It will ignore limit" + ), + ) + + def _get_inventories_paths(parsed_args, env_paths): """Return the paths to the Kayobe inventories.""" default_inventory = utils.get_data_files_path("ansible", "inventory") @@ -336,6 +499,70 @@ def run_playbooks(parsed_args, playbooks, sys.exit(e.returncode) +def build_long_opts(arg_dict): + """Convert dict to flat ['--flag', 'value', ...] list.""" + cmd = [] + for key, value in arg_dict.items(): + if value is None or value is False: + continue + flag = f'--{key.replace("_", "-")}' + if isinstance(value, bool): + if value: + cmd.append(flag) + elif isinstance(value, list): + for v in value: + cmd.extend([flag, str(v)]) + else: + cmd.extend([flag, str(value)]) + + return cmd + + +def run_ansible_inventory(parsed_args): + """Run the Ansible inventory command.""" + + cmd = ["ansible-inventory"] + environment_finder = utils.EnvironmentFinder( + parsed_args.config_path, parsed_args.environment + ) + env_paths = environment_finder.ordered_paths() + inventories = _get_inventories_paths(parsed_args, env_paths) + for inventory in inventories: + cmd += ["--inventory", inventory] + vars_paths = [parsed_args.config_path] + for env_path in env_paths: + vars_paths.append(env_path) + vars_files = _get_vars_files(vars_paths) + for vars_file in vars_files: + cmd += ["-e", "@%s" % vars_file] + cmd += vault.build_args(parsed_args, "--vault-password-file") + + env = _get_environment(parsed_args, external_playbook=False) + + passthrough_args = vars(parsed_args) + + # Remove arguments that are not passthrough args. + passthrough_args.pop("inventory", None) + passthrough_args.pop("environment", None) + passthrough_args.pop("config_path", None) + + # Remove positional argument 'group' from passthrough args + # prior to building inventory args, as we don't pass it as a long option. + # We do want to use it as a positional argument at the end of the + # command. + group = passthrough_args.pop("group", None) + + cmd += build_long_opts(passthrough_args) + + # Add positional args at the end, as required by ansible-inventory. + if group: + cmd += [group] + + LOG.debug("Running: %s" % " ".join(cmd)) + + os.execvpe(cmd[0], cmd, env) + + def run_playbook(parsed_args, playbook, *args, **kwargs): """Run a Kayobe Ansible playbook.""" return run_playbooks(parsed_args, [playbook], *args, **kwargs) diff --git a/kayobe/cli/commands.py b/kayobe/cli/commands.py index 4dbcb4cdc..650e52fbf 100644 --- a/kayobe/cli/commands.py +++ b/kayobe/cli/commands.py @@ -1136,6 +1136,20 @@ def take_action(self, parsed_args): extra_vars=extra_vars) +class Inventory(VaultMixin, Command): + """Wrapper for ansible-inventory showing Kayobe inventory.""" + + def get_parser(self, prog_name): + parser = super(Inventory, self).get_parser(prog_name) + group = parser.add_argument_group("Inventory") + ansible.add_inventory_args(group) + return parser + + def take_action(self, parsed_args): + self.app.LOG.debug("Running ansible-inventory") + ansible.run_ansible_inventory(parsed_args) + + class InfraVMHostUpgrade(KayobeAnsibleMixin, VaultMixin, Command): """Upgrade the infra VM host services. diff --git a/kayobe/tests/unit/test_ansible.py b/kayobe/tests/unit/test_ansible.py index 00600287d..eb5bd0e78 100644 --- a/kayobe/tests/unit/test_ansible.py +++ b/kayobe/tests/unit/test_ansible.py @@ -184,6 +184,92 @@ def test_reserved_environment_negative( parsed_args = parser.parse_args(args) ansible.run_playbooks(parsed_args, ["playbook1.yml", "playbook2.yml"]) + def test_build_long_opts(self): + # Ensure build_long_opts handles different types correctly. + opts = ansible.build_long_opts({ + "foo": "bar", + "baz": True, + "quux": False, + "items": ["a", "b"], + "none": None, + }) + self.assertIn("--foo", opts) + self.assertIn("bar", opts) + self.assertIn("--baz", opts) + self.assertNotIn("--quux", opts) + # ensure list items appear correctly as repeated flags + # exact expected output including list entries and bool flag. + expected = ["--foo", "bar", "--baz", "--items", "a", + "--items", "b"] + self.assertEqual(expected, opts) + self.assertNotIn("--none", opts) + + @mock.patch.object(os, "execvpe") + @mock.patch.object(ansible, "_get_inventories_paths") + @mock.patch.object(ansible, "_get_vars_files") + @mock.patch.object(vault, "build_args") + @mock.patch.object(ansible, "_get_environment") + def test_run_ansible_inventory_basic(self, mock_env, mock_vault_args, + mock_vars_files, mock_inventories, + mock_exec): + parser = argparse.ArgumentParser() + ansible.add_inventory_args(parser) + args = [ + "--config-path", "/etc/kayobe", + "--environment", "foo", + "--inventory", "/inv1", + "--inventory", "/inv2", + "--list", + ] + parsed_args = parser.parse_args(args) + mock_inventories.return_value = ["/inv1", "/inv2"] + mock_vars_files.return_value = ["/vars1.yml"] + mock_vault_args.return_value = ["--vault-password-file", "/pw"] + mock_env.return_value = {"FOO": "bar"} + ansible.run_ansible_inventory(parsed_args) + expected_cmd = [ + "ansible-inventory", + "--inventory", "/inv1", + "--inventory", "/inv2", + "-e", "@/vars1.yml", + "--vault-password-file", "/pw", + "--list", + ] + mock_exec.assert_called_once_with( + expected_cmd[0], expected_cmd, {"FOO": "bar"} + ) + + @mock.patch.object(os, "execvpe") + @mock.patch.object(ansible, "_get_inventories_paths") + @mock.patch.object(ansible, "_get_vars_files") + @mock.patch.object(vault, "build_args") + @mock.patch.object(ansible, "_get_environment") + def test_run_ansible_inventory_with_group(self, mock_env, mock_vault_args, + mock_vars_files, + mock_inventories, mock_exec): + parser = argparse.ArgumentParser() + ansible.add_inventory_args(parser) + args = [ + "--inventory", "/inv", + "--graph", + "web", + ] + parsed_args = parser.parse_args(args) + mock_inventories.return_value = ["/inv"] + mock_vars_files.return_value = [] + mock_vault_args.return_value = [] + mock_env.return_value = {} + ansible.run_ansible_inventory(parsed_args) + expected_cmd = [ + "ansible-inventory", + "--inventory", "/inv", + "--graph", + "web", + ] + mock_exec.assert_called_once_with( + expected_cmd[0], expected_cmd, {} + ) + @mock.patch.object(utils, "run_command") @mock.patch.object(ansible, "_get_vars_files") @mock.patch.object(ansible, "_validate_args") diff --git a/releasenotes/notes/adds-ansible-inventory-wrapper-3c3b88c0c0b33d16.yaml b/releasenotes/notes/adds-ansible-inventory-wrapper-3c3b88c0c0b33d16.yaml new file mode 100644 index 000000000..b15b68e0b --- /dev/null +++ b/releasenotes/notes/adds-ansible-inventory-wrapper-3c3b88c0c0b33d16.yaml @@ -0,0 +1,5 @@ +--- +feature: + - | + Adds a `kayobe inventory` command which wraps `ansible-inventory` to + display the Kayobe Ansible inventory. diff --git a/setup.cfg b/setup.cfg index 81bc91a1d..61c698637 100644 --- a/setup.cfg +++ b/setup.cfg @@ -53,6 +53,7 @@ kayobe.cli= control_host_upgrade = kayobe.cli.commands:ControlHostUpgrade configuration_dump = kayobe.cli.commands:ConfigurationDump environment_create = kayobe.cli.commands:EnvironmentCreate + inventory= kayobe.cli.commands:Inventory kolla_ansible_run = kayobe.cli.commands:KollaAnsibleRun network_connectivity_check = kayobe.cli.commands:NetworkConnectivityCheck overcloud_bios_raid_configure = kayobe.cli.commands:OvercloudBIOSRAIDConfigure From 69588969bd982a859961b0daade6b60c4e871081 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Wed, 22 Apr 2026 18:27:09 +0100 Subject: [PATCH 3/4] [CI] Disable NTP checks in CI These are proving unreliable. Re-enable once the root cause is fixed. Change-Id: Ic5ac59f0cdf0494ad3963cefb752b8d58626a43a Signed-off-by: Will Szumski --- playbooks/kayobe-overcloud-base/globals.yml.j2 | 3 +++ playbooks/kayobe-overcloud-upgrade-base/globals.yml.j2 | 3 +++ playbooks/kayobe-seed-base/globals.yml.j2 | 3 +++ playbooks/kayobe-seed-upgrade-base/globals.yml.j2 | 3 +++ 4 files changed, 12 insertions(+) diff --git a/playbooks/kayobe-overcloud-base/globals.yml.j2 b/playbooks/kayobe-overcloud-base/globals.yml.j2 index 58c4c7ec9..75a08f71a 100644 --- a/playbooks/kayobe-overcloud-base/globals.yml.j2 +++ b/playbooks/kayobe-overcloud-base/globals.yml.j2 @@ -24,3 +24,6 @@ kolla_admin_openrc_cacert: "/etc/pki/tls/certs/ca-bundle.crt" libvirt_tls: "yes" certificates_libvirt_output_dir: "{% raw %}{{ kayobe_env_config_path }}{% endraw %}/certificates/libvirt" {% endif %} + +# FIXME(wszumski): The time check is unreliable in CI +prechecks_enable_host_ntp_checks: false diff --git a/playbooks/kayobe-overcloud-upgrade-base/globals.yml.j2 b/playbooks/kayobe-overcloud-upgrade-base/globals.yml.j2 index 6dc842c47..561e1e9d5 100644 --- a/playbooks/kayobe-overcloud-upgrade-base/globals.yml.j2 +++ b/playbooks/kayobe-overcloud-upgrade-base/globals.yml.j2 @@ -14,3 +14,6 @@ openstack_service_rpc_workers: "1" # Reduce size of libvirt logs when OpenStack debug logging is enabled nova_libvirt_logging_debug: False + +# FIXME(wszumski): The time check is unreliable in CI +prechecks_enable_host_ntp_checks: false diff --git a/playbooks/kayobe-seed-base/globals.yml.j2 b/playbooks/kayobe-seed-base/globals.yml.j2 index 82a51b601..153934272 100644 --- a/playbooks/kayobe-seed-base/globals.yml.j2 +++ b/playbooks/kayobe-seed-base/globals.yml.j2 @@ -1,3 +1,6 @@ --- # Use HTTPS opendev quay.io registry proxy. docker_registry_insecure: no + +# FIXME(wszumski): The time check is unreliable in CI +prechecks_enable_host_ntp_checks: false diff --git a/playbooks/kayobe-seed-upgrade-base/globals.yml.j2 b/playbooks/kayobe-seed-upgrade-base/globals.yml.j2 index 82a51b601..153934272 100644 --- a/playbooks/kayobe-seed-upgrade-base/globals.yml.j2 +++ b/playbooks/kayobe-seed-upgrade-base/globals.yml.j2 @@ -1,3 +1,6 @@ --- # Use HTTPS opendev quay.io registry proxy. docker_registry_insecure: no + +# FIXME(wszumski): The time check is unreliable in CI +prechecks_enable_host_ntp_checks: false From 3d6afe204cb03552809402442bff9688cb8506e8 Mon Sep 17 00:00:00 2001 From: Will Szumski Date: Wed, 22 Apr 2026 09:40:37 +0100 Subject: [PATCH 4/4] [CI] Use kolla-ansible configure-ephemeral role Sometimes we land on a node with a 40G disk. We've seen disk space related failures on the upgrade jobs. Change-Id: I036d95cace1fe2083f669837c99e75e15e576032 Signed-off-by: Will Szumski --- roles/kayobe-ci-prep/tasks/main.yml | 10 ++++++++++ roles/kayobe-diagnostics/files/get_logs.sh | 2 ++ zuul.d/jobs.yaml | 2 ++ 3 files changed, 14 insertions(+) diff --git a/roles/kayobe-ci-prep/tasks/main.yml b/roles/kayobe-ci-prep/tasks/main.yml index c44508217..b3b9c25ad 100644 --- a/roles/kayobe-ci-prep/tasks/main.yml +++ b/roles/kayobe-ci-prep/tasks/main.yml @@ -1,4 +1,14 @@ --- +- name: Configure secondary storage drive + #NOTE(wszumski) Some instances provide a smaller 40G root disk with a + #secondary storage drive. This kolla-ansible provided role sets up the + #secondary drive for docker/podman storage. + # see: https://docs.opendev.org/opendev/infra-manual/latest/testing.html#unprivileged-single-use-vms + include_role: + name: configure-ephemeral + vars: + configure_ephemeral_mountpoint: "{{ '/var/lib/containers' if container_engine | default('docker') == 'podman' else '/var/lib/docker' }}" + - name: Set Rocky Linux mirror to download.rockylinux.org become: true ansible.builtin.shell: diff --git a/roles/kayobe-diagnostics/files/get_logs.sh b/roles/kayobe-diagnostics/files/get_logs.sh index cc880b9d6..267563523 100644 --- a/roles/kayobe-diagnostics/files/get_logs.sh +++ b/roles/kayobe-diagnostics/files/get_logs.sh @@ -114,6 +114,8 @@ copy_logs() { iptables-save > ${LOG_DIR}/system_logs/iptables.txt + lsblk | tee ${LOG_DIR}/system_logs/lsblk.txt + if [ `command -v dpkg` ]; then dpkg -l > ${LOG_DIR}/system_logs/dpkg-l.txt fi diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index ee817b185..ecd50c249 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -111,6 +111,8 @@ container_engine: 'docker' ci_network_engine: default ironic_boot_mode: "bios" + roles: + - zuul: openstack/kolla - job: name: kayobe-overcloud-base