--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/components/openstack/neutron/patches/07-ml2-ovs-support.patch Wed Sep 07 14:48:41 2016 -0700
@@ -0,0 +1,552 @@
+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)