cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #06594
Re: [Merge] ~chad.smith/cloud-init:feature/openstack-network-v2-multi-nic into cloud-init:master
Diff comments:
> diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
> index 8f06911..ceaf868 100644
> --- a/cloudinit/sources/helpers/openstack.py
> +++ b/cloudinit/sources/helpers/openstack.py
> @@ -496,8 +527,233 @@ class MetadataReader(BaseReader):
> retries=self.retries)
>
>
> -# Convert OpenStack ConfigDrive NetworkData json to network_config yaml
> +def _find_v2_device_type(name, net_v2):
> + """Return the netv2 physical device type containing matching name."""
> + for device_type in NET_V2_PHYSICAL_TYPES:
> + if name in net_v2.get(device_type, {}):
> + return device_type
> + return None
> +
> +
> +def _convert_network_json_network_to_net_v2(src_json):
> + """Parse a single network item from the networks list in network_data.json
> +
> + @param src_json: One network item from network_data.json 'networks' key.
> +
> + @return: Tuple of <interface_name>, network v2 configuration dict for the
> + src_json. For example: eth0, {'addresses': [...], 'dhcp4': True}
> + """
> + net_v2 = {'addresses': []}
> + ignored_keys = set()
> +
> + # In Liberty spec https://specs.openstack.org/openstack/nova-specs/
> + # specs/liberty/implemented/metadata-service-network-info.html
> + if src_json['type'] == 'ipv4_dhcp':
> + net_v2['dhcp4'] = True
> + elif src_json['type'] == 'ipv6_dhcp':
> + net_v2['dhcp6'] = True
> +
> + for service in src_json.get('services', []):
> + if service['type'] != 'dns':
> + ignored_keys.update(['services.type(%s)' % service['type']])
> + continue
> + if 'nameservers' not in net_v2:
> + net_v2['nameservers'] = {'addresses': [], 'search': []}
> + net_v2['nameservers']['addresses'].append(service['address'])
> + # In Rocky spec https://specs.openstack.org/openstack/nova-specs/specs/
> + # rocky/approved/multiple-fixed-ips-network-information.html
> + dns_nameservers = src_json.get('dns_nameservers', [])
> + if dns_nameservers:
> + if 'nameservers' not in net_v2:
> + net_v2['nameservers'] = {'addresses': [], 'search': []}
> + net_v2['nameservers']['addresses'] = copy.copy(dns_nameservers)
> +
> + # Parse routes for network, prefix and gateway
> + route_keys = set(['netmask', 'network', 'gateway'])
> + for route in src_json.get('routes', []):
> + ignored_route_keys = (set(route.keys()).difference(route_keys))
> + ignored_keys.update(['route.%s' % key for key in ignored_route_keys])
> + route_cfg = {
> + 'to': '{network}/{prefix}'.format(
> + network=route['network'],
> + prefix=net.network_state.mask_to_net_prefix(route['netmask'])),
> + 'via': route['gateway']}
> + if route.get('metric'):
> + route_cfg['metric'] = route.get('metric')
> + if 'routes' not in net_v2:
> + net_v2['routes'] = []
> + net_v2['routes'].append(route_cfg)
> +
> + # Parse ip addresses on Rocky and Liberty
> + for ip_cfg in src_json.get('ip_addresses', []):
> + if ip_cfg.get('netmask'):
> + prefix = net.network_state.mask_to_net_prefix(ip_cfg['netmask'])
> + cidr_fmt = '{ip}/{prefix}'
> + else:
> + cidr_fmt = '{ip}'
> + prefix = None
> + net_v2['addresses'].append(
> + cidr_fmt.format(ip=ip_cfg['address'], prefix=prefix))
> + liberty_ip = src_json.get('ip_address')
> + if liberty_ip:
> + if src_json.get('netmask'):
> + prefix = net.network_state.mask_to_net_prefix(src_json['netmask'])
> + cidr_fmt = '{ip}/{prefix}'
> + else:
> + cidr_fmt = '{ip}'
> + prefix = None
> + liberty_cidr = cidr_fmt.format(ip=liberty_ip, prefix=prefix)
> + if liberty_cidr not in net_v2['addresses']:
> + net_v2['addresses'].append(liberty_cidr)
> + if not net_v2['addresses']:
> + net_v2.pop('addresses')
> + if ignored_keys:
> + LOG.debug(
> + 'Ignoring the network_data.json %s config keys %s',
> + src_json['id'], ', '.join(ignored_keys))
> + return src_json['link'], net_v2
> +
> +
> +def _convert_network_json_to_net_v2(src_json, var_map):
> + """Return network v2 for an element of OpenStack NetworkData json.
> +
> + @param src_json: Dict of network_data.json for a single src_json object
> + @param var_map: Dict with a variable name map from network_data.json to
> + network v2
> +
> + @return Tuple of the interface name and the converted network v2 for the
> + src_json object. For example: eth0, {'match': {'macaddress': 'AA:BB'}}
> + """
> + net_v2 = {}
> + # Map openstack bond keys to network v2
> + # Copy key values
> + current_keys = set(src_json)
> + for key in current_keys.intersection(set(var_map)):
> + keyparts = var_map[key].split('.')
> + tmp_cfg = net_v2 # allow traversing net_v2 dict
> + while keyparts:
> + keypart = keyparts.pop(0)
> + if keyparts:
> + if keypart not in tmp_cfg:
> + tmp_cfg[keypart] = {}
> + tmp_cfg = tmp_cfg[keypart]
> + elif isinstance(src_json[key], list):
> + tmp_cfg[keypart] = copy.copy(src_json[key])
> + elif src_json[key]:
> + if keypart in LOWERCASE_KEY_VALUE_V2:
> + tmp_cfg[keypart] = src_json[key].lower()
> + else:
> + tmp_cfg[keypart] = src_json[key]
> + if 'key_rename' in var_map:
> + net_v2['key_rename'] = var_map['key_rename'].format(**net_v2)
> + return src_json['id'], net_v2
> +
> +
> def convert_net_json(network_json=None, known_macs=None):
> + """Parse OpenStack ConfigDrive NetworkData json, returning network cfg v2.
> +
> + 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.
> + """
> + if network_json is None:
> + return None
> + net_config = {'version': 2}
> + for link in network_json.get('links', []):
> + link_type = link['type']
> + v2_key = LINK_TYPE_TO_NETWORK_V2_KEYS.get(link_type)
> + if not v2_key:
> + v2_key = 'ethernets'
> + if link_type not in KNOWN_PHYSICAL_TYPES:
> + LOG.warning('Unknown network_data link type (%s); treating as'
> + ' physical ethernet', link_type)
> + if v2_key not in net_config:
> + net_config[v2_key] = {}
> + var_map = copy.deepcopy(NETWORK_DATA_TO_V2.get(v2_key))
> + if not var_map:
> + var_map = copy.deepcopy(NETWORK_DATA_TO_V2.get(link_type))
> +
> + # Add v2 config parameters map for this link_type if present
> + if link_type in network_state.NET_CONFIG_TO_V2:
> + var_map.update(dict(
> + (k.replace('-', '_'), 'parameters.{v}'.format(v=v))
> + for k, v in network_state.NET_CONFIG_TO_V2[link_type].items()))
> + intf_id, intf_cfg = _convert_network_json_to_net_v2(link, var_map)
> + if v2_key in ('ethernets', 'bonds') and 'name' not in intf_cfg:
> + if known_macs is None:
> + known_macs = net.get_interfaces_by_mac()
> + lower_known_macs = dict(
> + (k.lower(), v) for k, v in known_macs.items())
> + mac = intf_cfg.get( # top-level macaddress and match.macaddress
> + 'macaddress', intf_cfg.get('match', {}).get('macaddress'))
> + if not mac:
> + raise ValueError("No mac_address or name entry for %s" % d)
> + mac = mac.lower()
> + if mac not in lower_known_macs:
> + raise ValueError(
> + 'Unable to find a system nic for %s' % mac)
> + intf_cfg['key_rename'] = lower_known_macs[mac]
> + net_config[v2_key].update({intf_id: intf_cfg})
> + for network in network_json.get('networks', []):
> + v2_key = _find_v2_device_type(network['link'], net_config)
> + intf_id, network_cfg = _convert_network_json_network_to_net_v2(network)
> + for key, val in network_cfg.items():
> + if isinstance(val, list):
> + if key not in net_config[v2_key][intf_id]:
> + net_config[v2_key][intf_id][key] = []
> + net_config[v2_key][intf_id][key].extend(val)
> + else:
> + net_config[v2_key][intf_id][key] = val
> +
> + # Inject global nameserver values under each all interface which
> + # has addresses and do not already have a DNS configuration
> + ignored_keys = set()
> + global_dns = []
> + for service in network_json.get('services', []):
> + if service['type'] != 'dns':
> + ignored_keys.update('services.type(%s)' % service['type'])
> + continue
> + global_dns.append(service['address'])
> +
> + # Handle renames and global_dns
> + for dev_type in NET_V2_PHYSICAL_TYPES:
> + if dev_type not in net_config:
> + continue
> + renames = {}
> + for dev in net_config[dev_type]:
> + renames[dev] = net_config[dev_type][dev].pop('key_rename', None)
> + if not global_dns:
> + continue
> + dev_keys = set(net_config[dev_type][dev].keys())
> + if set(['nameservers', 'dhcp4', 'dhcp6']).intersection(dev_keys):
> + # Do not add nameservers if we already have dns config
> + continue
> + if 'addresses' not in net_config[dev_type][dev]:
> + # No configured address, needs no nameserver
> + continue
> + net_config[dev_type][dev]['nameservers'] = {
> + 'addresses': copy.copy(global_dns), 'search': []}
> + for dev, rename in renames.items():
> + if rename:
> + net_config[dev_type][rename] = net_config[dev_type].pop(dev)
> + if 'set-name' in net_config[dev_type][rename]:
> + net_config[dev_type][rename]['set-name'] = rename
> +
> + if ignored_keys:
> + LOG.debug(
> + 'Ignoring the network_data.json config keys %s',
> + ', '.join(ignored_keys))
> +
> + return net_config
> +
> +
> +# Convert OpenStack ConfigDrive NetworkData json to network_config yaml
> +def convert_net_json1(network_json=None, known_macs=None):
WIP will drop this entire function (which was the v1 version of convert_net_json). Left in place for easy comparison/testing v1 vs v2 output.
> """Return a dictionary of network_config by parsing provided
> OpenStack ConfigDrive NetworkData json format
>
--
https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/372009
Your team cloud-init commiters is requested to review the proposed merge of ~chad.smith/cloud-init:feature/openstack-network-v2-multi-nic into cloud-init:master.
References