diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 22ce8122b..9da798e01 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -37,6 +37,10 @@ if is_service_enabled df-ramcloud ; then source $DEST/dragonflow/devstack/ramcloud_driver NB_DRIVER_CLASS="dragonflow.db.drivers.ramcloud_db_driver.RamCloudDbDriver" fi +if is_service_enabled df-zookeeper ; then + source $DEST/dragonflow/devstack/zookeeper_driver + NB_DRIVER_CLASS="dragonflow.db.drivers.zookeeper_db_driver.ZookeeperDbDriver" +fi # Dragonflow installation uses functions from these files source $TOP_DIR/lib/neutron_plugins/ovs_base diff --git a/devstack/zookeeper_driver b/devstack/zookeeper_driver new file mode 100644 index 000000000..cf88d77b6 --- /dev/null +++ b/devstack/zookeeper_driver @@ -0,0 +1,73 @@ +#!/bin/bash +# +# +# ``plugin.sh`` calls the following methods in the sourced driver: +# +# - nb_db_driver_install_server +# - nb_db_driver_install_client +# - nb_db_driver_start_server +# - nb_db_driver_stop_server +# - nb_db_driver_clean + +HOSTNAME=`hostname -f` + +ZOOKEEPER_IP=${REMOTE_DB_IP:-${HOST_IP}} +ZOOKEEPER_PORT=${REMOTE_DB_PORT:-2181} +ZOOKEEPER_DATA_DIR="/var/lib/zookeeper" +ZOOKEEPER_LOG_DIR="/var/log/zookeeper" +ZOOKEEPER_DIR="/etc/zookeeper" +ZOOKEEPER_CONF_DIR="${ZOOKEEPER_DIR}/conf" +ZOOKEEPER_CONF_FILE="${ZOOKEEPER_CONF_DIR}/zoo.cfg" +ZOOKEEPER_TABLE_NAMES=${ZOOKEEPER_TABLE_NAMES:-'secgroup','dragonflow','chassis','lswitch','lport','lrouter','tunnel_key'} + +function nb_db_driver_install_server { + if is_service_enabled df-zookeeper ; then + if is_ubuntu; then + echo "Installing Zookeeper server" + sudo mkdir -p $ZOOKEEPER_DATA_DIR + sudo mkdir -p $ZOOKEEPER_LOG_DIR + sudo mkdir -p $ZOOKEEPER_CONF_DIR + install_package "zookeeperd" + sudo service zookeeper stop || true + echo "Configuring Zookeeper" + sudo sed -i "/^dataDir=/c dataDir=${ZOOKEEPER_DATA_DIR}" $ZOOKEEPER_CONF_FILE + sudo sed -i "/^dataLogDir=/c dataLogDir=${ZOOKEEPER_LOG_DIR}" $ZOOKEEPER_CONF_FILE + sudo sed -i "/^server.1=/c server.1=${HOSTNAME}:2888:3888" $ZOOKEEPER_CONF_FILE + echo "1" | sudo tee $ZOOKEEPER_CONF_DIR/myid + else + die $LINENO "Other distributions are not supported" + fi + fi +} + +function nb_db_driver_install_client { + echo "Installing Kazoo client" + sudo pip install kazoo +} + +function nb_db_driver_status_server +{ + if is_service_enabled df-zookeeper ; then + TEMP_PIDS=`pgrep -f "zookeeper"` + if [ -z "$TEMP_PIDS" ]; then + return 1 + fi + fi + return 0 +} + +function nb_db_driver_start_server { + if is_service_enabled df-zookeeper ; then + if is_ubuntu; then + sudo service zookeeper restart + fi + fi +} + +function nb_db_driver_stop_server { + if is_service_enabled df-zookeeper ; then + if is_ubuntu; then + sudo service zookeeper stop || true + fi + fi +} diff --git a/dragonflow/db/db_api.py b/dragonflow/db/db_api.py index 98c02979e..c072124b5 100644 --- a/dragonflow/db/db_api.py +++ b/dragonflow/db/db_api.py @@ -99,6 +99,7 @@ class DbApi(object): :param table: table name :type table: string :returns: list of values + :raises: DragonflowException.DBKeyNotFound if key not found """ @abc.abstractmethod diff --git a/dragonflow/db/drivers/zookeeper_db_driver.py b/dragonflow/db/drivers/zookeeper_db_driver.py new file mode 100644 index 000000000..7255810f8 --- /dev/null +++ b/dragonflow/db/drivers/zookeeper_db_driver.py @@ -0,0 +1,170 @@ +# 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 kazoo +from kazoo.client import KazooClient +from kazoo.handlers.eventlet import SequentialEventletHandler +from kazoo.retry import KazooRetry + +from oslo_log import log +import six + +from dragonflow.common import exceptions as df_exceptions +from dragonflow.db import db_api + +LOG = log.getLogger(__name__) + +ROOT_NS = '/openstack' + +CLIENT_CONNECTION_RETRIES = -1 + + +def _parse_hosts(hosts): + if isinstance(hosts, six.string_types): + return hosts.strip() + if isinstance(hosts, (dict)): + host_ports = [] + for (k, v) in six.iteritems(hosts): + host_ports.append("%s:%s" % (k, v)) + hosts = host_ports + if isinstance(hosts, (list, set, tuple)): + return ",".join([str(h) for h in hosts]) + return hosts + + +class ZookeeperDbDriver(db_api.DbApi): + + def __init__(self): + super(ZookeeperDbDriver, self).__init__() + self.client = None + self.db_ip = None + self.db_port = None + self.config = None + + def initialize(self, db_ip, db_port, **args): + self.db_ip = db_ip + self.db_port = db_port + self.config = args['config'] + + def _lazy_initialize(self): + if not self.client: + hosts = _parse_hosts(self.config.remote_db_hosts) + _handler = SequentialEventletHandler() + _retry = KazooRetry(max_tries=CLIENT_CONNECTION_RETRIES, + delay=0.5, + backoff=2, + sleep_func=_handler.sleep_func) + self.client = KazooClient(hosts=hosts, + handler=_handler, + connection_retry=_retry) + self.client.start() + self.client.ensure_path(ROOT_NS) + + def support_publish_subscribe(self): + return False + + def _generate_path(self, table, key): + if not key: + return ROOT_NS + '/' + table + else: + return ROOT_NS + '/' + table + '/' + key + + def get_key(self, table, key): + path = self._generate_path(table, key) + try: + self._lazy_initialize() + ret = self.client.get(path)[0] + return ret + except kazoo.exceptions.NoNodeError: + raise df_exceptions.DBKeyNotFound(key=key) + + def set_key(self, table, key, value): + path = self._generate_path(table, key) + try: + self._lazy_initialize() + self.client.set(path, value) + except kazoo.exceptions.NoNodeError: + raise df_exceptions.DBKeyNotFound(key=key) + except kazoo.exceptions.ZookeeperError as e: + LOG.exception(e) + + def create_key(self, table, key, value): + path = self._generate_path(table, key) + try: + self._lazy_initialize() + self.client.create(path, value, makepath=True) + except kazoo.exceptions.ZookeeperError as e: + LOG.exception(e) + + def delete_key(self, table, key): + path = self._generate_path(table, key) + try: + self._lazy_initialize() + self.client.delete(path) + except kazoo.exceptions.NoNodeError: + raise df_exceptions.DBKeyNotFound(key=key) + + def get_all_entries(self, table): + res = [] + path = self._generate_path(table, None) + try: + self._lazy_initialize() + directory = self.client.get_children(path) + for key in directory: + res.append(self.get_key(table, key)) + except kazoo.exceptions.NoNodeError: + raise df_exceptions.DBKeyNotFound(key=table) + return res + + def get_all_keys(self, table): + path = self._generate_path(table, None) + try: + self._lazy_initialize() + return self.client.get_children(path) + except kazoo.exceptions.NoNodeError: + raise df_exceptions.DBKeyNotFound(key=table) + + def _allocate_unique_key(self): + path = self._generate_path('tunnel_key', 'key') + self.client.ensure_path(path) + + prev_value = 0 + version_exception = True + while version_exception: + try: + prev_value, stat = self.client.get(path) + prev_value = int(prev_value) + prev_version = stat.version + self.client.set(path, str(prev_value + 1), prev_version) + return prev_value + 1 + except kazoo.exceptions.BadVersionError: + version_exception = True + except kazoo.exceptions.NoNodeError: + self.client.create(path, "1", makepath=True) + return 1 + + def allocate_unique_key(self): + self._lazy_initialize() + return self._allocate_unique_key() + + def register_notification_callback(self, callback): + #NOTE(nick-ma-z): The pub-sub mechanism is not initially supported. + # The watcher function of Zookeeper only supports + # one-time trigger. You have to continuously register + # watchers for each children. Moreover, the delay + # between trigger and registration causes lose of + # events. The DataWatch of Kazoo is also not that + # stable and easy to use. Thanks to build-in pub-sub + # of dragonflow, we don't need to work hard on zk side. + # Please set 'use_df_pub_sub=True' in the configuration + # to enable the build-in pub-sub function. + return diff --git a/releasenotes/notes/support-zookeeper-48a082d4e63026df.yaml b/releasenotes/notes/support-zookeeper-48a082d4e63026df.yaml new file mode 100644 index 000000000..be742459f --- /dev/null +++ b/releasenotes/notes/support-zookeeper-48a082d4e63026df.yaml @@ -0,0 +1,5 @@ +--- +prelude: > + Support Zookeeper driver for Dragonflow +features: + - Support Zookeeper driver for Dragonflow