Browse Source

Add a mac parameter to populate mac_map for introspection

Ironic introspection only provides the switch MAC and node's port
but ansible uses a name identifier to indicate which host to operate on.

This patch adds a mapping functionality that will populate the
ansible host value in playbooks when a switch's MAC is defined
in the ML2 ini for the device and the value matches the value provided
by lldp.

Change-Id: I489e06ce8a443dd0a96f0ee580847bb314640807
Dan Radez 5 months ago
parent
commit
d1a2b9a279

+ 7
- 0
doc/source/install/configure.rst View File

@@ -42,6 +42,7 @@ managed by networking-ansible.
42 42
       ansible_host=10.10.2.250
43 43
       ansible_user=ansible
44 44
       ansible_pass=password
45
+      mac=01:23:45:67:89:AB
45 46
 
46 47
     * myhostname is an arbitrary internal identifier used only in ironic's link_local_information.
47 48
     * ansible_network_os is a valid Ansible Networking value to indicate switch type.
@@ -51,6 +52,12 @@ managed by networking-ansible.
51 52
     * ansible_host is the IP address or hostname used to connect to the switch.
52 53
     * ansible_user username of the credentials used to connect to the switch.
53 54
     * ansible_pass password of the credentials used to connect to the switch.
55
+    * mac is the MAC address of the switch as provided by lldp. This is optional to provide and
56
+      specific to OpenStack ML2 use cases. It is used for zero touch provisioning using Ironic
57
+      introspection. Introspection gathers the switch's MAC and node's port provided by lldp
58
+      and populates the baremetal node's local_link_information. If this parameter is provided in
59
+      the ML2 ini configuration it will be used to match against the lldp provided MAC to
60
+      populate internally generated ansible playbooks with the appropriate host name for the switch.
54 61
 
55 62
     Additional available parameters:
56 63
 

+ 16
- 9
net_ansible_tempest/tests/scenario/test_basic_ops.py View File

@@ -61,7 +61,7 @@ class TestWithOvs(base.NetAnsibleAdminBaseTest):
61 61
         return self.admin_networks_client.show_network(
62 62
             network_id)['network']['provider:segmentation_id']
63 63
 
64
-    def create_port(self, network_id):
64
+    def create_port(self, network_id, local_link_info):
65 65
         port = self.admin_ports_client.create_port(
66 66
             network_id=network_id, name=self.ovs_port_name)['port']
67 67
         self.addCleanup(self.cleanup_port, port['id'])
@@ -69,10 +69,6 @@ class TestWithOvs(base.NetAnsibleAdminBaseTest):
69 69
         host = self.os_admin.hypervisor_client.list_hypervisors(
70 70
             )['hypervisors'][0]['hypervisor_hostname']
71 71
 
72
-        llc = [{'switch_info': self.switch_name,
73
-                'switch_id': self.ovs_bridge_mac,
74
-                'port_id': self.ovs_port_name}]
75
-
76 72
         update_args = {
77 73
             'device_owner': 'baremetal:none',
78 74
             'device_id': 'fake-instance-uuid',
@@ -80,7 +76,7 @@ class TestWithOvs(base.NetAnsibleAdminBaseTest):
80 76
             'binding:vnic_type': 'baremetal',
81 77
             'binding:host_id': host,
82 78
             'binding:profile': {
83
-                'local_link_information': llc
79
+                'local_link_information': local_link_info
84 80
             }
85 81
         }
86 82
         self.admin_ports_client.update_port(
@@ -89,9 +85,8 @@ class TestWithOvs(base.NetAnsibleAdminBaseTest):
89 85
         )
90 86
         return port
91 87
 
92
-    @decorators.idempotent_id('40b81fe4-1e9c-4f10-a808-c23f85aea5e2')
93
-    def test_update_port(self):
94
-        port = self.create_port(self.network['id'])
88
+    def _test_update_port(self, local_link_info):
89
+        port = self.create_port(self.network['id'], local_link_info)
95 90
         current_tag = self.ovs.db_get(
96 91
             'Port', self.ovs_port_name, 'tag').execute()
