diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 8319ba23f..74f64c772 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -1,3 +1,4 @@ +#@IgnoreInspection BashAddShebang # dragonflow.sh - Devstack extras script to install Dragonflow # Enable DPDK for Open vSwitch user space datapath @@ -50,6 +51,10 @@ if [[ "$ENABLE_AGING_APP" == "True" ]]; then DEFAULT_APPS_LIST="aging,$DEFAULT_APPS_LIST" fi +if is_service_enabled df-skydive ; then + SKYDIVE_ENDPOINT=${SKYDIVE_ENDPOINT:-$SERVICE_HOST:8082} +fi + DF_APPS_LIST=${DF_APPS_LIST:-$DEFAULT_APPS_LIST} TUNNEL_TYPES=${TUNNEL_TYPE:-$DEFAULT_TUNNEL_TYPES} @@ -222,6 +227,19 @@ function init_neutron_sample_config { fi } +function configure_df_skydive { + iniset $DRAGONFLOW_CONF df_skydive analyzer_endpoint "$SKYDIVE_ENDPOINT" + if [[ -n "$DF_SKYDIVE_USER" ]]; then + iniset $DRAGONFLOW_CONF df_skydive user "$DF_SKYDIVE_USER" + fi + local DF_SKYDIVE_PASSWORD=${DF_SKYDIVE_PASSWORD:-$ADMIN_PASSWORD} + iniset $DRAGONFLOW_CONF df_skydive password "$DF_SKYDIVE_PASSWORD" + if [[ -n "$DF_SKYDIVE_UPDATE_INTERVAL" ]]; then + iniset $DRAGONFLOW_CONF df_skydive update_interval "$DF_SKYDIVE_UPDATE_INTERVAL" + fi +} + + function configure_df_plugin { echo "Configuring Neutron for Dragonflow" @@ -305,6 +323,10 @@ function configure_df_plugin { iniset $DRAGONFLOW_CONF df enable_selective_topology_distribution \ "$DF_SELECTIVE_TOPO_DIST" configure_df_metadata_service + + if is_service_enabled df-skydive ; then + configure_df_skydive + fi } function install_zeromq { @@ -492,6 +514,20 @@ function start_df_bgp_service { fi } +function start_df_skydive { + if is_service_enabled df-skydive ; then + echo "Starting Dragonflow skydive service" + run_process df-skydive "$DF_SKYDIVE_SERVICE --config-file $NEUTRON_CONF --config-file $DRAGONFLOW_CONF" + fi +} + +function stop_df_skydive { + if is_service_enabled df-skydive ; then + echo "Stopping Dragonflow skydive service" + stop_process df-skydive + fi +} + function setup_rootwrap_filters { if [[ "$DF_INSTALL_DEBUG_ROOTWRAP_CONF" == "True" ]]; then echo "Adding rootwrap filters" @@ -569,6 +605,7 @@ function handle_df_stack_post_install { start_df_bgp_service setup_rootwrap_filters create_tables_script + start_df_skydive } function handle_df_stack { @@ -580,6 +617,7 @@ function handle_df_stack { } function handle_df_unstack { + stop_df_skydive stop_df_bgp_service stop_df_metadata_agent stop_df diff --git a/devstack/settings b/devstack/settings index bce6a7a8e..0ec5a4e87 100644 --- a/devstack/settings +++ b/devstack/settings @@ -28,6 +28,10 @@ DF_BGP_SERVICE=${DF_BGP_SERVICE:-"$NEUTRON_BIN_DIR/df-bgp-service"} # This can be overridden in the localrc file, set df-bgp to enable DR_MODE=${DR_MODE:-no-bgp} +# df-skydive +DF_SKYDIVE_SERVICE=${DF_SKYDIVE_SERVICE:-"$NEUTRON_BIN_DIR/df-skydive-service"} + + DF_L2_RESPONDER=${DF_L2_RESPONDER:-'True'} DF_MONITOR_TABLE_POLL_TIME=${DF_MONITOR_TABLE_POLL_TIME:-30} diff --git a/doc/source/single-node-conf/etcd_skydive_local_controller.conf b/doc/source/single-node-conf/etcd_skydive_local_controller.conf new file mode 100644 index 000000000..f9368096f --- /dev/null +++ b/doc/source/single-node-conf/etcd_skydive_local_controller.conf @@ -0,0 +1,35 @@ +[[local|localrc]] + +# These MUST come before the 'enable_plugin dragonflow' as the dragonflow +# assumes the skydive analyzer is already installed +enable_plugin skydive https://github.com/skydive-project/skydive.git +enable_service skydive-agent skydive-analyzer + +DATABASE_PASSWORD=password +RABBIT_PASSWORD=password +SERVICE_PASSWORD=password +SERVICE_TOKEN=password +ADMIN_PASSWORD=password + +enable_plugin dragonflow https://git.openstack.org/openstack/dragonflow +enable_service df-etcd +enable_service etcd3 +enable_service df-controller +enable_service df-etcd-pubsub-service + +disable_service n-net +enable_service q-svc +enable_service df-l3-agent +disable_service heat +disable_service tempest + +enable_service df-skydive + +# Enable df-metadata (Dragonflow metadata service proxy) once nova is being used. +enable_service df-metadata + +# We have to disable the neutron L2 agent. DF does not use the L2 agent. +disable_service q-agt + +# We have to disable the neutron dhcp agent. DF does not use the dhcp agent. +disable_service q-dhcp diff --git a/dragonflow/cmd/df_skydive_service.py b/dragonflow/cmd/df_skydive_service.py index dedfe1348..6e046a47f 100644 --- a/dragonflow/cmd/df_skydive_service.py +++ b/dragonflow/cmd/df_skydive_service.py @@ -9,11 +9,10 @@ # 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 argparse -import signal import sys import uuid +import cotyledon from jsonmodels import fields from oslo_log import log from skydive.rest.client import RESTClient @@ -22,6 +21,7 @@ from skydive.websocket import client as skydive_client from dragonflow.common import utils as df_utils from dragonflow import conf as cfg from dragonflow.controller import df_config +from dragonflow.controller import service as df_service from dragonflow.db import api_nb from dragonflow.db import model_framework as mf from dragonflow.db import model_proxy @@ -32,66 +32,60 @@ LOG = log.getLogger(__name__) DRAGONFLOW_HOST_ID = 'dragonflow-skydive' DF_SKYDIVE_NAMESPACE_UUID = uuid.UUID('8a527b24-f0f5-4c1f-8f3d-6de400aa0145') -global_skydive_client = None - -class SkydiveClient(object): +class SkydiveClient(cotyledon.Service): """Main class that manages all the skydive operation.""" - def __init__(self, nb_api): - protocol = WSClientDragonflowProtocol(nb_api) + def __init__(self, worker_id, nb_api): + super(SkydiveClient, self).__init__(worker_id) + self.protocol = WSClientDragonflowProtocol(nb_api) self.websocket_client = skydive_client.WSClient( host_id=DRAGONFLOW_HOST_ID, endpoint='ws://{0}/ws/publisher'.format( cfg.CONF.df_skydive.analyzer_endpoint), - protocol=lambda: protocol + protocol=lambda: self.protocol, + username=cfg.CONF.df_skydive.user, + password=cfg.CONF.df_skydive.password ) - logged_in = self.websocket_client.login( - cfg.CONF.df_skydive.analyzer_endpoint, - cfg.CONF.df_skydive.user, - cfg.CONF.df_skydive.password) - if not logged_in: - # TODO(snapiri) raise an exception - LOG.error('Failed authenticating with SkyDive analyzer at %s', - cfg.CONF.df_skydive.analyzer_endpoint) - return - self.websocket_client.connect() - @staticmethod - def create(): - """Create a new SkydiveClient - :return a newly allocated SkydiveClient - :rtype SkydiveClient - """ - df_utils.config_parse() - nb_api = api_nb.NbApi.get_instance(False, True) - return SkydiveClient(nb_api) + try: + self.websocket_client.connect() + except RuntimeError: + LOG.error('Failed connecting with SkyDive analyzer at %s', + cfg.CONF.df_skydive.analyzer_endpoint) + raise def clear_dragonflow_items(self): """Delete all the items created by DragonFlow""" - restclient = RESTClient(cfg.CONF.df_skydive.analyzer_endpoint) - edges = restclient.lookup_edges("G.E().Has('source': 'dragonflow')") - for edge in edges: + restclient = RESTClient(cfg.CONF.df_skydive.analyzer_endpoint, + username=cfg.CONF.df_skydive.user, + password=cfg.CONF.df_skydive.password) + items = restclient.lookup_edges("G.E().Has('source', 'dragonflow')") + for edge in items: edge_del_msg = skydive_client.WSMessage( "Graph", skydive_client.EdgeDeletedMsgType, edge ) - self.sendWSMessage(edge_del_msg) - nodes = restclient.lookup_nodes("G.V().Has('source': 'dragonflow')") - for node in nodes: + self.protocol.sendWSMessage(edge_del_msg) + items = restclient.lookup_nodes("G.V().Has('source', 'dragonflow')") + for node in items: node_del_msg = skydive_client.WSMessage( "Graph", skydive_client.NodeDeletedMsgType, node ) - self.sendWSMessage(node_del_msg) + self.protocol.sendWSMessage(node_del_msg) - def start(self): + def run(self): """Start communication with the SkyDive analyzer This starts the operaiton of periodically querying the nb_api and sending all the objects to the SkyDive analyzer. """ + super(SkydiveClient, self).run() + # First clear all existing items + self.clear_dragonflow_items() + # Now start the loop self.websocket_client.start() def schedule_stop(self, wait_time): @@ -100,10 +94,11 @@ class SkydiveClient(object): :type wait_time: int """ loop = self.websocket_client.loop - loop.call_later(wait_time, self.stop) + loop.call_later(wait_time, self.terminate) - def stop(self): + def terminate(self): """Stop the process of sending the updates to the SkyDive analyzer""" + super(SkydiveClient, self).terminate() self.websocket_client.stop() @@ -280,31 +275,24 @@ class WSClientDragonflowProtocol(skydive_client.WSClientDebugProtocol): super(WSClientDragonflowProtocol, self).onClose(wasClean, code, reason) -def signal_handler(signal, frame): - if global_skydive_client: - LOG.info('Stopping SkyDive service') - global_skydive_client.stop() - - -def set_signal_handler(): - signal.signal(signal.SIGINT, signal_handler) - signal.signal(signal.SIGHUP, signal_handler) +def start(is_service): + """main method""" + df_config.init(sys.argv) + df_utils.config_parse() + nb_api = api_nb.NbApi.get_instance(False, True) + if is_service: + df_service.register_service('df-skydive-service', nb_api) + service_manager = cotyledon.ServiceManager() + service_manager.add(SkydiveClient, workers=1, args=(nb_api,)) + service_manager.run() def main(): - """main method""" - df_config.init(sys.argv) - parser = argparse.ArgumentParser(description='SkyDive integration service') - parser.add_argument('-t', '--runtime', type=int, - help='Total runtime (default 0 = infinite)') - args = parser.parse_args() - global global_skydive_client - global_skydive_client = SkydiveClient.create() - if args.runtime: - global_skydive_client.schedule_stop(args.runtime) - global_skydive_client.clear_dragonflow_items() - set_signal_handler() - global_skydive_client.start() + start(False) + + +def service_main(): + start(True) if __name__ == '__main__': diff --git a/requirements.txt b/requirements.txt index cb0edadd7..930409697 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,9 +26,11 @@ six>=1.10.0 # MIT httplib2>=0.9.1 # MIT WebOb>=1.7.1 # MIT jsonmodels>=2.1.3 # BSD License (3 clause) -skydive-client>=0.4.1 # Apache-2.0 +skydive-client>=0.4.4 # Apache-2.0 +cotyledon>=1.3.0 # Apache-2.0 # These repos are installed from git in OpenStack CI if the job # configures them as required-projects: neutron>=12.0.0 # Apache-2.0 networking-sfc>=6.0.0 # Apache-2.0 + diff --git a/setup.cfg b/setup.cfg index 85697b0f3..5b96aa9ff 100644 --- a/setup.cfg +++ b/setup.cfg @@ -63,6 +63,7 @@ console_scripts = df-l3-agent = dragonflow.cmd.eventlet.df_l3_agent:main df-metadata-service = dragonflow.cmd.eventlet.df_metadata_service:main df-bgp-service = dragonflow.cmd.eventlet.df_bgp_service:main + df-skydive-service= dragonflow.cmd.df_skydive_service:service_main dragonflow.pubsub_driver = zmq_pubsub_driver = dragonflow.db.pubsub_drivers.zmq_pubsub_driver:ZMQPubSub zmq_pubsub_multiproc_driver = dragonflow.db.pubsub_drivers.zmq_pubsub_driver:ZMQPubSubMultiproc