From 337a1b029a1f144f577a78712413a4182dd525f8 Mon Sep 17 00:00:00 2001 From: Mark McLoughlin Date: Tue, 17 Nov 2015 10:17:44 +0000 Subject: [PATCH] servicegroup: remove the zookeeper driver We have had a "untested and risky to use in production" log warning message for this driver since Kilo, it is currently broken (see below), there are no obviously active users or contributors, and we are planning on enabling Zookeeper usage by adopting the tooz library. bug #1443910 shows that the driver fails to load because eventlet 0.17 broke evzookeeper by moving _SocketDuckForFd from eventlet.greenio to eventlet.greenio.py2 in commit 449c90a. The 0.17 release was in Feb, 2015. The evzookeeper library hasn't been updated since Sep 2012 and the sole maintainer is the original author of the zookeeper servicegroup driver. The tooz driver spec - Ibf70c2dbe308fc8e4dd277d8c75c4445b3dfce90 - proposes a formal deprecation period for the zk driver, during which existing zk driver users are encouraged to move to tooz. However, given the state of the zk driver, we must conclude that there are no existing users who need a graceful migration path. Removing the driver will avoid potential confusion for new users and simplify the path to adopting tooz. Closes-Bug: #1443910 Closes-Bug: #1414517 Closes-Bug: #1414536 Signed-off-by: Mark McLoughlin Change-Id: I2dba71e71b1ed7cf8476e8bfe9481e84be5df128 --- nova/api/opts.py | 1 - nova/exception.py | 5 - nova/opts.py | 2 - nova/servicegroup/api.py | 1 - nova/servicegroup/drivers/zk.py | 200 ------------------ .../tests/unit/servicegroup/test_zk_driver.py | 101 --------- ...group-driver-removed-c3bcaa6f9fe976ed.yaml | 15 ++ 7 files changed, 15 insertions(+), 310 deletions(-) delete mode 100644 nova/servicegroup/drivers/zk.py delete mode 100644 nova/tests/unit/servicegroup/test_zk_driver.py create mode 100644 releasenotes/notes/zookeeper-servicegroup-driver-removed-c3bcaa6f9fe976ed.yaml diff --git a/nova/api/opts.py b/nova/api/opts.py index 492b58637a1..8976afcbe2f 100644 --- a/nova/api/opts.py +++ b/nova/api/opts.py @@ -108,7 +108,6 @@ import nova.scheduler.weights.ram import nova.service import nova.servicegroup.api -import nova.servicegroup.drivers.zk import nova.spice import nova.utils import nova.vnc diff --git a/nova/exception.py b/nova/exception.py index 978747f1162..23c3c6eb7b4 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -1607,11 +1607,6 @@ class InstanceRecreateNotSupported(Invalid): msg_fmt = _('Instance recreate is not supported.') -class ServiceGroupUnavailable(NovaException): - msg_fmt = _("The service from servicegroup driver %(driver)s is " - "temporarily unavailable.") - - class DBNotAllowed(NovaException): msg_fmt = _('%(binary)s attempted direct database access which is ' 'not allowed by policy') diff --git a/nova/opts.py b/nova/opts.py index 3a974976975..d72798377d8 100644 --- a/nova/opts.py +++ b/nova/opts.py @@ -47,7 +47,6 @@ import nova.rdp import nova.service import nova.servicegroup.api -import nova.servicegroup.drivers.zk import nova.spice import nova.utils import nova.volume @@ -110,5 +109,4 @@ def list_opts(): [nova.consoleauth.rpcapi.rpcapi_cap_opt], )), ('workarounds', nova.utils.workarounds_opts), - ('zookeeper', nova.servicegroup.drivers.zk.zk_driver_opts) ] diff --git a/nova/servicegroup/api.py b/nova/servicegroup/api.py index 0197b736e17..2a09baa42f6 100644 --- a/nova/servicegroup/api.py +++ b/nova/servicegroup/api.py @@ -26,7 +26,6 @@ _driver_name_class_mapping = { 'db': 'nova.servicegroup.drivers.db.DbDriver', - 'zk': 'nova.servicegroup.drivers.zk.ZooKeeperDriver', 'mc': 'nova.servicegroup.drivers.mc.MemcachedDriver' } _default_driver = 'db' diff --git a/nova/servicegroup/drivers/zk.py b/nova/servicegroup/drivers/zk.py deleted file mode 100644 index 6962831ecbd..00000000000 --- a/nova/servicegroup/drivers/zk.py +++ /dev/null @@ -1,200 +0,0 @@ -# Copyright (c) AT&T 2012-2013 Yun Mao -# Copyright 2012 IBM Corp. -# -# 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 os - -import eventlet -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import importutils - -from nova import exception -from nova.i18n import _LE, _LW -from nova.servicegroup.drivers import base - -evzookeeper = importutils.try_import('evzookeeper') -membership = importutils.try_import('evzookeeper.membership') -zookeeper = importutils.try_import('zookeeper') - -zk_driver_opts = [ - cfg.StrOpt('address', - help='The ZooKeeper addresses for servicegroup service in the ' - 'format of host1:port,host2:port,host3:port'), - cfg.IntOpt('recv_timeout', - default=4000, - help='The recv_timeout parameter for the zk session'), - cfg.StrOpt('sg_prefix', - default="/servicegroups", - help='The prefix used in ZooKeeper to store ephemeral nodes'), - cfg.IntOpt('sg_retry_interval', - default=5, - help='Number of seconds to wait until retrying to join the ' - 'session'), - ] - -CONF = cfg.CONF -CONF.register_opts(zk_driver_opts, group="zookeeper") - -LOG = logging.getLogger(__name__) - - -class ZooKeeperDriver(base.Driver): - """ZooKeeper driver for the service group API.""" - - def __init__(self, *args, **kwargs): - """Create the zk session object.""" - if not all([evzookeeper, membership, zookeeper]): - raise ImportError('zookeeper module not found') - self._memberships = {} - self._monitors = {} - super(ZooKeeperDriver, self).__init__() - self._cached_session = None - - @property - def _session(self): - """Creates zookeeper session in lazy manner. - - Session is created in lazy manner to mitigate lock problem - in zookeeper. - - Lock happens when many processes try to use the same zk handle. - Lazy creation allows to deffer initialization of session until - is really required by worker (child process). - - :returns: ZKSession -- new or created earlier - """ - if self._cached_session is None: - self._cached_session = self._init_session() - return self._cached_session - - def _init_session(self): - """Initializes new session. - - Optionally creates required servicegroup prefix. - - :returns ZKSession - newly created session - """ - with open(os.devnull, "w") as null: - session = evzookeeper.ZKSession( - CONF.zookeeper.address, - recv_timeout=CONF.zookeeper.recv_timeout, - zklog_fd=null) - # Make sure the prefix exists - try: - session.create(CONF.zookeeper.sg_prefix, "", - acl=[evzookeeper.ZOO_OPEN_ACL_UNSAFE]) - except zookeeper.NodeExistsException: - pass - # Log a warning about quality for this driver. - LOG.warning(_LW('The ZooKeeper service group driver in Nova is not ' - 'tested by the OpenStack project and thus its quality ' - 'can not be ensured. This may change in the future, ' - 'but current deployers should be aware that the use ' - 'of it in production right now may be risky.')) - return session - - def join(self, member, group, service=None): - """Add a new member to a service group. - - :param member: the joined member ID/name - :param group: the group ID/name, of the joined member - :param service: a `nova.service.Service` object - """ - process_id = str(os.getpid()) - LOG.debug('ZooKeeperDriver: join new member %(id)s(%(pid)s) to the ' - '%(gr)s group, service=%(sr)s', - {'id': member, 'pid': process_id, - 'gr': group, 'sr': service}) - member = self._memberships.get((group, member), None) - if member is None: - # the first time to join. Generate a new object - path = "%s/%s/%s" % (CONF.zookeeper.sg_prefix, group, member) - try: - zk_member = membership.Membership(self._session, path, - process_id) - except RuntimeError: - LOG.exception(_LE("Unable to join. It is possible that either" - " another node exists with the same name, or" - " this node just restarted. We will try " - "again in a short while to make sure.")) - eventlet.sleep(CONF.zookeeper.sg_retry_interval) - zk_member = membership.Membership(self._session, path, member) - self._memberships[(group, member)] = zk_member - - def is_up(self, service_ref): - group_id = service_ref['topic'] - member_id = service_ref['host'] - all_members = self._get_all(group_id) - return member_id in all_members - - def _get_all(self, group_id): - """Return all members in a list, or a ServiceGroupUnavailable - exception. - """ - monitor = self._monitors.get(group_id, None) - if monitor is None: - path = "%s/%s" % (CONF.zookeeper.sg_prefix, group_id) - - with open(os.devnull, "w") as null: - local_session = evzookeeper.ZKSession( - CONF.zookeeper.address, - recv_timeout=CONF.zookeeper.recv_timeout, - zklog_fd=null) - - monitor = membership.MembershipMonitor(local_session, path) - self._monitors[group_id] = monitor - # Note(maoy): When initialized for the first time, it takes a - # while to retrieve all members from zookeeper. To prevent - # None to be returned, we sleep 5 sec max to wait for data to - # be ready. - timeout = 5 # seconds - interval = 0.1 - tries = int(timeout / interval) - for _retry in range(tries): - eventlet.sleep(interval) - all_members = monitor.get_all() - if all_members is not None: - # Stop the tries once the cache is populated - LOG.debug('got info about members in %r: %r', - path, ', '.join(all_members)) - break - else: - # if all_members, weren't populated - LOG.warning(_LW('Problem with acquiring the list of ' - 'children of %(path)r within a given ' - 'timeout=%(timeout)d seconds'), - path, timeout) - else: - all_members = monitor.get_all() - - if all_members is None: - raise exception.ServiceGroupUnavailable(driver="ZooKeeperDriver") - - def have_processes(member): - """Predicate that given member has processes (subnode exists).""" - value, stat = monitor.get_member_details(member) - # only check nodes that are created by Membership class - if value == 'ZKMembers': - num_children = stat['numChildren'] - return num_children > 0 - else: - # unknown type of node found - ignoring - return False - - # filter only this members that have processes running - all_members = filter(have_processes, all_members) - - return all_members diff --git a/nova/tests/unit/servicegroup/test_zk_driver.py b/nova/tests/unit/servicegroup/test_zk_driver.py deleted file mode 100644 index 1af71902d9c..00000000000 --- a/nova/tests/unit/servicegroup/test_zk_driver.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright (c) AT&T 2012-2013 Yun Mao -# Copyright 2012 IBM Corp. -# -# 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. - -"""Test the ZooKeeper driver for servicegroup. - -You need to install ZooKeeper locally and related dependencies -to run the test. It's unclear how to install python-zookeeper lib -in venv so you might have to run the test without it. - -To set up in Ubuntu 12.04: -$ sudo apt-get install zookeeper zookeeperd python-zookeeper -$ sudo pip install evzookeeper -$ nosetests nova.tests.unit.servicegroup.test_zk_driver -""" -import os - -import mock - -from nova import servicegroup -from nova.servicegroup.drivers import zk -from nova import test - - -class ZKServiceGroupTestCase(test.NoDBTestCase): - - def setUp(self): - super(ZKServiceGroupTestCase, self).setUp() - self.flags(servicegroup_driver='zk') - self.flags(address='localhost:2181', group="zookeeper") - try: - __import__('evzookeeper') - __import__('zookeeper') - except ImportError: - self.skipTest("Unable to test due to lack of ZooKeeper") - - # Need to do this here, as opposed to the setUp() method, otherwise - # the decorate will cause an import error... - @mock.patch('evzookeeper.ZKSession') - def _setup_sg_api(self, zk_sess_mock): - self.zk_sess = mock.MagicMock() - zk_sess_mock.return_value = self.zk_sess - self.flags(servicegroup_driver='zk') - self.flags(address='ignored', group="zookeeper") - self.servicegroup_api = servicegroup.API() - - def test_zookeeper_hierarchy_structure(self): - """Test that hierarchy created by join method contains process id.""" - from zookeeper import NoNodeException - self.servicegroup_api = servicegroup.API() - service_id = {'topic': 'unittest', 'host': 'serviceC'} - # use existing session object - session = self.servicegroup_api._driver._session - # prepare a path that contains process id - pid = os.getpid() - path = '/servicegroups/%s/%s/%s' % (service_id['topic'], - service_id['host'], - pid) - # assert that node doesn't exist yet - self.assertRaises(NoNodeException, session.get, path) - # join - self.servicegroup_api.join(service_id['host'], - service_id['topic'], - None) - # expected existing "process id" node - self.assertTrue(session.get(path)) - - def test_lazy_session(self): - """Session object (contains zk handle) should be created in - lazy manner, because handle cannot be shared by forked processes. - """ - # insied import because this test runs conditionaly (look at setUp) - import evzookeeper - driver = zk.ZooKeeperDriver() - # check that internal private attribute session is empty - self.assertIsNone(driver.__dict__['_ZooKeeperDriver__session']) - # after first use of property ... - driver._session - # check that internal private session attribute is ready - self.assertIsInstance(driver.__dict__['_ZooKeeperDriver__session'], - evzookeeper.ZKSession) - - @mock.patch('evzookeeper.membership.Membership') - def test_join(self, mem_mock): - self._setup_sg_api() - mem_mock.return_value = mock.sentinel.zk_mem - self.servicegroup_api.join('fake-host', 'fake-topic') - mem_mock.assert_called_once_with(self.zk_sess, - '/fake-topic', - 'fake-host') diff --git a/releasenotes/notes/zookeeper-servicegroup-driver-removed-c3bcaa6f9fe976ed.yaml b/releasenotes/notes/zookeeper-servicegroup-driver-removed-c3bcaa6f9fe976ed.yaml new file mode 100644 index 00000000000..5f5b6aa89e8 --- /dev/null +++ b/releasenotes/notes/zookeeper-servicegroup-driver-removed-c3bcaa6f9fe976ed.yaml @@ -0,0 +1,15 @@ +--- +upgrade: + - | + The Zookeeper Service Group driver has been removed. + + The driver has no known users and is not actively mantained. A warning log + message about the driver's state was added for the Kilo release. Also, + evzookeeper library that the driver depends on is unmaintained and + `incompatible with recent eventlet releases`_. + + A future release of Nova will `use the Tooz library to track + service liveliness`_, and Tooz supports Zookeeper. + + .. _`incompatible with recent eventlet releases`: https://bugs.launchpad.net/nova/+bug/1443910 + .. _`use the Tooz library to track service liveliness`: http://specs.openstack.org/openstack/nova-specs/specs/liberty/approved/service-group-using-tooz.html