components/openstack/neutron/patches/07-ml2-ovs-support.patch
author Laszlo Peter <laszlo.peter@oracle.com>
Wed, 07 Sep 2016 14:48:41 -0700
changeset 6848 8e252a37ed0d
child 7238 96025c3f5cac
permissions -rw-r--r--
PSARC 2016/268 Neutron EVS Plugin EOF 24465835 Update Neutron for the Mitaka release 22271305 EOF monolithic neutron plugin for Openstack 18734794 port-create --fixed-ip accepts invalid argument, creates port with wrong IP

Changes to Neutron Open vSwitch agent to port it to Solaris. These changes
will eventually be proposed upstream.

--- neutron-8.1.2/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py	2016-06-09 18:45:36.000000000 -0700
+++ new/neutron/plugins/ml2/drivers/openvswitch/agent/ovs_neutron_agent.py	2016-08-18 13:23:57.331696500 -0700
@@ -15,12 +15,14 @@
 
 import collections
 import functools
+import platform
 import signal
 import sys
 import time
 
 import netaddr
 from oslo_config import cfg
+from oslo_log import helpers as log_helpers
 from oslo_log import log as logging
 import oslo_messaging
 from oslo_service import loopingcall
@@ -36,6 +38,7 @@
 from neutron.agent.l2.extensions import manager as ext_manager
 from neutron.agent import rpc as agent_rpc
 from neutron.agent import securitygroups_rpc as sg_rpc
+from neutron.agent.solaris import net_lib
 from neutron.api.rpc.callbacks import resources
 from neutron.api.rpc.handlers import dvr_rpc
 from neutron.common import config
@@ -145,10 +148,16 @@
 
         self.fullsync = False
         # init bridge classes with configured datapath type.
-        self.br_int_cls, self.br_phys_cls, self.br_tun_cls = (
-            functools.partial(bridge_classes[b],
-                              datapath_type=ovs_conf.datapath_type)
-            for b in ('br_int', 'br_phys', 'br_tun'))
+        if platform.system() == "SunOS":
+            self.br_int_cls = functools.partial(bridge_classes['br_int'],
+                datapath_type=ovs_conf.datapath_type)
+            self.br_phys_cls = None
+            self.br_tun_cls = None
+        else:
+            self.br_int_cls, self.br_phys_cls, self.br_tun_cls = (
+                functools.partial(bridge_classes[b],
+                datapath_type=ovs_conf.datapath_type)
+                for b in ('br_int', 'br_phys', 'br_tun'))
 
         self.use_veth_interconnection = ovs_conf.use_veth_interconnection
         self.veth_mtu = agent_conf.veth_mtu
@@ -2081,9 +2090,489 @@
                                "in both the Agent and Server side."))
 
 
