Remove oslo-incubator fixture
Change-Id: I49249c5537c749bffed69875112ac80b6450ee7c
This commit is contained in:
parent
ca01f962c9
commit
27c66ec9ac
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
# The list of modules to copy from oslo-incubator.git
|
# The list of modules to copy from oslo-incubator.git
|
||||||
module = fileutils
|
module = fileutils
|
||||||
module = fixture
|
|
||||||
script = tools/run_cross_tests.sh
|
script = tools/run_cross_tests.sh
|
||||||
|
|
||||||
# The base module to hold the copy of openstack.common
|
# The base module to hold the copy of openstack.common
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
#
|
|
||||||
# Copyright 2013 Mirantis, Inc.
|
|
||||||
# Copyright 2013 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 fixtures
|
|
||||||
from oslo.config import cfg
|
|
||||||
import six
|
|
||||||
|
|
||||||
|
|
||||||
class Config(fixtures.Fixture):
|
|
||||||
"""Allows overriding configuration settings for the test.
|
|
||||||
|
|
||||||
`conf` will be reset on cleanup.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, conf=cfg.CONF):
|
|
||||||
self.conf = conf
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(Config, self).setUp()
|
|
||||||
# NOTE(morganfainberg): unregister must be added to cleanup before
|
|
||||||
# reset is because cleanup works in reverse order of registered items,
|
|
||||||
# and a reset must occur before unregistering options can occur.
|
|
||||||
self.addCleanup(self._unregister_config_opts)
|
|
||||||
self.addCleanup(self.conf.reset)
|
|
||||||
self._registered_config_opts = {}
|
|
||||||
|
|
||||||
def config(self, **kw):
|
|
||||||
"""Override configuration values.
|
|
||||||
|
|
||||||
The keyword arguments are the names of configuration options to
|
|
||||||
override and their values.
|
|
||||||
|
|
||||||
If a `group` argument is supplied, the overrides are applied to
|
|
||||||
the specified configuration option group, otherwise the overrides
|
|
||||||
are applied to the ``default`` group.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
group = kw.pop('group', None)
|
|
||||||
for k, v in six.iteritems(kw):
|
|
||||||
self.conf.set_override(k, v, group)
|
|
||||||
|
|
||||||
def _unregister_config_opts(self):
|
|
||||||
for group in self._registered_config_opts:
|
|
||||||
self.conf.unregister_opts(self._registered_config_opts[group],
|
|
||||||
group=group)
|
|
||||||
|
|
||||||
def register_opt(self, opt, group=None):
|
|
||||||
"""Register a single option for the test run.
|
|
||||||
|
|
||||||
Options registered in this manner will automatically be unregistered
|
|
||||||
during cleanup.
|
|
||||||
|
|
||||||
If a `group` argument is supplied, it will register the new option
|
|
||||||
to that group, otherwise the option is registered to the ``default``
|
|
||||||
group.
|
|
||||||
"""
|
|
||||||
self.conf.register_opt(opt, group=group)
|
|
||||||
self._registered_config_opts.setdefault(group, set()).add(opt)
|
|
||||||
|
|
||||||
def register_opts(self, opts, group=None):
|
|
||||||
"""Register multiple options for the test run.
|
|
||||||
|
|
||||||
This works in the same manner as register_opt() but takes a list of
|
|
||||||
options as the first argument. All arguments will be registered to the
|
|
||||||
same group if the ``group`` argument is supplied, otherwise all options
|
|
||||||
will be registered to the ``default`` group.
|
|
||||||
"""
|
|
||||||
for opt in opts:
|
|
||||||
self.register_opt(opt, group=group)
|
|
@ -1,51 +0,0 @@
|
|||||||
# Copyright 2011 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 fixtures
|
|
||||||
|
|
||||||
from oslo.concurrency.openstack.common import lockutils
|
|
||||||
|
|
||||||
|
|
||||||
class LockFixture(fixtures.Fixture):
|
|
||||||
"""External locking fixture.
|
|
||||||
|
|
||||||
This fixture is basically an alternative to the synchronized decorator with
|
|
||||||
the external flag so that tearDowns and addCleanups will be included in
|
|
||||||
the lock context for locking between tests. The fixture is recommended to
|
|
||||||
be the first line in a test method, like so::
|
|
||||||
|
|
||||||
def test_method(self):
|
|
||||||
self.useFixture(LockFixture)
|
|
||||||
...
|
|
||||||
|
|
||||||
or the first line in setUp if all the test methods in the class are
|
|
||||||
required to be serialized. Something like::
|
|
||||||
|
|
||||||
class TestCase(testtools.testcase):
|
|
||||||
def setUp(self):
|
|
||||||
self.useFixture(LockFixture)
|
|
||||||
super(TestCase, self).setUp()
|
|
||||||
...
|
|
||||||
|
|
||||||
This is because addCleanups are put on a LIFO queue that gets run after the
|
|
||||||
test method exits. (either by completing or raising an exception)
|
|
||||||
"""
|
|
||||||
def __init__(self, name, lock_file_prefix=None):
|
|
||||||
self.mgr = lockutils.lock(name, lock_file_prefix, True)
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(LockFixture, self).setUp()
|
|
||||||
self.addCleanup(self.mgr.__exit__, None, None, None)
|
|
||||||
self.lock = self.mgr.__enter__()
|
|
@ -1,34 +0,0 @@
|
|||||||
# 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 fixtures
|
|
||||||
|
|
||||||
|
|
||||||
def get_logging_handle_error_fixture():
|
|
||||||
"""returns a fixture to make logging raise formatting exceptions.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
self.useFixture(logging.get_logging_handle_error_fixture())
|
|
||||||
"""
|
|
||||||
return fixtures.MonkeyPatch('logging.Handler.handleError',
|
|
||||||
_handleError)
|
|
||||||
|
|
||||||
|
|
||||||
def _handleError(self, record):
|
|
||||||
"""Monkey patch for logging.Handler.handleError.
|
|
||||||
|
|
||||||
The default handleError just logs the error to stderr but we want
|
|
||||||
the option of actually raising an exception.
|
|
||||||
"""
|
|
||||||
raise
|
|
@ -1,62 +0,0 @@
|
|||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# DO NOT MODIFY THIS FILE
|
|
||||||
#
|
|
||||||
# This file is being graduated to the oslotest library. Please make all
|
|
||||||
# changes there, and only backport critical fixes here. - dhellmann
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
import fixtures
|
|
||||||
import mock
|
|
||||||
|
|
||||||
|
|
||||||
class PatchObject(fixtures.Fixture):
|
|
||||||
"""Deal with code around mock."""
|
|
||||||
|
|
||||||
def __init__(self, obj, attr, new=mock.DEFAULT, **kwargs):
|
|
||||||
self.obj = obj
|
|
||||||
self.attr = attr
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.new = new
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(PatchObject, self).setUp()
|
|
||||||
_p = mock.patch.object(self.obj, self.attr, self.new, **self.kwargs)
|
|
||||||
self.mock = _p.start()
|
|
||||||
self.addCleanup(_p.stop)
|
|
||||||
|
|
||||||
|
|
||||||
class Patch(fixtures.Fixture):
|
|
||||||
|
|
||||||
"""Deal with code around mock.patch."""
|
|
||||||
|
|
||||||
def __init__(self, obj, new=mock.DEFAULT, **kwargs):
|
|
||||||
self.obj = obj
|
|
||||||
self.kwargs = kwargs
|
|
||||||
self.new = new
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(Patch, self).setUp()
|
|
||||||
_p = mock.patch(self.obj, self.new, **self.kwargs)
|
|
||||||
self.mock = _p.start()
|
|
||||||
self.addCleanup(_p.stop)
|
|
@ -1,43 +0,0 @@
|
|||||||
# Copyright 2010 United States Government as represented by the
|
|
||||||
# Administrator of the National Aeronautics and Space Administration.
|
|
||||||
# Copyright 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
##############################################################################
|
|
||||||
##############################################################################
|
|
||||||
#
|
|
||||||
# DO NOT MODIFY THIS FILE
|
|
||||||
#
|
|
||||||
# This file is being graduated to the oslotest library. Please make all
|
|
||||||
# changes there, and only backport critical fixes here. - dhellmann
|
|
||||||
#
|
|
||||||
##############################################################################
|
|
||||||
##############################################################################
|
|
||||||
|
|
||||||
import fixtures
|
|
||||||
from six.moves import mox
|
|
||||||
|
|
||||||
|
|
||||||
class MoxStubout(fixtures.Fixture):
|
|
||||||
"""Deal with code around mox and stubout as a fixture."""
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(MoxStubout, self).setUp()
|
|
||||||
# emulate some of the mox stuff, we can't use the metaclass
|
|
||||||
# because it screws with our generators
|
|
||||||
self.mox = mox.Mox()
|
|
||||||
self.stubs = self.mox.stubs
|
|
||||||
self.addCleanup(self.mox.UnsetStubs)
|
|
||||||
self.addCleanup(self.mox.VerifyAll)
|
|
@ -1,379 +0,0 @@
|
|||||||
# Copyright 2011 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 contextlib
|
|
||||||
import errno
|
|
||||||
import functools
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
|
||||||
|
|
||||||
from oslo.concurrency.openstack.common import fileutils
|
|
||||||
from oslo.concurrency.openstack.common.gettextutils import _, _LE, _LI
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
util_opts = [
|
|
||||||
cfg.BoolOpt('disable_process_locking', default=False,
|
|
||||||
help='Enables or disables inter-process locks.'),
|
|
||||||
cfg.StrOpt('lock_path',
|
|
||||||
default=os.environ.get("OSLO.CONCURRENCY_LOCK_PATH"),
|
|
||||||
help='Directory to use for lock files.')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
CONF.register_opts(util_opts)
|
|
||||||
|
|
||||||
|
|
||||||
def set_defaults(lock_path):
|
|
||||||
cfg.set_defaults(util_opts, lock_path=lock_path)
|
|
||||||
|
|
||||||
|
|
||||||
class _FileLock(object):
|
|
||||||
"""Lock implementation which allows multiple locks, working around
|
|
||||||
issues like bugs.debian.org/cgi-bin/bugreport.cgi?bug=632857 and does
|
|
||||||
not require any cleanup. Since the lock is always held on a file
|
|
||||||
descriptor rather than outside of the process, the lock gets dropped
|
|
||||||
automatically if the process crashes, even if __exit__ is not executed.
|
|
||||||
|
|
||||||
There are no guarantees regarding usage by multiple green threads in a
|
|
||||||
single process here. This lock works only between processes. Exclusive
|
|
||||||
access between local threads should be achieved using the semaphores
|
|
||||||
in the @synchronized decorator.
|
|
||||||
|
|
||||||
Note these locks are released when the descriptor is closed, so it's not
|
|
||||||
safe to close the file descriptor while another green thread holds the
|
|
||||||
lock. Just opening and closing the lock file can break synchronisation,
|
|
||||||
so lock files must be accessed only using this abstraction.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, name):
|
|
||||||
self.lockfile = None
|
|
||||||
self.fname = name
|
|
||||||
|
|
||||||
def acquire(self):
|
|
||||||
basedir = os.path.dirname(self.fname)
|
|
||||||
|
|
||||||
if not os.path.exists(basedir):
|
|
||||||
fileutils.ensure_tree(basedir)
|
|
||||||
LOG.info(_LI('Created lock path: %s'), basedir)
|
|
||||||
|
|
||||||
self.lockfile = open(self.fname, 'w')
|
|
||||||
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# Using non-blocking locks since green threads are not
|
|
||||||
# patched to deal with blocking locking calls.
|
|
||||||
# Also upon reading the MSDN docs for locking(), it seems
|
|
||||||
# to have a laughable 10 attempts "blocking" mechanism.
|
|
||||||
self.trylock()
|
|
||||||
LOG.debug('Got file lock "%s"', self.fname)
|
|
||||||
return True
|
|
||||||
except IOError as e:
|
|
||||||
if e.errno in (errno.EACCES, errno.EAGAIN):
|
|
||||||
# external locks synchronise things like iptables
|
|
||||||
# updates - give it some time to prevent busy spinning
|
|
||||||
time.sleep(0.01)
|
|
||||||
else:
|
|
||||||
raise threading.ThreadError(_("Unable to acquire lock on"
|
|
||||||
" `%(filename)s` due to"
|
|
||||||
" %(exception)s") %
|
|
||||||
{
|
|
||||||
'filename': self.fname,
|
|
||||||
'exception': e,
|
|
||||||
})
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.acquire()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
try:
|
|
||||||
self.unlock()
|
|
||||||
self.lockfile.close()
|
|
||||||
LOG.debug('Released file lock "%s"', self.fname)
|
|
||||||
except IOError:
|
|
||||||
LOG.exception(_LE("Could not release the acquired lock `%s`"),
|
|
||||||
self.fname)
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
def exists(self):
|
|
||||||
return os.path.exists(self.fname)
|
|
||||||
|
|
||||||
def trylock(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
def unlock(self):
|
|
||||||
raise NotImplementedError()
|
|
||||||
|
|
||||||
|
|
||||||
class _WindowsLock(_FileLock):
|
|
||||||
def trylock(self):
|
|
||||||
msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_NBLCK, 1)
|
|
||||||
|
|
||||||
def unlock(self):
|
|
||||||
msvcrt.locking(self.lockfile.fileno(), msvcrt.LK_UNLCK, 1)
|
|
||||||
|
|
||||||
|
|
||||||
class _FcntlLock(_FileLock):
|
|
||||||
def trylock(self):
|
|
||||||
fcntl.lockf(self.lockfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
||||||
|
|
||||||
def unlock(self):
|
|
||||||
fcntl.lockf(self.lockfile, fcntl.LOCK_UN)
|
|
||||||
|
|
||||||
|
|
||||||
class _PosixLock(object):
|
|
||||||
def __init__(self, name):
|
|
||||||
# Hash the name because it's not valid to have POSIX semaphore
|
|
||||||
# names with things like / in them. Then use base64 to encode
|
|
||||||
# the digest() instead taking the hexdigest() because the
|
|
||||||
# result is shorter and most systems can't have shm sempahore
|
|
||||||
# names longer than 31 characters.
|
|
||||||
h = hashlib.sha1()
|
|
||||||
h.update(name.encode('ascii'))
|
|
||||||
self.name = str((b'/' + base64.urlsafe_b64encode(
|
|
||||||
h.digest())).decode('ascii'))
|
|
||||||
|
|
||||||
def acquire(self, timeout=None):
|
|
||||||
self.semaphore = posix_ipc.Semaphore(self.name,
|
|
||||||
flags=posix_ipc.O_CREAT,
|
|
||||||
initial_value=1)
|
|
||||||
self.semaphore.acquire(timeout)
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __enter__(self):
|
|
||||||
self.acquire()
|
|
||||||
return self
|
|
||||||
|
|
||||||
def release(self):
|
|
||||||
self.semaphore.release()
|
|
||||||
self.semaphore.close()
|
|
||||||
|
|
||||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
||||||
self.release()
|
|
||||||
|
|
||||||
def exists(self):
|
|
||||||
try:
|
|
||||||
semaphore = posix_ipc.Semaphore(self.name)
|
|
||||||
except posix_ipc.ExistentialError:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
semaphore.close()
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
import msvcrt
|
|
||||||
InterProcessLock = _WindowsLock
|
|
||||||
FileLock = _WindowsLock
|
|
||||||
else:
|
|
||||||
import base64
|
|
||||||
import fcntl
|
|
||||||
import hashlib
|
|
||||||
|
|
||||||
import posix_ipc
|
|
||||||
InterProcessLock = _PosixLock
|
|
||||||
FileLock = _FcntlLock
|
|
||||||
|
|
||||||
_semaphores = weakref.WeakValueDictionary()
|
|
||||||
_semaphores_lock = threading.Lock()
|
|
||||||
|
|
||||||
|
|
||||||
def _get_lock_path(name, lock_file_prefix, lock_path=None):
|
|
||||||
# NOTE(mikal): the lock name cannot contain directory
|
|
||||||
# separators
|
|
||||||
name = name.replace(os.sep, '_')
|
|
||||||
if lock_file_prefix:
|
|
||||||
sep = '' if lock_file_prefix.endswith('-') else '-'
|
|
||||||
name = '%s%s%s' % (lock_file_prefix, sep, name)
|
|
||||||
|
|
||||||
local_lock_path = lock_path or CONF.lock_path
|
|
||||||
|
|
||||||
if not local_lock_path:
|
|
||||||
# NOTE(bnemec): Create a fake lock path for posix locks so we don't
|
|
||||||
# unnecessarily raise the RequiredOptError below.
|
|
||||||
if InterProcessLock is not _PosixLock:
|
|
||||||
raise cfg.RequiredOptError('lock_path')
|
|
||||||
local_lock_path = 'posixlock:/'
|
|
||||||
|
|
||||||
return os.path.join(local_lock_path, name)
|
|
||||||
|
|
||||||
|
|
||||||
def external_lock(name, lock_file_prefix=None, lock_path=None):
|
|
||||||
LOG.debug('Attempting to grab external lock "%(lock)s"',
|
|
||||||
{'lock': name})
|
|
||||||
|
|
||||||
lock_file_path = _get_lock_path(name, lock_file_prefix, lock_path)
|
|
||||||
|
|
||||||
# NOTE(bnemec): If an explicit lock_path was passed to us then it
|
|
||||||
# means the caller is relying on file-based locking behavior, so
|
|
||||||
# we can't use posix locks for those calls.
|
|
||||||
if lock_path:
|
|
||||||
return FileLock(lock_file_path)
|
|
||||||
return InterProcessLock(lock_file_path)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_external_lock_file(name, lock_file_prefix=None):
|
|
||||||
"""Remove an external lock file when it's not used anymore
|
|
||||||
This will be helpful when we have a lot of lock files
|
|
||||||
"""
|
|
||||||
with internal_lock(name):
|
|
||||||
lock_file_path = _get_lock_path(name, lock_file_prefix)
|
|
||||||
try:
|
|
||||||
os.remove(lock_file_path)
|
|
||||||
except OSError:
|
|
||||||
LOG.info(_LI('Failed to remove file %(file)s'),
|
|
||||||
{'file': lock_file_path})
|
|
||||||
|
|
||||||
|
|
||||||
def internal_lock(name):
|
|
||||||
with _semaphores_lock:
|
|
||||||
try:
|
|
||||||
sem = _semaphores[name]
|
|
||||||
except KeyError:
|
|
||||||
sem = threading.Semaphore()
|
|
||||||
_semaphores[name] = sem
|
|
||||||
|
|
||||||
LOG.debug('Got semaphore "%(lock)s"', {'lock': name})
|
|
||||||
return sem
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def lock(name, lock_file_prefix=None, external=False, lock_path=None):
|
|
||||||
"""Context based lock
|
|
||||||
|
|
||||||
This function yields a `threading.Semaphore` instance (if we don't use
|
|
||||||
eventlet.monkey_patch(), else `semaphore.Semaphore`) unless external is
|
|
||||||
True, in which case, it'll yield an InterProcessLock instance.
|
|
||||||
|
|
||||||
:param lock_file_prefix: The lock_file_prefix argument is used to provide
|
|
||||||
lock files on disk with a meaningful prefix.
|
|
||||||
|
|
||||||
:param external: The external keyword argument denotes whether this lock
|
|
||||||
should work across multiple processes. This means that if two different
|
|
||||||
workers both run a method decorated with @synchronized('mylock',
|
|
||||||
external=True), only one of them will execute at a time.
|
|
||||||
"""
|
|
||||||
int_lock = internal_lock(name)
|
|
||||||
with int_lock:
|
|
||||||
if external and not CONF.disable_process_locking:
|
|
||||||
ext_lock = external_lock(name, lock_file_prefix, lock_path)
|
|
||||||
with ext_lock:
|
|
||||||
yield ext_lock
|
|
||||||
else:
|
|
||||||
yield int_lock
|
|
||||||
LOG.debug('Released semaphore "%(lock)s"', {'lock': name})
|
|
||||||
|
|
||||||
|
|
||||||
def synchronized(name, lock_file_prefix=None, external=False, lock_path=None):
|
|
||||||
"""Synchronization decorator.
|
|
||||||
|
|
||||||
Decorating a method like so::
|
|
||||||
|
|
||||||
@synchronized('mylock')
|
|
||||||
def foo(self, *args):
|
|
||||||
...
|
|
||||||
|
|
||||||
ensures that only one thread will execute the foo method at a time.
|
|
||||||
|
|
||||||
Different methods can share the same lock::
|
|
||||||
|
|
||||||
@synchronized('mylock')
|
|
||||||
def foo(self, *args):
|
|
||||||
...
|
|
||||||
|
|
||||||
@synchronized('mylock')
|
|
||||||
def bar(self, *args):
|
|
||||||
...
|
|
||||||
|
|
||||||
This way only one of either foo or bar can be executing at a time.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def wrap(f):
|
|
||||||
@functools.wraps(f)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
try:
|
|
||||||
with lock(name, lock_file_prefix, external, lock_path):
|
|
||||||
LOG.debug('Got semaphore / lock "%(function)s"',
|
|
||||||
{'function': f.__name__})
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
LOG.debug('Semaphore / lock released "%(function)s"',
|
|
||||||
{'function': f.__name__})
|
|
||||||
return inner
|
|
||||||
return wrap
|
|
||||||
|
|
||||||
|
|
||||||
def synchronized_with_prefix(lock_file_prefix):
|
|
||||||
"""Partial object generator for the synchronization decorator.
|
|
||||||
|
|
||||||
Redefine @synchronized in each project like so::
|
|
||||||
|
|
||||||
(in nova/utils.py)
|
|
||||||
from nova.openstack.common import lockutils
|
|
||||||
|
|
||||||
synchronized = lockutils.synchronized_with_prefix('nova-')
|
|
||||||
|
|
||||||
|
|
||||||
(in nova/foo.py)
|
|
||||||
from nova import utils
|
|
||||||
|
|
||||||
@utils.synchronized('mylock')
|
|
||||||
def bar(self, *args):
|
|
||||||
...
|
|
||||||
|
|
||||||
The lock_file_prefix argument is used to provide lock files on disk with a
|
|
||||||
meaningful prefix.
|
|
||||||
"""
|
|
||||||
|
|
||||||
return functools.partial(synchronized, lock_file_prefix=lock_file_prefix)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
|
||||||
"""Create a dir for locks and pass it to command from arguments
|
|
||||||
|
|
||||||
If you run this:
|
|
||||||
python -m openstack.common.lockutils python setup.py testr <etc>
|
|
||||||
|
|
||||||
a temporary directory will be created for all your locks and passed to all
|
|
||||||
your tests in an environment variable. The temporary dir will be deleted
|
|
||||||
afterwards and the return value will be preserved.
|
|
||||||
"""
|
|
||||||
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
os.environ["OSLO.CONCURRENCY_LOCK_PATH"] = lock_dir
|
|
||||||
try:
|
|
||||||
ret_val = subprocess.call(argv[1:])
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
return ret_val
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
sys.exit(main(sys.argv))
|
|
@ -29,7 +29,7 @@ from six import moves
|
|||||||
|
|
||||||
from oslo.concurrency.fixture import lockutils as fixtures
|
from oslo.concurrency.fixture import lockutils as fixtures
|
||||||
from oslo.concurrency import lockutils
|
from oslo.concurrency import lockutils
|
||||||
from oslo.concurrency.openstack.common.fixture import config
|
from oslo.config import fixture as config
|
||||||
|
|
||||||
|
|
||||||
class LockTestCase(test_base.BaseTestCase):
|
class LockTestCase(test_base.BaseTestCase):
|
||||||
|
@ -27,8 +27,8 @@ import mock
|
|||||||
from oslotest import base as test_base
|
from oslotest import base as test_base
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from oslo.concurrency.openstack.common.fixture import mockpatch
|
|
||||||
from oslo.concurrency import processutils
|
from oslo.concurrency import processutils
|
||||||
|
from oslotest import mockpatch
|
||||||
PROCESS_EXECUTION_ERROR_LOGGING_TEST = """#!/bin/bash
|
PROCESS_EXECUTION_ERROR_LOGGING_TEST = """#!/bin/bash
|
||||||
exit 41"""
|
exit 41"""
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user