From d27ae6c33e47bca1174802b78fb1700e4594d5f2 Mon Sep 17 00:00:00 2001
From: Michal Nasiadka <mnasiadka@gmail.com>
Date: Tue, 18 Jun 2024 17:22:11 +0200
Subject: [PATCH] Allow using Bifrost/Ironic introspection data MAC address

Currently Kayobe sets ipv4_interface_mac to pxe interface
MAC address.

In cases where provisioning network interface and admin
network interface are different - this feature can be used
to get MAC address from Ironic introspection data.

Change-Id: Ie3c9248f0b3e47e3645e1886c0492265d52969c9
---
 ansible/inventory/group_vars/all/bifrost      |  4 +++
 ansible/kolla-bifrost-hostvars.yml            | 32 ++++++++++++++++++-
 .../configuration/reference/bifrost.rst       | 25 +++++++++++++++
 etc/kayobe/bifrost.yml                        |  6 ++++
 kayobe/plugins/filter/networks.py             | 14 ++++++++
 .../unit/plugins/filter/test_networks.py      | 19 ++++++++++-
 ...se-introspection-mac-5765956eabc8366c.yaml |  8 +++++
 7 files changed, 106 insertions(+), 2 deletions(-)
 create mode 100644 releasenotes/notes/kolla-bifrost-use-introspection-mac-5765956eabc8366c.yaml

diff --git a/ansible/inventory/group_vars/all/bifrost b/ansible/inventory/group_vars/all/bifrost
index f99fcf48..4d2e41ae 100644
--- a/ansible/inventory/group_vars/all/bifrost
+++ b/ansible/inventory/group_vars/all/bifrost
@@ -157,3 +157,7 @@ kolla_bifrost_ipa_ramdisk_checksum_algorithm: "{{ inspector_ipa_ramdisk_checksum
 
 # Server inventory for Bifrost.
 kolla_bifrost_servers: {}
+
+###############################################################################
+# Node provisioning configuration
+kolla_bifrost_use_introspection_mac: false
diff --git a/ansible/kolla-bifrost-hostvars.yml b/ansible/kolla-bifrost-hostvars.yml
index 9fbed026..12081813 100644
--- a/ansible/kolla-bifrost-hostvars.yml
+++ b/ansible/kolla-bifrost-hostvars.yml
@@ -20,7 +20,7 @@
       addressing_mode: static
       deploy_image_filename: "{{ kolla_bifrost_deploy_image_filename }}"
       deploy_image_rootfs: "{{ kolla_bifrost_deploy_image_rootfs | default(omit, true) }}"
-      ipv4_interface_mac: "{% raw %}{{ extra.pxe_interface_mac | default }}{% endraw %}"
+      ipv4_interface_mac: "{% if kolla_bifrost_ipv4_interface_mac is defined %}{{ kolla_bifrost_ipv4_interface_mac }}{% else %}{% raw %}{{ extra.pxe_interface_mac | default }}{% endraw %}{% endif %}"
       ipv4_address: "{{ admin_oc_net_name | net_ip }}"
       ipv4_subnet_mask: "{{ admin_oc_net_name | net_mask }}"
       # If the admin network does not have a gateway defined and seed SNAT is
@@ -48,6 +48,36 @@
             force: True
           run_once: true
 
+        - block:
+            - name: Query overcloud nodes' hardware introspection data
+              command: >
+                docker exec bifrost_deploy
+                bash -c '
+                env BIFROST_INVENTORY_SOURCE=ironic BIFROST_NODE_NAMES="{{ inventory_hostname }}" OS_CLOUD=bifrost
+                ansible baremetal
+                --connection local
+                --inventory /etc/bifrost/inventory/
+                -e @/etc/bifrost/bifrost.yml
+                -e @/etc/bifrost/dib.yml
+                --limit {{ inventory_hostname }}
+                -m shell
+                -a "env OS_CLOUD=bifrost baremetal introspection data save {% raw %}{{ inventory_hostname }}{% endraw %}"'
+              register: save_result
+              changed_when: False
+              # Ignore errors, log a message later.
+              delegate_to: "{{ seed_host }}"
+              vars:
+                # NOTE: Without this, the seed's ansible_host variable will not be
+                # respected when using delegate_to.
+                ansible_host: "{{ hostvars[seed_host].ansible_host | default(seed_host) }}"
+
+            - name: Set interface MAC from Ironic introspection data
+              vars:
+                introspection_data: "{{ save_result.stdout_lines[1:] | join('\n') | from_json }}"
+              set_fact:
+                kolla_bifrost_ipv4_interface_mac: "{{ introspection_data.all_interfaces[admin_oc_net_name | net_physical_interface | first].mac }}"
+          when: kolla_bifrost_use_introspection_mac | bool
+
         - name: Ensure the Bifrost host variable files exist
           copy:
             content: |
diff --git a/doc/source/configuration/reference/bifrost.rst b/doc/source/configuration/reference/bifrost.rst
index 24f2aa3e..6cdf6e0b 100644
--- a/doc/source/configuration/reference/bifrost.rst
+++ b/doc/source/configuration/reference/bifrost.rst
@@ -34,6 +34,31 @@ For example, to install Bifrost from a custom git repository:
    kolla_bifrost_source_url: https://git.example.com/bifrost
    kolla_bifrost_source_version: downstream
 
