コード例 #1
0
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}
コード例 #2
0
ファイル: networking.py プロジェクト: waquidvp/cloud-init
 def get_ib_hwaddrs_by_interface(self) -> dict:
     return net.get_ib_hwaddrs_by_interface()
コード例 #3
0
ファイル: openstack.py プロジェクト: rongz609/cloud-init
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}