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-11-01 18:39:33.985866969 -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,492 @@
"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()
+ if self.enable_tunneling:
+ self.int_br.set_secure_mode()
+ else:
+ self.int_br.set_standalone_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.exception(_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.exception(_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 +2608,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)