+Bifrost interface configuration
+===============================
+
+Following option allows to configure ipv4 interface MAC for the provisioned
+server in cases where the default (PXE interface MAC) is not a suitable
+solution for admin network (e.g. separate interfaces for provisioning and
+admin):
+
+.. code-block:: yaml
+   :caption: ``bifrost.yml``
+
+   kolla_bifrost_use_introspection_mac: true
+
+It will cause the ``overloud provision`` command to query Bifrost's
+Introspection data for MAC address of the interface that is bound to admin
+network. Limitation of that option is that Kayobe will use the first
+physical NIC if the interface is bond or bridge.
+
+Alternatively you can set following in host_vars of a specific host:
+
+.. code-block:: yaml
+   :caption: ``host_vars``
+
+   kolla_bifrost_ipv4_interface_mac: "<mac_address_goes_here>"
+
 .. _configuration-bifrost-overcloud-root-image:
 
 Overcloud root disk image configuration
diff --git a/etc/kayobe/bifrost.yml b/etc/kayobe/bifrost.yml
index 123c53d2..1f03b1a1 100644
--- a/etc/kayobe/bifrost.yml
+++ b/etc/kayobe/bifrost.yml
@@ -158,6 +158,12 @@
 # Server inventory for Bifrost.
 #kolla_bifrost_servers:
 
+###############################################################################
+# Node provisioning configuration
+# Whether to use Ironic introspection data for admin interface MAC address
+# Default is false.
+kolla_bifrost_use_introspection_mac:
+
 ###############################################################################
 # Dummy variable to allow Ansible to accept this file.
 workaround_ansible_issue_8743: yes
diff --git a/kayobe/plugins/filter/networks.py b/kayobe/plugins/filter/networks.py
index 97d4fd7e..bdf591c3 100644
--- a/kayobe/plugins/filter/networks.py
+++ b/kayobe/plugins/filter/networks.py
@@ -748,6 +748,19 @@ def net_ovs_veths(context, names, inventory_hostname=None):
         for veth in veths
     ]
 
+@jinja2.pass_context
+def net_physical_interface(context, name, inventory_hostname=None):
+    """Return a list of bridge ports, bond slaves or a direct interface name
+
+    Depending on the interface type - return a list of child interfaces or
+    (if it's not a bridge/bond) direct interface name.
+    """
+    if _net_interface_type(context, name, inventory_hostname) == 'bridge':
+        return net_bridge_ports(context, name, inventory_hostname)
+    elif  _net_interface_type(context, name, inventory_hostname) == 'bond':
+        return net_bond_slaves(context, name, inventory_hostname)
+    else:
+        return [net_attr(context, name, 'interface', inventory_hostname)]
 
 def get_filters():
     return {
@@ -799,4 +812,5 @@ def get_filters():
         'net_libvirt_network': net_libvirt_network,
         'net_libvirt_vm_network': net_libvirt_vm_network,
         'net_ovs_veths': net_ovs_veths,
+        'net_physical_interface': net_physical_interface,
     }
diff --git a/kayobe/tests/unit/plugins/filter/test_networks.py b/kayobe/tests/unit/plugins/filter/test_networks.py
index d8d786d5..c09d937a 100644
--- a/kayobe/tests/unit/plugins/filter/test_networks.py
+++ b/kayobe/tests/unit/plugins/filter/test_networks.py
@@ -35,7 +35,7 @@ class BaseNetworksTest(unittest.TestCase):
         "net2_vlan": 2,
         # net3: bridge on br0 with ports eth0 and eth1.
         "net3_interface": "br0",
-        "net3_bridge_ports": [],
+        "net3_bridge_ports": ['eth0', 'eth1'],
         # net4: VLAN on br0.4 with VLAN 4 on bridge br0.
         "net4_interface": "br0.4",
         "net4_vlan": 4,
@@ -203,3 +203,20 @@ class TestNetworks(BaseNetworksTest):
         self._update_context({"net3_bridge_ports": "ens3"})
         self.assertRaises(errors.AnsibleFilterError, networks.net_bridge_ports,
                           self.context, "net3")
+
+    def test_physical_interface_bond(self):
+        self._update_context({"net6_interface": "bond0", "net6_bond_slaves": ["eth3", "eth4"]})
+        interface = networks.net_physical_interface(self.context, "net6")
+        expected = ['eth3', 'eth4']
+        self.assertEqual(expected, interface)
+
+    def test_physical_interface_bridge(self):
+        interface = networks.net_physical_interface(self.context, "net3")
+        expected = ['eth0', 'eth1']
+        self.assertEqual(expected, interface)
+
+    def test_physical_interface_direct(self):
+        interface = networks.net_physical_interface(self.context, "net1")
+        expected = ['eth0']
+        self.assertEqual(expected, interface)
+
diff --git a/releasenotes/notes/kolla-bifrost-use-introspection-mac-5765956eabc8366c.yaml b/releasenotes/notes/kolla-bifrost-use-introspection-mac-5765956eabc8366c.yaml
new file mode 100644
index 00000000..9ef0cad6
--- /dev/null
+++ b/releasenotes/notes/kolla-bifrost-use-introspection-mac-5765956eabc8366c.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Adds support for using different interface than Bifrost PXE one for
+    admin interface during ``overcloud provision``.
+    This can be enabled by setting ``kolla_bifrost_use_introspection_mac``
+    to ``true`` or setting ``kolla_bifrost_ipv4_interface_mac`` in
+    respective host ``host_vars``.
-- 
GitLab