+class SolarisOVSNeutronAgent(OVSNeutronAgent):
+    """Solaris implementation of OVS L2 Agent"""
+
+    def __init__(self, bridge_classes, conf=None):
+        '''Constructor.
+
+        :param bridge_classes: a dict for bridge classes.
+        :param conf: an instance of ConfigOpts
+        '''
+        self.tun_ofport = None
+        # mapping of VNIC's OpenFlow Port Number (ofport) to
+        # VXLAN segmentation id.
+        self.br_port_segid = {}
+        # mapping of VXLAN sgementation id to set of ports on that segment.
+        # The port is a ovs_lib.VifPort object.
+        self.br_segid_ports = {}
+        # mapping of Neutron port UUID to ovs_lib.VifPort object.
+        self.vif_ports = {}
+        super(SolarisOVSNeutronAgent, self).__init__(bridge_classes, conf)
+
+    def _parse_bridge_mappings(self, bridge_mappings):
+        try:
+            return n_utils.parse_mappings(bridge_mappings, unique_values=False)
+        except ValueError as e:
+            raise ValueError(_("Parsing bridge_mappings failed: %s.") % e)
+
+    def check_changed_vlans(self):
+        # Not applicable to Solaris
+        return []
+
+    def _setup_tunnel_port(self, br, port_name, remote_ip, tunnel_type):
+        LOG.debug(_("Setting up tunnel(%s) for remote_ip: %s") %
+                  (tunnel_type, remote_ip))
+        if tunnel_type != p_const.TYPE_VXLAN:
+            return
+        self.tun_br_ofports[tunnel_type][remote_ip] = remote_ip
+        remote_ips = self.tun_br_ofports[tunnel_type].values()
+        LOG.debug(_("current list of remote_ips: %s"), remote_ips)
+        for ofport, segmentation_id in self.br_port_segid.iteritems():
+            flood_local_ofports = self.br_segid_ports[segmentation_id]
+            self._mod_flood_to_tun_flows(ofport, remote_ips, segmentation_id,
+                                         flood_local_ofports - set([ofport]))
+
+    def cleanup_tunnel_port(self, br, remote_ip, tunnel_type):
+        LOG.debug(_("Cleaning up tunnel(%s) for remote_ip: %s") %
+                  (tunnel_type, remote_ip))
+        if tunnel_type != p_const.TYPE_VXLAN:
+            return
+        self.tun_br_ofports[tunnel_type].pop(remote_ip, None)
+        remote_ips = self.tun_br_ofports[tunnel_type].values()
+        for ofport, segmentation_id in self.br_port_segid.iteritems():
+            flood_local_ofports = self.br_segid_ports[segmentation_id]
+            self._mod_flood_to_tun_flows(ofport, remote_ips, segmentation_id,
+                                         flood_local_ofports - set([ofport]))
+
+    # The following methods are called through RPC.
+    #     add_fdb_entries(), remove_fdb_entries(), update_fdb_entries()
+    # These methods are overridden from L2populationRpcCallBackMixin class.
+    @log_helpers.log_method_call
+    def add_fdb_entries(self, context, fdb_entries, host=None):
+        # Needed for L2 Population support. Will be added later
+        pass
+
+    @log_helpers.log_method_call
+    def remove_fdb_entries(self, context, fdb_entries, host=None):
+        # Needed for L2 Population support. Will be added later
+        pass
+
+    @log_helpers.log_method_call
+    def update_fdb_entries(self, context, fdb_entries, host=None):
+        # Needed for L2 Population support. Will be added later
+        pass
+
+    def port_dead(self, port, log_errors=True):
+        '''Once a port has no binding or it is administratively disabled,
+           add a flow to drop packets coming from that port.
+        '''
+        cur_tag = self.int_br.db_get_val("Port", port.port_name, "tag",
+                                         log_errors=log_errors)
+        if cur_tag:
+            self.int_br.clear_db_attribute("Port", port.port_name, "tag")
+        if port.ofport != -1:
+            self.int_br.drop_port(in_port=port.ofport)
+
+    def setup_integration_br(self):
+        '''Setup the integration bridge and remove all existing flows.'''
+
+        # Ensure the integration bridge is created.
+        # ovs_lib.OVSBridge.create() will run
+        #   ovs-vsctl -- --may-exist add-br BRIDGE_NAME
+        # which does nothing if bridge already exists.
+        self.int_br.create()
+        self.int_br.set_secure_mode()
+        self.int_br.setup_controllers(self.conf)
+
+        if self.conf.AGENT.drop_flows_on_start:
+            self.int_br.delete_flows()
+        # Switch all traffic using normal-mode OVS only if tunneling
+        # is disabled. Otherwise, we will need to add various OpenFlow tables
+        # and flows to switch traffic.
+        if not self.enable_tunneling:
+            self.int_br.install_normal()
+        # Add a canary flow to int_br to track OVS restarts
+        self.int_br.setup_canary_table()
+
+    def setup_physical_bridges(self, bridge_mappings):
+        '''Makes sure that the uplink port for a given physical network
+        exists in the integration bridge.
+        '''
+        self.phys_brs = {}
+        # We do not use either int_ofports or phys_ofports below, however
+        # we need to initialize them to empty values since it is used in
+        # the common code which is mostly no-op for us.
+        self.int_ofports = {}
+        self.phys_ofports = {}
+        ovs = ovs_lib.BaseOVS()
+        for physical_network, uplink_port in bridge_mappings.iteritems():
+            LOG.info(_LI("Mapping physical network %(physical_network)s to "
+                         "uplink port %(uplink_port)s"),
+                     {'physical_network': physical_network,
+                      'uplink_port': uplink_port})
+            if not ovs.port_exists(uplink_port):
+                LOG.error(_LE("Uplink port %(uplink_port)s for physical "
+                              "network %(physical_network)s does not exist. "
+                              "Agent terminated!"),
+                          {'physical_network': physical_network,
+                           'uplink_port': uplink_port})
+                sys.exit(1)
+            self.phys_brs[physical_network] = uplink_port
+
+    def setup_ancillary_bridges(self, integ_br, tun_br):
+        '''Setup ancillary bridges - for example br-ex.'''
+        ovs = ovs_lib.BaseOVS()
+        ovs_bridges = set(ovs.get_bridges())
+        # Remove all known bridges
+        ovs_bridges.remove(integ_br)
+
+        # Filter list of bridges to those that have external
+        # bridge-id's configured
+        br_names = []
+        for bridge in ovs_bridges:
+            bridge_id = ovs.get_bridge_external_bridge_id(bridge)
+            if bridge_id != bridge:
+                br_names.append(bridge)
+        ovs_bridges.difference_update(br_names)
+        ancillary_bridges = []
+        for bridge in ovs_bridges:
+            br = ovs_lib.OVSBridge(bridge)
+            LOG.info(_LI('Adding %s to list of bridges.'), bridge)
+            ancillary_bridges.append(br)
+        return ancillary_bridges
+
+    def setup_tunnel_br(self, tun_br_name=None):
+        '''(re)initialize the tunnel bridge.
+
+        :param tun_br_name: the name of the tunnel bridge.
+        '''
+        # Solaris doesn't have a separate tunnel bridge, instead we
+        # re-use the integration bridge itself.
+        if self.tun_br is None:
+            self.tun_br = self.int_br
+
+        # create ovs.vxlan1 datalink and add it to integration bridge
+        if not self.local_ip:
+            LOG.error(_LE("local_ip parameter is not set. Cannot have "
+                          "tunneling enabled without it. Agent terminated!"))
+            exit(1)
+        if not net_lib.Datalink.datalink_exists("ovs.vxlan1"):
+            # create the required vxlan
+            cmd = ['/usr/sbin/dladm', 'create-vxlan', '-t', '-p',
+                   'addr=%s,vni=flow' % (self.local_ip), 'ovs.vxlan1']
+            try:
+                utils.execute(cmd)
+            except Exception as e:
+                LOG.error(_LE("failed to create VXLAN tunnel end point "
+                              "ovs.vxlan1: %s. Agent terminated!") % (e))
+                exit(1)
+        # set openvswitch property to on
+        try:
+            cmd = ['/usr/sbin/dladm', 'show-linkprop', '-p',
+                   'openvswitch', '-co', 'value', 'ovs.vxlan1']
+            stdout = utils.execute(cmd)
+            if stdout.strip() == 'off':
+                cmd = ['/usr/sbin/dladm', 'set-linkprop', '-t', '-p',
+                       'openvswitch=on', 'ovs.vxlan1']
+                utils.execute(cmd)
+        except Exception as e:
+            LOG.error(_LE("failed to set 'openvswitch' property on "
+                          "ovs.vxlan1: %s. Agent terminated!") % (e))
+            exit(1)
+
+        attrs = [('type', 'vxlan'),
+                 ('options', {'remote_ip': 'flow'}),
+                 ('options', {'key': 'flow'})]
+        self.tun_br.replace_port('ovs.vxlan1', *attrs)
+        self.tun_ofport = self.tun_br.get_port_ofport('ovs.vxlan1')
+        if self.tun_ofport == constants.OFPORT_INVALID:
+            LOG.error(_LE("Failed to add ovs.vxlan1 to integration bridge. "
+                          "Cannot have tunneling enabled on this agent. "
+                          "Agent terminated!"))
+            exit(1)
+
+    def setup_tunnel_br_flows(self):
+        '''Setup the tunnel bridge
+
+        Add all flows to the tunnel bridge.
+        '''
+        self.tun_br.setup_default_tunnel_table(self.tun_ofport,
+                                               self.arp_responder_enabled)
+
+    def _mod_flood_to_tun_flows(self, ofport, remote_ips, segmentation_id,
+                                local_ofports):
+        LOG.debug(_("Modifying flooding for %s to all %s for VNI %s on %s") %
+                  (ofport, remote_ips, segmentation_id, local_ofports))
+        if not local_ofports and not remote_ips:
+            return
+        action_prefix = ""
+        if local_ofports:
+            action_prefix = ("output:%s" %
+                             self.tun_br._ofport_set_to_str(local_ofports))
+        if not remote_ips:
+            assert local_ofports
+            self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
+                                 in_port="%s" % ofport,
+                                 actions="%s" % action_prefix)
+            return
+        action_str = ""
+        if action_prefix:
+            action_str = "%s," % action_prefix
+        action_str += "set_tunnel:%s" % segmentation_id
+        # for each of the remote_ip
+        for remote_ip in remote_ips:
+            action_str += ",set_field:%s->tun_dst,output:%s" % \
+                (remote_ip, self.tun_ofport)
+
+        self.tun_br.mod_flow(table=constants.FLOOD_TO_TUN,
+                             in_port="%s" % ofport,
+                             actions="%s" % action_str)
+
+    def port_bound(self, port, net_uuid,
+                   network_type, physical_network,
+                   segmentation_id, fixed_ips, device_owner,
+                   ovs_restarted):
+        '''Bind port to net_uuid/lsw_id and install flow for inbound traffic
+        to vm.
+
+        :param port: a ovslib.VifPort object.
+        :param net_uuid: the net_uuid this port is to be associated with.
+        :param network_type: the network type ('gre', 'vlan', 'flat', 'local')
+        :param physical_network: the physical network for 'vlan' or 'flat'
+        :param segmentation_id: the VID for 'vlan' or tunnel ID for 'tunnel'
+        :param fixed_ips: the ip addresses assigned to this port
+        :param device_owner: the string indicative of owner of this port
+        :param ovs_restarted: indicates if this is called for an OVS restart.
+        '''
+
+        LOG.info(_LI("Setting up datapath for port: %s connected to "
+                     "network: %s of type: %s") % (port.vif_id, net_uuid,
+                                                   network_type))
+
+        if network_type in constants.TUNNEL_NETWORK_TYPES:
+            if self.enable_tunneling:
+                # delete any drop flows
+                self.int_br.delete_flows(in_port=port.ofport)
+                remote_ips = self.tun_br_ofports[network_type].values()
+                # add a flow to flood Broadcast/Unknown Unicast/Multicast
+                # packets from this port to all the remote_ips and local
+                # ports on port's segmentation_id.
+                self._mod_flood_to_tun_flows(port.ofport, remote_ips,
+                    segmentation_id, self.br_segid_ports.get(segmentation_id))
+
+                # add segmentation id for all the packets from this port and
+                # send it to table 2 for learning.
+                self.tun_br.add_flow(priority=1,
+                                     in_port="%s" % (port.ofport),
+                                     actions="set_tunnel:%s,"
+                                     "resubmit(,%s)" %
+                                     (segmentation_id,
+                                      constants.LEARN_FROM_PORTS))
+
+                # update flow that steers inbound broadcast/unknown/multicast
+                # packets on this segmentation id to all of the ports
+                # (including this port)
+                self.br_port_segid[port.ofport] = segmentation_id
+                if self.br_segid_ports.get(segmentation_id):
+                    self.br_segid_ports[segmentation_id].add(port.ofport)
+                else:
+                    self.br_segid_ports[segmentation_id] = set([port.ofport])
+                ofports_str = self.tun_br._ofport_set_to_str(
+                    self.br_segid_ports[segmentation_id])
+                self.tun_br.mod_flow(table=constants.INBOUND_BUM_TABLE,
+                                     tun_id=segmentation_id,
+                                     actions="output:%s" % ofports_str)
+                # we need to modify flows for other ports that are part of
+                # this segmentation ID
+                ofports = self.br_segid_ports[segmentation_id]
+                for ofport in ofports:
+                    if ofport == port.ofport:
+                        continue
+                    self._mod_flood_to_tun_flows(ofport, remote_ips,
+                                                 segmentation_id,
+                                                 ofports - set([ofport]))
+                self.vif_ports[port.vif_id] = port
+            else:
+                LOG.error(_LE("Cannot provision %(network_type)s network for "
+                              "net-id=%(net_uuid)s - tunneling disabled"),
+                          {'network_type': network_type,
+                           'net_uuid': net_uuid})
+                return False
+        elif network_type == p_const.TYPE_FLAT:
+            if physical_network not in self.phys_brs:
+                LOG.error(_LE("Cannot provision flat network for "
+                              "net-id=%(net_uuid)s - no uplink port for "
+                              "physical_network %(physical_network)s"),
+                          {'net_uuid': net_uuid,
+                           'physical_network': physical_network})
+                return False
+            self.vif_ports[port.vif_id] = port
+            # delete any drop flows
+            self.int_br.delete_flows(in_port=port.ofport)
+        elif network_type == p_const.TYPE_VLAN:
+            if physical_network in self.phys_brs:
+                # Do not bind a port if it's already bound
+                cur_tag = self.int_br.db_get_val("Port", port.port_name, "tag")
+                if cur_tag != segmentation_id:
+                    self.int_br.set_db_attribute("Port", port.port_name, "tag",
+                                                 segmentation_id)
+                if port.ofport != -1:
+                    self.int_br.delete_flows(in_port=port.ofport)
+                self.vif_ports[port.vif_id] = port
+            else:
+                LOG.error(_LE("Cannot provision VLAN network for "
+                              "net-id=%(net_uuid)s - no uplink-port for "
+                              "physical_network %(physical_network)s"),
+                          {'net_uuid': net_uuid,
+                           'physical_network': physical_network})
+                return False
+        else:
+            LOG.error(_LE("Cannot provision unknown network type "
+                          "%(network_type)s for net-id=%(net_uuid)s"),
+                      {'network_type': network_type, 'net_uuid': net_uuid})
+            return False
+        return True
+
+    def _add_port_tag_info(self, need_binding_ports):
+        pass
+
+    def _bind_devices(self, need_binding_ports):
+        devices_up = []
+        devices_down = []
+        failed_devices = []
+        for port_detail in need_binding_ports:
+            device = port_detail['device']
+            # update plugin about port status
+            # FIXME(salv-orlando): Failures while updating device status
+            # must be handled appropriately. Otherwise this might prevent
+            # neutron server from sending network-vif-* events to the nova
+            # API server, thus possibly preventing instance spawn.
+            if port_detail.get('admin_state_up'):
+                LOG.debug("Setting status for %s to UP", device)
+                devices_up.append(device)
+            else:
+                LOG.debug("Setting status for %s to DOWN", device)
+                devices_down.append(device)
+        if devices_up or devices_down:
+            devices_set = self.plugin_rpc.update_device_list(
+                self.context, devices_up, devices_down, self.agent_id,
+                self.conf.host)
+            failed_devices = (devices_set.get('failed_devices_up') +
+                devices_set.get('failed_devices_down'))
+            if failed_devices:
+                LOG.error(_LE("Configuration for devices %s failed!"),
+                          failed_devices)
+        LOG.info(_LI("Configuration for devices up %(up)s and devices "
+                     "down %(down)s completed."),
+                 {'up': devices_up, 'down': devices_down})
+        return set(failed_devices)
+
+    def port_unbound(self, vif_id, net_uuid=None):
+        '''Unbind port.
+
+        Removes all the OpenFlow rules associated with the port going away.
+
+        :param vif_id: the id of the vif
+        :param net_uuid: the net_uuid this port is associated with.
+        '''
+        LOG.info(_LI("Removing flows for port: %s" % (vif_id)))
+        port = self.vif_ports.pop(vif_id, None)
+        if port is None:
+            return
+        if self.enable_tunneling:
+            # remove all the OpenFlows that we have added for this port
+            # across all the tables.
+            self.tun_br.delete_flows(in_port=port.ofport)
+            self.tun_br.delete_flows(table=constants.FLOOD_TO_TUN,
+                                     in_port=port.ofport)
+            segid = self.br_port_segid.pop(port.ofport, None)
+            if segid is None:
+                return
+            self.tun_br.delete_flows(table=constants.INBOUND_UCAST_TABLE,
+                                     tun_id=segid, dl_dst=port.vif_mac)
+            self.tun_br.delete_flows(table=constants.UCAST_TO_TUN,
+                                     tun_id=segid, dl_dst=port.vif_mac)
+            if self.br_segid_ports.get(segid) is None:
+                return
+            self.br_segid_ports[segid].discard(port.ofport)
+            ofports = self.br_segid_ports[segid]
+            if ofports:
+                # update brodcast/multicast table to not to include this port
+                ofportstr = self.tun_br._ofport_set_to_str(ofports)
+                self.tun_br.mod_flow(table=constants.INBOUND_BUM_TABLE,
+                                     tun_id=segid,
+                                     actions="output:%s" % ofportstr)
+                for ofport in ofports:
+                    remote_ips = \
+                        self.tun_br_ofports[p_const.TYPE_VXLAN].values()
+                    self._mod_flood_to_tun_flows(ofport, remote_ips, segid,
+                                                 ofports - set([ofport]))
+            else:
+                # if this was the last port for that segmentation ID, then
+                # remove all associated flows from broadcast/multicast table
+                self.tun_br.delete_flows(table=constants.INBOUND_BUM_TABLE,
+                                         tun_id=segid)
+        else:
+            self.int_br.delete_flows(in_port=port.ofport)
+
+    def update_stale_ofport_rules(self):
+        # Not required for Solaris since we don't support ARP spoofing
+        # protection yet
+        pass
+
+    def _rewire_zones_anet(self):
+        port_names = self.int_br.get_port_name_list()
+        for port_name in port_names:
+            if '/' not in port_name:
+                continue
+            cmd = ['/usr/sbin/dladm', 'show-linkprop', '-p', 'ofport',
+                   '-co', 'value', port_name]
+            try:
+                stdout = utils.execute(cmd, log_fail_as_error=False)
+            except:
+                continue
+            if stdout.strip() != '0':
+                continue
+
+            LOG.debug(_LE("Zone's anet '%s' was rebooted from within the zone,"
+                          " so we need to delete and add the corresponding"
+                          " OVS port") % (port_name))
+            # needs re-wiring. So delete and add the port
+            external_ids = self.int_br.db_get_val('Interface', port_name,
+                                                  'external_ids')
+            self.int_br.delete_port(port_name)
+            self.int_br.add_port(port_name, ('external_ids', external_ids))
+
+    def _agent_has_updates(self, polling_manager):
+        # check if any anet ports on OVS bridge requires re-wiring. This is
+        # needed if an user reboots the zone from inside the zone. This
+        # workaround is needed until OVS is integrated with Zones and is only
+        # needed on nova compute
+        cmd = ['/usr/bin/pgrep', 'nova-compute']
+        try:
+            stdout = utils.execute(cmd, log_fail_as_error=False)
+        except:
+            stdout = ""
+        if stdout:
+            self._rewire_zones_anet()
+
+        return super(SolarisOVSNeutronAgent, self)._agent_has_updates(
+            polling_manager)
+
+    def cleanup_stale_flows(self):
+        LOG.info(_LI("Cleaning stale %s flows"), self.int_br.br_name)
+        self.int_br.cleanup_flows()
+
+
 def validate_local_ip(local_ip):
     """Verify if the ip exists on the agent's host."""
-    if not ip_lib.IPWrapper().get_device_by_ip(local_ip):
+    if platform.system() == "SunOS":
+        local_ip_valid = net_lib.IPInterface.ipaddr_exists(local_ip)
+    else:
+        local_ip_valid = ip_lib.IPWrapper().get_device_by_ip(local_ip)
+
+    if not local_ip_valid:
         LOG.error(_LE("Tunneling can't be enabled with invalid local_ip '%s'."
                       " IP couldn't be found on this host's interfaces."),
                   local_ip)
@@ -2116,7 +2605,10 @@
     validate_tunnel_config(cfg.CONF.AGENT.tunnel_types, cfg.CONF.OVS.local_ip)
 
     try:
-        agent = OVSNeutronAgent(bridge_classes, cfg.CONF)
+        if platform.system() == "SunOS":
+            agent = SolarisOVSNeutronAgent(bridge_classes, cfg.CONF)
+        else:
+            agent = OVSNeutronAgent(bridge_classes, cfg.CONF)
     except (RuntimeError, ValueError) as e:
         LOG.error(_LE("%s Agent terminated!"), e)
         sys.exit(1)