dragonflow/dragonflow/controller/df_local_controller.py

551 lines
21 KiB
Python

# Copyright (c) 2015 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 socket
import sys
import time
from neutron.agent.common import config
from neutron.common import config as common_config
from oslo_config import cfg
from oslo_log import log
from ryu.base.app_manager import AppManager
from dragonflow._i18n import _LI, _LW
from dragonflow.common import common_params
from dragonflow.common import constants
from dragonflow.common import utils as df_utils
from dragonflow.controller.ryu_base_app import RyuDFAdapter
from dragonflow.controller.topology import Topology
from dragonflow.db import api_nb
from dragonflow.db import db_store
from dragonflow.db.drivers import ovsdb_vswitch_impl
config.setup_logging()
LOG = log.getLogger("dragonflow.controller.df_local_controller")
cfg.CONF.register_opts(common_params.df_opts, 'df')
class DfLocalController(object):
def __init__(self, chassis_name):
self.next_network_id = 0
self.db_store = db_store.DbStore()
self.chassis_name = chassis_name
self.ip = cfg.CONF.df.local_ip
self.tunnel_type = cfg.CONF.df.tunnel_type
self.sync_finished = False
nb_driver = df_utils.load_driver(
cfg.CONF.df.nb_db_class,
df_utils.DF_NB_DB_DRIVER_NAMESPACE)
self.nb_api = api_nb.NbApi(
nb_driver,
use_pubsub=cfg.CONF.df.enable_df_pub_sub)
self.vswitch_api = ovsdb_vswitch_impl.OvsdbSwitchApi(
self.ip, self.nb_api)
kwargs = dict(
nb_api=self.nb_api,
vswitch_api=self.vswitch_api,
db_store=self.db_store
)
app_mgr = AppManager.get_instance()
self.open_flow_app = app_mgr.instantiate(RyuDFAdapter, **kwargs)
self.topology = None
self.enable_selective_topo_dist = \
cfg.CONF.df.enable_selective_topology_distribution
self.integration_bridge = cfg.CONF.df.integration_bridge
def run(self):
self.nb_api.initialize(db_ip=cfg.CONF.df.remote_db_ip,
db_port=cfg.CONF.df.remote_db_port)
self.vswitch_api.initialize()
self.topology = Topology(self, self.enable_selective_topo_dist)
self.vswitch_api.sync()
# both set_controller and del_controller will delete flows.
# for reliability, here we should check if controller is set for OVS,
# if yes, don't set controller and don't delete controller.
# if no, set controller
# TODO(heshan) port should be configured in cfg file
targets = 'tcp:' + self.ip + ':6633'
is_controller_set = self.vswitch_api.check_controller(targets)
if not is_controller_set:
self.vswitch_api.set_controllers(self.integration_bridge,
[targets]).execute()
is_fail_mode_set = self.vswitch_api.check_controller_fail_mode(
'secure')
if not is_fail_mode_set:
self.vswitch_api.set_controller_fail_mode(
self.integration_bridge, 'secure').execute()
self.open_flow_app.start()
self.db_sync_loop()
def db_sync_loop(self):
while True:
time.sleep(1)
self.run_db_poll()
if self.sync_finished and (
self.nb_api.support_publish_subscribe()):
self.nb_api.register_notification_callback(self)
def run_sync(self):
self.sync_finished = True
while True:
time.sleep(1)
self.run_db_poll()
if self.sync_finished:
return
def run_db_poll(self):
try:
self.nb_api.sync()
self.register_chassis()
self.create_tunnels()
if not self.enable_selective_topo_dist:
self.read_switches()
self.read_security_groups()
self.port_mappings()
self.read_routers()
self.read_floatingip()
self.sync_finished = True
except Exception as e:
self.sync_finished = False
LOG.warning(_LW("run_db_poll - suppressing exception"))
LOG.exception(e)
def chassis_created(self, chassis):
# Check if tunnel already exists to this chassis
t_ports = self.vswitch_api.get_tunnel_ports()
remote_chassis_name = chassis.get_id()
if self.chassis_name == remote_chassis_name:
return
for t_port in t_ports:
if t_port.get_chassis_id() == remote_chassis_name:
LOG.info(_LI("remote Chassis Tunnel already installed = %s") %
chassis.__str__())
return
# Create tunnel port to this chassis
LOG.info(_LI("Adding tunnel to remote chassis = %s") %
chassis.__str__())
self.vswitch_api.add_tunnel_port(chassis).execute()
def chassis_deleted(self, chassis_id):
LOG.info(_LI("Deleting tunnel to remote chassis = %s") % chassis_id)
tunnel_ports = self.vswitch_api.get_tunnel_ports()
for port in tunnel_ports:
if port.get_chassis_id() == chassis_id:
self.vswitch_api.delete_port(port).execute()
return
def read_switches(self):
for lswitch in self.nb_api.get_all_logical_switches():
self.logical_switch_updated(lswitch)
def logical_switch_updated(self, lswitch):
old_lswitch = self.db_store.get_lswitch(lswitch.get_id())
if old_lswitch == lswitch:
return
#Make sure we have a local network_id mapped before we dispatch
network_id = self.get_network_id(
lswitch.get_id(),
)
lswitch_conf = {'network_id': network_id, 'lswitch':
lswitch.__str__()}
LOG.info(_LI("Adding/Updating Logical Switch = %s") % lswitch_conf)
self.db_store.set_lswitch(lswitch.get_id(), lswitch)
self.open_flow_app.notify_update_logical_switch(lswitch)
def logical_switch_deleted(self, lswitch_id):
lswitch = self.db_store.get_lswitch(lswitch_id)
LOG.info(_LI("Removing Logical Switch = %s") % lswitch_id)
if lswitch is None:
LOG.warning(_LW("Try to delete a nonexistent lswitch(%s)") %
lswitch_id)
return
self.open_flow_app.notify_remove_logical_switch(lswitch)
self.db_store.del_lswitch(lswitch_id)
self.db_store.del_network_id(lswitch_id)
def _logical_port_process(self, lport, original_lport=None):
chassis = lport.get_chassis()
if chassis in (None,
'',
constants.DRAGONFLOW_VIRTUAL_PORT):
LOG.debug(("Port %s has not been bound or it is a vPort ") %
lport.get_id())
return
chassis_to_ofport, lport_to_ofport = (
self.vswitch_api.get_local_ports_to_ofport_mapping())
local_network_id = self.get_network_id(
lport.get_lswitch_id(),
)
lswitch = self.db_store.get_lswitch(lport.get_lswitch_id())
if lswitch is not None:
network_type = lswitch.get_network_type()
segment_id = lswitch.get_segment_id()
lport.set_external_value('network_type', network_type)
if segment_id is not None:
lport.set_external_value('segmentation_id',
int(segment_id))
lport.set_external_value('local_network_id', local_network_id)
if chassis == self.chassis_name:
lport.set_external_value('is_local', True)
ofport = lport_to_ofport.get(lport.get_id(), 0)
if ofport != 0:
lport.set_external_value('ofport', ofport)
if original_lport is None:
LOG.info(_LI("Adding new local logical port = %s") %
str(lport))
self.open_flow_app.notify_add_local_port(lport)
else:
LOG.info(_LI("Updating local logical port = %(port)s, "
"original port = %(original_port)s") %
{'port': str(lport),
'original_port': str(original_lport)})
self.open_flow_app.notify_update_local_port(lport,
original_lport)
self.db_store.set_port(lport.get_id(), lport, True)
else:
LOG.info(_LI("Local logical port %s was not created yet") %
str(lport))
else:
lport.set_external_value('is_local', False)
ofport = chassis_to_ofport.get(chassis, 0)
if ofport != 0:
lport.set_external_value('ofport', ofport)
if original_lport is None:
LOG.info(_LI("Adding new remote logical port = %s") %
str(lport))
self.open_flow_app.notify_add_remote_port(lport)
else:
LOG.info(_LI("Updating remote logical port = %(port)s, "
"original port = %(original_port)s") %
{'port': str(lport),
'original_port': str(original_lport)})
self.open_flow_app.notify_update_remote_port(
lport, original_lport)
self.db_store.set_port(lport.get_id(), lport, False)
else:
# TODO(gampel) add handling for this use case
# remote port but no tunnel to remote Host
# if this should never happen raise an exception
LOG.warning(_LW("No tunnel for remote logical port %s") %
str(lport))
def logical_port_created(self, lport):
self._logical_port_process(lport)
def logical_port_updated(self, lport):
original_lport = self.db_store.get_port(lport.get_id())
self._logical_port_process(lport, original_lport)
def logical_port_deleted(self, lport_id):
lport = self.db_store.get_port(lport_id)
if lport is None:
return
if lport.get_external_value('is_local'):
LOG.info(_LI("Removing local logical port = %s") %
str(lport))
if lport.get_external_value('ofport') is not None:
self.open_flow_app.notify_remove_local_port(lport)
self.db_store.delete_port(lport.get_id(), True)
else:
LOG.info(_LI("Removing remote logical port = %s") %
str(lport))
if lport.get_external_value('ofport') is not None:
self.open_flow_app.notify_remove_remote_port(lport)
self.db_store.delete_port(lport.get_id(), False)
def bridge_port_updated(self, lport):
self.open_flow_app.notify_update_bridge_port(lport)
def router_updated(self, lrouter):
old_lrouter = self.db_store.get_router(lrouter.get_id())
if old_lrouter is None:
LOG.info(_LI("Logical Router created = %s") %
lrouter.__str__())
self._add_new_lrouter(lrouter)
return
self._update_router_interfaces(old_lrouter, lrouter)
self.db_store.update_router(lrouter.get_id(), lrouter)
def router_deleted(self, lrouter_id):
old_lrouter = self.db_store.get_router(lrouter_id)
if old_lrouter is None:
return
old_router_ports = old_lrouter.get_ports()
for old_port in old_router_ports:
self._delete_router_port(old_port)
self.db_store.delete_router(lrouter_id)
def security_group_updated(self, secgroup):
old_secgroup = self.db_store.get_security_group(secgroup.get_id())
if old_secgroup is None:
LOG.info(_LI("Security Group created = %s") %
secgroup)
self._add_new_security_group(secgroup)
return
self._update_security_group_rules(old_secgroup, secgroup)
self.db_store.update_security_group(secgroup.get_id(), secgroup)
def security_group_deleted(self, secgroup_id):
old_secgroup = self.db_store.get_security_group(secgroup_id)
if old_secgroup is None:
return
self._delete_old_security_group(old_secgroup)
def register_chassis(self):
chassis = self.nb_api.get_chassis(self.chassis_name)
# TODO(gsagie) Support tunnel type change here ?
if chassis is None:
self.nb_api.add_chassis(self.chassis_name,
self.ip,
self.tunnel_type)
def create_tunnels(self):
tunnel_ports = {}
t_ports = self.vswitch_api.get_tunnel_ports()
for t_port in t_ports:
tunnel_ports[t_port.get_chassis_id()] = t_port
for chassis in self.nb_api.get_all_chassis():
if chassis.get_id() in tunnel_ports:
del tunnel_ports[chassis.get_id()]
elif chassis.get_id() == self.chassis_name:
pass
else:
self.chassis_created(chassis)
# Iterate all tunnel ports that needs to be deleted
for port in tunnel_ports.values():
self.vswitch_api.delete_port(port).execute()
def port_mappings(self):
ports_to_remove = self.db_store.get_port_keys()
for lport in self.nb_api.get_all_logical_ports():
self.logical_port_updated(lport)
if lport.get_id() in ports_to_remove:
ports_to_remove.remove(lport.get_id())
for port_to_remove in ports_to_remove:
self.logical_port_deleted(port_to_remove)
def get_network_id(self, logical_dp_id):
network_id = self.db_store.get_network_id(logical_dp_id)
if network_id is not None:
return network_id
else:
self.next_network_id += 1
# TODO(gsagie) verify self.next_network_id didnt wrap
self.db_store.set_network_id(
logical_dp_id,
self.next_network_id,
)
return self.next_network_id
def read_routers(self):
for lrouter in self.nb_api.get_routers():
self.router_updated(lrouter)
def _update_router_interfaces(self, old_router, new_router):
new_router_ports = new_router.get_ports()
old_router_ports = old_router.get_ports()
for new_port in new_router_ports:
if new_port not in old_router_ports:
self._add_new_router_port(new_router, new_port)
else:
old_router_ports.remove(new_port)
for old_port in old_router_ports:
self._delete_router_port(old_port)
def _add_new_router_port(self, router, router_port):
LOG.info(_LI("Adding new logical router interface = %s") %
router_port.__str__())
local_network_id = self.db_store.get_network_id(
router_port.get_lswitch_id()
)
self.open_flow_app.notify_add_router_port(
router, router_port, local_network_id)
def _delete_router_port(self, router_port):
LOG.info(_LI("Removing logical router interface = %s") %
router_port.__str__())
local_network_id = self.db_store.get_network_id(
router_port.get_lswitch_id()
)
self.open_flow_app.notify_remove_router_port(
router_port, local_network_id)
def _add_new_lrouter(self, lrouter):
for new_port in lrouter.get_ports():
self._add_new_router_port(lrouter, new_port)
self.db_store.update_router(lrouter.get_id(), lrouter)
def read_security_groups(self):
secgroups_to_remove = self.db_store.get_security_group_keys()
for secgroup in self.nb_api.get_security_groups():
self.security_group_updated(secgroup)
if secgroup.get_id() in secgroups_to_remove:
secgroups_to_remove.remove(secgroup.get_id())
for secgroup_to_remove in secgroups_to_remove:
self.security_group_deleted(secgroup_to_remove)
def _update_security_group_rules(self, old_secgroup, new_secgroup):
new_secgroup_rules = new_secgroup.get_rules()
old_secgroup_rules = old_secgroup.get_rules()
for new_rule in new_secgroup_rules:
if new_rule not in old_secgroup_rules:
self._add_new_security_group_rule(new_secgroup, new_rule)
else:
old_secgroup_rules.remove(new_rule)
for old_rule in old_secgroup_rules:
self._delete_security_group_rule(old_secgroup, old_rule)
def _add_new_security_group(self, secgroup):
for new_rule in secgroup.get_rules():
self._add_new_security_group_rule(secgroup, new_rule)
self.db_store.update_security_group(secgroup.get_id(), secgroup)
def _delete_old_security_group(self, secgroup):
for rule in secgroup.get_rules():
self._delete_security_group_rule(secgroup, rule)
self.db_store.delete_security_group(secgroup.get_id())
def _add_new_security_group_rule(self, secgroup, secgroup_rule):
LOG.info(_LI("Adding new secgroup rule = %s") %
secgroup_rule)
self.open_flow_app.notify_add_security_group_rule(
secgroup, secgroup_rule)
def _delete_security_group_rule(self, secgroup, secgroup_rule):
LOG.info(_LI("Removing secgroup rule = %s") %
secgroup_rule)
self.open_flow_app.notify_remove_security_group_rule(
secgroup, secgroup_rule)
def read_floatingip(self):
for floatingip in self.nb_api.get_floatingips():
self.floatingip_updated(floatingip)
def floatingip_updated(self, floatingip):
# check whether this floatingip is associated with a lport or not
if floatingip.get_lport_id():
if self.db_store.get_local_port(floatingip.get_lport_id()) is None:
return
old_floatingip = self.db_store.get_floatingip(floatingip.get_id())
if old_floatingip is None:
# The new floatingip should be associated with a lport
if not floatingip.get_lport_id():
return
self._associate_floatingip(floatingip)
return
self._update_floatingip(old_floatingip, floatingip)
def floatingip_deleted(self, floatingip_id):
floatingip = self.db_store.get_floatingip(floatingip_id)
if not floatingip:
return
self.open_flow_app.notify_delete_floatingip(floatingip)
LOG.info(_LI("Floatingip is deleted. Floatingip = %s") %
str(floatingip))
def publisher_updated(self, publisher):
self.db_store.update_publisher(publisher.get_id(), publisher)
LOG.info(_LI('Registering to new publisher: %s'), str(publisher))
self.nb_api.subscriber.register_listen_address(publisher.get_uri())
def publisher_deleted(self, uuid):
publisher = self.db_store.get_publisher(uuid)
if publisher:
LOG.info(_LI('Deleting publisher: %s'), str(publisher))
self.nb_api.subscriber.unregister_listen_address(
publisher.get_uri()
)
self.db_store.delete_publisher(uuid)
def _associate_floatingip(self, floatingip):
self.db_store.update_floatingip(floatingip.get_id(), floatingip)
self.open_flow_app.notify_associate_floatingip(floatingip)
LOG.info(_LI("Floatingip is associated with port. Floatingip = %s") %
str(floatingip))
def _disassociate_floatingip(self, floatingip):
self.db_store.delete_floatingip(floatingip.get_id())
self.open_flow_app.notify_disassociate_floatingip(floatingip)
LOG.info(_LI("Floatingip is disassociated from port."
" Floatingip = %s") % str(floatingip))
def _update_floatingip(self, old_floatingip, new_floatingip):
if new_floatingip.get_lport_id() != old_floatingip.get_lport_id():
self._disassociate_floatingip(old_floatingip)
if new_floatingip.get_lport_id():
self._associate_floatingip(new_floatingip)
def ovs_port_updated(self, ovs_port):
self.topology.ovs_port_updated(ovs_port)
def ovs_port_deleted(self, ovs_port_id):
self.topology.ovs_port_deleted(ovs_port_id)
def ovs_sync_finished(self):
self.open_flow_app.notify_ovs_sync_finished()
def ovs_sync_started(self):
self.open_flow_app.notify_ovs_sync_started()
def get_nb_api(self):
return self.nb_api
def get_db_store(self):
return self.db_store
def get_openflow_app(self):
return self.open_flow_app
def get_chassis_name(self):
return self.chassis_name
# Run this application like this:
# python df_local_controller.py <chassis_unique_name>
# <local ip address> <southbound_db_ip_address>
def main():
chassis_name = socket.gethostname()
common_config.init(sys.argv[1:])
controller = DfLocalController(chassis_name)
controller.run()