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 <markmc@redhat.com>
Change-Id: I2dba71e71b1ed7cf8476e8bfe9481e84be5df128
This commit is contained in:
Mark McLoughlin 2015-11-17 10:17:44 +00:00 committed by Sean Dague
parent d51c5670d8
commit 337a1b029a
7 changed files with 15 additions and 310 deletions

View File

@ -108,7 +108,6 @@ import nova.scheduler.weights.metrics
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

View File

@ -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')

View File

@ -47,7 +47,6 @@ import nova.quota
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)
]

View File

@ -26,7 +26,6 @@ LOG = logging.getLogger(__name__)
_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'

View File

@ -1,200 +0,0 @@
# Copyright (c) AT&T 2012-2013 Yun Mao <yunmao@gmail.com>
# 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

View File

@ -1,101 +0,0 @@
# Copyright (c) AT&T 2012-2013 Yun Mao <yunmao@gmail.com>
# 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')

View File

@ -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