97 92
         network_segmentation_id = self.get_network_segmentation_id(
@@ -102,3 +97,15 @@ class TestWithOvs(base.NetAnsibleAdminBaseTest):
102 97
         current_tag = self.ovs.db_get(
103 98
             'Port', self.ovs_port_name, 'tag').execute()
104 99
         self.assertEqual([], current_tag)
100
+
101
+    @decorators.idempotent_id('40b81fe4-1e9c-4f10-a808-c23f85aea5e2')
102
+    def test_update_port(self):
103
+        local_link_info = [{'switch_info': self.switch_name,
104
+                            'port_id': self.ovs_port_name}]
105
+        self._test_update_port(local_link_info)
106
+
107
+    @decorators.idempotent_id('40b81fe4-1e9c-4f10-a808-c23f85aea5e3')
108
+    def test_update_port_no_info(self):
109
+        local_link_info = [{'switch_id': '01:23:45:67:89:AB',
110
+                            'port_id': self.ovs_port_name}]
111
+        self._test_update_port(local_link_info)

+ 12
- 0
networking_ansible/ml2/mech_driver.py View File

@@ -40,6 +40,12 @@ class AnsibleMechanismDriver(ml2api.MechanismDriver):
40 40
         LOG.debug("Initializing Ansible ML2 driver")
41 41
 
42 42
         inventory = config.build_ansible_inventory()
43
+        # create a dict of switches that have macs defined
44
+        # dict uses mac for key and name for value
45
+        hosts = inventory['all']['hosts']
46
+        self.mac_map = {
47
+            h['mac'].upper(): name for name, h in hosts.items() if 'mac' in h
48
+        }
43 49
         self.ansnet = api.NetworkingAnsible(inventory)
44 50
 
45 51
     def create_network_postcommit(self, context):
@@ -314,8 +320,14 @@ class AnsibleMechanismDriver(ml2api.MechanismDriver):
314 320
                   'binding:profile'.format(port_id=port['id'])
315 321
             LOG.debug(msg)
316 322
             raise exceptions.LocalLinkInfoMissingException(msg)
323
+        switch_mac = local_link_info[0].get('switch_id')
317 324
         switch_name = local_link_info[0].get('switch_info')
318 325
         switch_port = local_link_info[0].get('port_id')
326
+        # fill in the switch name if mac exists but name is not defined
327
+        # this provides support for introspection when the switch's mac is
328
+        # also provided in the ML2 conf for ansible-networking
329
+        if not switch_name and switch_mac in self.mac_map:
330
+            switch_name = self.mac_map[switch_mac.upper()]
319 331
         segmentation_id = network.get('provider:segmentation_id', '')
320 332
         return switch_name, switch_port, segmentation_id
321 333
 

+ 27
- 12
networking_ansible/tests/unit/base.py View File

@@ -19,7 +19,6 @@ from oslo_config import cfg
19 19
 from oslotest import base
20 20
 import pbr
21 21
 
22
-from networking_ansible import api
23 22
 from networking_ansible import config
24 23
 from networking_ansible.ml2 import mech_driver
25 24
 
@@ -51,8 +50,17 @@ class BaseTestCase(base.BaseTestCase):
51 50
 
52 51
         self.ansconfig = config
53 52
         self.testhost = 'testhost'
53
+        self.testmac = '01:23:45:67:89:AB'
54 54
         self.empty_inventory = {'all': {'hosts': {}}}
55
-        self.inventory = {'all': {'hosts': {self.testhost: {}}}}
55
+        self.inventory = {
56
+            'all': {
57
+                'hosts': {
58
+                    self.testhost: {
59
+                        'mac': self.testmac
60
+                    }
61
+                }
62
+            }
63
+        }
56 64
 
57 65
     def setup_config(self):
58 66
         """Create the default configurations."""
@@ -69,8 +77,10 @@ class NetworkingAnsibleTestCase(BaseTestCase):
69 77
     def setUp(self):
70 78
         patch_neutron_quotas()
71 79
         super(NetworkingAnsibleTestCase, self).setUp()
72
-        self.mech = mech_driver.AnsibleMechanismDriver()
73
-        self.mech.initialize()
80
+        with mock.patch('networking_ansible.ml2.mech_driver.config') as m_cfg:
81
+            m_cfg.build_ansible_inventory.return_value = self.inventory
82
+            self.mech = mech_driver.AnsibleMechanismDriver()
83
+            self.mech.initialize()
74 84
         self.testsegid = '37'
75 85
         self.testport = 'switchportid'
76 86
 
@@ -86,14 +96,21 @@ class NetworkingAnsibleTestCase(BaseTestCase):
86 96
         # define mocked port context
87 97
         self.mock_port_context = mock.create_autospec(
88 98
             driver_context.PortContext).return_value
99
+        self.lli_no_mac = {
100
+            'local_link_information': [{
101
+                'switch_info': self.testhost,
102
+                'port_id': self.testport,
103
+            }]
104
+        }
105
+        self.lli_no_info = {
106
+            'local_link_information': [{
107
+                'switch_id': self.testmac,
108
+                'port_id': self.testport,
109
+            }]
110
+        }
89 111
         self.mock_port_context.current = {
90 112
             'id': 'aaaa-bbbb-cccc',
91
-            'binding:profile': {
92
-                'local_link_information': [{
93
-                    'switch_info': self.testhost,
94
-                    'port_id': self.testport,
95
-                }]
96
-            },
113
+            'binding:profile': self.lli_no_mac,
97 114
             'binding:vnic_type': 'baremetal',
98 115
             'binding:vif_type': 'other',
99 116
         }
@@ -107,5 +124,3 @@ class NetworkingAnsibleTestCase(BaseTestCase):
107 124
         self.mock_port_context.segments_to_bind = [
108 125
             self.mock_port_context.network.current
109 126
         ]
110
-
111
-        self.mech.ansnet = api.NetworkingAnsible(self.inventory)

+ 11
- 1
networking_ansible/tests/unit/ml2/test_mech_driver.py View File

@@ -54,7 +54,17 @@ class NetAnsibleML2Base(test_plugin.Ml2PluginV2TestCase):
54 54
 @mock.patch('networking_ansible.ml2.mech_driver.provisioning_blocks',
55 55
             autospec=True)
56 56
 class TestBindPort(base.NetworkingAnsibleTestCase):
57
-    def test_bind_port(self, mock_prov_blks, mock_update_access_port):
57
+    def test_bind_port_info_no_mac(self,
58
+                                   mock_prov_blks,
59
+                                   mock_update_access_port):
60
+        self.mech.bind_port(self.mock_port_context)
61
+        mock_update_access_port.assert_called_once()
62
+
63
+    def test_bind_port_mac_no_info_local_link_info(self,
64
+                                                   mock_prov_blks,
65
+                                                   mock_update_access_port):
66
+        bind_prof = 'binding:profile'
67
+        self.mock_port_context.current[bind_prof] = self.lli_no_info
58 68
         self.mech.bind_port(self.mock_port_context)
59 69
         mock_update_access_port.assert_called_once()
60 70
 

+ 1
- 1
networking_ansible/tests/unit/test_config.py View File

@@ -26,7 +26,7 @@ class MockedConfigParser(mock.Mock):
26 26
         self.sections = sections
27 27
 
28 28
     def parse(self):
29
-        section_data = {'ansible:testhost': {}}
29
+        section_data = {'ansible:testhost': {'mac': ['01:23:45:67:89:AB']}}
30 30
         if self.conffile == 'foo2':
31 31
             section_data = {
32 32
                 'ansible:h1': {'manage_vlans': ['0']},

+ 1
- 0
zuul.d/jobs.yaml View File

@@ -32,6 +32,7 @@
32 32
             ansible:ovs_test:
33 33
               ansible_network_os: openvswitch
34 34
               ansible_host: localhost
35
+              mac: 01:23:45:67:89:AB
35 36
         test-config:
36 37
           $TEMPEST_CONFIG:
37 38
             net_ansible_openvswitch:

Loading…
Cancel
Save