def convert_net_json(network_json=None, known_macs=None): """Return a dictionary of network_config by parsing provided OpenStack ConfigDrive NetworkData json format OpenStack network_data.json provides a 3 element dictionary - "links" (links are network devices, physical or virtual) - "networks" (networks are ip network configurations for one or more links) - services (non-ip services, like dns) networks and links are combined via network items referencing specific links via a 'link_id' which maps to a links 'id' field. To convert this format to network_config yaml, we first iterate over the links and then walk the network list to determine if any of the networks utilize the current link; if so we generate a subnet entry for the device We also need to map network_data.json fields to network_config fields. For example, the network_data links 'id' field is equivalent to network_config 'name' field for devices. We apply more of this mapping to the various link types that we encounter. There are additional fields that are populated in the network_data.json from OpenStack that are not relevant to network_config yaml, so we enumerate a dictionary of valid keys for network_yaml and apply filtering to drop these superflous keys from the network_config yaml. """ if network_json is None: return None # dict of network_config key for filtering network_json valid_keys = { 'physical': [ 'name', 'type', 'mac_address', 'subnets', 'params', 'mtu', ], 'subnet': [ 'type', 'address', 'netmask', 'broadcast', 'metric', 'gateway', 'pointopoint', 'scope', 'dns_nameservers', 'dns_search', 'routes', ], } links = network_json.get('links', []) networks = network_json.get('networks', []) services = network_json.get('services', []) link_updates = [] link_id_info = {} bond_name_fmt = "bond%d" bond_number = 0 config = [] for link in links: subnets = [] cfg = dict( (k, v) for k, v in link.items() if k in valid_keys['physical']) # 'name' is not in openstack spec yet, but we will support it if it is # present. The 'id' in the spec is currently implemented as the host # nic's name, meaning something like 'tap-adfasdffd'. We do not want # to name guest devices with such ugly names. if 'name' in link: cfg['name'] = link['name'] link_mac_addr = None if link.get('ethernet_mac_address'): link_mac_addr = link.get('ethernet_mac_address').lower() link_id_info[link['id']] = link_mac_addr curinfo = { 'name': cfg.get('name'), 'mac': link_mac_addr, 'id': link['id'], 'type': link['type'] } for network in [n for n in networks if n['link'] == link['id']]: subnet = dict((k, v) for k, v in network.items() if k in valid_keys['subnet']) if 'dhcp' in network['type']: t = (network['type'] if network['type'].startswith('ipv6') else 'dhcp4') subnet.update({ 'type': t, }) else: subnet.update({ 'type': 'static', 'address': network.get('ip_address'), }) if network['type'] == 'ipv4': subnet['ipv4'] = True if network['type'] == 'ipv6': subnet['ipv6'] = True subnets.append(subnet) cfg.update({'subnets': subnets}) if link['type'] in ['bond']: params = {} if link_mac_addr: params['mac_address'] = link_mac_addr for k, v in link.items(): if k == 'bond_links': continue elif k.startswith('bond'): params.update({k: v}) # openstack does not provide a name for the bond. # they do provide an 'id', but that is possibly non-sensical. # so we just create our own name. link_name = bond_name_fmt % bond_number bond_number += 1 # bond_links reference links by their id, but we need to add # to the network config by their nic name. # store that in bond_links_needed, and update these later. link_updates.append((cfg, 'bond_interfaces', '%s', copy.deepcopy(link['bond_links']))) cfg.update({'params': params, 'name': link_name}) curinfo['name'] = link_name elif link['type'] in ['vlan']: name = "%s.%s" % (link['vlan_link'], link['vlan_id']) cfg.update({ 'name': name, 'vlan_id': link['vlan_id'], 'mac_address': link['vlan_mac_address'], }) link_updates.append((cfg, 'vlan_link', '%s', link['vlan_link'])) link_updates.append( (cfg, 'name', "%%s.%s" % link['vlan_id'], link['vlan_link'])) curinfo.update({'mac': link['vlan_mac_address'], 'name': name}) else: if link['type'] not in KNOWN_PHYSICAL_TYPES: LOG.warning( 'Unknown network_data link type (%s); treating as' ' physical', link['type']) cfg.update({'type': 'physical', 'mac_address': link_mac_addr}) config.append(cfg) link_id_info[curinfo['id']] = curinfo need_names = [ d for d in config if d.get('type') == 'physical' and 'name' not in d ] if need_names or link_updates: if known_macs is None: known_macs = net.get_interfaces_by_mac() # go through and fill out the link_id_info with names for _link_id, info in link_id_info.items(): if info.get('name'): continue if info.get('mac') in known_macs: info['name'] = known_macs[info['mac']] for d in need_names: mac = d.get('mac_address') if not mac: raise ValueError("No mac_address or name entry for %s" % d) if mac not in known_macs: raise ValueError("Unable to find a system nic for %s" % d) d['name'] = known_macs[mac] for cfg, key, fmt, target in link_updates: if isinstance(target, (list, tuple)): cfg[key] = [fmt % link_id_info[l]['name'] for l in target] else: cfg[key] = fmt % link_id_info[target]['name'] # Infiniband interfaces may be referenced in network_data.json by a 6 byte # Ethernet MAC-style address, and we use that address to look up the # interface name above. Now ensure that the hardware address is set to the # full 20 byte address. ib_known_hwaddrs = net.get_ib_hwaddrs_by_interface() if ib_known_hwaddrs: for cfg in config: if cfg['name'] in ib_known_hwaddrs: cfg['mac_address'] = ib_known_hwaddrs[cfg['name']] cfg['type'] = 'infiniband' for service in services: cfg = service cfg.update({'type': 'nameserver'}) config.append(cfg) return {'version': 1, 'config': config}
def get_ib_hwaddrs_by_interface(self) -> dict: return net.get_ib_hwaddrs_by_interface()
def convert_net_json(network_json=None, known_macs=None): """Return a dictionary of network_config by parsing provided OpenStack ConfigDrive NetworkData json format OpenStack network_data.json provides a 3 element dictionary - "links" (links are network devices, physical or virtual) - "networks" (networks are ip network configurations for one or more links) - services (non-ip services, like dns) networks and links are combined via network items referencing specific links via a 'link_id' which maps to a links 'id' field. To convert this format to network_config yaml, we first iterate over the links and then walk the network list to determine if any of the networks utilize the current link; if so we generate a subnet entry for the device We also need to map network_data.json fields to network_config fields. For example, the network_data links 'id' field is equivalent to network_config 'name' field for devices. We apply more of this mapping to the various link types that we encounter. There are additional fields that are populated in the network_data.json from OpenStack that are not relevant to network_config yaml, so we enumerate a dictionary of valid keys for network_yaml and apply filtering to drop these superflous keys from the network_config yaml. """ if network_json is None: return None # dict of network_config key for filtering network_json valid_keys = { "physical": [ "name", "type", "mac_address", "subnets", "params", "mtu", ], "subnet": [ "type", "address", "netmask", "broadcast", "metric", "gateway", "pointopoint", "scope", "dns_nameservers", "dns_search", "routes", ], } links = network_json.get("links", []) networks = network_json.get("networks", []) services = network_json.get("services", []) link_updates = [] link_id_info = {} bond_name_fmt = "bond%d" bond_number = 0 config = [] for link in links: subnets = [] cfg = dict( (k, v) for k, v in link.items() if k in valid_keys["physical"]) # 'name' is not in openstack spec yet, but we will support it if it is # present. The 'id' in the spec is currently implemented as the host # nic's name, meaning something like 'tap-adfasdffd'. We do not want # to name guest devices with such ugly names. if "name" in link: cfg["name"] = link["name"] link_mac_addr = None if link.get("ethernet_mac_address"): link_mac_addr = link.get("ethernet_mac_address").lower() link_id_info[link["id"]] = link_mac_addr curinfo = { "name": cfg.get("name"), "mac": link_mac_addr, "id": link["id"], "type": link["type"], } for network in [n for n in networks if n["link"] == link["id"]]: subnet = dict((k, v) for k, v in network.items() if k in valid_keys["subnet"]) if network["type"] == "ipv4_dhcp": subnet.update({"type": "dhcp4"}) elif network["type"] == "ipv6_dhcp": subnet.update({"type": "dhcp6"}) elif network["type"] in [ "ipv6_slaac", "ipv6_dhcpv6-stateless", "ipv6_dhcpv6-stateful", ]: subnet.update({"type": network["type"]}) elif network["type"] in ["ipv4", "static"]: subnet.update({ "type": "static", "address": network.get("ip_address"), }) elif network["type"] in ["ipv6", "static6"]: cfg.update({"accept-ra": False}) subnet.update({ "type": "static6", "address": network.get("ip_address"), }) # Enable accept_ra for stateful and legacy ipv6_dhcp types if network["type"] in ["ipv6_dhcpv6-stateful", "ipv6_dhcp"]: cfg.update({"accept-ra": True}) if network["type"] == "ipv4": subnet["ipv4"] = True if network["type"] == "ipv6": subnet["ipv6"] = True subnets.append(subnet) cfg.update({"subnets": subnets}) if link["type"] in ["bond"]: params = {} if link_mac_addr: params["mac_address"] = link_mac_addr for k, v in link.items(): if k == "bond_links": continue elif k.startswith("bond"): params.update({k: v}) # openstack does not provide a name for the bond. # they do provide an 'id', but that is possibly non-sensical. # so we just create our own name. link_name = bond_name_fmt % bond_number bond_number += 1 # bond_links reference links by their id, but we need to add # to the network config by their nic name. # store that in bond_links_needed, and update these later. link_updates.append(( cfg, "bond_interfaces", "%s", copy.deepcopy(link["bond_links"]), )) cfg.update({"params": params, "name": link_name}) curinfo["name"] = link_name elif link["type"] in ["vlan"]: name = "%s.%s" % (link["vlan_link"], link["vlan_id"]) cfg.update({ "name": name, "vlan_id": link["vlan_id"], "mac_address": link["vlan_mac_address"], }) link_updates.append((cfg, "vlan_link", "%s", link["vlan_link"])) link_updates.append( (cfg, "name", "%%s.%s" % link["vlan_id"], link["vlan_link"])) curinfo.update({"mac": link["vlan_mac_address"], "name": name}) else: if link["type"] not in KNOWN_PHYSICAL_TYPES: LOG.warning( "Unknown network_data link type (%s); treating as" " physical", link["type"], ) cfg.update({"type": "physical", "mac_address": link_mac_addr}) config.append(cfg) link_id_info[curinfo["id"]] = curinfo need_names = [ d for d in config if d.get("type") == "physical" and "name" not in d ] if need_names or link_updates: if known_macs is None: known_macs = net.get_interfaces_by_mac() # go through and fill out the link_id_info with names for _link_id, info in link_id_info.items(): if info.get("name"): continue if info.get("mac") in known_macs: info["name"] = known_macs[info["mac"]] for d in need_names: mac = d.get("mac_address") if not mac: raise ValueError("No mac_address or name entry for %s" % d) if mac not in known_macs: raise ValueError("Unable to find a system nic for %s" % d) d["name"] = known_macs[mac] for cfg, key, fmt, targets in link_updates: if isinstance(targets, (list, tuple)): cfg[key] = [ fmt % link_id_info[target]["name"] for target in targets ] else: cfg[key] = fmt % link_id_info[targets]["name"] # Infiniband interfaces may be referenced in network_data.json by a 6 byte # Ethernet MAC-style address, and we use that address to look up the # interface name above. Now ensure that the hardware address is set to the # full 20 byte address. ib_known_hwaddrs = net.get_ib_hwaddrs_by_interface() if ib_known_hwaddrs: for cfg in config: if cfg["name"] in ib_known_hwaddrs: cfg["mac_address"] = ib_known_hwaddrs[cfg["name"]] cfg["type"] = "infiniband" for service in services: cfg = service cfg.update({"type": "nameserver"}) config.append(cfg) return {"version": 1, "config": config}