# Copyright 2011 OpenStack Foundation # All Rights Reserved. # # 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. import functools import netaddr from oslo_serialization import jsonutils from nova import exception from nova.i18n import _ from nova import utils # Constants for the 'vif_type' field in VIF class VIF_TYPE_OVS = 'ovs' VIF_TYPE_IVS = 'ivs' VIF_TYPE_DVS = 'dvs' VIF_TYPE_IOVISOR = 'iovisor' VIF_TYPE_BRIDGE = 'bridge' VIF_TYPE_802_QBG = '802.1qbg' VIF_TYPE_802_QBH = '802.1qbh' VIF_TYPE_HW_VEB = 'hw_veb' VIF_TYPE_HYPERV = 'hyperv' VIF_TYPE_HOSTDEV = 'hostdev_physical' VIF_TYPE_IB_HOSTDEV = 'ib_hostdev' VIF_TYPE_MIDONET = 'midonet' VIF_TYPE_VHOSTUSER = 'vhostuser' VIF_TYPE_VROUTER = 'vrouter' VIF_TYPE_OTHER = 'other' VIF_TYPE_TAP = 'tap' VIF_TYPE_MACVTAP = 'macvtap' VIF_TYPE_AGILIO_OVS = 'agilio_ovs' VIF_TYPE_BINDING_FAILED = 'binding_failed' VIF_TYPE_VIF = 'vif' VIF_TYPE_UNBOUND = 'unbound' # Constants for dictionary keys in the 'vif_details' field in the VIF # class VIF_DETAILS_PORT_FILTER = 'port_filter' VIF_DETAILS_OVS_HYBRID_PLUG = 'ovs_hybrid_plug' VIF_DETAILS_PHYSICAL_NETWORK = 'physical_network' VIF_DETAILS_BRIDGE_NAME = 'bridge_name' VIF_DETAILS_OVS_DATAPATH_TYPE = 'datapath_type' # The following constant defines an SR-IOV related parameter in the # 'vif_details'. 'profileid' should be used for VIF_TYPE_802_QBH VIF_DETAILS_PROFILEID = 'profileid' # The following constant defines an SR-IOV and macvtap related parameter in # the 'vif_details'. 'vlan' should be used for VIF_TYPE_HW_VEB or # VIF_TYPE_MACVTAP VIF_DETAILS_VLAN = 'vlan' # The following three constants define the macvtap related fields in # the 'vif_details'. VIF_DETAILS_MACVTAP_SOURCE = 'macvtap_source' VIF_DETAILS_MACVTAP_MODE = 'macvtap_mode' VIF_DETAILS_PHYS_INTERFACE = 'physical_interface' # Constants for vhost-user related fields in 'vif_details'. # Sets mode on vhost-user socket, valid values are 'client' # and 'server' VIF_DETAILS_VHOSTUSER_MODE = 'vhostuser_mode' # vhost-user socket path VIF_DETAILS_VHOSTUSER_SOCKET = 'vhostuser_socket' # Specifies whether vhost-user socket should be plugged # into ovs bridge. Valid values are True and False VIF_DETAILS_VHOSTUSER_OVS_PLUG = 'vhostuser_ovs_plug' # Specifies whether vhost-user socket should be used to # create a fp netdevice interface. VIF_DETAILS_VHOSTUSER_FP_PLUG = 'vhostuser_fp_plug' # Specifies whether vhost-user socket should be used to # create a vrouter netdevice interface # TODO(mhenkel): Consider renaming this to be contrail-specific. VIF_DETAILS_VHOSTUSER_VROUTER_PLUG = 'vhostuser_vrouter_plug' # Constants for dictionary keys in the 'vif_details' field that are # valid for VIF_TYPE_TAP. VIF_DETAILS_TAP_MAC_ADDRESS = 'mac_address' # Open vSwitch datapath types. VIF_DETAILS_OVS_DATAPATH_SYSTEM = 'system' VIF_DETAILS_OVS_DATAPATH_NETDEV = 'netdev' # Define supported virtual NIC types. VNIC_TYPE_DIRECT and VNIC_TYPE_MACVTAP # are used for SR-IOV ports VNIC_TYPE_NORMAL = 'normal' VNIC_TYPE_DIRECT = 'direct' VNIC_TYPE_MACVTAP = 'macvtap' VNIC_TYPE_DIRECT_PHYSICAL = 'direct-physical' VNIC_TYPE_BAREMETAL = 'baremetal' VNIC_TYPE_VIRTIO_FORWARDER = 'virtio-forwarder' VNIC_TYPE_VDPA = 'vdpa' VNIC_TYPE_ACCELERATOR_DIRECT = 'accelerator-direct' VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL = 'accelerator-direct-physical' # Define list of ports which needs pci request. # Note: The macvtap port needs a PCI request as it is a tap interface # with VF as the lower physical interface. # Note: Currently, VNIC_TYPE_VIRTIO_FORWARDER assumes a 1:1 # relationship with a VF. This is expected to change in the future. # Note: # VNIC_TYPE_ACCELERATOR_DIRECT and VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL # does not need a PCI request, these devices are not tracked by the pci # tracker in nova but tracked by cyborg. The scheduling will use the # cyborg provided resource request to find a compute with such devices, # and the device claiming will be done via binding the cyborg arqs to the # selected compute node. VNIC_TYPES_SRIOV = ( VNIC_TYPE_DIRECT, VNIC_TYPE_MACVTAP, VNIC_TYPE_DIRECT_PHYSICAL, VNIC_TYPE_VIRTIO_FORWARDER, VNIC_TYPE_VDPA) # Define list of ports which are passthrough to the guest # and need a special treatment on snapshot and suspend/resume VNIC_TYPES_DIRECT_PASSTHROUGH = (VNIC_TYPE_DIRECT, VNIC_TYPE_DIRECT_PHYSICAL, VNIC_TYPE_ACCELERATOR_DIRECT, VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL) # Define list of ports which contains devices managed by cyborg. VNIC_TYPES_ACCELERATOR = ( VNIC_TYPE_ACCELERATOR_DIRECT, VNIC_TYPE_ACCELERATOR_DIRECT_PHYSICAL ) # Constants for the 'vif_model' values VIF_MODEL_VIRTIO = 'virtio' VIF_MODEL_NE2K_PCI = 'ne2k_pci' VIF_MODEL_PCNET = 'pcnet' VIF_MODEL_RTL8139 = 'rtl8139' VIF_MODEL_E1000 = 'e1000' VIF_MODEL_E1000E = 'e1000e' VIF_MODEL_NETFRONT = 'netfront' VIF_MODEL_SPAPR_VLAN = 'spapr-vlan' VIF_MODEL_LAN9118 = 'lan9118' VIF_MODEL_SRIOV = 'sriov' VIF_MODEL_VMXNET = 'vmxnet' VIF_MODEL_VMXNET3 = 'vmxnet3' VIF_MODEL_ALL = ( VIF_MODEL_VIRTIO, VIF_MODEL_NE2K_PCI, VIF_MODEL_PCNET, VIF_MODEL_RTL8139, VIF_MODEL_E1000, VIF_MODEL_E1000E, VIF_MODEL_NETFRONT, VIF_MODEL_SPAPR_VLAN, VIF_MODEL_LAN9118, VIF_MODEL_SRIOV, VIF_MODEL_VMXNET, VIF_MODEL_VMXNET3, ) # these types have been leaked to guests in network_data.json LEGACY_EXPOSED_VIF_TYPES = ( VIF_TYPE_BRIDGE, VIF_TYPE_DVS, VIF_TYPE_HW_VEB, VIF_TYPE_HYPERV, VIF_TYPE_OVS, VIF_TYPE_TAP, VIF_TYPE_VHOSTUSER, VIF_TYPE_VIF, ) # Constant for max length of network interface names # eg 'bridge' in the Network class or 'devname' in # the VIF class NIC_NAME_LEN = 14 class Model(dict): """Defines some necessary structures for most of the network models.""" def __repr__(self): return jsonutils.dumps(self) def _set_meta(self, kwargs): # pull meta out of kwargs if it's there self['meta'] = kwargs.pop('meta', {}) # update meta with any additional kwargs that may exist self['meta'].update(kwargs) def get_meta(self, key, default=None): """calls get(key, default) on self['meta'].""" return self['meta'].get(key, default) class IP(Model): """Represents an IP address in Nova.""" def __init__(self, address=None, type=None, **kwargs): super(IP, self).__init__() self['address'] = address self['type'] = type self['version'] = kwargs.pop('version', None) self._set_meta(kwargs) # determine version from address if not passed in if self['address'] and not self['version']: try: self['version'] = netaddr.IPAddress(self['address']).version except netaddr.AddrFormatError: msg = _("Invalid IP format %s") % self['address'] raise exception.InvalidIpAddressError(msg) def __eq__(self, other): keys = ['address', 'type', 'version'] return all(self[k] == other[k] for k in keys) def __ne__(self, other): return not self.__eq__(other) def is_in_subnet(self, subnet): if self['address'] and subnet['cidr']: return (netaddr.IPAddress(self['address']) in netaddr.IPNetwork(subnet['cidr'])) else: return False @classmethod def hydrate(cls, ip): if ip: return cls(**ip) return None class FixedIP(IP): """Represents a Fixed IP address in Nova.""" def __init__(self, floating_ips=None, **kwargs): super(FixedIP, self).__init__(**kwargs) self['floating_ips'] = floating_ips or [] if not self['type']: self['type'] = 'fixed' def add_floating_ip(self, floating_ip): if floating_ip not in self['floating_ips']: self['floating_ips'].append(floating_ip) def floating_ip_addresses(self): return [ip['address'] for ip in self['floating_ips']] @staticmethod def hydrate(fixed_ip): fixed_ip = FixedIP(**fixed_ip) fixed_ip['floating_ips'] = [IP.hydrate(floating_ip) for floating_ip in fixed_ip['floating_ips']] return fixed_ip def __eq__(self, other): keys = ['address', 'type', 'version', 'floating_ips'] return all(self[k] == other[k] for k in keys) def __ne__(self, other): return not self.__eq__(other) class Route(Model): """Represents an IP Route in Nova.""" def __init__(self, cidr=None, gateway=None, interface=None, **kwargs): super(Route, self).__init__() self['cidr'] = cidr self['gateway'] = gateway # FIXME(mriedem): Is this actually used? It's never set. self['interface'] = interface self._set_meta(kwargs) @classmethod def hydrate(cls, route): route = cls(**route) route['gateway'] = IP.hydrate(route['gateway']) return route class Subnet(Model): """Represents a Subnet in Nova.""" def __init__(self, cidr=None, dns=None, gateway=None, ips=None, routes=None, **kwargs): super(Subnet, self).__init__() self['cidr'] = cidr self['dns'] = dns or [] self['gateway'] = gateway self['ips'] = ips or [] self['routes'] = routes or [] self['version'] = kwargs.pop('version', None) self._set_meta(kwargs) if self['cidr'] and not self['version']: self['version'] = netaddr.IPNetwork(self['cidr']).version def __eq__(self, other): keys = ['cidr', 'dns', 'gateway', 'ips', 'routes', 'version'] return all(self[k] == other[k] for k in keys) def __ne__(self, other): return not self.__eq__(other) def add_route(self, new_route): if new_route not in self['routes']: self['routes'].append(new_route) def add_dns(self, dns): if dns not in self['dns']: self['dns'].append(dns) def add_ip(self, ip): if ip not in self['ips']: self['ips'].append(ip) def as_netaddr(self): """Convenient function to get cidr as a netaddr object.""" return netaddr.IPNetwork(self['cidr']) @classmethod def hydrate(cls, subnet): subnet = cls(**subnet) subnet['dns'] = [IP.hydrate(dns) for dns in subnet['dns']] subnet['ips'] = [FixedIP.hydrate(ip) for ip in subnet['ips']] subnet['routes'] = [Route.hydrate(route) for route in subnet['routes']] subnet['gateway'] = IP.hydrate(subnet['gateway']) return subnet class Network(Model): """Represents a Network in Nova.""" def __init__(self, id=None, bridge=None, label=None, subnets=None, **kwargs): super(Network, self).__init__() self['id'] = id self['bridge'] = bridge self['label'] = label self['subnets'] = subnets or [] self._set_meta(kwargs) def add_subnet(self, subnet): if subnet not in self['subnets']: self['subnets'].append(subnet) @classmethod def hydrate(cls, network): if network: network = cls(**network) network['subnets'] = [Subnet.hydrate(subnet) for subnet in network['subnets']] return network def __eq__(self, other): keys = ['id', 'bridge', 'label', 'subnets'] return all(self[k] == other[k] for k in keys) def __ne__(self, other): return not self.__eq__(other) class VIF8021QbgParams(Model): """Represents the parameters for a 802.1qbg VIF.""" def __init__(self, managerid, typeid, typeidversion, instanceid): super(VIF8021QbgParams, self).__init__() self['managerid'] = managerid self['typeid'] = typeid self['typeidversion'] = typeidversion self['instanceid'] = instanceid class VIF8021QbhParams(Model): """Represents the parameters for a 802.1qbh VIF.""" def __init__(self, profileid): super(VIF8021QbhParams, self).__init__() self['profileid'] = profileid class VIF(Model): """Represents a Virtual Interface in Nova.""" def __init__(self, id=None, address=None, network=None, type=None, details=None, devname=None, ovs_interfaceid=None, qbh_params=None, qbg_params=None, active=False, vnic_type=VNIC_TYPE_NORMAL, profile=None, preserve_on_delete=False, delegate_create=False, **kwargs): super(VIF, self).__init__() self['id'] = id self['address'] = address self['network'] = network or None self['type'] = type self['details'] = details or {} self['devname'] = devname self['ovs_interfaceid'] = ovs_interfaceid self['qbh_params'] = qbh_params self['qbg_params'] = qbg_params self['active'] = active self['vnic_type'] = vnic_type self['profile'] = profile self['preserve_on_delete'] = preserve_on_delete self['delegate_create'] = delegate_create self._set_meta(kwargs) def __eq__(self, other): keys = ['id', 'address', 'network', 'vnic_type', 'type', 'profile', 'details', 'devname', 'ovs_interfaceid', 'qbh_params', 'qbg_params', 'active', 'preserve_on_delete', 'delegate_create'] return all(self[k] == other[k] for k in keys) def __ne__(self, other): return not self.__eq__(other) def fixed_ips(self): if self['network']: return [fixed_ip for subnet in self['network']['subnets'] for fixed_ip in subnet['ips']] else: return [] def floating_ips(self): return [floating_ip for fixed_ip in self.fixed_ips() for floating_ip in fixed_ip['floating_ips']] def labeled_ips(self): """Returns the list of all IPs The return value looks like this flat structure:: {'network_label': 'my_network', 'network_id': 'n8v29837fn234782f08fjxk3ofhb84', 'ips': [{'address': '123.123.123.123', 'version': 4, 'type: 'fixed', 'meta': {...}}, {'address': '124.124.124.124', 'version': 4, 'type': 'floating', 'meta': {...}}, {'address': 'fe80::4', 'version': 6, 'type': 'fixed', 'meta': {...}}] """ if self['network']: # remove unnecessary fields on fixed_ips ips = [IP(**ip) for ip in self.fixed_ips()] for ip in ips: # remove floating ips from IP, since this is a flat structure # of all IPs del ip['meta']['floating_ips'] # add floating ips to list (if any) ips.extend(self.floating_ips()) return {'network_label': self['network']['label'], 'network_id': self['network']['id'], 'ips': ips} return [] def has_bind_time_event(self, migration): """Returns whether this VIF's network-vif-plugged external event will be sent by Neutron at "bind-time" - in other words, as soon as the port binding is updated. This is in the context of updating the port binding to a host that already has the instance in a shutoff state - in practice, this means reverting either a cold migration or a non-same-host resize. """ return (self.is_hybrid_plug_enabled() and not migration.is_same_host()) @property def has_live_migration_plug_time_event(self): """Returns whether this VIF's network-vif-plugged external event will be sent by Neutron at "plugtime" - in other words, as soon as neutron completes configuring the network backend. """ return self.is_hybrid_plug_enabled() def is_hybrid_plug_enabled(self): return self['details'].get(VIF_DETAILS_OVS_HYBRID_PLUG, False) def is_neutron_filtering_enabled(self): return self['details'].get(VIF_DETAILS_PORT_FILTER, False) def get_physical_network(self): phy_network = self['network']['meta'].get('physical_network') if not phy_network: phy_network = self['details'].get(VIF_DETAILS_PHYSICAL_NETWORK) return phy_network @classmethod def hydrate(cls, vif): vif = cls(**vif) vif['network'] = Network.hydrate(vif['network']) return vif def has_allocation(self): return self['profile'] and bool(self['profile'].get('allocation')) def get_netmask(ip, subnet): """Returns the netmask appropriate for injection into a guest.""" if ip['version'] == 4: return str(subnet.as_netaddr().netmask) return subnet.as_netaddr()._prefixlen class NetworkInfo(list): """Stores and manipulates network information for a Nova instance.""" # NetworkInfo is a list of VIFs def fixed_ips(self): """Returns all fixed_ips without floating_ips attached.""" return [ip for vif in self for ip in vif.fixed_ips()] def floating_ips(self): """Returns all floating_ips.""" return [ip for vif in self for ip in vif.floating_ips()] @classmethod def hydrate(cls, network_info): if isinstance(network_info, str): network_info = jsonutils.loads(network_info) return cls([VIF.hydrate(vif) for vif in network_info]) def wait(self, do_raise=True): """Wait for asynchronous call to finish.""" # There is no asynchronous call for this class, so this is a no-op # here, but subclasses may override to provide asynchronous # capabilities. Must be defined here in the parent class so that code # which works with both parent and subclass types can reference this # method. pass def json(self): return jsonutils.dumps(self) def get_bind_time_events(self, migration): """Returns a list of external events for any VIFs that have "bind-time" events during cold migration. """ return [('network-vif-plugged', vif['id']) for vif in self if vif.has_bind_time_event(migration)] def get_live_migration_plug_time_events(self): """Returns a list of external events for any VIFs that have "plug-time" events during live migration. """ return [('network-vif-plugged', vif['id']) for vif in self if vif.has_live_migration_plug_time_event] def get_plug_time_events(self, migration): """Returns a list of external events for any VIFs that have "plug-time" events during cold migration. """ return [('network-vif-plugged', vif['id']) for vif in self if not vif.has_bind_time_event(migration)] def has_port_with_allocation(self): return any(vif.has_allocation() for vif in self) class NetworkInfoAsyncWrapper(NetworkInfo): """Wrapper around NetworkInfo that allows retrieving NetworkInfo in an async manner. This allows one to start querying for network information before you know you will need it. If you have a long-running operation, this allows the network model retrieval to occur in the background. When you need the data, it will ensure the async operation has completed. As an example: def allocate_net_info(arg1, arg2) return call_neutron_to_allocate(arg1, arg2) network_info = NetworkInfoAsyncWrapper(allocate_net_info, arg1, arg2) [do a long running operation -- real network_info will be retrieved in the background] [do something with network_info] """ def __init__(self, async_method, *args, **kwargs): super(NetworkInfoAsyncWrapper, self).__init__() self._gt = utils.spawn(async_method, *args, **kwargs) methods = ['json', 'fixed_ips', 'floating_ips'] for method in methods: fn = getattr(self, method) wrapper = functools.partial(self._sync_wrapper, fn) functools.update_wrapper(wrapper, fn) setattr(self, method, wrapper) def _sync_wrapper(self, wrapped, *args, **kwargs): """Synchronize the model before running a method.""" self.wait() return wrapped(*args, **kwargs) def __getitem__(self, *args, **kwargs): fn = super(NetworkInfoAsyncWrapper, self).__getitem__ return self._sync_wrapper(fn, *args, **kwargs) def __iter__(self, *args, **kwargs): fn = super(NetworkInfoAsyncWrapper, self).__iter__ return self._sync_wrapper(fn, *args, **kwargs) def __len__(self, *args, **kwargs): fn = super(NetworkInfoAsyncWrapper, self).__len__ return self._sync_wrapper(fn, *args, **kwargs) def __str__(self, *args, **kwargs): fn = super(NetworkInfoAsyncWrapper, self).__str__ return self._sync_wrapper(fn, *args, **kwargs) def __repr__(self, *args, **kwargs): fn = super(NetworkInfoAsyncWrapper, self).__repr__ return self._sync_wrapper(fn, *args, **kwargs) def wait(self, do_raise=True): """Wait for asynchronous call to finish.""" if self._gt is not None: try: # NOTE(comstud): This looks funky, but this object is # subclassed from list. In other words, 'self' is really # just a list with a bunch of extra methods. So this # line just replaces the current list (which should be # empty) with the result. self[:] = self._gt.wait() except Exception: if do_raise: raise finally: self._gt = None