# Copyright (c) 2016 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 collections import os from oslo_log import log import six from six.moves.urllib import parse from neutron.extensions import portbindings from neutron.plugins.common import constants from neutron.plugins.ml2 import driver_api from neutron_lib import constants as n_const from networking_odl._i18n import _ from networking_odl.ml2 import network_topology LOG = log.getLogger(__name__) class OvsdbNetworkTopologyParser(network_topology.NetworkTopologyParser): def new_element(self, uuid): return OvsdbNetworkTopologyElement(uuid=uuid) def parse_network_topology(self, network_topologies): elements_by_uuid = collections.OrderedDict() for topology in network_topologies[ 'network-topology']['topology']: if topology['topology-id'].startswith('ovsdb:'): for node in topology['node']: # expected url format: ovsdb://uuid/[/]] node_url = parse.urlparse(node['node-id']) if node_url.scheme == 'ovsdb'\ and node_url.netloc == 'uuid': # split_res = ['', '', ''] split_res = node_url.path.split('/', 2) # uuid is used to identify nodes referring to the same # element uuid = split_res[1] element = elements_by_uuid.get(uuid) if element is None: elements_by_uuid[uuid] = element =\ self.new_element(uuid) # inner_path can be [] or [] inner_path = split_res[2:] self._update_element_from_json_ovsdb_topology_node( node, element, uuid, *inner_path) # There can be more OVS instances connected beside the same IP address # Cache will yield more instaces for the same key for __, element in six.iteritems(elements_by_uuid): yield element def _update_element_from_json_ovsdb_topology_node( self, node, element, uuid, path=None): if not path: # global element section (root path) # fetch remote IP address element.remote_ip = node["ovsdb:connection-info"]["remote-ip"] for vif_type_entry in node.get( "ovsdb:interface-type-entry", []): # Is this a good place to add others OVS VIF types? if vif_type_entry.get("interface-type") ==\ "ovsdb:interface-type-dpdkvhostuser": element.support_vhost_user = True break else: LOG.debug( 'Interface type not found in network topology node %r.', uuid) LOG.debug( 'Topology element updated:\n' ' - uuid: %(uuid)r\n' ' - remote_ip: %(remote_ip)r\n' ' - support_vhost_user: %(support_vhost_user)r', {'uuid': uuid, 'remote_ip': element.remote_ip, 'support_vhost_user': element.support_vhost_user}) elif path == 'bridge/br-int': datapath_type = node.get("ovsdb:datapath-type") if datapath_type == "ovsdb:datapath-type-netdev": element.has_datapath_type_netdev = True LOG.debug( 'Topology element updated:\n' ' - uuid: %(uuid)r\n' ' - has_datapath_type_netdev: %(' 'has_datapath_type_netdev)r', {'uuid': uuid, 'has_datapath_type_netdev': element.has_datapath_type_netdev}) class OvsdbNetworkTopologyElement(network_topology.NetworkTopologyElement): uuid = None remote_ip = None # it can be None or a string has_datapath_type_netdev = False # it can be False or True support_vhost_user = False # it can be False or True # location for vhostuser sockets vhostuser_socket_dir = '/var/run/openvswitch' # prefix for ovs port port_prefix = 'vhu' def __init__(self, **kwargs): for name, value in six.iteritems(kwargs): setattr(self, name, value) @property def host_addresses(self): # For now it support only the remote IP found in connection info return self.remote_ip, @property def valid_vif_types(self): if self.has_datapath_type_netdev and self.support_vhost_user: return [ portbindings.VIF_TYPE_VHOST_USER, portbindings.VIF_TYPE_OVS] else: return [portbindings.VIF_TYPE_OVS] def bind_port(self, port_context, vif_type, vif_details): port_context_id = port_context.current['id'] network_context_id = port_context.network.current['id'] # Bind port to the first valid segment for segment in port_context.segments_to_bind: if self._is_valid_segment(segment): # Guest best VIF type for given host vif_details = self._get_vif_details( vif_details=vif_details, port_context_id=port_context_id, vif_type=vif_type) LOG.debug( 'Bind port with valid segment:\n' '\tport: %(port)r\n' '\tnetwork: %(network)r\n' '\tsegment: %(segment)r\n' '\tVIF type: %(vif_type)r\n' '\tVIF details: %(vif_details)r', {'port': port_context_id, 'network': network_context_id, 'segment': segment, 'vif_type': vif_type, 'vif_details': vif_details}) port_context.set_binding( segment[driver_api.ID], vif_type, vif_details, status=n_const.PORT_STATUS_ACTIVE) return raise ValueError( _('Unable to find any valid segment in given context.')) def to_dict(self): data = super(OvsdbNetworkTopologyElement, self).to_dict() data.update( {'uuid': self.uuid, 'has_datapath_type_netdev': self.has_datapath_type_netdev, 'support_vhost_user': self.support_vhost_user, 'valid_vif_types': self.valid_vif_types}) if portbindings.VIF_TYPE_VHOST_USER in self.valid_vif_types: data.update({'port_prefix': self.port_prefix, 'vhostuser_socket_dir': self.vhostuser_socket_dir}) return data def _is_valid_segment(self, segment): """Verify a segment is valid for the OpenDaylight MechanismDriver. Verify the requested segment is supported by ODL and return True or False to indicate this to callers. """ network_type = segment[driver_api.NETWORK_TYPE] return network_type in [constants.TYPE_LOCAL, constants.TYPE_GRE, constants.TYPE_VXLAN, constants.TYPE_VLAN] def _get_vif_details(self, vif_details, port_context_id, vif_type): vif_details = dict(vif_details) if vif_type == portbindings.VIF_TYPE_VHOST_USER: socket_path = os.path.join( self.vhostuser_socket_dir, (self.port_prefix + port_context_id)[:14]) vif_details.update({ portbindings.VHOST_USER_MODE: portbindings.VHOST_USER_MODE_CLIENT, portbindings.VHOST_USER_OVS_PLUG: True, portbindings.VHOST_USER_SOCKET: socket_path }) return vif_details def __setattr__(self, name, value): # raises Attribute error if the class hasn't this attribute getattr(type(self), name) super(OvsdbNetworkTopologyElement, self).__setattr__(name, value)