397e883d67
This add the vf_trusted field for network metadata. Currently only libvirt is supporting it. Implements blueprint bp/sriov-trusted-vfs Change-Id: I4aa2786cfa95b923078679a398bdfe4de622be57 Signed-off-by: Sahid Orentino Ferdjaoui <sahid.ferdjaoui@redhat.com>
361 lines
11 KiB
Python
361 lines
11 KiB
Python
# Copyright 2010 United States Government as represented by the
|
|
# Administrator of the National Aeronautics and Space Administration.
|
|
# All Rights Reserved.
|
|
# Copyright (c) 2010 Citrix Systems, Inc.
|
|
# Copyright 2013 IBM Corp.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
|
|
"""Network-related utilities for supporting libvirt connection code."""
|
|
|
|
import os
|
|
|
|
import jinja2
|
|
import netaddr
|
|
from oslo_utils import strutils
|
|
|
|
import nova.conf
|
|
from nova.network import model
|
|
|
|
CONF = nova.conf.CONF
|
|
|
|
|
|
def get_net_and_mask(cidr):
|
|
net = netaddr.IPNetwork(cidr)
|
|
return str(net.ip), str(net.netmask)
|
|
|
|
|
|
def get_net_and_prefixlen(cidr):
|
|
net = netaddr.IPNetwork(cidr)
|
|
return str(net.ip), str(net._prefixlen)
|
|
|
|
|
|
def get_ip_version(cidr):
|
|
net = netaddr.IPNetwork(cidr)
|
|
return int(net.version)
|
|
|
|
|
|
def _get_first_network(network, version):
|
|
# Using a generator expression with a next() call for the first element
|
|
# of a list since we don't want to evaluate the whole list as we can
|
|
# have a lot of subnets
|
|
try:
|
|
return next(i for i in network['subnets']
|
|
if i['version'] == version)
|
|
except StopIteration:
|
|
pass
|
|
|
|
|
|
def get_injected_network_template(network_info, template=None,
|
|
libvirt_virt_type=None):
|
|
"""Returns a rendered network template for the given network_info.
|
|
|
|
:param network_info:
|
|
:py:meth:`~nova.network.manager.NetworkManager.get_instance_nw_info`
|
|
:param template: Path to the interfaces template file.
|
|
:param libvirt_virt_type: The Libvirt `virt_type`, will be `None` for
|
|
other hypervisors..
|
|
"""
|
|
if not template:
|
|
template = CONF.injected_network_template
|
|
|
|
if not (network_info and template):
|
|
return
|
|
|
|
nets = []
|
|
ifc_num = -1
|
|
ipv6_is_available = False
|
|
|
|
for vif in network_info:
|
|
if not vif['network'] or not vif['network']['subnets']:
|
|
continue
|
|
|
|
network = vif['network']
|
|
# NOTE(bnemec): The template only supports a single subnet per
|
|
# interface and I'm not sure how/if that can be fixed, so this
|
|
# code only takes the first subnet of the appropriate type.
|
|
subnet_v4 = _get_first_network(network, 4)
|
|
subnet_v6 = _get_first_network(network, 6)
|
|
|
|
ifc_num += 1
|
|
|
|
if not network.get_meta('injected'):
|
|
continue
|
|
|
|
hwaddress = vif.get('address')
|
|
address = None
|
|
netmask = None
|
|
gateway = ''
|
|
broadcast = None
|
|
dns = None
|
|
routes = []
|
|
if subnet_v4:
|
|
if subnet_v4.get_meta('dhcp_server') is not None:
|
|
continue
|
|
|
|
if subnet_v4['ips']:
|
|
ip = subnet_v4['ips'][0]
|
|
address = ip['address']
|
|
netmask = model.get_netmask(ip, subnet_v4)
|
|
if subnet_v4['gateway']:
|
|
gateway = subnet_v4['gateway']['address']
|
|
broadcast = str(subnet_v4.as_netaddr().broadcast)
|
|
dns = ' '.join([i['address'] for i in subnet_v4['dns']])
|
|
for route_ref in subnet_v4['routes']:
|
|
(net, mask) = get_net_and_mask(route_ref['cidr'])
|
|
route = {'gateway': str(route_ref['gateway']['address']),
|
|
'cidr': str(route_ref['cidr']),
|
|
'network': net,
|
|
'netmask': mask}
|
|
routes.append(route)
|
|
|
|
address_v6 = None
|
|
gateway_v6 = ''
|
|
netmask_v6 = None
|
|
dns_v6 = None
|
|
if subnet_v6:
|
|
if subnet_v6.get_meta('dhcp_server') is not None:
|
|
continue
|
|
|
|
if subnet_v6['ips']:
|
|
ipv6_is_available = True
|
|
ip_v6 = subnet_v6['ips'][0]
|
|
address_v6 = ip_v6['address']
|
|
netmask_v6 = model.get_netmask(ip_v6, subnet_v6)
|
|
if subnet_v6['gateway']:
|
|
gateway_v6 = subnet_v6['gateway']['address']
|
|
dns_v6 = ' '.join([i['address'] for i in subnet_v6['dns']])
|
|
|
|
net_info = {'name': 'eth%d' % ifc_num,
|
|
'hwaddress': hwaddress,
|
|
'address': address,
|
|
'netmask': netmask,
|
|
'gateway': gateway,
|
|
'broadcast': broadcast,
|
|
'dns': dns,
|
|
'routes': routes,
|
|
'address_v6': address_v6,
|
|
'gateway_v6': gateway_v6,
|
|
'netmask_v6': netmask_v6,
|
|
'dns_v6': dns_v6,
|
|
}
|
|
nets.append(net_info)
|
|
|
|
if not nets:
|
|
return
|
|
|
|
tmpl_path, tmpl_file = os.path.split(template)
|
|
env = jinja2.Environment( # nosec
|
|
loader=jinja2.FileSystemLoader(tmpl_path), # nosec
|
|
trim_blocks=True)
|
|
template = env.get_template(tmpl_file)
|
|
return template.render({'interfaces': nets,
|
|
'use_ipv6': ipv6_is_available,
|
|
'libvirt_virt_type': libvirt_virt_type})
|
|
|
|
|
|
def get_network_metadata(network_info):
|
|
"""Gets a more complete representation of the instance network information.
|
|
|
|
This data is exposed as network_data.json in the metadata service and
|
|
the config drive.
|
|
|
|
:param network_info: `nova.network.models.NetworkInfo` object describing
|
|
the network metadata.
|
|
"""
|
|
if not network_info:
|
|
return
|
|
|
|
# IPv4 or IPv6 networks
|
|
nets = []
|
|
# VIFs, physical NICs, or VLANs. Physical NICs will have type 'phy'.
|
|
links = []
|
|
# Non-network bound services, such as DNS
|
|
services = []
|
|
ifc_num = -1
|
|
net_num = -1
|
|
|
|
for vif in network_info:
|
|
if not vif.get('network') or not vif['network'].get('subnets'):
|
|
continue
|
|
|
|
network = vif['network']
|
|
# NOTE(JoshNang) currently, only supports the first IPv4 and first
|
|
# IPv6 subnet on network, a limitation that also exists in the
|
|
# network template.
|
|
subnet_v4 = _get_first_network(network, 4)
|
|
subnet_v6 = _get_first_network(network, 6)
|
|
|
|
ifc_num += 1
|
|
link = None
|
|
|
|
# Get the VIF or physical NIC data
|
|
if subnet_v4 or subnet_v6:
|
|
link = _get_eth_link(vif, ifc_num)
|
|
links.append(link)
|
|
|
|
# Add IPv4 and IPv6 networks if they exist
|
|
if subnet_v4 and subnet_v4.get('ips'):
|
|
net_num += 1
|
|
nets.append(_get_nets(vif, subnet_v4, 4, net_num, link['id']))
|
|
services += [dns for dns in _get_dns_services(subnet_v4)
|
|
if dns not in services]
|
|
if subnet_v6 and subnet_v6.get('ips'):
|
|
net_num += 1
|
|
nets.append(_get_nets(vif, subnet_v6, 6, net_num, link['id']))
|
|
services += [dns for dns in _get_dns_services(subnet_v6)
|
|
if dns not in services]
|
|
|
|
return {
|
|
"links": links,
|
|
"networks": nets,
|
|
"services": services
|
|
}
|
|
|
|
|
|
def _get_eth_link(vif, ifc_num):
|
|
"""Get a VIF or physical NIC representation.
|
|
|
|
:param vif: Neutron VIF
|
|
:param ifc_num: Interface index for generating name if the VIF's
|
|
'devname' isn't defined.
|
|
:return: A dict with 'id', 'vif_id', 'type', 'mtu' and
|
|
'ethernet_mac_address' as keys
|
|
"""
|
|
link_id = vif.get('devname')
|
|
if not link_id:
|
|
link_id = 'interface%d' % ifc_num
|
|
|
|
# Use 'phy' for physical links. Ethernet can be confusing
|
|
if vif.get('type') in model.LEGACY_EXPOSED_VIF_TYPES:
|
|
nic_type = vif.get('type')
|
|
else:
|
|
nic_type = 'phy'
|
|
|
|
link = {
|
|
'id': link_id,
|
|
'vif_id': vif['id'],
|
|
'type': nic_type,
|
|
'mtu': vif['network']['meta'].get('mtu'),
|
|
'ethernet_mac_address': vif.get('address'),
|
|
}
|
|
return link
|
|
|
|
|
|
def _get_nets(vif, subnet, version, net_num, link_id):
|
|
"""Get networks for the given VIF and subnet
|
|
|
|
:param vif: Neutron VIF
|
|
:param subnet: Neutron subnet
|
|
:param version: IP version as an int, either '4' or '6'
|
|
:param net_num: Network index for generating name of each network
|
|
:param link_id: Arbitrary identifier for the link the networks are
|
|
attached to
|
|
"""
|
|
net_type = ''
|
|
if subnet.get_meta('ipv6_address_mode') is not None:
|
|
net_type = '_%s' % subnet.get_meta('ipv6_address_mode')
|
|
elif subnet.get_meta('dhcp_server') is not None:
|
|
net_info = {
|
|
'id': 'network%d' % net_num,
|
|
'type': 'ipv%d_dhcp' % version,
|
|
'link': link_id,
|
|
'network_id': vif['network']['id']
|
|
}
|
|
return net_info
|
|
|
|
ip = subnet['ips'][0]
|
|
address = ip['address']
|
|
if version == 4:
|
|
netmask = model.get_netmask(ip, subnet)
|
|
elif version == 6:
|
|
netmask = str(subnet.as_netaddr().netmask)
|
|
|
|
net_info = {
|
|
'id': 'network%d' % net_num,
|
|
'type': 'ipv%d%s' % (version, net_type),
|
|
'link': link_id,
|
|
'ip_address': address,
|
|
'netmask': netmask,
|
|
'routes': _get_default_route(version, subnet),
|
|
'network_id': vif['network']['id']
|
|
}
|
|
|
|
# Add any additional routes beyond the default route
|
|
for route in subnet['routes']:
|
|
route_addr = netaddr.IPNetwork(route['cidr'])
|
|
new_route = {
|
|
'network': str(route_addr.network),
|
|
'netmask': str(route_addr.netmask),
|
|
'gateway': route['gateway']['address']
|
|
}
|
|
net_info['routes'].append(new_route)
|
|
|
|
net_info['services'] = _get_dns_services(subnet)
|
|
|
|
return net_info
|
|
|
|
|
|
def _get_default_route(version, subnet):
|
|
"""Get a default route for a network
|
|
|
|
:param version: IP version as an int, either '4' or '6'
|
|
:param subnet: Neutron subnet
|
|
"""
|
|
if subnet.get('gateway') and subnet['gateway'].get('address'):
|
|
gateway = subnet['gateway']['address']
|
|
else:
|
|
return []
|
|
|
|
if version == 4:
|
|
return [{
|
|
'network': '0.0.0.0',
|
|
'netmask': '0.0.0.0',
|
|
'gateway': gateway
|
|
}]
|
|
elif version == 6:
|
|
return [{
|
|
'network': '::',
|
|
'netmask': '::',
|
|
'gateway': gateway
|
|
}]
|
|
|
|
|
|
def _get_dns_services(subnet):
|
|
"""Get the DNS servers for the subnet."""
|
|
services = []
|
|
if not subnet.get('dns'):
|
|
return services
|
|
return [{'type': 'dns', 'address': ip.get('address')}
|
|
for ip in subnet['dns']]
|
|
|
|
|
|
def get_cached_vifs_with_vlan(network_info):
|
|
"""Generates a dict from a list of VIFs that has a vlan tag, with
|
|
MAC, VLAN as a key, value.
|
|
"""
|
|
if network_info is None:
|
|
return {}
|
|
return {vif['address']: vif['details']['vlan'] for vif in network_info
|
|
if vif.get('details', {}).get('vlan')}
|
|
|
|
|
|
def get_cached_vifs_with_trusted(network_info):
|
|
"""Generates a dict from a list of VIFs that trusted, MAC as key"""
|
|
if network_info is None:
|
|
return {}
|
|
return {vif['address']: strutils.bool_from_string(
|
|
vif['profile'].get('trusted', 'False')) for vif in network_info
|
|
if vif.get('profile')}
|