330 lines
14 KiB
Python
330 lines
14 KiB
Python
# Copyright (c) 2015 OpenStack Foundation.
|
|
#
|
|
# 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 signal
|
|
import sys
|
|
import threading
|
|
|
|
import eventlet
|
|
|
|
eventlet.monkey_patch()
|
|
|
|
from six import moves
|
|
|
|
from oslo_config import cfg
|
|
|
|
from neutron.agent.common import config
|
|
from neutron.agent.linux import ip_lib
|
|
from neutron.agent.ovsdb import api as ovsdb
|
|
|
|
from neutron.common import config as common_config
|
|
from neutron.common import constants as q_const
|
|
from neutron.common import utils as q_utils
|
|
|
|
from neutron.i18n import _, _LE, _LI
|
|
from neutron.plugins.common import constants as p_const
|
|
from neutron.plugins.openvswitch.agent import ovs_neutron_agent as ona
|
|
from neutron.plugins.openvswitch.common import constants
|
|
from oslo_log import log as logging
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
agent_additional_opts = [
|
|
cfg.StrOpt('L3controller_ip_list',
|
|
default='tcp:localhost:6633',
|
|
help=("L3 Controler IP list list tcp:ip_addr:port;"
|
|
"tcp:ip_addr:port..;..")),
|
|
cfg.BoolOpt('enable_l3_controller', default=True,
|
|
help=_("L3 SDN Controller")),
|
|
cfg.IntOpt('tunnel_map_check_rate', default=5,
|
|
help=_("Rate in multiple of the rpc loop")),
|
|
]
|
|
|
|
cfg.CONF.register_opts(agent_additional_opts, "AGENT")
|
|
TUN_TRANSLATE_TABLE = 60
|
|
|
|
|
|
class L2OVSControllerAgent(ona.OVSNeutronAgent):
|
|
def __init__(self, integ_br, tun_br, local_ip,
|
|
bridge_mappings, polling_interval, tunnel_types=None,
|
|
veth_mtu=None, l2_population=False,
|
|
enable_distributed_routing=False,
|
|
minimize_polling=False,
|
|
ovsdb_monitor_respawn_interval=(
|
|
constants.DEFAULT_OVSDBMON_RESPAWN),
|
|
arp_responder=False,
|
|
prevent_arp_spoofing=False,
|
|
use_veth_interconnection=False,
|
|
quitting_rpc_timeout=None):
|
|
|
|
if prevent_arp_spoofing:
|
|
LOG.error(_LE("ARP Spoofing prevention is not"
|
|
" yet supported in Dragonflow feature disabled"))
|
|
prevent_arp_spoofing = False
|
|
|
|
'''
|
|
Sync lock for Race condition set_controller <--> check_ovs_status
|
|
when setting the controller all the flow table are deleted
|
|
by the time we set the CANARY_TABLE again.
|
|
'''
|
|
self.set_controller_lock = threading.Lock()
|
|
self.enable_l3_controller = cfg.CONF.AGENT.enable_l3_controller
|
|
self.tunnel_map_check_rate = cfg.CONF.AGENT.tunnel_map_check_rate
|
|
|
|
super(L2OVSControllerAgent, self) \
|
|
.__init__(integ_br,
|
|
tun_br, local_ip,
|
|
bridge_mappings,
|
|
polling_interval,
|
|
tunnel_types,
|
|
veth_mtu, l2_population,
|
|
enable_distributed_routing,
|
|
minimize_polling,
|
|
ovsdb_monitor_respawn_interval,
|
|
arp_responder,
|
|
prevent_arp_spoofing,
|
|
use_veth_interconnection,
|
|
quitting_rpc_timeout)
|
|
|
|
self.df_available_local_vlans = set(moves.range(q_const.MIN_VLAN_TAG,
|
|
q_const.MAX_VLAN_TAG))
|
|
#mapping from vlan to destination tunnel ip
|
|
self.df_local_to_vlan_map = {}
|
|
self.controllers_ip_list = cfg.CONF.AGENT.L3controller_ip_list
|
|
# Initialize controller
|
|
self.set_controller_for_br(self.int_br, self.controllers_ip_list)
|
|
|
|
def set_controller_for_br(self, bridge, ip_address_list):
|
|
'''Set OpenFlow Controller on the Bridge .
|
|
:param bridge: the bridge object.
|
|
:param ip_address_list: tcp:ip_address:port;tcp:ip_address2:port
|
|
'''
|
|
if not self.enable_l3_controller:
|
|
LOG.info(_LI("Controller Base l3 is disabled on Agent"))
|
|
return
|
|
|
|
ip_address_ = ip_address_list.split(";")
|
|
LOG.debug("Set Controllers on br %s to %s", bridge.br_name,
|
|
ip_address_)
|
|
|
|
with self.set_controller_lock:
|
|
bridge.del_controller()
|
|
bridge.set_controller(ip_address_)
|
|
self.set_connection_mode(bridge, "out-of-band")
|
|
|
|
bridge.ovsdb.set_fail_mode(bridge.br_name,
|
|
"standalone").execute(check_error=True)
|
|
bridge.add_flow(priority=0, actions="normal")
|
|
bridge.add_flow(table=constants.CANARY_TABLE,
|
|
priority=0,
|
|
actions="drop")
|
|
if self.patch_tun_ofport > 0:
|
|
# Mark the tunnel ID so the data will be transferred to the
|
|
# br-tun virtual switch, tun id and metadata are local
|
|
self._add_tun_translate_pack_mark_flow(bridge)
|
|
# add the normal flow higher priority than the drop
|
|
for br in self.phys_brs.values():
|
|
br.add_flow(priority=3, actions="normal")
|
|
|
|
# add the vlan flows
|
|
cur_ports = self.int_br.get_vif_ports()
|
|
# use to initialize once each local vlan
|
|
l_vlan_map = set()
|
|
for port in cur_ports:
|
|
local_vlan_map = self.int_br.db_get_val("Port", port.port_name,
|
|
"other_config")
|
|
local_vlan = self.int_br.db_get_val("Port", port.port_name,
|
|
"tag")
|
|
net_uuid = local_vlan_map.get('net_uuid')
|
|
if (net_uuid and local_vlan != ona.DEAD_VLAN_TAG and
|
|
net_uuid not in l_vlan_map):
|
|
l_vlan_map.add(net_uuid)
|
|
self.provision_local_vlan2(
|
|
local_vlan_map['net_uuid'],
|
|
local_vlan_map['network_type'],
|
|
local_vlan_map['physical_network'],
|
|
local_vlan_map['segmentation_id'])
|
|
|
|
def _add_tun_translate_pack_mark_flow(self, bridge):
|
|
bridge.add_flow(table=TUN_TRANSLATE_TABLE, priority=1,
|
|
actions="move:NXM_NX_TUN_ID[0..31]"
|
|
"->NXM_NX_PKT_MARK[],"
|
|
"output:%s" %
|
|
(self.patch_tun_ofport))
|
|
|
|
def _check_tunnel_map_table(self):
|
|
"""Verify that the tunnel ip mapping is installed
|
|
on the integration bridge
|
|
"""
|
|
|
|
if p_const.TYPE_VLAN in self.tunnel_types:
|
|
# TODO(gampel) check for the vlan flows here
|
|
return
|
|
if not self.df_local_to_vlan_map:
|
|
return
|
|
tunnel_flows = self.int_br.dump_flows_for_table(
|
|
TUN_TRANSLATE_TABLE)
|
|
for tunnel_ip in self.df_local_to_vlan_map:
|
|
vlan_action = "mod_vlan_vid:%d" % (
|
|
self.df_local_to_vlan_map[tunnel_ip])
|
|
if vlan_action not in tunnel_flows:
|
|
self.tunnel_sync()
|
|
self._add_tun_translate_pack_mark_flow(self.int_br)
|
|
|
|
def check_ovs_status(self):
|
|
if not self.enable_l3_controller:
|
|
return super(L2OVSControllerAgent, self).check_ovs_status()
|
|
# Check for the canary flow
|
|
# Add lock to avoid race condition of flows
|
|
with self.set_controller_lock:
|
|
ret = super(L2OVSControllerAgent, self).check_ovs_status()
|
|
if not self.iter_num % self.tunnel_map_check_rate:
|
|
self._check_tunnel_map_table()
|
|
return ret
|
|
|
|
def _claim_df_tunnel_local_vlan(self, tunnel_ip_hex):
|
|
lvid = None
|
|
if tunnel_ip_hex in self.df_local_to_vlan_map:
|
|
lvid = self.df_local_to_vlan_map[tunnel_ip_hex]
|
|
else:
|
|
lvid = self.df_available_local_vlans.pop()
|
|
self.df_local_to_vlan_map[tunnel_ip_hex] = lvid
|
|
return lvid
|
|
|
|
def _release_df_tunnel_local_vlan(self, tunnel_ip_hex):
|
|
lvid = self.df_local_to_vlan_map.pop(tunnel_ip_hex, None)
|
|
self.df_available_local_vlans.add(lvid)
|
|
|
|
def cleanup_tunnel_port(self, br, tun_ofport, tunnel_type):
|
|
items = list(self.tun_br_ofports[tunnel_type].items())
|
|
for remote_ip, ofport in items:
|
|
if ofport == tun_ofport:
|
|
tunnel_ip_hex = "0x%s" % self.get_ip_in_hex(remote_ip)
|
|
lvid = self.df_local_to_vlan_map[tunnel_ip_hex]
|
|
self.int_br.delete_flows(
|
|
table=TUN_TRANSLATE_TABLE,
|
|
reg7=tunnel_ip_hex)
|
|
br.delete_flows(
|
|
table=constants.UCAST_TO_TUN,
|
|
dl_vlan=lvid)
|
|
self._release_df_tunnel_local_vlan(tunnel_ip_hex)
|
|
|
|
return super(L2OVSControllerAgent, self).cleanup_tunnel_port(
|
|
br,
|
|
tun_ofport,
|
|
tunnel_type)
|
|
|
|
def _setup_tunnel_port(self, br, port_name, remote_ip, tunnel_type):
|
|
ofport = super(L2OVSControllerAgent, self) \
|
|
._setup_tunnel_port(
|
|
br,
|
|
port_name,
|
|
remote_ip,
|
|
tunnel_type)
|
|
if p_const.TYPE_VLAN not in self.tunnel_types:
|
|
tunnel_ip_hex = "0x%s" % self.get_ip_in_hex(remote_ip)
|
|
lvid = self._claim_df_tunnel_local_vlan(tunnel_ip_hex)
|
|
self.int_br.add_flow(
|
|
table=TUN_TRANSLATE_TABLE,
|
|
priority=2000,
|
|
reg7=tunnel_ip_hex,
|
|
actions="mod_vlan_vid:%s,"
|
|
"load:0->NXM_NX_REG7[0..31],"
|
|
"resubmit(,%s)" %
|
|
(lvid, TUN_TRANSLATE_TABLE))
|
|
br.add_flow(table=constants.UCAST_TO_TUN,
|
|
priority=100,
|
|
dl_vlan=lvid,
|
|
pkt_mark="0x80000000/0x80000000",
|
|
actions="strip_vlan,move:NXM_NX_PKT_MARK[0..30]"
|
|
"->NXM_NX_TUN_ID[0..30],"
|
|
"output:%s" %
|
|
(ofport))
|
|
if ofport > 0:
|
|
ofports = (ona._ofport_set_to_str
|
|
(self.tun_br_ofports[tunnel_type].values()))
|
|
if self.enable_l3_controller:
|
|
if ofports:
|
|
br.add_flow(table=constants.FLOOD_TO_TUN,
|
|
actions="move:NXM_NX_PKT_MARK[0..30]"
|
|
"->NXM_NX_TUN_ID[0..30],"
|
|
"output:%s" %
|
|
(ofports))
|
|
return ofport
|
|
|
|
def provision_local_vlan2(self, net_uuid, network_type, physical_network,
|
|
segmentation_id):
|
|
if network_type == p_const.TYPE_VLAN:
|
|
if physical_network in self.phys_brs:
|
|
#outbound
|
|
# The global vlan id is set in table 60
|
|
# from segmentation id/tun id
|
|
self.int_br.add_flow(table=TUN_TRANSLATE_TABLE,
|
|
priority=1,
|
|
actions="move:NXM_NX_TUN_ID[0..11]"
|
|
"->OXM_OF_VLAN_VID[],"
|
|
"output:%s" %
|
|
(self.int_ofports[physical_network]))
|
|
lvid = self.local_vlan_map.get(net_uuid).vlan
|
|
# inbound
|
|
self.int_br.add_flow(priority=1000,
|
|
in_port=self.
|
|
int_ofports[physical_network],
|
|
dl_vlan=segmentation_id,
|
|
actions="mod_vlan_vid:%s,normal" % lvid)
|
|
else:
|
|
LOG.error(_LE("Cannot provision VLAN network for "
|
|
"net-id=%(net_uuid)s - no bridge for "
|
|
"physical_network %(physical_network)s"),
|
|
{'net_uuid': net_uuid,
|
|
'physical_network': physical_network})
|
|
|
|
def set_connection_mode(self, bridge, mode):
|
|
ovsdb_api = ovsdb.API.get(bridge)
|
|
attrs = [('connection-mode', mode)]
|
|
ovsdb_api.db_set('controller', bridge.br_name, *attrs).execute(
|
|
check_error=True)
|
|
|
|
|
|
def main():
|
|
cfg.CONF.register_opts(ip_lib.OPTS)
|
|
config.register_root_helper(cfg.CONF)
|
|
common_config.init(sys.argv[1:])
|
|
common_config.setup_logging()
|
|
q_utils.log_opt_values(LOG)
|
|
|
|
try:
|
|
agent_config = ona.create_agent_config_map(cfg.CONF)
|
|
except ValueError as e:
|
|
LOG.error(_LE('%s Agent terminated!'), e)
|
|
sys.exit(1)
|
|
|
|
is_xen_compute_host = 'rootwrap-xen-dom0' in cfg.CONF.AGENT.root_helper
|
|
if is_xen_compute_host:
|
|
# Force ip_lib to always use the root helper to ensure that ip
|
|
# commands target xen dom0 rather than domU.
|
|
cfg.CONF.set_default('ip_lib_force_root', True)
|
|
|
|
agent = L2OVSControllerAgent(**agent_config)
|
|
|
|
signal.signal(signal.SIGTERM, agent._handle_sigterm)
|
|
|
|
# Start everything.
|
|
LOG.info(_LI("Agent initialized successfully, now running... "))
|
|
agent.daemon_loop()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|