|
1 | | -# Copyright (c) 2018 StackHPC Ltd. |
2 | | -# |
3 | 1 | # Licensed under the Apache License, Version 2.0 (the "License"); |
4 | 2 | # you may not use this file except in compliance with the License. |
5 | 3 | # You may obtain a copy of the License at |
|
13 | 11 | # See the License for the specific language governing permissions and |
14 | 12 | # limitations under the License. |
15 | 13 |
|
16 | | -from ironic_inspector import utils |
17 | 14 | from oslo_config import cfg |
| 15 | +from oslo_log import log as logging |
18 | 16 |
|
19 | | -from ironic_inspector.plugins import base_physnet |
20 | | - |
21 | | -LOG = utils.getProcessingLogger(__name__) |
| 17 | +from ironic.drivers.modules.inspector.hooks import base |
| 18 | +from ironic.drivers.modules.inspector import lldp_parsers |
| 19 | +from ironic import objects |
22 | 20 |
|
23 | 21 | CONF = cfg.CONF |
| 22 | +LOG = logging.getLogger(__name__) |
24 | 23 |
|
25 | 24 |
|
26 | | -class IBPhysnetHook(base_physnet.BasePhysnetHook): |
27 | | - """Inspector hook to assign ports a physical network for IB interfaces. |
| 25 | +class IBPhysnetHook(base.InspectionHook): |
| 26 | + """Hook to set the port's physical_network field. |
28 | 27 |
|
29 | | - This plugin sets the physical network for ports that are determined to be |
30 | | - Infiniband ports. The physical network is given by the configuration |
31 | | - option [port_physnet] ib_physnet. |
| 28 | + Set the ironic port's physical_network field based on a CIDR to physical |
| 29 | + network mapping in the configuration. |
32 | 30 | """ |
33 | 31 |
|
34 | | - def get_physnet(self, port, iface_name, introspection_data): |
| 32 | + dependencies = ['validate-interfaces'] |
| 33 | + |
| 34 | + def get_physical_network(self, interface, plugin_data): |
35 | 35 | """Return a physical network to apply to a port. |
36 | 36 |
|
37 | 37 | :param port: The ironic port to patch. |
38 | 38 | :param iface_name: Name of the interface. |
39 | 39 | :param introspection_data: Introspection data. |
40 | 40 | :returns: The physical network to set, or None. |
41 | 41 | """ |
42 | | - proc_data = introspection_data['all_interfaces'][iface_name] |
| 42 | + iface_name = interface['name'] |
| 43 | + proc_data = plugin_data['all_interfaces'][iface_name] |
43 | 44 | if proc_data.get('client_id'): |
44 | 45 | LOG.debug("Interface %s is an Infiniband port, physnet %s", |
45 | 46 | iface_name, CONF.port_physnet.ib_physnet) |
46 | 47 | return CONF.port_physnet.ib_physnet |
| 48 | + |
| 49 | + def __call__(self, task, inventory, plugin_data): |
| 50 | + """Process inspection data and patch the port's physical network.""" |
| 51 | + |
| 52 | + node_ports = objects.Port.list_by_node_id(task.context, task.node.id) |
| 53 | + ports_dict = {p.address: p for p in node_ports} |
| 54 | + |
| 55 | + for interface in inventory['interfaces']: |
| 56 | + if interface['name'] not in plugin_data['all_interfaces']: |
| 57 | + continue |
| 58 | + |
| 59 | + mac_address = interface['mac_address'] |
| 60 | + port = ports_dict.get(mac_address) |
| 61 | + if not port: |
| 62 | + LOG.debug("Skipping physical network processing for interface " |
| 63 | + "%s on node %s - matching port not found in Ironic.", |
| 64 | + mac_address, task.node.uuid) |
| 65 | + continue |
| 66 | + |
| 67 | + # Determine the physical network for this port, using the interface |
| 68 | + # IPs and CIDR map configuration. |
| 69 | + phys_network = self.get_physical_network(interface, plugin_data) |
| 70 | + if phys_network is None: |
| 71 | + LOG.debug("Skipping physical network processing for interface " |
| 72 | + "%s on node %s - no physical network mapping.", |
| 73 | + mac_address, |
| 74 | + task.node.uuid) |
| 75 | + continue |
| 76 | + |
| 77 | + if getattr(port, 'physical_network', '') != phys_network: |
| 78 | + port.physical_network = phys_network |
| 79 | + port.save() |
| 80 | + LOG.info('Updated physical_network of port %s to %s', |
| 81 | + port.uuid, port.physical_network) |
| 82 | + |
| 83 | + |
| 84 | +def parse_mappings(mapping_list): |
| 85 | + """Parse a list of mapping strings into a dictionary. |
| 86 | +
|
| 87 | + Adapted from neutron_lib.utils.helpers.parse_mappings. |
| 88 | +
|
| 89 | + :param mapping_list: A list of strings of the form '<key>:<value>'. |
| 90 | + :returns: A dict mapping keys to values or to list of values. |
| 91 | + :raises ValueError: Upon malformed data or duplicate keys. |
| 92 | + """ |
| 93 | + mappings = {} |
| 94 | + for mapping in mapping_list: |
| 95 | + mapping = mapping.strip() |
| 96 | + if not mapping: |
| 97 | + continue |
| 98 | + split_result = mapping.split(':') |
| 99 | + if len(split_result) != 2: |
| 100 | + raise ValueError("Invalid mapping: '%s'" % mapping) |
| 101 | + key = split_result[0].strip() |
| 102 | + if not key: |
| 103 | + raise ValueError("Missing key in mapping: '%s'" % mapping) |
| 104 | + value = split_result[1].strip() |
| 105 | + if not value: |
| 106 | + raise ValueError("Missing value in mapping: '%s'" % mapping) |
| 107 | + if key in mappings: |
| 108 | + raise ValueError("Key %(key)s in mapping: '%(mapping)s' not " |
| 109 | + "unique" % {'key': key, 'mapping': mapping}) |
| 110 | + mappings[key] = value |
| 111 | + return mappings |
| 112 | + |
| 113 | + |
| 114 | +class SystemNamePhysnetHook(IBPhysnetHook): |
| 115 | + """Inspector hook to assign ports a physical network based on switch name. |
| 116 | +
|
| 117 | + This plugin uses the configuration option [port_physnet] |
| 118 | + switch_sys_name_mapping to map switch names to a physical network. If a |
| 119 | + port has received LLDP data with a switch system name in the mapping, the |
| 120 | + corresponding physical network will be applied to the port. |
| 121 | + """ |
| 122 | + |
| 123 | + def _get_switch_sys_name_mapping(self): |
| 124 | + """Return a dict mapping switch system names to physical networks.""" |
| 125 | + if not hasattr(self, '_switch_sys_name_mapping'): |
| 126 | + self._switch_sys_name_mapping = parse_mappings( |
| 127 | + CONF.port_physnet.switch_sys_name_mapping) |
| 128 | + return self._switch_sys_name_mapping |
| 129 | + |
| 130 | + def get_physical_network(self, interface, plugin_data): |
| 131 | + """Return a physical network to apply to a port. |
| 132 | +
|
| 133 | + :param port: The ironic port to patch. |
| 134 | + :param iface_name: Name of the interface. |
| 135 | + :param introspection_data: Introspection data. |
| 136 | + :returns: The physical network to set, or None. |
| 137 | + """ |
| 138 | + # Check if LLDP data was already processed by lldp_basic plugin |
| 139 | + # which stores data in 'all_interfaces' |
| 140 | + iface_name = interface['name'] |
| 141 | + proc_data = plugin_data['all_interfaces'][iface_name] |
| 142 | + if 'parsed_lldp' not in proc_data: |
| 143 | + return |
| 144 | + |
| 145 | + lldp_proc = proc_data['parsed_lldp'] |
| 146 | + |
| 147 | + # Switch system name mapping. |
| 148 | + switch_sys_name = lldp_proc.get(lldp_parsers.LLDP_SYS_NAME_NM) |
| 149 | + if switch_sys_name: |
| 150 | + mapping = self._get_switch_sys_name_mapping() |
| 151 | + if switch_sys_name in mapping: |
| 152 | + return mapping[switch_sys_name] |
0 commit comments