Retire repo
This repo was created by accident, use deb-python-oslo.concurrency instead. Needed-By: I1ac1a06931c8b6dd7c2e73620a0302c29e605f03 Change-Id: I81894aea69b9d09b0977039623c26781093a397a
This commit is contained in:
parent
2e8d5481b1
commit
95d65706ce
@ -1,8 +0,0 @@
|
|||||||
[run]
|
|
||||||
branch = True
|
|
||||||
source = oslo_concurrency
|
|
||||||
omit = oslo_concurrency/tests/*
|
|
||||||
|
|
||||||
[report]
|
|
||||||
ignore_errors = True
|
|
||||||
precision = 2
|
|
55
.gitignore
vendored
55
.gitignore
vendored
@ -1,55 +0,0 @@
|
|||||||
*.py[cod]
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Packages
|
|
||||||
*.egg
|
|
||||||
*.egg-info
|
|
||||||
dist
|
|
||||||
build
|
|
||||||
eggs
|
|
||||||
parts
|
|
||||||
bin
|
|
||||||
var
|
|
||||||
sdist
|
|
||||||
develop-eggs
|
|
||||||
.installed.cfg
|
|
||||||
lib
|
|
||||||
lib64
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
.coverage
|
|
||||||
cover
|
|
||||||
.tox
|
|
||||||
nosetests.xml
|
|
||||||
.testrepository
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Mr Developer
|
|
||||||
.mr.developer.cfg
|
|
||||||
.project
|
|
||||||
.pydevproject
|
|
||||||
|
|
||||||
# Complexity
|
|
||||||
output/*.html
|
|
||||||
output/*/index.html
|
|
||||||
|
|
||||||
# Sphinx
|
|
||||||
doc/build
|
|
||||||
|
|
||||||
# pbr generates these
|
|
||||||
AUTHORS
|
|
||||||
ChangeLog
|
|
||||||
|
|
||||||
# Editors
|
|
||||||
*~
|
|
||||||
.*.swp
|
|
||||||
|
|
||||||
# reno build
|
|
||||||
releasenotes/build
|
|
@ -1,4 +0,0 @@
|
|||||||
[gerrit]
|
|
||||||
host=review.openstack.org
|
|
||||||
port=29418
|
|
||||||
project=openstack/oslo.concurrency.git
|
|
3
.mailmap
3
.mailmap
@ -1,3 +0,0 @@
|
|||||||
# Format is:
|
|
||||||
# <preferred e-mail> <other e-mail 1>
|
|
||||||
# <preferred e-mail> <other e-mail 2>
|
|
@ -1,7 +0,0 @@
|
|||||||
[DEFAULT]
|
|
||||||
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
|
|
||||||
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
|
|
||||||
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
|
|
||||||
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
|
|
||||||
test_id_option=--load-list $IDFILE
|
|
||||||
test_list_option=--list
|
|
@ -1,16 +0,0 @@
|
|||||||
If you would like to contribute to the development of OpenStack,
|
|
||||||
you must follow the steps in this page:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html
|
|
||||||
|
|
||||||
Once those steps have been completed, changes to OpenStack
|
|
||||||
should be submitted for review via the Gerrit tool, following
|
|
||||||
the workflow documented at:
|
|
||||||
|
|
||||||
http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
|
||||||
|
|
||||||
Pull requests submitted through GitHub will be ignored.
|
|
||||||
|
|
||||||
Bugs should be filed on Launchpad, not GitHub:
|
|
||||||
|
|
||||||
https://bugs.launchpad.net/oslo.concurrency
|
|
@ -1,4 +0,0 @@
|
|||||||
oslo.concurrency Style Commandments
|
|
||||||
======================================================
|
|
||||||
|
|
||||||
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
|
|
175
LICENSE
175
LICENSE
@ -1,175 +0,0 @@
|
|||||||
|
|
||||||
Apache License
|
|
||||||
Version 2.0, January 2004
|
|
||||||
http://www.apache.org/licenses/
|
|
||||||
|
|
||||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
||||||
|
|
||||||
1. Definitions.
|
|
||||||
|
|
||||||
"License" shall mean the terms and conditions for use, reproduction,
|
|
||||||
and distribution as defined by Sections 1 through 9 of this document.
|
|
||||||
|
|
||||||
"Licensor" shall mean the copyright owner or entity authorized by
|
|
||||||
the copyright owner that is granting the License.
|
|
||||||
|
|
||||||
"Legal Entity" shall mean the union of the acting entity and all
|
|
||||||
other entities that control, are controlled by, or are under common
|
|
||||||
control with that entity. For the purposes of this definition,
|
|
||||||
"control" means (i) the power, direct or indirect, to cause the
|
|
||||||
direction or management of such entity, whether by contract or
|
|
||||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
||||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
||||||
|
|
||||||
"You" (or "Your") shall mean an individual or Legal Entity
|
|
||||||
exercising permissions granted by this License.
|
|
||||||
|
|
||||||
"Source" form shall mean the preferred form for making modifications,
|
|
||||||
including but not limited to software source code, documentation
|
|
||||||
source, and configuration files.
|
|
||||||
|
|
||||||
"Object" form shall mean any form resulting from mechanical
|
|
||||||
transformation or translation of a Source form, including but
|
|
||||||
not limited to compiled object code, generated documentation,
|
|
||||||
and conversions to other media types.
|
|
||||||
|
|
||||||
"Work" shall mean the work of authorship, whether in Source or
|
|
||||||
Object form, made available under the License, as indicated by a
|
|
||||||
copyright notice that is included in or attached to the work
|
|
||||||
(an example is provided in the Appendix below).
|
|
||||||
|
|
||||||
"Derivative Works" shall mean any work, whether in Source or Object
|
|
||||||
form, that is based on (or derived from) the Work and for which the
|
|
||||||
editorial revisions, annotations, elaborations, or other modifications
|
|
||||||
represent, as a whole, an original work of authorship. For the purposes
|
|
||||||
of this License, Derivative Works shall not include works that remain
|
|
||||||
separable from, or merely link (or bind by name) to the interfaces of,
|
|
||||||
the Work and Derivative Works thereof.
|
|
||||||
|
|
||||||
"Contribution" shall mean any work of authorship, including
|
|
||||||
the original version of the Work and any modifications or additions
|
|
||||||
to that Work or Derivative Works thereof, that is intentionally
|
|
||||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
||||||
or by an individual or Legal Entity authorized to submit on behalf of
|
|
||||||
the copyright owner. For the purposes of this definition, "submitted"
|
|
||||||
means any form of electronic, verbal, or written communication sent
|
|
||||||
to the Licensor or its representatives, including but not limited to
|
|
||||||
communication on electronic mailing lists, source code control systems,
|
|
||||||
and issue tracking systems that are managed by, or on behalf of, the
|
|
||||||
Licensor for the purpose of discussing and improving the Work, but
|
|
||||||
excluding communication that is conspicuously marked or otherwise
|
|
||||||
designated in writing by the copyright owner as "Not a Contribution."
|
|
||||||
|
|
||||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
||||||
on behalf of whom a Contribution has been received by Licensor and
|
|
||||||
subsequently incorporated within the Work.
|
|
||||||
|
|
||||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
copyright license to reproduce, prepare Derivative Works of,
|
|
||||||
publicly display, publicly perform, sublicense, and distribute the
|
|
||||||
Work and such Derivative Works in Source or Object form.
|
|
||||||
|
|
||||||
3. Grant of Patent License. Subject to the terms and conditions of
|
|
||||||
this License, each Contributor hereby grants to You a perpetual,
|
|
||||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
||||||
(except as stated in this section) patent license to make, have made,
|
|
||||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
||||||
where such license applies only to those patent claims licensable
|
|
||||||
by such Contributor that are necessarily infringed by their
|
|
||||||
Contribution(s) alone or by combination of their Contribution(s)
|
|
||||||
with the Work to which such Contribution(s) was submitted. If You
|
|
||||||
institute patent litigation against any entity (including a
|
|
||||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
||||||
or a Contribution incorporated within the Work constitutes direct
|
|
||||||
or contributory patent infringement, then any patent licenses
|
|
||||||
granted to You under this License for that Work shall terminate
|
|
||||||
as of the date such litigation is filed.
|
|
||||||
|
|
||||||
4. Redistribution. You may reproduce and distribute copies of the
|
|
||||||
Work or Derivative Works thereof in any medium, with or without
|
|
||||||
modifications, and in Source or Object form, provided that You
|
|
||||||
meet the following conditions:
|
|
||||||
|
|
||||||
(a) You must give any other recipients of the Work or
|
|
||||||
Derivative Works a copy of this License; and
|
|
||||||
|
|
||||||
(b) You must cause any modified files to carry prominent notices
|
|
||||||
stating that You changed the files; and
|
|
||||||
|
|
||||||
(c) You must retain, in the Source form of any Derivative Works
|
|
||||||
that You distribute, all copyright, patent, trademark, and
|
|
||||||
attribution notices from the Source form of the Work,
|
|
||||||
excluding those notices that do not pertain to any part of
|
|
||||||
the Derivative Works; and
|
|
||||||
|
|
||||||
(d) If the Work includes a "NOTICE" text file as part of its
|
|
||||||
distribution, then any Derivative Works that You distribute must
|
|
||||||
include a readable copy of the attribution notices contained
|
|
||||||
within such NOTICE file, excluding those notices that do not
|
|
||||||
pertain to any part of the Derivative Works, in at least one
|
|
||||||
of the following places: within a NOTICE text file distributed
|
|
||||||
as part of the Derivative Works; within the Source form or
|
|
||||||
documentation, if provided along with the Derivative Works; or,
|
|
||||||
within a display generated by the Derivative Works, if and
|
|
||||||
wherever such third-party notices normally appear. The contents
|
|
||||||
of the NOTICE file are for informational purposes only and
|
|
||||||
do not modify the License. You may add Your own attribution
|
|
||||||
notices within Derivative Works that You distribute, alongside
|
|
||||||
or as an addendum to the NOTICE text from the Work, provided
|
|
||||||
that such additional attribution notices cannot be construed
|
|
||||||
as modifying the License.
|
|
||||||
|
|
||||||
You may add Your own copyright statement to Your modifications and
|
|
||||||
may provide additional or different license terms and conditions
|
|
||||||
for use, reproduction, or distribution of Your modifications, or
|
|
||||||
for any such Derivative Works as a whole, provided Your use,
|
|
||||||
reproduction, and distribution of the Work otherwise complies with
|
|
||||||
the conditions stated in this License.
|
|
||||||
|
|
||||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
||||||
any Contribution intentionally submitted for inclusion in the Work
|
|
||||||
by You to the Licensor shall be under the terms and conditions of
|
|
||||||
this License, without any additional terms or conditions.
|
|
||||||
Notwithstanding the above, nothing herein shall supersede or modify
|
|
||||||
the terms of any separate license agreement you may have executed
|
|
||||||
with Licensor regarding such Contributions.
|
|
||||||
|
|
||||||
6. Trademarks. This License does not grant permission to use the trade
|
|
||||||
names, trademarks, service marks, or product names of the Licensor,
|
|
||||||
except as required for reasonable and customary use in describing the
|
|
||||||
origin of the Work and reproducing the content of the NOTICE file.
|
|
||||||
|
|
||||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
||||||
agreed to in writing, Licensor provides the Work (and each
|
|
||||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
||||||
implied, including, without limitation, any warranties or conditions
|
|
||||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
||||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
||||||
appropriateness of using or redistributing the Work and assume any
|
|
||||||
risks associated with Your exercise of permissions under this License.
|
|
||||||
|
|
||||||
8. Limitation of Liability. In no event and under no legal theory,
|
|
||||||
whether in tort (including negligence), contract, or otherwise,
|
|
||||||
unless required by applicable law (such as deliberate and grossly
|
|
||||||
negligent acts) or agreed to in writing, shall any Contributor be
|
|
||||||
liable to You for damages, including any direct, indirect, special,
|
|
||||||
incidental, or consequential damages of any character arising as a
|
|
||||||
result of this License or out of the use or inability to use the
|
|
||||||
Work (including but not limited to damages for loss of goodwill,
|
|
||||||
work stoppage, computer failure or malfunction, or any and all
|
|
||||||
other commercial damages or losses), even if such Contributor
|
|
||||||
has been advised of the possibility of such damages.
|
|
||||||
|
|
||||||
9. Accepting Warranty or Additional Liability. While redistributing
|
|
||||||
the Work or Derivative Works thereof, You may choose to offer,
|
|
||||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
||||||
or other liability obligations and/or rights consistent with this
|
|
||||||
License. However, in accepting such obligations, You may act only
|
|
||||||
on Your own behalf and on Your sole responsibility, not on behalf
|
|
||||||
of any other Contributor, and only if You agree to indemnify,
|
|
||||||
defend, and hold each Contributor harmless for any liability
|
|
||||||
incurred by, or claims asserted against, such Contributor by reason
|
|
||||||
of your accepting any such warranty or additional liability.
|
|
20
README.rst
20
README.rst
@ -1,20 +0,0 @@
|
|||||||
==================
|
|
||||||
oslo.concurrency
|
|
||||||
==================
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/v/oslo.concurrency.svg
|
|
||||||
:target: https://pypi.python.org/pypi/oslo.concurrency/
|
|
||||||
:alt: Latest Version
|
|
||||||
|
|
||||||
.. image:: https://img.shields.io/pypi/dm/oslo.concurrency.svg
|
|
||||||
:target: https://pypi.python.org/pypi/oslo.concurrency/
|
|
||||||
:alt: Downloads
|
|
||||||
|
|
||||||
The oslo.concurrency library has utilities for safely running multi-thread,
|
|
||||||
multi-process applications using locking mechanisms and for running
|
|
||||||
external processes.
|
|
||||||
|
|
||||||
* Free software: Apache license
|
|
||||||
* Documentation: http://docs.openstack.org/developer/oslo.concurrency
|
|
||||||
* Source: http://git.openstack.org/cgit/openstack/oslo.concurrency
|
|
||||||
* Bugs: http://bugs.launchpad.net/oslo.concurrency
|
|
13
README.txt
Normal file
13
README.txt
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
This project is no longer maintained.
|
||||||
|
|
||||||
|
The contents of this repository are still available in the Git
|
||||||
|
source code management system. To see the contents of this
|
||||||
|
repository before it reached its end of life, please check out the
|
||||||
|
previous commit with "git checkout HEAD^1".
|
||||||
|
|
||||||
|
Use instead the project deb-python-oslo.concurrency at
|
||||||
|
http://git.openstack.org/cgit/openstack/deb-python-oslo.concurrency .
|
||||||
|
|
||||||
|
For any further questions, please email
|
||||||
|
openstack-dev@lists.openstack.org or join #openstack-dev on
|
||||||
|
Freenode.
|
@ -1,8 +0,0 @@
|
|||||||
===========================================
|
|
||||||
:mod:`oslo_concurrency.fixture.lockutils`
|
|
||||||
===========================================
|
|
||||||
|
|
||||||
.. automodule:: oslo_concurrency.fixture.lockutils
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,8 +0,0 @@
|
|||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
fixture.lockutils
|
|
||||||
lockutils
|
|
||||||
opts
|
|
||||||
processutils
|
|
||||||
watchdog
|
|
@ -1,8 +0,0 @@
|
|||||||
===================================
|
|
||||||
:mod:`oslo_concurrency.lockutils`
|
|
||||||
===================================
|
|
||||||
|
|
||||||
.. automodule:: oslo_concurrency.lockutils
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,8 +0,0 @@
|
|||||||
==============================
|
|
||||||
:mod:`oslo_concurrency.opts`
|
|
||||||
==============================
|
|
||||||
|
|
||||||
.. automodule:: oslo_concurrency.opts
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,8 +0,0 @@
|
|||||||
======================================
|
|
||||||
:mod:`oslo_concurrency.processutils`
|
|
||||||
======================================
|
|
||||||
|
|
||||||
.. automodule:: oslo_concurrency.processutils
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,8 +0,0 @@
|
|||||||
==================================
|
|
||||||
:mod:`oslo_concurrency.watchdog`
|
|
||||||
==================================
|
|
||||||
|
|
||||||
.. automodule:: oslo_concurrency.watchdog
|
|
||||||
:members:
|
|
||||||
:undoc-members:
|
|
||||||
:show-inheritance:
|
|
@ -1,80 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# 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 sys
|
|
||||||
|
|
||||||
|
|
||||||
sys.path.insert(0, os.path.abspath('../..'))
|
|
||||||
# -- General configuration ----------------------------------------------------
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
|
|
||||||
extensions = [
|
|
||||||
'sphinx.ext.autodoc',
|
|
||||||
'oslosphinx',
|
|
||||||
'oslo_config.sphinxext',
|
|
||||||
]
|
|
||||||
|
|
||||||
# autodoc generation is a bit aggressive and a nuisance when doing heavy
|
|
||||||
# text edit cycles.
|
|
||||||
# execute "export SPHINX_DEBUG=1" in your terminal to disable
|
|
||||||
|
|
||||||
# A list of glob-style patterns that should be excluded when looking for source
|
|
||||||
# files.
|
|
||||||
exclude_patterns = []
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'oslo.concurrency'
|
|
||||||
copyright = u'2014, OpenStack Foundation'
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
add_module_names = True
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# -- Options for HTML output --------------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. Major themes that come with
|
|
||||||
# Sphinx are currently 'default' and 'sphinxdoc'.
|
|
||||||
# html_theme_path = ["."]
|
|
||||||
# html_theme = '_theme'
|
|
||||||
# html_static_path = ['static']
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = '%sdoc' % project
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title, author, documentclass
|
|
||||||
# [howto/manual]).
|
|
||||||
latex_documents = [
|
|
||||||
('index',
|
|
||||||
'%s.tex' % project,
|
|
||||||
u'%s Documentation' % project,
|
|
||||||
u'OpenStack Foundation', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Example configuration for intersphinx: refer to the Python standard library.
|
|
||||||
# intersphinx_mapping = {'http://docs.python.org/': None}
|
|
@ -1,5 +0,0 @@
|
|||||||
==============
|
|
||||||
Contributing
|
|
||||||
==============
|
|
||||||
|
|
||||||
.. include:: ../../CONTRIBUTING.rst
|
|
@ -1 +0,0 @@
|
|||||||
.. include:: ../../ChangeLog
|
|
@ -1,40 +0,0 @@
|
|||||||
============================================
|
|
||||||
Welcome to oslo.concurrency's documentation!
|
|
||||||
============================================
|
|
||||||
|
|
||||||
The `oslo`_ concurrency library has utilities for safely running multi-thread,
|
|
||||||
multi-process applications using locking mechanisms and for running
|
|
||||||
external processes.
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
installation
|
|
||||||
usage
|
|
||||||
opts
|
|
||||||
contributing
|
|
||||||
|
|
||||||
API Documentation
|
|
||||||
=================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
api/index
|
|
||||||
|
|
||||||
Release Notes
|
|
||||||
=============
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
history
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
|
||||||
.. _oslo: https://wiki.openstack.org/wiki/Oslo
|
|
@ -1,12 +0,0 @@
|
|||||||
============
|
|
||||||
Installation
|
|
||||||
============
|
|
||||||
|
|
||||||
At the command line::
|
|
||||||
|
|
||||||
$ pip install oslo.concurrency
|
|
||||||
|
|
||||||
Or, if you have virtualenvwrapper installed::
|
|
||||||
|
|
||||||
$ mkvirtualenv oslo.concurrency
|
|
||||||
$ pip install oslo.concurrency
|
|
@ -1,8 +0,0 @@
|
|||||||
=======================
|
|
||||||
Configuration Options
|
|
||||||
=======================
|
|
||||||
|
|
||||||
oslo.concurrency uses oslo.config to define and manage configuration options
|
|
||||||
to allow the deployer to control how an application uses this library.
|
|
||||||
|
|
||||||
.. show-options:: oslo.concurrency
|
|
@ -1,74 +0,0 @@
|
|||||||
=======
|
|
||||||
Usage
|
|
||||||
=======
|
|
||||||
|
|
||||||
To use oslo.concurrency in a project, import the relevant module. For
|
|
||||||
example::
|
|
||||||
|
|
||||||
from oslo_concurrency import lockutils
|
|
||||||
from oslo_concurrency import processutils
|
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
|
|
||||||
* :doc:`API Documentation <api/index>`
|
|
||||||
|
|
||||||
Locking a function (local to a process)
|
|
||||||
=======================================
|
|
||||||
|
|
||||||
To ensure that a function (which is not thread safe) is only used in
|
|
||||||
a thread safe manner (typically such type of function should be refactored
|
|
||||||
to avoid this problem but if not then the following can help)::
|
|
||||||
|
|
||||||
@lockutils.synchronized('not_thread_safe')
|
|
||||||
def not_thread_safe():
|
|
||||||
pass
|
|
||||||
|
|
||||||
Once decorated later callers of this function will be able to call into
|
|
||||||
this method and the contract that two threads will **not** enter this
|
|
||||||
function at the same time will be upheld. Make sure that the names of the
|
|
||||||
locks used are carefully chosen (typically by namespacing them to your
|
|
||||||
app so that other apps will not chose the same names).
|
|
||||||
|
|
||||||
Locking a function (local to a process as well as across process)
|
|
||||||
=================================================================
|
|
||||||
|
|
||||||
To ensure that a function (which is not thread safe **or** multi-process
|
|
||||||
safe) is only used in a safe manner (typically such type of function should
|
|
||||||
be refactored to avoid this problem but if not then the following can help)::
|
|
||||||
|
|
||||||
@lockutils.synchronized('not_thread_process_safe', external=True)
|
|
||||||
def not_thread_process_safe():
|
|
||||||
pass
|
|
||||||
|
|
||||||
Once decorated later callers of this function will be able to call into
|
|
||||||
this method and the contract that two threads (or any two processes)
|
|
||||||
will **not** enter this function at the same time will be upheld. Make
|
|
||||||
sure that the names of the locks used are carefully chosen (typically by
|
|
||||||
namespacing them to your app so that other apps will not chose the same
|
|
||||||
names).
|
|
||||||
|
|
||||||
Common ways to prefix/namespace the synchronized decorator
|
|
||||||
==========================================================
|
|
||||||
|
|
||||||
Since it is **highly** recommended to prefix (or namespace) the usage
|
|
||||||
of the synchronized there are a few helpers that can make this much easier
|
|
||||||
to achieve.
|
|
||||||
|
|
||||||
An example is::
|
|
||||||
|
|
||||||
myapp_synchronized = lockutils.synchronized_with_prefix("myapp")
|
|
||||||
|
|
||||||
Then further usage of the ``lockutils.synchronized`` would instead now use
|
|
||||||
this decorator created above instead of using ``lockutils.synchronized``
|
|
||||||
directly.
|
|
||||||
|
|
||||||
Command Line Wrapper
|
|
||||||
====================
|
|
||||||
|
|
||||||
``oslo.concurrency`` includes a command line tool for use in test jobs
|
|
||||||
that need the environment variable :envvar:`OSLO_LOCK_PATH` set. To
|
|
||||||
use it, prefix the command to be run with
|
|
||||||
:command:`lockutils-wrapper`. For example::
|
|
||||||
|
|
||||||
$ lockutils-wrapper env | grep OSLO_LOCK_PATH
|
|
||||||
OSLO_LOCK_PATH=/tmp/tmpbFHK45
|
|
@ -1,32 +0,0 @@
|
|||||||
# Copyright 2014 Mirantis Inc.
|
|
||||||
#
|
|
||||||
# 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 oslo_i18n
|
|
||||||
|
|
||||||
_translators = oslo_i18n.TranslatorFactory(domain='oslo_concurrency')
|
|
||||||
|
|
||||||
# The primary translation function using the well-known name "_"
|
|
||||||
_ = _translators.primary
|
|
||||||
|
|
||||||
# Translators for log levels.
|
|
||||||
#
|
|
||||||
# The abbreviated names are meant to reflect the usual use of a short
|
|
||||||
# name like '_'. The "L" is for "log" and the other letter comes from
|
|
||||||
# the level.
|
|
||||||
_LI = _translators.log_info
|
|
||||||
_LW = _translators.log_warning
|
|
||||||
_LE = _translators.log_error
|
|
||||||
_LC = _translators.log_critical
|
|
@ -1,78 +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_config import fixture as config
|
|
||||||
|
|
||||||
from oslo_concurrency 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('lock_name'))
|
|
||||||
...
|
|
||||||
|
|
||||||
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('lock_name'))
|
|
||||||
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__()
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalLockFixture(fixtures.Fixture):
|
|
||||||
"""Configure lock_path so external locks can be used in unit tests.
|
|
||||||
|
|
||||||
Creates a temporary directory to hold file locks and sets the oslo.config
|
|
||||||
lock_path opt to use it. This can be used to enable external locking
|
|
||||||
on a per-test basis, rather than globally with the OSLO_LOCK_PATH
|
|
||||||
environment variable.
|
|
||||||
|
|
||||||
Example::
|
|
||||||
|
|
||||||
def test_method(self):
|
|
||||||
self.useFixture(ExternalLockFixture())
|
|
||||||
something_that_needs_external_locks()
|
|
||||||
|
|
||||||
Alternatively, the useFixture call could be placed in a test class's
|
|
||||||
setUp method to provide this functionality to all tests in the class.
|
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
|
||||||
"""
|
|
||||||
def setUp(self):
|
|
||||||
super(ExternalLockFixture, self).setUp()
|
|
||||||
temp_dir = self.useFixture(fixtures.TempDir())
|
|
||||||
conf = self.useFixture(config.Config(lockutils.CONF)).config
|
|
||||||
conf(lock_path=temp_dir.path, group='oslo_concurrency')
|
|
@ -1,19 +0,0 @@
|
|||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.9.1.dev2\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-06-04 05:27+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-06-02 07:05+0000\n"
|
|
||||||
"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
|
|
||||||
"Language-Team: German\n"
|
|
||||||
"Language: de\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Failed to remove file %(file)s"
|
|
||||||
msgstr "Fehler beim Entfernen der Datei %(file)s"
|
|
@ -1,101 +0,0 @@
|
|||||||
# Translations template for oslo.concurrency.
|
|
||||||
# Copyright (C) 2015 ORGANIZATION
|
|
||||||
# This file is distributed under the same license as the oslo.concurrency
|
|
||||||
# project.
|
|
||||||
#
|
|
||||||
# Translators:
|
|
||||||
# Christian Berendt <berendt@b1-systems.de>, 2014
|
|
||||||
# Ettore Atalan <atalanttore@googlemail.com>, 2014
|
|
||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.9.1.dev3\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-06-07 17:48+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-06-08 06:36+0000\n"
|
|
||||||
"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
|
|
||||||
"Language: de\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Generated-By: Babel 2.0\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Language-Team: German\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"exit code: %(code)r\n"
|
|
||||||
"stdout: %(stdout)r\n"
|
|
||||||
"stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"Kommando: %(cmd)r\n"
|
|
||||||
"Abschlusscode: %(code)r\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Command: %(cmd)s\n"
|
|
||||||
"Exit code: %(exit_code)s\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Befehl: %(cmd)s.\n"
|
|
||||||
"Beendigungscode: %(exit_code)s.\n"
|
|
||||||
"Standardausgabe: %(stdout)r\n"
|
|
||||||
"Standardfehler: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Not Retrying."
|
|
||||||
msgstr "%r fehlgeschlagen. Wird nicht wiederholt."
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Retrying."
|
|
||||||
msgstr "%r fehlgeschlagen. Neuversuch."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Calling lockutils directly is no longer supported. Please use the lockutils-"
|
|
||||||
"wrapper console script instead."
|
|
||||||
msgstr ""
|
|
||||||
"Ein direkter Aufruf von lockutils wird nicht mehr unterstützt. Verwenden Sie "
|
|
||||||
"stattdessen das lockutils-wrapper Konsolescript."
|
|
||||||
|
|
||||||
msgid "Command requested root, but did not specify a root helper."
|
|
||||||
msgstr "Kommando braucht root, es wurde aber kein root helper spezifiziert."
|
|
||||||
|
|
||||||
msgid "Environment not supported over SSH"
|
|
||||||
msgstr "Umgebung wird nicht über SSH unterstützt"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Got an OSError\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"errno: %(errno)r"
|
|
||||||
msgstr ""
|
|
||||||
"OS Fehler aufgetreten:\n"
|
|
||||||
"Kommando: %(cmd)r\n"
|
|
||||||
"Fehlernummer: %(errno)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got invalid arg log_errors: %r"
|
|
||||||
msgstr "Ungültiges Argument für log_errors: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got unknown keyword args: %r"
|
|
||||||
msgstr "Ungültige Schlüsswelwortargumente: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Running cmd (subprocess): %s"
|
|
||||||
msgstr "Führe Kommando (subprocess) aus: %s"
|
|
||||||
|
|
||||||
msgid "Unexpected error while running command."
|
|
||||||
msgstr "Unerwarteter Fehler bei der Ausführung des Kommandos."
|
|
||||||
|
|
||||||
msgid "process_input not supported over SSH"
|
|
||||||
msgstr "process_input wird nicht über SSH unterstützt"
|
|
@ -1,27 +0,0 @@
|
|||||||
# Translations template for oslo.concurrency.
|
|
||||||
# Copyright (C) 2015 ORGANIZATION
|
|
||||||
# This file is distributed under the same license as the oslo.concurrency
|
|
||||||
# project.
|
|
||||||
#
|
|
||||||
# Translators:
|
|
||||||
# Andi Chandler <andi@gowling.com>, 2014
|
|
||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.6.1.dev10\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-04-19 12:20+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2015-06-10 11:06+0000\n"
|
|
||||||
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
|
|
||||||
"Language: en-GB\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Generated-By: Babel 2.0\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Language-Team: English (United Kingdom)\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Failed to remove file %(file)s"
|
|
||||||
msgstr "Failed to remove file %(file)s"
|
|
@ -1,100 +0,0 @@
|
|||||||
# Translations template for oslo.concurrency.
|
|
||||||
# Copyright (C) 2015 ORGANIZATION
|
|
||||||
# This file is distributed under the same license as the oslo.concurrency
|
|
||||||
# project.
|
|
||||||
#
|
|
||||||
# Translators:
|
|
||||||
# Andi Chandler <andi@gowling.com>, 2014-2015
|
|
||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.6.1.dev10\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-04-19 12:20+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2015-06-10 11:06+0000\n"
|
|
||||||
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
|
|
||||||
"Language: en-GB\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Generated-By: Babel 2.0\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Language-Team: English (United Kingdom)\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"exit code: %(code)r\n"
|
|
||||||
"stdout: %(stdout)r\n"
|
|
||||||
"stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"exit code: %(code)r\n"
|
|
||||||
"stdout: %(stdout)r\n"
|
|
||||||
"stderr: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Command: %(cmd)s\n"
|
|
||||||
"Exit code: %(exit_code)s\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Command: %(cmd)s\n"
|
|
||||||
"Exit code: %(exit_code)s\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Not Retrying."
|
|
||||||
msgstr "%r failed. Not Retrying."
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Retrying."
|
|
||||||
msgstr "%r failed. Retrying."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Calling lockutils directly is no longer supported. Please use the lockutils-"
|
|
||||||
"wrapper console script instead."
|
|
||||||
msgstr ""
|
|
||||||
"Calling lockutils directly is no longer supported. Please use the lockutils-"
|
|
||||||
"wrapper console script instead."
|
|
||||||
|
|
||||||
msgid "Command requested root, but did not specify a root helper."
|
|
||||||
msgstr "Command requested root, but did not specify a root helper."
|
|
||||||
|
|
||||||
msgid "Environment not supported over SSH"
|
|
||||||
msgstr "Environment not supported over SSH"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Got an OSError\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"errno: %(errno)r"
|
|
||||||
msgstr ""
|
|
||||||
"Got an OSError\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"errno: %(errno)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got invalid arg log_errors: %r"
|
|
||||||
msgstr "Got invalid arg log_errors: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got unknown keyword args: %r"
|
|
||||||
msgstr "Got unknown keyword args: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Running cmd (subprocess): %s"
|
|
||||||
msgstr "Running cmd (subprocess): %s"
|
|
||||||
|
|
||||||
msgid "Unexpected error while running command."
|
|
||||||
msgstr "Unexpected error while running command."
|
|
||||||
|
|
||||||
msgid "process_input not supported over SSH"
|
|
||||||
msgstr "process_input not supported over SSH"
|
|
@ -1,27 +0,0 @@
|
|||||||
# Translations template for oslo.concurrency.
|
|
||||||
# Copyright (C) 2015 ORGANIZATION
|
|
||||||
# This file is distributed under the same license as the oslo.concurrency
|
|
||||||
# project.
|
|
||||||
#
|
|
||||||
# Translators:
|
|
||||||
# Adriana Chisco Landazábal <achisco94@gmail.com>, 2015
|
|
||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.6.1.dev10\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-04-19 12:20+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2015-06-22 09:27+0000\n"
|
|
||||||
"Last-Translator: Adriana Chisco Landazábal <achisco94@gmail.com>\n"
|
|
||||||
"Language: es\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Generated-By: Babel 2.0\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Language-Team: Spanish\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Failed to remove file %(file)s"
|
|
||||||
msgstr "No se ha podido eliminar el fichero %(file)s"
|
|
@ -1,100 +0,0 @@
|
|||||||
# Translations template for oslo.concurrency.
|
|
||||||
# Copyright (C) 2015 ORGANIZATION
|
|
||||||
# This file is distributed under the same license as the oslo.concurrency
|
|
||||||
# project.
|
|
||||||
#
|
|
||||||
# Translators:
|
|
||||||
# Adriana Chisco Landazábal <achisco94@gmail.com>, 2015
|
|
||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.6.1.dev10\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-04-19 12:20+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2015-06-22 09:35+0000\n"
|
|
||||||
"Last-Translator: Adriana Chisco Landazábal <achisco94@gmail.com>\n"
|
|
||||||
"Language: es\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"Generated-By: Babel 2.0\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Language-Team: Spanish\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"exit code: %(code)r\n"
|
|
||||||
"stdout: %(stdout)r\n"
|
|
||||||
"stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"comando: %(cmd)r\n"
|
|
||||||
"código de salida: %(code)r\n"
|
|
||||||
"stdout: %(stdout)r\n"
|
|
||||||
"stderr: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Command: %(cmd)s\n"
|
|
||||||
"Exit code: %(exit_code)s\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Comando: %(cmd)s\n"
|
|
||||||
"Código de salida: %(exit_code)s\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Not Retrying."
|
|
||||||
msgstr "%r ha fallado. No se está intentando de nuevo."
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Retrying."
|
|
||||||
msgstr "%r ha fallado. Intentando de nuevo."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Calling lockutils directly is no longer supported. Please use the lockutils-"
|
|
||||||
"wrapper console script instead."
|
|
||||||
msgstr ""
|
|
||||||
"Ya no se soporta llamar LockUtil. Por favor utilice a cambio la consola "
|
|
||||||
"script lockutils-wrapper."
|
|
||||||
|
|
||||||
msgid "Command requested root, but did not specify a root helper."
|
|
||||||
msgstr "Comando ha solicitado root, pero no especificó un auxiliar root."
|
|
||||||
|
|
||||||
msgid "Environment not supported over SSH"
|
|
||||||
msgstr "Ambiente no soportado a través de SSH"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Got an OSError\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"errno: %(errno)r"
|
|
||||||
msgstr ""
|
|
||||||
"Se obtuvo error de Sistema Operativo\n"
|
|
||||||
"comando: %(cmd)r\n"
|
|
||||||
"errno: %(errno)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got invalid arg log_errors: %r"
|
|
||||||
msgstr "Se obtuvo argumento no válido: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got unknown keyword args: %r"
|
|
||||||
msgstr "Se obtuvieron argumentos de palabra clave: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Running cmd (subprocess): %s"
|
|
||||||
msgstr "Ejecutando cmd (subproceso): %s"
|
|
||||||
|
|
||||||
msgid "Unexpected error while running command."
|
|
||||||
msgstr "Error inesperado mientras se ejecutaba el comando."
|
|
||||||
|
|
||||||
msgid "process_input not supported over SSH"
|
|
||||||
msgstr "entrada de proceso no soportada a través de SSH"
|
|
@ -1,27 +0,0 @@
|
|||||||
# Translations template for oslo.concurrency.
|
|
||||||
# Copyright (C) 2015 ORGANIZATION
|
|
||||||
# This file is distributed under the same license as the oslo.concurrency
|
|
||||||
# project.
|
|
||||||
#
|
|
||||||
# Translators:
|
|
||||||
# Maxime COQUEREL <max.coquerel@gmail.com>, 2015
|
|
||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.6.1.dev10\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-04-19 12:20+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2015-06-10 11:06+0000\n"
|
|
||||||
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
|
|
||||||
"Language: fr\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
"Generated-By: Babel 2.0\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Language-Team: French\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Failed to remove file %(file)s"
|
|
||||||
msgstr "Échec lors de la suppression du fichier %(file)s"
|
|
@ -1,100 +0,0 @@
|
|||||||
# Translations template for oslo.concurrency.
|
|
||||||
# Copyright (C) 2015 ORGANIZATION
|
|
||||||
# This file is distributed under the same license as the oslo.concurrency
|
|
||||||
# project.
|
|
||||||
#
|
|
||||||
# Translators:
|
|
||||||
# Maxime COQUEREL <max.coquerel@gmail.com>, 2015
|
|
||||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency 3.6.1.dev10\n"
|
|
||||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
|
||||||
"POT-Creation-Date: 2016-04-19 12:20+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2015-06-10 11:06+0000\n"
|
|
||||||
"Last-Translator: openstackjenkins <jenkins@openstack.org>\n"
|
|
||||||
"Language: fr\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
|
||||||
"Generated-By: Babel 2.0\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Language-Team: French\n"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"exit code: %(code)r\n"
|
|
||||||
"stdout: %(stdout)r\n"
|
|
||||||
"stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(desc)r\n"
|
|
||||||
"commande: %(cmd)r\n"
|
|
||||||
"Code de sortie: %(code)r\n"
|
|
||||||
"stdout: %(stdout)r\n"
|
|
||||||
"stderr: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Command: %(cmd)s\n"
|
|
||||||
"Exit code: %(exit_code)s\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
msgstr ""
|
|
||||||
"%(description)s\n"
|
|
||||||
"Commande: %(cmd)s\n"
|
|
||||||
"Code de sortie: %(exit_code)s\n"
|
|
||||||
"Stdout: %(stdout)r\n"
|
|
||||||
"Stderr: %(stderr)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Not Retrying."
|
|
||||||
msgstr "Echec de %r. Nouvelle tentative."
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "%r failed. Retrying."
|
|
||||||
msgstr "Echec de %r. Nouvelle tentative."
|
|
||||||
|
|
||||||
msgid ""
|
|
||||||
"Calling lockutils directly is no longer supported. Please use the lockutils-"
|
|
||||||
"wrapper console script instead."
|
|
||||||
msgstr ""
|
|
||||||
"Lockutils appelant directement n'est plus pris en charge. Merci d'utiliser "
|
|
||||||
"le script de la console lockutils -wrapper à la place."
|
|
||||||
|
|
||||||
msgid "Command requested root, but did not specify a root helper."
|
|
||||||
msgstr "La commande exigeait root, mais n'indiquait pas comment obtenir root."
|
|
||||||
|
|
||||||
msgid "Environment not supported over SSH"
|
|
||||||
msgstr "Environnement non prise en charge sur SSH"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid ""
|
|
||||||
"Got an OSError\n"
|
|
||||||
"command: %(cmd)r\n"
|
|
||||||
"errno: %(errno)r"
|
|
||||||
msgstr ""
|
|
||||||
"Erreur du Système\n"
|
|
||||||
"commande: %(cmd)r\n"
|
|
||||||
"errno: %(errno)r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got invalid arg log_errors: %r"
|
|
||||||
msgstr "Argument reçu non valide log_errors: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Got unknown keyword args: %r"
|
|
||||||
msgstr "Ags, mot clé inconnu: %r"
|
|
||||||
|
|
||||||
#, python-format
|
|
||||||
msgid "Running cmd (subprocess): %s"
|
|
||||||
msgstr "Exécution de la commande (sous-processus): %s"
|
|
||||||
|
|
||||||
msgid "Unexpected error while running command."
|
|
||||||
msgstr "Erreur inattendue lors de l’exécution de la commande."
|
|
||||||
|
|
||||||
msgid "process_input not supported over SSH"
|
|
||||||
msgstr "process_input non pris en charge sur SSH"
|
|
@ -1,371 +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 functools
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import threading
|
|
||||||
import weakref
|
|
||||||
|
|
||||||
import fasteners
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslo_utils import reflection
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_concurrency._i18n import _, _LI
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
_opts = [
|
|
||||||
cfg.BoolOpt('disable_process_locking', default=False,
|
|
||||||
help='Enables or disables inter-process locks.',
|
|
||||||
deprecated_group='DEFAULT'),
|
|
||||||
cfg.StrOpt('lock_path',
|
|
||||||
default=os.environ.get("OSLO_LOCK_PATH"),
|
|
||||||
help='Directory to use for lock files. For security, the '
|
|
||||||
'specified directory should only be writable by the user '
|
|
||||||
'running the processes that need locking. '
|
|
||||||
'Defaults to environment variable OSLO_LOCK_PATH. '
|
|
||||||
'If external locks are used, a lock path must be set.',
|
|
||||||
deprecated_group='DEFAULT')
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _register_opts(conf):
|
|
||||||
conf.register_opts(_opts, group='oslo_concurrency')
|
|
||||||
|
|
||||||
|
|
||||||
CONF = cfg.CONF
|
|
||||||
_register_opts(CONF)
|
|
||||||
|
|
||||||
|
|
||||||
def set_defaults(lock_path):
|
|
||||||
"""Set value for lock_path.
|
|
||||||
|
|
||||||
This can be used by tests to set lock_path to a temporary directory.
|
|
||||||
"""
|
|
||||||
cfg.set_defaults(_opts, lock_path=lock_path)
|
|
||||||
|
|
||||||
|
|
||||||
def get_lock_path(conf):
|
|
||||||
"""Return the path used for external file-based locks.
|
|
||||||
|
|
||||||
:param conf: Configuration object
|
|
||||||
:type conf: oslo_config.cfg.ConfigOpts
|
|
||||||
|
|
||||||
.. versionadded:: 1.8
|
|
||||||
"""
|
|
||||||
_register_opts(conf)
|
|
||||||
return conf.oslo_concurrency.lock_path
|
|
||||||
|
|
||||||
|
|
||||||
InterProcessLock = fasteners.InterProcessLock
|
|
||||||
ReaderWriterLock = fasteners.ReaderWriterLock
|
|
||||||
"""A reader/writer lock.
|
|
||||||
|
|
||||||
.. versionadded:: 0.4
|
|
||||||
"""
|
|
||||||
|
|
||||||
|
|
||||||
class Semaphores(object):
|
|
||||||
"""A garbage collected container of semaphores.
|
|
||||||
|
|
||||||
This collection internally uses a weak value dictionary so that when a
|
|
||||||
semaphore is no longer in use (by any threads) it will automatically be
|
|
||||||
removed from this container by the garbage collector.
|
|
||||||
|
|
||||||
.. versionadded:: 0.3
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self._semaphores = weakref.WeakValueDictionary()
|
|
||||||
self._lock = threading.Lock()
|
|
||||||
|
|
||||||
def get(self, name):
|
|
||||||
"""Gets (or creates) a semaphore with a given name.
|
|
||||||
|
|
||||||
:param name: The semaphore name to get/create (used to associate
|
|
||||||
previously created names with the same semaphore).
|
|
||||||
|
|
||||||
Returns an newly constructed semaphore (or an existing one if it was
|
|
||||||
already created for the given name).
|
|
||||||
"""
|
|
||||||
with self._lock:
|
|
||||||
try:
|
|
||||||
return self._semaphores[name]
|
|
||||||
except KeyError:
|
|
||||||
sem = threading.Semaphore()
|
|
||||||
self._semaphores[name] = sem
|
|
||||||
return sem
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
"""Returns how many semaphores exist at the current time."""
|
|
||||||
return len(self._semaphores)
|
|
||||||
|
|
||||||
|
|
||||||
_semaphores = Semaphores()
|
|
||||||
|
|
||||||
|
|
||||||
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.oslo_concurrency.lock_path
|
|
||||||
|
|
||||||
if not local_lock_path:
|
|
||||||
raise cfg.RequiredOptError('lock_path')
|
|
||||||
|
|
||||||
return os.path.join(local_lock_path, name)
|
|
||||||
|
|
||||||
|
|
||||||
def external_lock(name, lock_file_prefix=None, lock_path=None):
|
|
||||||
lock_file_path = _get_lock_path(name, lock_file_prefix, lock_path)
|
|
||||||
|
|
||||||
return InterProcessLock(lock_file_path)
|
|
||||||
|
|
||||||
|
|
||||||
def remove_external_lock_file(name, lock_file_prefix=None, lock_path=None,
|
|
||||||
semaphores=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, semaphores=semaphores):
|
|
||||||
lock_file_path = _get_lock_path(name, lock_file_prefix, lock_path)
|
|
||||||
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, semaphores=None):
|
|
||||||
if semaphores is None:
|
|
||||||
semaphores = _semaphores
|
|
||||||
return semaphores.get(name)
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def lock(name, lock_file_prefix=None, external=False, lock_path=None,
|
|
||||||
do_log=True, semaphores=None, delay=0.01):
|
|
||||||
"""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.
|
|
||||||
|
|
||||||
:param lock_path: The path in which to store external lock files. For
|
|
||||||
external locking to work properly, this must be the same for all
|
|
||||||
references to the lock.
|
|
||||||
|
|
||||||
:param do_log: Whether to log acquire/release messages. This is primarily
|
|
||||||
intended to reduce log message duplication when `lock` is used from the
|
|
||||||
`synchronized` decorator.
|
|
||||||
|
|
||||||
:param semaphores: Container that provides semaphores to use when locking.
|
|
||||||
This ensures that threads inside the same application can not collide,
|
|
||||||
due to the fact that external process locks are unaware of a processes
|
|
||||||
active threads.
|
|
||||||
|
|
||||||
:param delay: Delay between acquisition attempts (in seconds).
|
|
||||||
|
|
||||||
.. versionchanged:: 0.2
|
|
||||||
Added *do_log* optional parameter.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.3
|
|
||||||
Added *delay* and *semaphores* optional parameters.
|
|
||||||
"""
|
|
||||||
int_lock = internal_lock(name, semaphores=semaphores)
|
|
||||||
with int_lock:
|
|
||||||
if do_log:
|
|
||||||
LOG.debug('Acquired semaphore "%(lock)s"', {'lock': name})
|
|
||||||
try:
|
|
||||||
if external and not CONF.oslo_concurrency.disable_process_locking:
|
|
||||||
ext_lock = external_lock(name, lock_file_prefix, lock_path)
|
|
||||||
ext_lock.acquire(delay=delay)
|
|
||||||
try:
|
|
||||||
yield ext_lock
|
|
||||||
finally:
|
|
||||||
ext_lock.release()
|
|
||||||
else:
|
|
||||||
yield int_lock
|
|
||||||
finally:
|
|
||||||
if do_log:
|
|
||||||
LOG.debug('Releasing semaphore "%(lock)s"', {'lock': name})
|
|
||||||
|
|
||||||
|
|
||||||
def synchronized(name, lock_file_prefix=None, external=False, lock_path=None,
|
|
||||||
semaphores=None, delay=0.01):
|
|
||||||
"""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.
|
|
||||||
|
|
||||||
.. versionchanged:: 0.3
|
|
||||||
Added *delay* and *semaphores* optional parameter.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def wrap(f):
|
|
||||||
|
|
||||||
@six.wraps(f)
|
|
||||||
def inner(*args, **kwargs):
|
|
||||||
t1 = timeutils.now()
|
|
||||||
t2 = None
|
|
||||||
try:
|
|
||||||
with lock(name, lock_file_prefix, external, lock_path,
|
|
||||||
do_log=False, semaphores=semaphores, delay=delay):
|
|
||||||
t2 = timeutils.now()
|
|
||||||
LOG.debug('Lock "%(name)s" acquired by "%(function)s" :: '
|
|
||||||
'waited %(wait_secs)0.3fs',
|
|
||||||
{'name': name,
|
|
||||||
'function': reflection.get_callable_name(f),
|
|
||||||
'wait_secs': (t2 - t1)})
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
finally:
|
|
||||||
t3 = timeutils.now()
|
|
||||||
if t2 is None:
|
|
||||||
held_secs = "N/A"
|
|
||||||
else:
|
|
||||||
held_secs = "%0.3fs" % (t3 - t2)
|
|
||||||
LOG.debug('Lock "%(name)s" released by "%(function)s" :: held '
|
|
||||||
'%(held_secs)s',
|
|
||||||
{'name': name,
|
|
||||||
'function': reflection.get_callable_name(f),
|
|
||||||
'held_secs': held_secs})
|
|
||||||
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 oslo_concurrency 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 remove_external_lock_file_with_prefix(lock_file_prefix):
|
|
||||||
"""Partial object generator for the remove lock file function.
|
|
||||||
|
|
||||||
Redefine remove_external_lock_file_with_prefix in each project like so::
|
|
||||||
|
|
||||||
(in nova/utils.py)
|
|
||||||
from oslo_concurrency import lockutils
|
|
||||||
|
|
||||||
synchronized = lockutils.synchronized_with_prefix('nova-')
|
|
||||||
synchronized_remove = lockutils.remove_external_lock_file_with_prefix(
|
|
||||||
'nova-')
|
|
||||||
|
|
||||||
(in nova/foo.py)
|
|
||||||
from nova import utils
|
|
||||||
|
|
||||||
@utils.synchronized('mylock')
|
|
||||||
def bar(self, *args):
|
|
||||||
...
|
|
||||||
|
|
||||||
<eventually call synchronized_remove('mylock') to cleanup>
|
|
||||||
|
|
||||||
The lock_file_prefix argument is used to provide lock files on disk with a
|
|
||||||
meaningful prefix.
|
|
||||||
"""
|
|
||||||
return functools.partial(remove_external_lock_file,
|
|
||||||
lock_file_prefix=lock_file_prefix)
|
|
||||||
|
|
||||||
|
|
||||||
def _lock_wrapper(argv):
|
|
||||||
"""Create a dir for locks and pass it to command from arguments
|
|
||||||
|
|
||||||
This is exposed as a console script entry point named
|
|
||||||
lockutils-wrapper
|
|
||||||
|
|
||||||
If you run this:
|
|
||||||
lockutils-wrapper 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_LOCK_PATH"] = lock_dir
|
|
||||||
try:
|
|
||||||
ret_val = subprocess.call(argv[1:])
|
|
||||||
finally:
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
return ret_val
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
sys.exit(_lock_wrapper(sys.argv))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
raise NotImplementedError(_('Calling lockutils directly is no longer '
|
|
||||||
'supported. Please use the '
|
|
||||||
'lockutils-wrapper console script instead.'))
|
|
@ -1,45 +0,0 @@
|
|||||||
# Copyright 2014 Red Hat, Inc.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
'list_opts',
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from oslo_concurrency import lockutils
|
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
|
||||||
"""Return a list of oslo.config options available in the library.
|
|
||||||
|
|
||||||
The returned list includes all oslo.config options which may be registered
|
|
||||||
at runtime by the library.
|
|
||||||
|
|
||||||
Each element of the list is a tuple. The first element is the name of the
|
|
||||||
group under which the list of elements in the second element will be
|
|
||||||
registered. A group name of None corresponds to the [DEFAULT] group in
|
|
||||||
config files.
|
|
||||||
|
|
||||||
This function is also discoverable via the 'oslo_concurrency' entry point
|
|
||||||
under the 'oslo.config.opts' namespace.
|
|
||||||
|
|
||||||
The purpose of this is to allow tools like the Oslo sample config file
|
|
||||||
generator to discover the options exposed to users by this library.
|
|
||||||
|
|
||||||
:returns: a list of (group_name, opts) tuples
|
|
||||||
"""
|
|
||||||
return [('oslo_concurrency', copy.deepcopy(lockutils._opts))]
|
|
@ -1,110 +0,0 @@
|
|||||||
# Copyright 2016 Red Hat.
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import argparse
|
|
||||||
import os
|
|
||||||
import resource
|
|
||||||
import sys
|
|
||||||
|
|
||||||
USAGE_PROGRAM = ('%s -m oslo_concurrency.prlimit'
|
|
||||||
% os.path.basename(sys.executable))
|
|
||||||
|
|
||||||
RESOURCES = (
|
|
||||||
# argparse argument => resource
|
|
||||||
('as', resource.RLIMIT_AS),
|
|
||||||
('core', resource.RLIMIT_CORE),
|
|
||||||
('cpu', resource.RLIMIT_CPU),
|
|
||||||
('data', resource.RLIMIT_DATA),
|
|
||||||
('fsize', resource.RLIMIT_FSIZE),
|
|
||||||
('memlock', resource.RLIMIT_MEMLOCK),
|
|
||||||
('nofile', resource.RLIMIT_NOFILE),
|
|
||||||
('nproc', resource.RLIMIT_NPROC),
|
|
||||||
('rss', resource.RLIMIT_RSS),
|
|
||||||
('stack', resource.RLIMIT_STACK),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def parse_args():
|
|
||||||
parser = argparse.ArgumentParser(description='prlimit', prog=USAGE_PROGRAM)
|
|
||||||
parser.add_argument('--as', type=int,
|
|
||||||
help='Address space limit in bytes')
|
|
||||||
parser.add_argument('--core', type=int,
|
|
||||||
help='Core file size limit in bytes')
|
|
||||||
parser.add_argument('--cpu', type=int,
|
|
||||||
help='CPU time limit in seconds')
|
|
||||||
parser.add_argument('--data', type=int,
|
|
||||||
help='Data size limit in bytes')
|
|
||||||
parser.add_argument('--fsize', type=int,
|
|
||||||
help='File size limit in bytes')
|
|
||||||
parser.add_argument('--memlock', type=int,
|
|
||||||
help='Locked memory limit in bytes')
|
|
||||||
parser.add_argument('--nofile', type=int,
|
|
||||||
help='Maximum number of open files')
|
|
||||||
parser.add_argument('--nproc', type=int,
|
|
||||||
help='Maximum number of processes')
|
|
||||||
parser.add_argument('--rss', type=int,
|
|
||||||
help='Maximum Resident Set Size (RSS) in bytes')
|
|
||||||
parser.add_argument('--stack', type=int,
|
|
||||||
help='Stack size limit in bytes')
|
|
||||||
parser.add_argument('program',
|
|
||||||
help='Program (absolute path)')
|
|
||||||
parser.add_argument('program_args', metavar="arg", nargs='...',
|
|
||||||
help='Program parameters')
|
|
||||||
|
|
||||||
args = parser.parse_args()
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
args = parse_args()
|
|
||||||
|
|
||||||
program = args.program
|
|
||||||
if not os.path.isabs(program):
|
|
||||||
# program uses a relative path: try to find the absolute path
|
|
||||||
# to the executable
|
|
||||||
if sys.version_info >= (3, 3):
|
|
||||||
import shutil
|
|
||||||
program_abs = shutil.which(program)
|
|
||||||
else:
|
|
||||||
import distutils.spawn
|
|
||||||
program_abs = distutils.spawn.find_executable(program)
|
|
||||||
if program_abs:
|
|
||||||
program = program_abs
|
|
||||||
|
|
||||||
for arg_name, rlimit in RESOURCES:
|
|
||||||
value = getattr(args, arg_name)
|
|
||||||
if value is None:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
resource.setrlimit(rlimit, (value, value))
|
|
||||||
except ValueError as exc:
|
|
||||||
print("%s: failed to set the %s resource limit: %s"
|
|
||||||
% (USAGE_PROGRAM, arg_name.upper(), exc),
|
|
||||||
file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
os.execv(program, [program] + args.program_args)
|
|
||||||
except Exception as exc:
|
|
||||||
print("%s: failed to execute %s: %s"
|
|
||||||
% (USAGE_PROGRAM, program, exc),
|
|
||||||
file=sys.stderr)
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
@ -1,542 +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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
System-level utilities and helper functions.
|
|
||||||
"""
|
|
||||||
|
|
||||||
import functools
|
|
||||||
import logging
|
|
||||||
import multiprocessing
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import shlex
|
|
||||||
import signal
|
|
||||||
import sys
|
|
||||||
import time
|
|
||||||
|
|
||||||
import enum
|
|
||||||
from oslo_utils import importutils
|
|
||||||
from oslo_utils import strutils
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_concurrency._i18n import _
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE(bnemec): eventlet doesn't monkey patch subprocess, so we need to
|
|
||||||
# determine the proper subprocess module to use ourselves. I'm using the
|
|
||||||
# time module as the check because that's a monkey patched module we use
|
|
||||||
# in combination with subprocess below, so they need to match.
|
|
||||||
eventlet = importutils.try_import('eventlet')
|
|
||||||
if eventlet and eventlet.patcher.is_monkey_patched(time):
|
|
||||||
from eventlet.green import subprocess
|
|
||||||
else:
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidArgumentError(Exception):
|
|
||||||
def __init__(self, message=None):
|
|
||||||
super(InvalidArgumentError, self).__init__(message)
|
|
||||||
|
|
||||||
|
|
||||||
class UnknownArgumentError(Exception):
|
|
||||||
def __init__(self, message=None):
|
|
||||||
super(UnknownArgumentError, self).__init__(message)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessExecutionError(Exception):
|
|
||||||
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
|
|
||||||
description=None):
|
|
||||||
super(ProcessExecutionError, self).__init__(
|
|
||||||
stdout, stderr, exit_code, cmd, description)
|
|
||||||
self.exit_code = exit_code
|
|
||||||
self.stderr = stderr
|
|
||||||
self.stdout = stdout
|
|
||||||
self.cmd = cmd
|
|
||||||
self.description = description
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
description = self.description
|
|
||||||
if description is None:
|
|
||||||
description = _("Unexpected error while running command.")
|
|
||||||
|
|
||||||
exit_code = self.exit_code
|
|
||||||
if exit_code is None:
|
|
||||||
exit_code = '-'
|
|
||||||
|
|
||||||
message = _('%(description)s\n'
|
|
||||||
'Command: %(cmd)s\n'
|
|
||||||
'Exit code: %(exit_code)s\n'
|
|
||||||
'Stdout: %(stdout)r\n'
|
|
||||||
'Stderr: %(stderr)r') % {'description': description,
|
|
||||||
'cmd': self.cmd,
|
|
||||||
'exit_code': exit_code,
|
|
||||||
'stdout': self.stdout,
|
|
||||||
'stderr': self.stderr}
|
|
||||||
return message
|
|
||||||
|
|
||||||
|
|
||||||
class NoRootWrapSpecified(Exception):
|
|
||||||
def __init__(self, message=None):
|
|
||||||
super(NoRootWrapSpecified, self).__init__(message)
|
|
||||||
|
|
||||||
|
|
||||||
def _subprocess_setup(on_preexec_fn):
|
|
||||||
# Python installs a SIGPIPE handler by default. This is usually not what
|
|
||||||
# non-Python subprocesses expect.
|
|
||||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
|
||||||
if on_preexec_fn:
|
|
||||||
on_preexec_fn()
|
|
||||||
|
|
||||||
|
|
||||||
@enum.unique
|
|
||||||
class LogErrors(enum.IntEnum):
|
|
||||||
"""Enumerations that affect if stdout and stderr are logged on error.
|
|
||||||
|
|
||||||
.. versionadded:: 2.7
|
|
||||||
"""
|
|
||||||
|
|
||||||
#: No logging on errors.
|
|
||||||
DEFAULT = 0
|
|
||||||
|
|
||||||
#: Log an error on **each** occurence of an error.
|
|
||||||
ALL = 1
|
|
||||||
|
|
||||||
#: Log an error on the last attempt that errored **only**.
|
|
||||||
FINAL = 2
|
|
||||||
|
|
||||||
|
|
||||||
# Retain these aliases for a number of releases...
|
|
||||||
LOG_ALL_ERRORS = LogErrors.ALL
|
|
||||||
LOG_FINAL_ERROR = LogErrors.FINAL
|
|
||||||
LOG_DEFAULT_ERROR = LogErrors.DEFAULT
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessLimits(object):
|
|
||||||
"""Resource limits on a process.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
|
|
||||||
* address_space: Address space limit in bytes
|
|
||||||
* core_file_size: Core file size limit in bytes
|
|
||||||
* cpu_time: CPU time limit in seconds
|
|
||||||
* data_size: Data size limit in bytes
|
|
||||||
* file_size: File size limit in bytes
|
|
||||||
* memory_locked: Locked memory limit in bytes
|
|
||||||
* number_files: Maximum number of open files
|
|
||||||
* number_processes: Maximum number of processes
|
|
||||||
* resident_set_size: Maximum Resident Set Size (RSS) in bytes
|
|
||||||
* stack_size: Stack size limit in bytes
|
|
||||||
|
|
||||||
This object can be used for the *prlimit* parameter of :func:`execute`.
|
|
||||||
"""
|
|
||||||
|
|
||||||
_LIMITS = {
|
|
||||||
"address_space": "--as",
|
|
||||||
"core_file_size": "--core",
|
|
||||||
"cpu_time": "--cpu",
|
|
||||||
"data_size": "--data",
|
|
||||||
"file_size": "--fsize",
|
|
||||||
"memory_locked": "--memlock",
|
|
||||||
"number_files": "--nofile",
|
|
||||||
"number_processes": "--nproc",
|
|
||||||
"resident_set_size": "--rss",
|
|
||||||
"stack_size": "--stack",
|
|
||||||
}
|
|
||||||
|
|
||||||
def __init__(self, **kw):
|
|
||||||
for limit in self._LIMITS.keys():
|
|
||||||
setattr(self, limit, kw.pop(limit, None))
|
|
||||||
|
|
||||||
if kw:
|
|
||||||
raise ValueError("invalid limits: %s"
|
|
||||||
% ', '.join(sorted(kw.keys())))
|
|
||||||
|
|
||||||
def prlimit_args(self):
|
|
||||||
"""Create a list of arguments for the prlimit command line."""
|
|
||||||
args = []
|
|
||||||
for limit in self._LIMITS.keys():
|
|
||||||
val = getattr(self, limit)
|
|
||||||
if val is not None:
|
|
||||||
args.append("%s=%s" % (self._LIMITS[limit], val))
|
|
||||||
return args
|
|
||||||
|
|
||||||
|
|
||||||
def execute(*cmd, **kwargs):
|
|
||||||
"""Helper method to shell out and execute a command through subprocess.
|
|
||||||
|
|
||||||
Allows optional retry.
|
|
||||||
|
|
||||||
:param cmd: Passed to subprocess.Popen.
|
|
||||||
:type cmd: string
|
|
||||||
:param cwd: Set the current working directory
|
|
||||||
:type cwd: string
|
|
||||||
:param process_input: Send to opened process.
|
|
||||||
:type process_input: string
|
|
||||||
:param env_variables: Environment variables and their values that
|
|
||||||
will be set for the process.
|
|
||||||
:type env_variables: dict
|
|
||||||
:param check_exit_code: Single bool, int, or list of allowed exit
|
|
||||||
codes. Defaults to [0]. Raise
|
|
||||||
:class:`ProcessExecutionError` unless
|
|
||||||
program exits with one of these code.
|
|
||||||
:type check_exit_code: boolean, int, or [int]
|
|
||||||
:param delay_on_retry: True | False. Defaults to True. If set to True,
|
|
||||||
wait a short amount of time before retrying.
|
|
||||||
:type delay_on_retry: boolean
|
|
||||||
:param attempts: How many times to retry cmd.
|
|
||||||
:type attempts: int
|
|
||||||
:param run_as_root: True | False. Defaults to False. If set to True,
|
|
||||||
the command is prefixed by the command specified
|
|
||||||
in the root_helper kwarg.
|
|
||||||
:type run_as_root: boolean
|
|
||||||
:param root_helper: command to prefix to commands called with
|
|
||||||
run_as_root=True
|
|
||||||
:type root_helper: string
|
|
||||||
:param shell: whether or not there should be a shell used to
|
|
||||||
execute this command. Defaults to false.
|
|
||||||
:type shell: boolean
|
|
||||||
:param loglevel: log level for execute commands.
|
|
||||||
:type loglevel: int. (Should be logging.DEBUG or logging.INFO)
|
|
||||||
:param log_errors: Should stdout and stderr be logged on error?
|
|
||||||
Possible values are
|
|
||||||
:py:attr:`~.LogErrors.DEFAULT`,
|
|
||||||
:py:attr:`~.LogErrors.FINAL`, or
|
|
||||||
:py:attr:`~.LogErrors.ALL`. Note that the
|
|
||||||
values :py:attr:`~.LogErrors.FINAL` and
|
|
||||||
:py:attr:`~.LogErrors.ALL`
|
|
||||||
are **only** relevant when multiple attempts of
|
|
||||||
command execution are requested using the
|
|
||||||
``attempts`` parameter.
|
|
||||||
:type log_errors: :py:class:`~.LogErrors`
|
|
||||||
:param binary: On Python 3, return stdout and stderr as bytes if
|
|
||||||
binary is True, as Unicode otherwise.
|
|
||||||
:type binary: boolean
|
|
||||||
:param on_execute: This function will be called upon process creation
|
|
||||||
with the object as a argument. The Purpose of this
|
|
||||||
is to allow the caller of `processutils.execute` to
|
|
||||||
track process creation asynchronously.
|
|
||||||
:type on_execute: function(:class:`subprocess.Popen`)
|
|
||||||
:param on_completion: This function will be called upon process
|
|
||||||
completion with the object as a argument. The
|
|
||||||
Purpose of this is to allow the caller of
|
|
||||||
`processutils.execute` to track process completion
|
|
||||||
asynchronously.
|
|
||||||
:type on_completion: function(:class:`subprocess.Popen`)
|
|
||||||
:param preexec_fn: This function will be called
|
|
||||||
in the child process just before the child
|
|
||||||
is executed. WARNING: On windows, we silently
|
|
||||||
drop this preexec_fn as it is not supported by
|
|
||||||
subprocess.Popen on windows (throws a
|
|
||||||
ValueError)
|
|
||||||
:type preexec_fn: function()
|
|
||||||
:param prlimit: Set resource limits on the child process. See
|
|
||||||
below for a detailed description.
|
|
||||||
:type prlimit: :class:`ProcessLimits`
|
|
||||||
:returns: (stdout, stderr) from process execution
|
|
||||||
:raises: :class:`UnknownArgumentError` on
|
|
||||||
receiving unknown arguments
|
|
||||||
:raises: :class:`ProcessExecutionError`
|
|
||||||
:raises: :class:`OSError`
|
|
||||||
|
|
||||||
The *prlimit* parameter can be used to set resource limits on the child
|
|
||||||
process. If this parameter is used, the child process will be spawned by a
|
|
||||||
wrapper process which will set limits before spawning the command.
|
|
||||||
|
|
||||||
.. versionchanged:: 3.4
|
|
||||||
Added *prlimit* optional parameter.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.5
|
|
||||||
Added *cwd* optional parameter.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.9
|
|
||||||
Added *binary* optional parameter. On Python 3, *stdout* and *stdout*
|
|
||||||
are now returned as Unicode strings by default, or bytes if *binary* is
|
|
||||||
true.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.1
|
|
||||||
Added *on_execute* and *on_completion* optional parameters.
|
|
||||||
|
|
||||||
.. versionchanged:: 2.3
|
|
||||||
Added *preexec_fn* optional parameter.
|
|
||||||
"""
|
|
||||||
|
|
||||||
cwd = kwargs.pop('cwd', None)
|
|
||||||
process_input = kwargs.pop('process_input', None)
|
|
||||||
env_variables = kwargs.pop('env_variables', None)
|
|
||||||
check_exit_code = kwargs.pop('check_exit_code', [0])
|
|
||||||
ignore_exit_code = False
|
|
||||||
delay_on_retry = kwargs.pop('delay_on_retry', True)
|
|
||||||
attempts = kwargs.pop('attempts', 1)
|
|
||||||
run_as_root = kwargs.pop('run_as_root', False)
|
|
||||||
root_helper = kwargs.pop('root_helper', '')
|
|
||||||
shell = kwargs.pop('shell', False)
|
|
||||||
loglevel = kwargs.pop('loglevel', logging.DEBUG)
|
|
||||||
log_errors = kwargs.pop('log_errors', None)
|
|
||||||
if log_errors is None:
|
|
||||||
log_errors = LogErrors.DEFAULT
|
|
||||||
binary = kwargs.pop('binary', False)
|
|
||||||
on_execute = kwargs.pop('on_execute', None)
|
|
||||||
on_completion = kwargs.pop('on_completion', None)
|
|
||||||
preexec_fn = kwargs.pop('preexec_fn', None)
|
|
||||||
prlimit = kwargs.pop('prlimit', None)
|
|
||||||
|
|
||||||
if isinstance(check_exit_code, bool):
|
|
||||||
ignore_exit_code = not check_exit_code
|
|
||||||
check_exit_code = [0]
|
|
||||||
elif isinstance(check_exit_code, int):
|
|
||||||
check_exit_code = [check_exit_code]
|
|
||||||
|
|
||||||
if kwargs:
|
|
||||||
raise UnknownArgumentError(_('Got unknown keyword args: %r') % kwargs)
|
|
||||||
|
|
||||||
if isinstance(log_errors, six.integer_types):
|
|
||||||
log_errors = LogErrors(log_errors)
|
|
||||||
if not isinstance(log_errors, LogErrors):
|
|
||||||
raise InvalidArgumentError(_('Got invalid arg log_errors: %r') %
|
|
||||||
log_errors)
|
|
||||||
|
|
||||||
if run_as_root and hasattr(os, 'geteuid') and os.geteuid() != 0:
|
|
||||||
if not root_helper:
|
|
||||||
raise NoRootWrapSpecified(
|
|
||||||
message=_('Command requested root, but did not '
|
|
||||||
'specify a root helper.'))
|
|
||||||
if shell:
|
|
||||||
# root helper has to be injected into the command string
|
|
||||||
cmd = [' '.join((root_helper, cmd[0]))] + list(cmd[1:])
|
|
||||||
else:
|
|
||||||
# root helper has to be tokenized into argument list
|
|
||||||
cmd = shlex.split(root_helper) + list(cmd)
|
|
||||||
|
|
||||||
cmd = [str(c) for c in cmd]
|
|
||||||
|
|
||||||
if prlimit:
|
|
||||||
args = [sys.executable, '-m', 'oslo_concurrency.prlimit']
|
|
||||||
args.extend(prlimit.prlimit_args())
|
|
||||||
args.append('--')
|
|
||||||
args.extend(cmd)
|
|
||||||
cmd = args
|
|
||||||
|
|
||||||
sanitized_cmd = strutils.mask_password(' '.join(cmd))
|
|
||||||
|
|
||||||
watch = timeutils.StopWatch()
|
|
||||||
while attempts > 0:
|
|
||||||
attempts -= 1
|
|
||||||
watch.restart()
|
|
||||||
|
|
||||||
try:
|
|
||||||
LOG.log(loglevel, _('Running cmd (subprocess): %s'), sanitized_cmd)
|
|
||||||
_PIPE = subprocess.PIPE # pylint: disable=E1101
|
|
||||||
|
|
||||||
if os.name == 'nt':
|
|
||||||
on_preexec_fn = None
|
|
||||||
close_fds = False
|
|
||||||
else:
|
|
||||||
on_preexec_fn = functools.partial(_subprocess_setup,
|
|
||||||
preexec_fn)
|
|
||||||
close_fds = True
|
|
||||||
|
|
||||||
obj = subprocess.Popen(cmd,
|
|
||||||
stdin=_PIPE,
|
|
||||||
stdout=_PIPE,
|
|
||||||
stderr=_PIPE,
|
|
||||||
close_fds=close_fds,
|
|
||||||
preexec_fn=on_preexec_fn,
|
|
||||||
shell=shell,
|
|
||||||
cwd=cwd,
|
|
||||||
env=env_variables)
|
|
||||||
|
|
||||||
if on_execute:
|
|
||||||
on_execute(obj)
|
|
||||||
|
|
||||||
try:
|
|
||||||
result = obj.communicate(process_input)
|
|
||||||
|
|
||||||
obj.stdin.close() # pylint: disable=E1101
|
|
||||||
_returncode = obj.returncode # pylint: disable=E1101
|
|
||||||
LOG.log(loglevel, 'CMD "%s" returned: %s in %0.3fs',
|
|
||||||
sanitized_cmd, _returncode, watch.elapsed())
|
|
||||||
finally:
|
|
||||||
if on_completion:
|
|
||||||
on_completion(obj)
|
|
||||||
|
|
||||||
if not ignore_exit_code and _returncode not in check_exit_code:
|
|
||||||
(stdout, stderr) = result
|
|
||||||
if six.PY3:
|
|
||||||
stdout = os.fsdecode(stdout)
|
|
||||||
stderr = os.fsdecode(stderr)
|
|
||||||
sanitized_stdout = strutils.mask_password(stdout)
|
|
||||||
sanitized_stderr = strutils.mask_password(stderr)
|
|
||||||
raise ProcessExecutionError(exit_code=_returncode,
|
|
||||||
stdout=sanitized_stdout,
|
|
||||||
stderr=sanitized_stderr,
|
|
||||||
cmd=sanitized_cmd)
|
|
||||||
if six.PY3 and not binary and result is not None:
|
|
||||||
(stdout, stderr) = result
|
|
||||||
# Decode from the locale using using the surrogateescape error
|
|
||||||
# handler (decoding cannot fail)
|
|
||||||
stdout = os.fsdecode(stdout)
|
|
||||||
stderr = os.fsdecode(stderr)
|
|
||||||
return (stdout, stderr)
|
|
||||||
else:
|
|
||||||
return result
|
|
||||||
|
|
||||||
except (ProcessExecutionError, OSError) as err:
|
|
||||||
# if we want to always log the errors or if this is
|
|
||||||
# the final attempt that failed and we want to log that.
|
|
||||||
if log_errors == LOG_ALL_ERRORS or (
|
|
||||||
log_errors == LOG_FINAL_ERROR and not attempts):
|
|
||||||
if isinstance(err, ProcessExecutionError):
|
|
||||||
format = _('%(desc)r\ncommand: %(cmd)r\n'
|
|
||||||
'exit code: %(code)r\nstdout: %(stdout)r\n'
|
|
||||||
'stderr: %(stderr)r')
|
|
||||||
LOG.log(loglevel, format, {"desc": err.description,
|
|
||||||
"cmd": err.cmd,
|
|
||||||
"code": err.exit_code,
|
|
||||||
"stdout": err.stdout,
|
|
||||||
"stderr": err.stderr})
|
|
||||||
else:
|
|
||||||
format = _('Got an OSError\ncommand: %(cmd)r\n'
|
|
||||||
'errno: %(errno)r')
|
|
||||||
LOG.log(loglevel, format, {"cmd": sanitized_cmd,
|
|
||||||
"errno": err.errno})
|
|
||||||
|
|
||||||
if not attempts:
|
|
||||||
LOG.log(loglevel, _('%r failed. Not Retrying.'),
|
|
||||||
sanitized_cmd)
|
|
||||||
raise
|
|
||||||
else:
|
|
||||||
LOG.log(loglevel, _('%r failed. Retrying.'),
|
|
||||||
sanitized_cmd)
|
|
||||||
if delay_on_retry:
|
|
||||||
time.sleep(random.randint(20, 200) / 100.0)
|
|
||||||
finally:
|
|
||||||
# NOTE(termie): this appears to be necessary to let the subprocess
|
|
||||||
# call clean something up in between calls, without
|
|
||||||
# it two execute calls in a row hangs the second one
|
|
||||||
# NOTE(bnemec): termie's comment above is probably specific to the
|
|
||||||
# eventlet subprocess module, but since we still
|
|
||||||
# have to support that we're leaving the sleep. It
|
|
||||||
# won't hurt anything in the stdlib case anyway.
|
|
||||||
time.sleep(0)
|
|
||||||
|
|
||||||
|
|
||||||
def trycmd(*args, **kwargs):
|
|
||||||
"""A wrapper around execute() to more easily handle warnings and errors.
|
|
||||||
|
|
||||||
Returns an (out, err) tuple of strings containing the output of
|
|
||||||
the command's stdout and stderr. If 'err' is not empty then the
|
|
||||||
command can be considered to have failed.
|
|
||||||
|
|
||||||
:discard_warnings True | False. Defaults to False. If set to True,
|
|
||||||
then for succeeding commands, stderr is cleared
|
|
||||||
|
|
||||||
"""
|
|
||||||
discard_warnings = kwargs.pop('discard_warnings', False)
|
|
||||||
|
|
||||||
try:
|
|
||||||
out, err = execute(*args, **kwargs)
|
|
||||||
failed = False
|
|
||||||
except ProcessExecutionError as exn:
|
|
||||||
out, err = '', six.text_type(exn)
|
|
||||||
failed = True
|
|
||||||
|
|
||||||
if not failed and discard_warnings and err:
|
|
||||||
# Handle commands that output to stderr but otherwise succeed
|
|
||||||
err = ''
|
|
||||||
|
|
||||||
return out, err
|
|
||||||
|
|
||||||
|
|
||||||
def ssh_execute(ssh, cmd, process_input=None,
|
|
||||||
addl_env=None, check_exit_code=True,
|
|
||||||
binary=False, timeout=None):
|
|
||||||
"""Run a command through SSH.
|
|
||||||
|
|
||||||
.. versionchanged:: 1.9
|
|
||||||
Added *binary* optional parameter.
|
|
||||||
"""
|
|
||||||
sanitized_cmd = strutils.mask_password(cmd)
|
|
||||||
LOG.debug('Running cmd (SSH): %s', sanitized_cmd)
|
|
||||||
if addl_env:
|
|
||||||
raise InvalidArgumentError(_('Environment not supported over SSH'))
|
|
||||||
|
|
||||||
if process_input:
|
|
||||||
# This is (probably) fixable if we need it...
|
|
||||||
raise InvalidArgumentError(_('process_input not supported over SSH'))
|
|
||||||
|
|
||||||
stdin_stream, stdout_stream, stderr_stream = ssh.exec_command(
|
|
||||||
cmd, timeout=timeout)
|
|
||||||
channel = stdout_stream.channel
|
|
||||||
|
|
||||||
# NOTE(justinsb): This seems suspicious...
|
|
||||||
# ...other SSH clients have buffering issues with this approach
|
|
||||||
stdout = stdout_stream.read()
|
|
||||||
stderr = stderr_stream.read()
|
|
||||||
|
|
||||||
stdin_stream.close()
|
|
||||||
|
|
||||||
exit_status = channel.recv_exit_status()
|
|
||||||
|
|
||||||
if six.PY3:
|
|
||||||
# Decode from the locale using using the surrogateescape error handler
|
|
||||||
# (decoding cannot fail). Decode even if binary is True because
|
|
||||||
# mask_password() requires Unicode on Python 3
|
|
||||||
stdout = os.fsdecode(stdout)
|
|
||||||
stderr = os.fsdecode(stderr)
|
|
||||||
stdout = strutils.mask_password(stdout)
|
|
||||||
stderr = strutils.mask_password(stderr)
|
|
||||||
|
|
||||||
# exit_status == -1 if no exit code was returned
|
|
||||||
if exit_status != -1:
|
|
||||||
LOG.debug('Result was %s' % exit_status)
|
|
||||||
if check_exit_code and exit_status != 0:
|
|
||||||
raise ProcessExecutionError(exit_code=exit_status,
|
|
||||||
stdout=stdout,
|
|
||||||
stderr=stderr,
|
|
||||||
cmd=sanitized_cmd)
|
|
||||||
|
|
||||||
if binary:
|
|
||||||
if six.PY2:
|
|
||||||
# On Python 2, stdout is a bytes string if mask_password() failed
|
|
||||||
# to decode it, or an Unicode string otherwise. Encode to the
|
|
||||||
# default encoding (ASCII) because mask_password() decodes from
|
|
||||||
# the same encoding.
|
|
||||||
if isinstance(stdout, unicode):
|
|
||||||
stdout = stdout.encode()
|
|
||||||
if isinstance(stderr, unicode):
|
|
||||||
stderr = stderr.encode()
|
|
||||||
else:
|
|
||||||
# fsencode() is the reverse operation of fsdecode()
|
|
||||||
stdout = os.fsencode(stdout)
|
|
||||||
stderr = os.fsencode(stderr)
|
|
||||||
|
|
||||||
return (stdout, stderr)
|
|
||||||
|
|
||||||
|
|
||||||
def get_worker_count():
|
|
||||||
"""Utility to get the default worker count.
|
|
||||||
|
|
||||||
@return: The number of CPUs if that can be determined, else a default
|
|
||||||
worker count of 1 is returned.
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
return multiprocessing.cpu_count()
|
|
||||||
except NotImplementedError:
|
|
||||||
return 1
|
|
@ -1,19 +0,0 @@
|
|||||||
# Copyright 2014 Red Hat, Inc.
|
|
||||||
#
|
|
||||||
# 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
|
|
||||||
|
|
||||||
if os.environ.get('TEST_EVENTLET'):
|
|
||||||
import eventlet
|
|
||||||
eventlet.monkey_patch()
|
|
@ -1,527 +0,0 @@
|
|||||||
# Copyright 2011 Justin Santa Barbara
|
|
||||||
#
|
|
||||||
# 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 collections
|
|
||||||
import fcntl
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import signal
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
import threading
|
|
||||||
import time
|
|
||||||
|
|
||||||
from oslo_config import cfg
|
|
||||||
from oslotest import base as test_base
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_concurrency.fixture import lockutils as fixtures
|
|
||||||
from oslo_concurrency import lockutils
|
|
||||||
from oslo_config import fixture as config
|
|
||||||
|
|
||||||
|
|
||||||
class LockTestCase(test_base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(LockTestCase, self).setUp()
|
|
||||||
self.config = self.useFixture(config.Config(lockutils.CONF)).config
|
|
||||||
|
|
||||||
def test_synchronized_wrapped_function_metadata(self):
|
|
||||||
@lockutils.synchronized('whatever', 'test-')
|
|
||||||
def foo():
|
|
||||||
"""Bar."""
|
|
||||||
pass
|
|
||||||
|
|
||||||
self.assertEqual('Bar.', foo.__doc__, "Wrapped function's docstring "
|
|
||||||
"got lost")
|
|
||||||
self.assertEqual('foo', foo.__name__, "Wrapped function's name "
|
|
||||||
"got mangled")
|
|
||||||
|
|
||||||
def test_lock_internally_different_collections(self):
|
|
||||||
s1 = lockutils.Semaphores()
|
|
||||||
s2 = lockutils.Semaphores()
|
|
||||||
trigger = threading.Event()
|
|
||||||
who_ran = collections.deque()
|
|
||||||
|
|
||||||
def f(name, semaphores, pull_trigger):
|
|
||||||
with lockutils.internal_lock('testing', semaphores=semaphores):
|
|
||||||
if pull_trigger:
|
|
||||||
trigger.set()
|
|
||||||
else:
|
|
||||||
trigger.wait()
|
|
||||||
who_ran.append(name)
|
|
||||||
|
|
||||||
threads = [
|
|
||||||
threading.Thread(target=f, args=(1, s1, True)),
|
|
||||||
threading.Thread(target=f, args=(2, s2, False)),
|
|
||||||
]
|
|
||||||
for thread in threads:
|
|
||||||
thread.start()
|
|
||||||
for thread in threads:
|
|
||||||
thread.join()
|
|
||||||
self.assertEqual([1, 2], sorted(who_ran))
|
|
||||||
|
|
||||||
def test_lock_internally(self):
|
|
||||||
"""We can lock across multiple threads."""
|
|
||||||
saved_sem_num = len(lockutils._semaphores)
|
|
||||||
seen_threads = list()
|
|
||||||
|
|
||||||
def f(_id):
|
|
||||||
with lockutils.lock('testlock2', 'test-', external=False):
|
|
||||||
for x in range(10):
|
|
||||||
seen_threads.append(_id)
|
|
||||||
|
|
||||||
threads = []
|
|
||||||
for i in range(10):
|
|
||||||
thread = threading.Thread(target=f, args=(i,))
|
|
||||||
threads.append(thread)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
for thread in threads:
|
|
||||||
thread.join()
|
|
||||||
|
|
||||||
self.assertEqual(100, len(seen_threads))
|
|
||||||
# Looking at the seen threads, split it into chunks of 10, and verify
|
|
||||||
# that the last 9 match the first in each chunk.
|
|
||||||
for i in range(10):
|
|
||||||
for j in range(9):
|
|
||||||
self.assertEqual(seen_threads[i * 10],
|
|
||||||
seen_threads[i * 10 + 1 + j])
|
|
||||||
|
|
||||||
self.assertEqual(saved_sem_num, len(lockutils._semaphores),
|
|
||||||
"Semaphore leak detected")
|
|
||||||
|
|
||||||
def test_nested_synchronized_external_works(self):
|
|
||||||
"""We can nest external syncs."""
|
|
||||||
tempdir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
self.config(lock_path=tempdir, group='oslo_concurrency')
|
|
||||||
sentinel = object()
|
|
||||||
|
|
||||||
@lockutils.synchronized('testlock1', 'test-', external=True)
|
|
||||||
def outer_lock():
|
|
||||||
|
|
||||||
@lockutils.synchronized('testlock2', 'test-', external=True)
|
|
||||||
def inner_lock():
|
|
||||||
return sentinel
|
|
||||||
return inner_lock()
|
|
||||||
|
|
||||||
self.assertEqual(sentinel, outer_lock())
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if os.path.exists(tempdir):
|
|
||||||
shutil.rmtree(tempdir)
|
|
||||||
|
|
||||||
def _do_test_lock_externally(self):
|
|
||||||
"""We can lock across multiple processes."""
|
|
||||||
|
|
||||||
def lock_files(handles_dir):
|
|
||||||
|
|
||||||
with lockutils.lock('external', 'test-', external=True):
|
|
||||||
# Open some files we can use for locking
|
|
||||||
handles = []
|
|
||||||
for n in range(50):
|
|
||||||
path = os.path.join(handles_dir, ('file-%s' % n))
|
|
||||||
handles.append(open(path, 'w'))
|
|
||||||
|
|
||||||
# Loop over all the handles and try locking the file
|
|
||||||
# without blocking, keep a count of how many files we
|
|
||||||
# were able to lock and then unlock. If the lock fails
|
|
||||||
# we get an IOError and bail out with bad exit code
|
|
||||||
count = 0
|
|
||||||
for handle in handles:
|
|
||||||
try:
|
|
||||||
fcntl.flock(handle, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
||||||
count += 1
|
|
||||||
fcntl.flock(handle, fcntl.LOCK_UN)
|
|
||||||
except IOError:
|
|
||||||
os._exit(2)
|
|
||||||
finally:
|
|
||||||
handle.close()
|
|
||||||
|
|
||||||
# Check if we were able to open all files
|
|
||||||
self.assertEqual(50, count)
|
|
||||||
|
|
||||||
handles_dir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
children = []
|
|
||||||
for n in range(50):
|
|
||||||
pid = os.fork()
|
|
||||||
if pid:
|
|
||||||
children.append(pid)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
lock_files(handles_dir)
|
|
||||||
finally:
|
|
||||||
os._exit(0)
|
|
||||||
|
|
||||||
for child in children:
|
|
||||||
(pid, status) = os.waitpid(child, 0)
|
|
||||||
if pid:
|
|
||||||
self.assertEqual(0, status)
|
|
||||||
finally:
|
|
||||||
if os.path.exists(handles_dir):
|
|
||||||
shutil.rmtree(handles_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_lock_externally(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._do_test_lock_externally()
|
|
||||||
finally:
|
|
||||||
if os.path.exists(lock_dir):
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_lock_externally_lock_dir_not_exist(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
os.rmdir(lock_dir)
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
|
|
||||||
try:
|
|
||||||
self._do_test_lock_externally()
|
|
||||||
finally:
|
|
||||||
if os.path.exists(lock_dir):
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_synchronized_with_prefix(self):
|
|
||||||
lock_name = 'mylock'
|
|
||||||
lock_pfix = 'mypfix-'
|
|
||||||
|
|
||||||
foo = lockutils.synchronized_with_prefix(lock_pfix)
|
|
||||||
|
|
||||||
@foo(lock_name, external=True)
|
|
||||||
def bar(dirpath, pfix, name):
|
|
||||||
return True
|
|
||||||
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
|
|
||||||
self.assertTrue(bar(lock_dir, lock_pfix, lock_name))
|
|
||||||
|
|
||||||
def test_synchronized_without_prefix(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
|
|
||||||
@lockutils.synchronized('lock', external=True)
|
|
||||||
def test_without_prefix():
|
|
||||||
# We can't check much
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
test_without_prefix()
|
|
||||||
finally:
|
|
||||||
if os.path.exists(lock_dir):
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_synchronized_prefix_without_hypen(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
|
|
||||||
@lockutils.synchronized('lock', 'hypen', True)
|
|
||||||
def test_without_hypen():
|
|
||||||
# We can't check much
|
|
||||||
pass
|
|
||||||
|
|
||||||
try:
|
|
||||||
test_without_hypen()
|
|
||||||
finally:
|
|
||||||
if os.path.exists(lock_dir):
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_contextlock(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Note(flaper87): Lock is not external, which means
|
|
||||||
# a semaphore will be yielded
|
|
||||||
with lockutils.lock("test") as sem:
|
|
||||||
if six.PY2:
|
|
||||||
self.assertTrue(isinstance(sem, threading._Semaphore))
|
|
||||||
else:
|
|
||||||
self.assertTrue(isinstance(sem, threading.Semaphore))
|
|
||||||
|
|
||||||
# NOTE(flaper87): Lock is external so an InterProcessLock
|
|
||||||
# will be yielded.
|
|
||||||
with lockutils.lock("test2", external=True) as lock:
|
|
||||||
self.assertTrue(lock.exists())
|
|
||||||
|
|
||||||
with lockutils.lock("test1",
|
|
||||||
external=True) as lock1:
|
|
||||||
self.assertTrue(isinstance(lock1,
|
|
||||||
lockutils.InterProcessLock))
|
|
||||||
finally:
|
|
||||||
if os.path.exists(lock_dir):
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_contextlock_unlocks(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
|
|
||||||
sem = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
with lockutils.lock("test") as sem:
|
|
||||||
if six.PY2:
|
|
||||||
self.assertTrue(isinstance(sem, threading._Semaphore))
|
|
||||||
else:
|
|
||||||
self.assertTrue(isinstance(sem, threading.Semaphore))
|
|
||||||
|
|
||||||
with lockutils.lock("test2", external=True) as lock:
|
|
||||||
self.assertTrue(lock.exists())
|
|
||||||
|
|
||||||
# NOTE(flaper87): Lock should be free
|
|
||||||
with lockutils.lock("test2", external=True) as lock:
|
|
||||||
self.assertTrue(lock.exists())
|
|
||||||
|
|
||||||
# NOTE(flaper87): Lock should be free
|
|
||||||
# but semaphore should already exist.
|
|
||||||
with lockutils.lock("test") as sem2:
|
|
||||||
self.assertEqual(sem, sem2)
|
|
||||||
finally:
|
|
||||||
if os.path.exists(lock_dir):
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def _test_remove_lock_external_file(self, lock_dir, use_external=False):
|
|
||||||
lock_name = 'mylock'
|
|
||||||
lock_pfix = 'mypfix-remove-lock-test-'
|
|
||||||
|
|
||||||
if use_external:
|
|
||||||
lock_path = lock_dir
|
|
||||||
else:
|
|
||||||
lock_path = None
|
|
||||||
|
|
||||||
lockutils.remove_external_lock_file(lock_name, lock_pfix, lock_path)
|
|
||||||
|
|
||||||
for ent in os.listdir(lock_dir):
|
|
||||||
self.assertRaises(OSError, ent.startswith, lock_pfix)
|
|
||||||
|
|
||||||
if os.path.exists(lock_dir):
|
|
||||||
shutil.rmtree(lock_dir, ignore_errors=True)
|
|
||||||
|
|
||||||
def test_remove_lock_external_file(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self.config(lock_path=lock_dir, group='oslo_concurrency')
|
|
||||||
self._test_remove_lock_external_file(lock_dir)
|
|
||||||
|
|
||||||
def test_remove_lock_external_file_lock_path(self):
|
|
||||||
lock_dir = tempfile.mkdtemp()
|
|
||||||
self._test_remove_lock_external_file(lock_dir,
|
|
||||||
use_external=True)
|
|
||||||
|
|
||||||
def test_no_slash_in_b64(self):
|
|
||||||
# base64(sha1(foobar)) has a slash in it
|
|
||||||
with lockutils.lock("foobar"):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def test_deprecated_names(self):
|
|
||||||
paths = self.create_tempfiles([['fake.conf', '\n'.join([
|
|
||||||
'[DEFAULT]',
|
|
||||||
'lock_path=foo',
|
|
||||||
'disable_process_locking=True'])
|
|
||||||
]])
|
|
||||||
conf = cfg.ConfigOpts()
|
|
||||||
conf(['--config-file', paths[0]])
|
|
||||||
conf.register_opts(lockutils._opts, 'oslo_concurrency')
|
|
||||||
self.assertEqual('foo', conf.oslo_concurrency.lock_path)
|
|
||||||
self.assertTrue(conf.oslo_concurrency.disable_process_locking)
|
|
||||||
|
|
||||||
|
|
||||||
class FileBasedLockingTestCase(test_base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(FileBasedLockingTestCase, self).setUp()
|
|
||||||
self.lock_dir = tempfile.mkdtemp()
|
|
||||||
|
|
||||||
def test_lock_file_exists(self):
|
|
||||||
lock_file = os.path.join(self.lock_dir, 'lock-file')
|
|
||||||
|
|
||||||
@lockutils.synchronized('lock-file', external=True,
|
|
||||||
lock_path=self.lock_dir)
|
|
||||||
def foo():
|
|
||||||
self.assertTrue(os.path.exists(lock_file))
|
|
||||||
|
|
||||||
foo()
|
|
||||||
|
|
||||||
def test_interprocess_lock(self):
|
|
||||||
lock_file = os.path.join(self.lock_dir, 'processlock')
|
|
||||||
|
|
||||||
pid = os.fork()
|
|
||||||
if pid:
|
|
||||||
# Make sure the child grabs the lock first
|
|
||||||
start = time.time()
|
|
||||||
while not os.path.exists(lock_file):
|
|
||||||
if time.time() - start > 5:
|
|
||||||
self.fail('Timed out waiting for child to grab lock')
|
|
||||||
time.sleep(0)
|
|
||||||
lock1 = lockutils.InterProcessLock('foo')
|
|
||||||
lock1.lockfile = open(lock_file, 'w')
|
|
||||||
# NOTE(bnemec): There is a brief window between when the lock file
|
|
||||||
# is created and when it actually becomes locked. If we happen to
|
|
||||||
# context switch in that window we may succeed in locking the
|
|
||||||
# file. Keep retrying until we either get the expected exception
|
|
||||||
# or timeout waiting.
|
|
||||||
while time.time() - start < 5:
|
|
||||||
try:
|
|
||||||
lock1.trylock()
|
|
||||||
lock1.unlock()
|
|
||||||
time.sleep(0)
|
|
||||||
except IOError:
|
|
||||||
# This is what we expect to happen
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
self.fail('Never caught expected lock exception')
|
|
||||||
# We don't need to wait for the full sleep in the child here
|
|
||||||
os.kill(pid, signal.SIGKILL)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
lock2 = lockutils.InterProcessLock('foo')
|
|
||||||
lock2.lockfile = open(lock_file, 'w')
|
|
||||||
have_lock = False
|
|
||||||
while not have_lock:
|
|
||||||
try:
|
|
||||||
lock2.trylock()
|
|
||||||
have_lock = True
|
|
||||||
except IOError:
|
|
||||||
pass
|
|
||||||
finally:
|
|
||||||
# NOTE(bnemec): This is racy, but I don't want to add any
|
|
||||||
# synchronization primitives that might mask a problem
|
|
||||||
# with the one we're trying to test here.
|
|
||||||
time.sleep(.5)
|
|
||||||
os._exit(0)
|
|
||||||
|
|
||||||
def test_interthread_external_lock(self):
|
|
||||||
call_list = []
|
|
||||||
|
|
||||||
@lockutils.synchronized('foo', external=True, lock_path=self.lock_dir)
|
|
||||||
def foo(param):
|
|
||||||
"""Simulate a long-running threaded operation."""
|
|
||||||
call_list.append(param)
|
|
||||||
# NOTE(bnemec): This is racy, but I don't want to add any
|
|
||||||
# synchronization primitives that might mask a problem
|
|
||||||
# with the one we're trying to test here.
|
|
||||||
time.sleep(.5)
|
|
||||||
call_list.append(param)
|
|
||||||
|
|
||||||
def other(param):
|
|
||||||
foo(param)
|
|
||||||
|
|
||||||
thread = threading.Thread(target=other, args=('other',))
|
|
||||||
thread.start()
|
|
||||||
# Make sure the other thread grabs the lock
|
|
||||||
# NOTE(bnemec): File locks do not actually work between threads, so
|
|
||||||
# this test is verifying that the local semaphore is still enforcing
|
|
||||||
# external locks in that case. This means this test does not have
|
|
||||||
# the same race problem as the process test above because when the
|
|
||||||
# file is created the semaphore has already been grabbed.
|
|
||||||
start = time.time()
|
|
||||||
while not os.path.exists(os.path.join(self.lock_dir, 'foo')):
|
|
||||||
if time.time() - start > 5:
|
|
||||||
self.fail('Timed out waiting for thread to grab lock')
|
|
||||||
time.sleep(0)
|
|
||||||
thread1 = threading.Thread(target=other, args=('main',))
|
|
||||||
thread1.start()
|
|
||||||
thread1.join()
|
|
||||||
thread.join()
|
|
||||||
self.assertEqual(['other', 'other', 'main', 'main'], call_list)
|
|
||||||
|
|
||||||
def test_non_destructive(self):
|
|
||||||
lock_file = os.path.join(self.lock_dir, 'not-destroyed')
|
|
||||||
with open(lock_file, 'w') as f:
|
|
||||||
f.write('test')
|
|
||||||
with lockutils.lock('not-destroyed', external=True,
|
|
||||||
lock_path=self.lock_dir):
|
|
||||||
with open(lock_file) as f:
|
|
||||||
self.assertEqual('test', f.read())
|
|
||||||
|
|
||||||
|
|
||||||
class LockutilsModuleTestCase(test_base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(LockutilsModuleTestCase, self).setUp()
|
|
||||||
self.old_env = os.environ.get('OSLO_LOCK_PATH')
|
|
||||||
if self.old_env is not None:
|
|
||||||
del os.environ['OSLO_LOCK_PATH']
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
if self.old_env is not None:
|
|
||||||
os.environ['OSLO_LOCK_PATH'] = self.old_env
|
|
||||||
super(LockutilsModuleTestCase, self).tearDown()
|
|
||||||
|
|
||||||
def test_main(self):
|
|
||||||
script = '\n'.join([
|
|
||||||
'import os',
|
|
||||||
'lock_path = os.environ.get("OSLO_LOCK_PATH")',
|
|
||||||
'assert lock_path is not None',
|
|
||||||
'assert os.path.isdir(lock_path)',
|
|
||||||
])
|
|
||||||
argv = ['', sys.executable, '-c', script]
|
|
||||||
retval = lockutils._lock_wrapper(argv)
|
|
||||||
self.assertEqual(0, retval, "Bad OSLO_LOCK_PATH has been set")
|
|
||||||
|
|
||||||
def test_return_value_maintained(self):
|
|
||||||
script = '\n'.join([
|
|
||||||
'import sys',
|
|
||||||
'sys.exit(1)',
|
|
||||||
])
|
|
||||||
argv = ['', sys.executable, '-c', script]
|
|
||||||
retval = lockutils._lock_wrapper(argv)
|
|
||||||
self.assertEqual(1, retval)
|
|
||||||
|
|
||||||
def test_direct_call_explodes(self):
|
|
||||||
cmd = [sys.executable, '-m', 'oslo_concurrency.lockutils']
|
|
||||||
with open(os.devnull, 'w') as devnull:
|
|
||||||
retval = subprocess.call(cmd, stderr=devnull)
|
|
||||||
self.assertEqual(1, retval)
|
|
||||||
|
|
||||||
|
|
||||||
class TestLockFixture(test_base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestLockFixture, self).setUp()
|
|
||||||
self.config = self.useFixture(config.Config(lockutils.CONF)).config
|
|
||||||
self.tempdir = tempfile.mkdtemp()
|
|
||||||
|
|
||||||
def _check_in_lock(self):
|
|
||||||
self.assertTrue(self.lock.exists())
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self._check_in_lock()
|
|
||||||
super(TestLockFixture, self).tearDown()
|
|
||||||
|
|
||||||
def test_lock_fixture(self):
|
|
||||||
# Setup lock fixture to test that teardown is inside the lock
|
|
||||||
self.config(lock_path=self.tempdir, group='oslo_concurrency')
|
|
||||||
fixture = fixtures.LockFixture('test-lock')
|
|
||||||
self.useFixture(fixture)
|
|
||||||
self.lock = fixture.lock
|
|
||||||
|
|
||||||
|
|
||||||
class TestGetLockPath(test_base.BaseTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestGetLockPath, self).setUp()
|
|
||||||
self.conf = self.useFixture(config.Config(lockutils.CONF)).conf
|
|
||||||
|
|
||||||
def test_get_default(self):
|
|
||||||
lockutils.set_defaults(lock_path='/the/path')
|
|
||||||
self.assertEqual('/the/path', lockutils.get_lock_path(self.conf))
|
|
||||||
|
|
||||||
def test_get_override(self):
|
|
||||||
lockutils._register_opts(self.conf)
|
|
||||||
self.conf.set_override('lock_path', '/alternate/path',
|
|
||||||
group='oslo_concurrency')
|
|
||||||
self.assertEqual('/alternate/path', lockutils.get_lock_path(self.conf))
|
|
@ -1,59 +0,0 @@
|
|||||||
# Copyright 2011 Justin Santa Barbara
|
|
||||||
#
|
|
||||||
# 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 shutil
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import eventlet
|
|
||||||
from eventlet import greenpool
|
|
||||||
from oslotest import base as test_base
|
|
||||||
|
|
||||||
from oslo_concurrency import lockutils
|
|
||||||
|
|
||||||
|
|
||||||
class TestFileLocks(test_base.BaseTestCase):
|
|
||||||
|
|
||||||
def test_concurrent_green_lock_succeeds(self):
|
|
||||||
"""Verify spawn_n greenthreads with two locks run concurrently."""
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
try:
|
|
||||||
self.completed = False
|
|
||||||
|
|
||||||
def locka(wait):
|
|
||||||
a = lockutils.InterProcessLock(os.path.join(tmpdir, 'a'))
|
|
||||||
with a:
|
|
||||||
wait.wait()
|
|
||||||
self.completed = True
|
|
||||||
|
|
||||||
def lockb(wait):
|
|
||||||
b = lockutils.InterProcessLock(os.path.join(tmpdir, 'b'))
|
|
||||||
with b:
|
|
||||||
wait.wait()
|
|
||||||
|
|
||||||
wait1 = eventlet.event.Event()
|
|
||||||
wait2 = eventlet.event.Event()
|
|
||||||
pool = greenpool.GreenPool()
|
|
||||||
pool.spawn_n(locka, wait1)
|
|
||||||
pool.spawn_n(lockb, wait2)
|
|
||||||
wait2.send()
|
|
||||||
eventlet.sleep(0)
|
|
||||||
wait1.send()
|
|
||||||
pool.waitall()
|
|
||||||
|
|
||||||
self.assertTrue(self.completed)
|
|
||||||
|
|
||||||
finally:
|
|
||||||
if os.path.exists(tmpdir):
|
|
||||||
shutil.rmtree(tmpdir)
|
|
@ -1,897 +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.
|
|
||||||
|
|
||||||
from __future__ import print_function
|
|
||||||
|
|
||||||
import errno
|
|
||||||
import logging
|
|
||||||
import multiprocessing
|
|
||||||
import os
|
|
||||||
import pickle
|
|
||||||
import resource
|
|
||||||
import socket
|
|
||||||
import stat
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
|
|
||||||
import fixtures
|
|
||||||
import mock
|
|
||||||
from oslotest import base as test_base
|
|
||||||
import six
|
|
||||||
|
|
||||||
from oslo_concurrency import processutils
|
|
||||||
from oslotest import mockpatch
|
|
||||||
|
|
||||||
|
|
||||||
PROCESS_EXECUTION_ERROR_LOGGING_TEST = """#!/bin/bash
|
|
||||||
exit 41"""
|
|
||||||
|
|
||||||
TEST_EXCEPTION_AND_MASKING_SCRIPT = """#!/bin/bash
|
|
||||||
# This is to test stdout and stderr
|
|
||||||
# and the command returned in an exception
|
|
||||||
# when a non-zero exit code is returned
|
|
||||||
echo onstdout --password='"secret"'
|
|
||||||
echo onstderr --password='"secret"' 1>&2
|
|
||||||
exit 38"""
|
|
||||||
|
|
||||||
# This byte sequence is undecodable from most encoding
|
|
||||||
UNDECODABLE_BYTES = b'[a\x80\xe9\xff]'
|
|
||||||
|
|
||||||
TRUE_UTILITY = (sys.platform.startswith('darwin') and
|
|
||||||
'/usr/bin/true' or '/bin/true')
|
|
||||||
|
|
||||||
|
|
||||||
class UtilsTest(test_base.BaseTestCase):
|
|
||||||
# NOTE(jkoelker) Moar tests from nova need to be ported. But they
|
|
||||||
# need to be mock'd out. Currently they require actually
|
|
||||||
# running code.
|
|
||||||
def test_execute_unknown_kwargs(self):
|
|
||||||
self.assertRaises(processutils.UnknownArgumentError,
|
|
||||||
processutils.execute,
|
|
||||||
hozer=True)
|
|
||||||
|
|
||||||
@mock.patch.object(multiprocessing, 'cpu_count', return_value=8)
|
|
||||||
def test_get_worker_count(self, mock_cpu_count):
|
|
||||||
self.assertEqual(8, processutils.get_worker_count())
|
|
||||||
|
|
||||||
@mock.patch.object(multiprocessing, 'cpu_count',
|
|
||||||
side_effect=NotImplementedError())
|
|
||||||
def test_get_worker_count_cpu_count_not_implemented(self,
|
|
||||||
mock_cpu_count):
|
|
||||||
self.assertEqual(1, processutils.get_worker_count())
|
|
||||||
|
|
||||||
def test_execute_with_callback(self):
|
|
||||||
on_execute_callback = mock.Mock()
|
|
||||||
on_completion_callback = mock.Mock()
|
|
||||||
processutils.execute(TRUE_UTILITY)
|
|
||||||
self.assertEqual(0, on_execute_callback.call_count)
|
|
||||||
self.assertEqual(0, on_completion_callback.call_count)
|
|
||||||
|
|
||||||
processutils.execute(TRUE_UTILITY, on_execute=on_execute_callback,
|
|
||||||
on_completion=on_completion_callback)
|
|
||||||
self.assertEqual(1, on_execute_callback.call_count)
|
|
||||||
self.assertEqual(1, on_completion_callback.call_count)
|
|
||||||
|
|
||||||
@mock.patch.object(subprocess.Popen, "communicate")
|
|
||||||
def test_execute_with_callback_and_errors(self, mock_comm):
|
|
||||||
on_execute_callback = mock.Mock()
|
|
||||||
on_completion_callback = mock.Mock()
|
|
||||||
|
|
||||||
def fake_communicate(*args):
|
|
||||||
raise IOError("Broken pipe")
|
|
||||||
|
|
||||||
mock_comm.side_effect = fake_communicate
|
|
||||||
|
|
||||||
self.assertRaises(IOError,
|
|
||||||
processutils.execute,
|
|
||||||
TRUE_UTILITY,
|
|
||||||
on_execute=on_execute_callback,
|
|
||||||
on_completion=on_completion_callback)
|
|
||||||
self.assertEqual(1, on_execute_callback.call_count)
|
|
||||||
self.assertEqual(1, on_completion_callback.call_count)
|
|
||||||
|
|
||||||
def test_execute_with_preexec_fn(self):
|
|
||||||
# NOTE(dims): preexec_fn is set to a callable object, this object
|
|
||||||
# will be called in the child process just before the child is
|
|
||||||
# executed. So we cannot pass share variables etc, simplest is to
|
|
||||||
# check if a specific exception is thrown which can be caught here.
|
|
||||||
def preexec_fn():
|
|
||||||
raise processutils.InvalidArgumentError()
|
|
||||||
|
|
||||||
processutils.execute(TRUE_UTILITY)
|
|
||||||
|
|
||||||
expected_exception = (processutils.InvalidArgumentError if six.PY2
|
|
||||||
else subprocess.SubprocessError)
|
|
||||||
self.assertRaises(expected_exception,
|
|
||||||
processutils.execute,
|
|
||||||
TRUE_UTILITY,
|
|
||||||
preexec_fn=preexec_fn)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessExecutionErrorTest(test_base.BaseTestCase):
|
|
||||||
|
|
||||||
def test_defaults(self):
|
|
||||||
err = processutils.ProcessExecutionError()
|
|
||||||
self.assertTrue('None\n' in six.text_type(err))
|
|
||||||
self.assertTrue('code: -\n' in six.text_type(err))
|
|
||||||
|
|
||||||
def test_with_description(self):
|
|
||||||
description = 'The Narwhal Bacons at Midnight'
|
|
||||||
err = processutils.ProcessExecutionError(description=description)
|
|
||||||
self.assertTrue(description in six.text_type(err))
|
|
||||||
|
|
||||||
def test_with_exit_code(self):
|
|
||||||
exit_code = 0
|
|
||||||
err = processutils.ProcessExecutionError(exit_code=exit_code)
|
|
||||||
self.assertTrue(str(exit_code) in six.text_type(err))
|
|
||||||
|
|
||||||
def test_with_cmd(self):
|
|
||||||
cmd = 'telinit'
|
|
||||||
err = processutils.ProcessExecutionError(cmd=cmd)
|
|
||||||
self.assertTrue(cmd in six.text_type(err))
|
|
||||||
|
|
||||||
def test_with_stdout(self):
|
|
||||||
stdout = """
|
|
||||||
Lo, praise of the prowess of people-kings
|
|
||||||
of spear-armed Danes, in days long sped,
|
|
||||||
we have heard, and what honot the athelings won!
|
|
||||||
Oft Scyld the Scefing from squadroned foes,
|
|
||||||
from many a tribe, the mead-bench tore,
|
|
||||||
awing the earls. Since erse he lay
|
|
||||||
friendless, a foundling, fate repaid him:
|
|
||||||
for he waxed under welkin, in wealth he trove,
|
|
||||||
till before him the folk, both far and near,
|
|
||||||
who house by the whale-path, heard his mandate,
|
|
||||||
gabe him gits: a good king he!
|
|
||||||
To him an heir was afterward born,
|
|
||||||
a son in his halls, whom heaven sent
|
|
||||||
to favor the fol, feeling their woe
|
|
||||||
that erst they had lacked an earl for leader
|
|
||||||
so long a while; the Lord endowed him,
|
|
||||||
the Wielder of Wonder, with world's renown.
|
|
||||||
""".strip()
|
|
||||||
err = processutils.ProcessExecutionError(stdout=stdout)
|
|
||||||
print(six.text_type(err))
|
|
||||||
self.assertTrue('people-kings' in six.text_type(err))
|
|
||||||
|
|
||||||
def test_with_stderr(self):
|
|
||||||
stderr = 'Cottonian library'
|
|
||||||
err = processutils.ProcessExecutionError(stderr=stderr)
|
|
||||||
self.assertTrue(stderr in six.text_type(err))
|
|
||||||
|
|
||||||
def test_retry_on_failure(self):
|
|
||||||
fd, tmpfilename = tempfile.mkstemp()
|
|
||||||
_, tmpfilename2 = tempfile.mkstemp()
|
|
||||||
try:
|
|
||||||
fp = os.fdopen(fd, 'w+')
|
|
||||||
fp.write('''#!/bin/sh
|
|
||||||
# If stdin fails to get passed during one of the runs, make a note.
|
|
||||||
if ! grep -q foo
|
|
||||||
then
|
|
||||||
echo 'failure' > "$1"
|
|
||||||
fi
|
|
||||||
# If stdin has failed to get passed during this or a previous run, exit early.
|
|
||||||
if grep failure "$1"
|
|
||||||
then
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
runs="$(cat $1)"
|
|
||||||
if [ -z "$runs" ]
|
|
||||||
then
|
|
||||||
runs=0
|
|
||||||
fi
|
|
||||||
runs=$(($runs + 1))
|
|
||||||
echo $runs > "$1"
|
|
||||||
exit 1
|
|
||||||
''')
|
|
||||||
fp.close()
|
|
||||||
os.chmod(tmpfilename, 0o755)
|
|
||||||
self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.execute,
|
|
||||||
tmpfilename, tmpfilename2, attempts=10,
|
|
||||||
process_input=b'foo',
|
|
||||||
delay_on_retry=False)
|
|
||||||
fp = open(tmpfilename2, 'r')
|
|
||||||
runs = fp.read()
|
|
||||||
fp.close()
|
|
||||||
self.assertNotEqual('failure', 'stdin did not '
|
|
||||||
'always get passed '
|
|
||||||
'correctly',
|
|
||||||
runs.strip())
|
|
||||||
runs = int(runs.strip())
|
|
||||||
self.assertEqual(10, runs, 'Ran %d times instead of 10.' % (runs,))
|
|
||||||
finally:
|
|
||||||
os.unlink(tmpfilename)
|
|
||||||
os.unlink(tmpfilename2)
|
|
||||||
|
|
||||||
def test_unknown_kwargs_raises_error(self):
|
|
||||||
self.assertRaises(processutils.UnknownArgumentError,
|
|
||||||
processutils.execute,
|
|
||||||
'/usr/bin/env', 'true',
|
|
||||||
this_is_not_a_valid_kwarg=True)
|
|
||||||
|
|
||||||
def test_check_exit_code_boolean(self):
|
|
||||||
processutils.execute('/usr/bin/env', 'false', check_exit_code=False)
|
|
||||||
self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.execute,
|
|
||||||
'/usr/bin/env', 'false', check_exit_code=True)
|
|
||||||
|
|
||||||
def test_check_cwd(self):
|
|
||||||
tmpdir = tempfile.mkdtemp()
|
|
||||||
out, err = processutils.execute('/usr/bin/env',
|
|
||||||
'sh', '-c', 'pwd',
|
|
||||||
cwd=tmpdir)
|
|
||||||
self.assertIn(tmpdir, out)
|
|
||||||
|
|
||||||
def test_check_exit_code_list(self):
|
|
||||||
processutils.execute('/usr/bin/env', 'sh', '-c', 'exit 101',
|
|
||||||
check_exit_code=(101, 102))
|
|
||||||
processutils.execute('/usr/bin/env', 'sh', '-c', 'exit 102',
|
|
||||||
check_exit_code=(101, 102))
|
|
||||||
self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.execute,
|
|
||||||
'/usr/bin/env', 'sh', '-c', 'exit 103',
|
|
||||||
check_exit_code=(101, 102))
|
|
||||||
self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.execute,
|
|
||||||
'/usr/bin/env', 'sh', '-c', 'exit 0',
|
|
||||||
check_exit_code=(101, 102))
|
|
||||||
|
|
||||||
def test_no_retry_on_success(self):
|
|
||||||
fd, tmpfilename = tempfile.mkstemp()
|
|
||||||
_, tmpfilename2 = tempfile.mkstemp()
|
|
||||||
try:
|
|
||||||
fp = os.fdopen(fd, 'w+')
|
|
||||||
fp.write("""#!/bin/sh
|
|
||||||
# If we've already run, bail out.
|
|
||||||
grep -q foo "$1" && exit 1
|
|
||||||
# Mark that we've run before.
|
|
||||||
echo foo > "$1"
|
|
||||||
# Check that stdin gets passed correctly.
|
|
||||||
grep foo
|
|
||||||
""")
|
|
||||||
fp.close()
|
|
||||||
os.chmod(tmpfilename, 0o755)
|
|
||||||
processutils.execute(tmpfilename,
|
|
||||||
tmpfilename2,
|
|
||||||
process_input=b'foo',
|
|
||||||
attempts=2)
|
|
||||||
finally:
|
|
||||||
os.unlink(tmpfilename)
|
|
||||||
os.unlink(tmpfilename2)
|
|
||||||
|
|
||||||
# This test and the one below ensures that when communicate raises
|
|
||||||
# an OSError, we do the right thing(s)
|
|
||||||
def test_exception_on_communicate_error(self):
|
|
||||||
mock = self.useFixture(mockpatch.Patch(
|
|
||||||
'subprocess.Popen.communicate',
|
|
||||||
side_effect=OSError(errno.EAGAIN, 'fake-test')))
|
|
||||||
|
|
||||||
self.assertRaises(OSError,
|
|
||||||
processutils.execute,
|
|
||||||
'/usr/bin/env',
|
|
||||||
'false',
|
|
||||||
check_exit_code=False)
|
|
||||||
|
|
||||||
self.assertEqual(1, mock.mock.call_count)
|
|
||||||
|
|
||||||
def test_retry_on_communicate_error(self):
|
|
||||||
mock = self.useFixture(mockpatch.Patch(
|
|
||||||
'subprocess.Popen.communicate',
|
|
||||||
side_effect=OSError(errno.EAGAIN, 'fake-test')))
|
|
||||||
|
|
||||||
self.assertRaises(OSError,
|
|
||||||
processutils.execute,
|
|
||||||
'/usr/bin/env',
|
|
||||||
'false',
|
|
||||||
check_exit_code=False,
|
|
||||||
attempts=5)
|
|
||||||
|
|
||||||
self.assertEqual(5, mock.mock.call_count)
|
|
||||||
|
|
||||||
def _test_and_check_logging_communicate_errors(self, log_errors=None,
|
|
||||||
attempts=None):
|
|
||||||
mock = self.useFixture(mockpatch.Patch(
|
|
||||||
'subprocess.Popen.communicate',
|
|
||||||
side_effect=OSError(errno.EAGAIN, 'fake-test')))
|
|
||||||
|
|
||||||
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
|
||||||
kwargs = {}
|
|
||||||
|
|
||||||
if log_errors:
|
|
||||||
kwargs.update({"log_errors": log_errors})
|
|
||||||
|
|
||||||
if attempts:
|
|
||||||
kwargs.update({"attempts": attempts})
|
|
||||||
|
|
||||||
self.assertRaises(OSError,
|
|
||||||
processutils.execute,
|
|
||||||
'/usr/bin/env',
|
|
||||||
'false',
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
self.assertEqual(attempts if attempts else 1, mock.mock.call_count)
|
|
||||||
self.assertIn('Got an OSError', fixture.output)
|
|
||||||
self.assertIn('errno: %d' % errno.EAGAIN, fixture.output)
|
|
||||||
self.assertIn("'/usr/bin/env false'", fixture.output)
|
|
||||||
|
|
||||||
def test_logging_on_communicate_error_1(self):
|
|
||||||
self._test_and_check_logging_communicate_errors(
|
|
||||||
log_errors=processutils.LOG_FINAL_ERROR,
|
|
||||||
attempts=None)
|
|
||||||
|
|
||||||
def test_logging_on_communicate_error_2(self):
|
|
||||||
self._test_and_check_logging_communicate_errors(
|
|
||||||
log_errors=processutils.LOG_FINAL_ERROR,
|
|
||||||
attempts=1)
|
|
||||||
|
|
||||||
def test_logging_on_communicate_error_3(self):
|
|
||||||
self._test_and_check_logging_communicate_errors(
|
|
||||||
log_errors=processutils.LOG_FINAL_ERROR,
|
|
||||||
attempts=5)
|
|
||||||
|
|
||||||
def test_logging_on_communicate_error_4(self):
|
|
||||||
self._test_and_check_logging_communicate_errors(
|
|
||||||
log_errors=processutils.LOG_ALL_ERRORS,
|
|
||||||
attempts=None)
|
|
||||||
|
|
||||||
def test_logging_on_communicate_error_5(self):
|
|
||||||
self._test_and_check_logging_communicate_errors(
|
|
||||||
log_errors=processutils.LOG_ALL_ERRORS,
|
|
||||||
attempts=1)
|
|
||||||
|
|
||||||
def test_logging_on_communicate_error_6(self):
|
|
||||||
self._test_and_check_logging_communicate_errors(
|
|
||||||
log_errors=processutils.LOG_ALL_ERRORS,
|
|
||||||
attempts=5)
|
|
||||||
|
|
||||||
def test_with_env_variables(self):
|
|
||||||
env_vars = {'SUPER_UNIQUE_VAR': 'The answer is 42'}
|
|
||||||
|
|
||||||
out, err = processutils.execute('/usr/bin/env', env_variables=env_vars)
|
|
||||||
self.assertIsInstance(out, str)
|
|
||||||
self.assertIsInstance(err, str)
|
|
||||||
|
|
||||||
self.assertIn('SUPER_UNIQUE_VAR=The answer is 42', out)
|
|
||||||
|
|
||||||
def test_binary(self):
|
|
||||||
env_vars = {'SUPER_UNIQUE_VAR': 'The answer is 42'}
|
|
||||||
|
|
||||||
out, err = processutils.execute('/usr/bin/env',
|
|
||||||
env_variables=env_vars,
|
|
||||||
binary=True)
|
|
||||||
self.assertIsInstance(out, bytes)
|
|
||||||
self.assertIsInstance(err, bytes)
|
|
||||||
|
|
||||||
self.assertIn(b'SUPER_UNIQUE_VAR=The answer is 42', out)
|
|
||||||
|
|
||||||
def test_exception_and_masking(self):
|
|
||||||
tmpfilename = self.create_tempfiles(
|
|
||||||
[["test_exceptions_and_masking",
|
|
||||||
TEST_EXCEPTION_AND_MASKING_SCRIPT]], ext='bash')[0]
|
|
||||||
|
|
||||||
os.chmod(tmpfilename, (stat.S_IRWXU |
|
|
||||||
stat.S_IRGRP |
|
|
||||||
stat.S_IXGRP |
|
|
||||||
stat.S_IROTH |
|
|
||||||
stat.S_IXOTH))
|
|
||||||
|
|
||||||
err = self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.execute,
|
|
||||||
tmpfilename, 'password="secret"',
|
|
||||||
'something')
|
|
||||||
|
|
||||||
self.assertEqual(38, err.exit_code)
|
|
||||||
self.assertIsInstance(err.stdout, six.text_type)
|
|
||||||
self.assertIsInstance(err.stderr, six.text_type)
|
|
||||||
self.assertIn('onstdout --password="***"', err.stdout)
|
|
||||||
self.assertIn('onstderr --password="***"', err.stderr)
|
|
||||||
self.assertEqual(' '.join([tmpfilename,
|
|
||||||
'password="***"',
|
|
||||||
'something']),
|
|
||||||
err.cmd)
|
|
||||||
self.assertNotIn('secret', str(err))
|
|
||||||
|
|
||||||
def execute_undecodable_bytes(self, out_bytes, err_bytes,
|
|
||||||
exitcode=0, binary=False):
|
|
||||||
if six.PY3:
|
|
||||||
code = ';'.join(('import sys',
|
|
||||||
'sys.stdout.buffer.write(%a)' % out_bytes,
|
|
||||||
'sys.stdout.flush()',
|
|
||||||
'sys.stderr.buffer.write(%a)' % err_bytes,
|
|
||||||
'sys.stderr.flush()',
|
|
||||||
'sys.exit(%s)' % exitcode))
|
|
||||||
else:
|
|
||||||
code = ';'.join(('import sys',
|
|
||||||
'sys.stdout.write(%r)' % out_bytes,
|
|
||||||
'sys.stdout.flush()',
|
|
||||||
'sys.stderr.write(%r)' % err_bytes,
|
|
||||||
'sys.stderr.flush()',
|
|
||||||
'sys.exit(%s)' % exitcode))
|
|
||||||
|
|
||||||
return processutils.execute(sys.executable, '-c', code, binary=binary)
|
|
||||||
|
|
||||||
def check_undecodable_bytes(self, binary):
|
|
||||||
out_bytes = b'out: ' + UNDECODABLE_BYTES
|
|
||||||
err_bytes = b'err: ' + UNDECODABLE_BYTES
|
|
||||||
out, err = self.execute_undecodable_bytes(out_bytes, err_bytes,
|
|
||||||
binary=binary)
|
|
||||||
if six.PY3 and not binary:
|
|
||||||
self.assertEqual(os.fsdecode(out_bytes), out)
|
|
||||||
self.assertEqual(os.fsdecode(err_bytes), err)
|
|
||||||
else:
|
|
||||||
self.assertEqual(out, out_bytes)
|
|
||||||
self.assertEqual(err, err_bytes)
|
|
||||||
|
|
||||||
def test_undecodable_bytes(self):
|
|
||||||
self.check_undecodable_bytes(False)
|
|
||||||
|
|
||||||
def test_binary_undecodable_bytes(self):
|
|
||||||
self.check_undecodable_bytes(True)
|
|
||||||
|
|
||||||
def check_undecodable_bytes_error(self, binary):
|
|
||||||
out_bytes = b'out: password="secret1" ' + UNDECODABLE_BYTES
|
|
||||||
err_bytes = b'err: password="secret2" ' + UNDECODABLE_BYTES
|
|
||||||
exc = self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
self.execute_undecodable_bytes,
|
|
||||||
out_bytes, err_bytes, exitcode=1,
|
|
||||||
binary=binary)
|
|
||||||
|
|
||||||
out = exc.stdout
|
|
||||||
err = exc.stderr
|
|
||||||
out_bytes = b'out: password="***" ' + UNDECODABLE_BYTES
|
|
||||||
err_bytes = b'err: password="***" ' + UNDECODABLE_BYTES
|
|
||||||
if six.PY3:
|
|
||||||
# On Python 3, stdout and stderr attributes of
|
|
||||||
# ProcessExecutionError must always be Unicode
|
|
||||||
self.assertEqual(os.fsdecode(out_bytes), out)
|
|
||||||
self.assertEqual(os.fsdecode(err_bytes), err)
|
|
||||||
else:
|
|
||||||
# On Python 2, stdout and stderr attributes of
|
|
||||||
# ProcessExecutionError must always be bytes
|
|
||||||
self.assertEqual(out_bytes, out)
|
|
||||||
self.assertEqual(err_bytes, err)
|
|
||||||
|
|
||||||
def test_undecodable_bytes_error(self):
|
|
||||||
self.check_undecodable_bytes_error(False)
|
|
||||||
|
|
||||||
def test_binary_undecodable_bytes_error(self):
|
|
||||||
self.check_undecodable_bytes_error(True)
|
|
||||||
|
|
||||||
def test_picklable(self):
|
|
||||||
exc = processutils.ProcessExecutionError(
|
|
||||||
stdout='my stdout', stderr='my stderr',
|
|
||||||
exit_code=42, cmd='my cmd',
|
|
||||||
description='my description')
|
|
||||||
exc_message = str(exc)
|
|
||||||
|
|
||||||
exc = pickle.loads(pickle.dumps(exc))
|
|
||||||
self.assertEqual('my stdout', exc.stdout)
|
|
||||||
self.assertEqual('my stderr', exc.stderr)
|
|
||||||
self.assertEqual(42, exc.exit_code)
|
|
||||||
self.assertEqual('my cmd', exc.cmd)
|
|
||||||
self.assertEqual('my description', exc.description)
|
|
||||||
self.assertEqual(str(exc), exc_message)
|
|
||||||
|
|
||||||
|
|
||||||
class ProcessExecutionErrorLoggingTest(test_base.BaseTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(ProcessExecutionErrorLoggingTest, self).setUp()
|
|
||||||
self.tmpfilename = self.create_tempfiles(
|
|
||||||
[["process_execution_error_logging_test",
|
|
||||||
PROCESS_EXECUTION_ERROR_LOGGING_TEST]],
|
|
||||||
ext='bash')[0]
|
|
||||||
|
|
||||||
os.chmod(self.tmpfilename, (stat.S_IRWXU | stat.S_IRGRP |
|
|
||||||
stat.S_IXGRP | stat.S_IROTH |
|
|
||||||
stat.S_IXOTH))
|
|
||||||
|
|
||||||
def _test_and_check(self, log_errors=None, attempts=None):
|
|
||||||
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
|
||||||
kwargs = {}
|
|
||||||
|
|
||||||
if log_errors:
|
|
||||||
kwargs.update({"log_errors": log_errors})
|
|
||||||
|
|
||||||
if attempts:
|
|
||||||
kwargs.update({"attempts": attempts})
|
|
||||||
|
|
||||||
err = self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.execute,
|
|
||||||
self.tmpfilename,
|
|
||||||
**kwargs)
|
|
||||||
|
|
||||||
self.assertEqual(41, err.exit_code)
|
|
||||||
self.assertIn(self.tmpfilename, fixture.output)
|
|
||||||
|
|
||||||
def test_with_invalid_log_errors(self):
|
|
||||||
self.assertRaises(processutils.InvalidArgumentError,
|
|
||||||
processutils.execute,
|
|
||||||
self.tmpfilename,
|
|
||||||
log_errors='invalid')
|
|
||||||
|
|
||||||
def test_with_log_errors_NONE(self):
|
|
||||||
self._test_and_check(log_errors=None, attempts=None)
|
|
||||||
|
|
||||||
def test_with_log_errors_final(self):
|
|
||||||
self._test_and_check(log_errors=processutils.LOG_FINAL_ERROR,
|
|
||||||
attempts=None)
|
|
||||||
|
|
||||||
def test_with_log_errors_all(self):
|
|
||||||
self._test_and_check(log_errors=processutils.LOG_ALL_ERRORS,
|
|
||||||
attempts=None)
|
|
||||||
|
|
||||||
def test_multiattempt_with_log_errors_NONE(self):
|
|
||||||
self._test_and_check(log_errors=None, attempts=3)
|
|
||||||
|
|
||||||
def test_multiattempt_with_log_errors_final(self):
|
|
||||||
self._test_and_check(log_errors=processutils.LOG_FINAL_ERROR,
|
|
||||||
attempts=3)
|
|
||||||
|
|
||||||
def test_multiattempt_with_log_errors_all(self):
|
|
||||||
self._test_and_check(log_errors=processutils.LOG_ALL_ERRORS,
|
|
||||||
attempts=3)
|
|
||||||
|
|
||||||
|
|
||||||
def fake_execute(*cmd, **kwargs):
|
|
||||||
return 'stdout', 'stderr'
|
|
||||||
|
|
||||||
|
|
||||||
def fake_execute_raises(*cmd, **kwargs):
|
|
||||||
raise processutils.ProcessExecutionError(exit_code=42,
|
|
||||||
stdout='stdout',
|
|
||||||
stderr='stderr',
|
|
||||||
cmd=['this', 'is', 'a',
|
|
||||||
'command'])
|
|
||||||
|
|
||||||
|
|
||||||
class TryCmdTestCase(test_base.BaseTestCase):
|
|
||||||
def test_keep_warnings(self):
|
|
||||||
self.useFixture(fixtures.MonkeyPatch(
|
|
||||||
'oslo_concurrency.processutils.execute', fake_execute))
|
|
||||||
o, e = processutils.trycmd('this is a command'.split(' '))
|
|
||||||
self.assertNotEqual('', o)
|
|
||||||
self.assertNotEqual('', e)
|
|
||||||
|
|
||||||
def test_keep_warnings_from_raise(self):
|
|
||||||
self.useFixture(fixtures.MonkeyPatch(
|
|
||||||
'oslo_concurrency.processutils.execute', fake_execute_raises))
|
|
||||||
o, e = processutils.trycmd('this is a command'.split(' '),
|
|
||||||
discard_warnings=True)
|
|
||||||
self.assertIsNotNone(o)
|
|
||||||
self.assertNotEqual('', e)
|
|
||||||
|
|
||||||
def test_discard_warnings(self):
|
|
||||||
self.useFixture(fixtures.MonkeyPatch(
|
|
||||||
'oslo_concurrency.processutils.execute', fake_execute))
|
|
||||||
o, e = processutils.trycmd('this is a command'.split(' '),
|
|
||||||
discard_warnings=True)
|
|
||||||
self.assertIsNotNone(o)
|
|
||||||
self.assertEqual('', e)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeSshChannel(object):
|
|
||||||
def __init__(self, rc):
|
|
||||||
self.rc = rc
|
|
||||||
|
|
||||||
def recv_exit_status(self):
|
|
||||||
return self.rc
|
|
||||||
|
|
||||||
|
|
||||||
class FakeSshStream(six.BytesIO):
|
|
||||||
def setup_channel(self, rc):
|
|
||||||
self.channel = FakeSshChannel(rc)
|
|
||||||
|
|
||||||
|
|
||||||
class FakeSshConnection(object):
|
|
||||||
def __init__(self, rc, out=b'stdout', err=b'stderr'):
|
|
||||||
self.rc = rc
|
|
||||||
self.out = out
|
|
||||||
self.err = err
|
|
||||||
|
|
||||||
def exec_command(self, cmd, timeout=None):
|
|
||||||
if timeout:
|
|
||||||
raise socket.timeout()
|
|
||||||
stdout = FakeSshStream(self.out)
|
|
||||||
stdout.setup_channel(self.rc)
|
|
||||||
return (six.BytesIO(),
|
|
||||||
stdout,
|
|
||||||
six.BytesIO(self.err))
|
|
||||||
|
|
||||||
|
|
||||||
class SshExecuteTestCase(test_base.BaseTestCase):
|
|
||||||
def test_invalid_addl_env(self):
|
|
||||||
self.assertRaises(processutils.InvalidArgumentError,
|
|
||||||
processutils.ssh_execute,
|
|
||||||
None, 'ls', addl_env='important')
|
|
||||||
|
|
||||||
def test_invalid_process_input(self):
|
|
||||||
self.assertRaises(processutils.InvalidArgumentError,
|
|
||||||
processutils.ssh_execute,
|
|
||||||
None, 'ls', process_input='important')
|
|
||||||
|
|
||||||
def test_timeout_error(self):
|
|
||||||
self.assertRaises(socket.timeout,
|
|
||||||
processutils.ssh_execute,
|
|
||||||
FakeSshConnection(0), 'ls',
|
|
||||||
timeout=10)
|
|
||||||
|
|
||||||
def test_works(self):
|
|
||||||
out, err = processutils.ssh_execute(FakeSshConnection(0), 'ls')
|
|
||||||
self.assertEqual('stdout', out)
|
|
||||||
self.assertEqual('stderr', err)
|
|
||||||
self.assertIsInstance(out, six.text_type)
|
|
||||||
self.assertIsInstance(err, six.text_type)
|
|
||||||
|
|
||||||
def test_binary(self):
|
|
||||||
o, e = processutils.ssh_execute(FakeSshConnection(0), 'ls',
|
|
||||||
binary=True)
|
|
||||||
self.assertEqual(b'stdout', o)
|
|
||||||
self.assertEqual(b'stderr', e)
|
|
||||||
self.assertIsInstance(o, bytes)
|
|
||||||
self.assertIsInstance(e, bytes)
|
|
||||||
|
|
||||||
def check_undecodable_bytes(self, binary):
|
|
||||||
out_bytes = b'out: ' + UNDECODABLE_BYTES
|
|
||||||
err_bytes = b'err: ' + UNDECODABLE_BYTES
|
|
||||||
conn = FakeSshConnection(0, out=out_bytes, err=err_bytes)
|
|
||||||
|
|
||||||
out, err = processutils.ssh_execute(conn, 'ls', binary=binary)
|
|
||||||
if six.PY3 and not binary:
|
|
||||||
self.assertEqual(os.fsdecode(out_bytes), out)
|
|
||||||
self.assertEqual(os.fsdecode(err_bytes), err)
|
|
||||||
else:
|
|
||||||
self.assertEqual(out_bytes, out)
|
|
||||||
self.assertEqual(err_bytes, err)
|
|
||||||
|
|
||||||
def test_undecodable_bytes(self):
|
|
||||||
self.check_undecodable_bytes(False)
|
|
||||||
|
|
||||||
def test_binary_undecodable_bytes(self):
|
|
||||||
self.check_undecodable_bytes(True)
|
|
||||||
|
|
||||||
def check_undecodable_bytes_error(self, binary):
|
|
||||||
out_bytes = b'out: password="secret1" ' + UNDECODABLE_BYTES
|
|
||||||
err_bytes = b'err: password="secret2" ' + UNDECODABLE_BYTES
|
|
||||||
conn = FakeSshConnection(1, out=out_bytes, err=err_bytes)
|
|
||||||
|
|
||||||
out_bytes = b'out: password="***" ' + UNDECODABLE_BYTES
|
|
||||||
err_bytes = b'err: password="***" ' + UNDECODABLE_BYTES
|
|
||||||
|
|
||||||
exc = self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.ssh_execute,
|
|
||||||
conn, 'ls',
|
|
||||||
binary=binary, check_exit_code=True)
|
|
||||||
|
|
||||||
out = exc.stdout
|
|
||||||
err = exc.stderr
|
|
||||||
if six.PY3:
|
|
||||||
# On Python 3, stdout and stderr attributes of
|
|
||||||
# ProcessExecutionError must always be Unicode
|
|
||||||
self.assertEqual(os.fsdecode(out_bytes), out)
|
|
||||||
self.assertEqual(os.fsdecode(err_bytes), err)
|
|
||||||
else:
|
|
||||||
# On Python 2, stdout and stderr attributes of
|
|
||||||
# ProcessExecutionError must always be bytes
|
|
||||||
self.assertEqual(out_bytes, out)
|
|
||||||
self.assertEqual(err_bytes, err)
|
|
||||||
|
|
||||||
def test_undecodable_bytes_error(self):
|
|
||||||
self.check_undecodable_bytes_error(False)
|
|
||||||
|
|
||||||
def test_binary_undecodable_bytes_error(self):
|
|
||||||
self.check_undecodable_bytes_error(True)
|
|
||||||
|
|
||||||
def test_fails(self):
|
|
||||||
self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.ssh_execute, FakeSshConnection(1), 'ls')
|
|
||||||
|
|
||||||
def _test_compromising_ssh(self, rc, check):
|
|
||||||
fixture = self.useFixture(fixtures.FakeLogger(level=logging.DEBUG))
|
|
||||||
fake_stdin = six.BytesIO()
|
|
||||||
|
|
||||||
fake_stdout = mock.Mock()
|
|
||||||
fake_stdout.channel.recv_exit_status.return_value = rc
|
|
||||||
fake_stdout.read.return_value = b'password="secret"'
|
|
||||||
|
|
||||||
fake_stderr = six.BytesIO(b'password="foobar"')
|
|
||||||
|
|
||||||
command = 'ls --password="bar"'
|
|
||||||
|
|
||||||
connection = mock.Mock()
|
|
||||||
connection.exec_command.return_value = (fake_stdin, fake_stdout,
|
|
||||||
fake_stderr)
|
|
||||||
|
|
||||||
if check and rc != -1 and rc != 0:
|
|
||||||
err = self.assertRaises(processutils.ProcessExecutionError,
|
|
||||||
processutils.ssh_execute,
|
|
||||||
connection, command,
|
|
||||||
check_exit_code=check)
|
|
||||||
|
|
||||||
self.assertEqual(rc, err.exit_code)
|
|
||||||
self.assertEqual('password="***"', err.stdout)
|
|
||||||
self.assertEqual('password="***"', err.stderr)
|
|
||||||
self.assertEqual('ls --password="***"', err.cmd)
|
|
||||||
self.assertNotIn('secret', str(err))
|
|
||||||
self.assertNotIn('foobar', str(err))
|
|
||||||
else:
|
|
||||||
o, e = processutils.ssh_execute(connection, command,
|
|
||||||
check_exit_code=check)
|
|
||||||
self.assertEqual('password="***"', o)
|
|
||||||
self.assertEqual('password="***"', e)
|
|
||||||
self.assertIn('password="***"', fixture.output)
|
|
||||||
self.assertNotIn('bar', fixture.output)
|
|
||||||
|
|
||||||
def test_compromising_ssh1(self):
|
|
||||||
self._test_compromising_ssh(rc=-1, check=True)
|
|
||||||
|
|
||||||
def test_compromising_ssh2(self):
|
|
||||||
self._test_compromising_ssh(rc=0, check=True)
|
|
||||||
|
|
||||||
def test_compromising_ssh3(self):
|
|
||||||
self._test_compromising_ssh(rc=1, check=True)
|
|
||||||
|
|
||||||
def test_compromising_ssh4(self):
|
|
||||||
self._test_compromising_ssh(rc=1, check=False)
|
|
||||||
|
|
||||||
def test_compromising_ssh5(self):
|
|
||||||
self._test_compromising_ssh(rc=0, check=False)
|
|
||||||
|
|
||||||
def test_compromising_ssh6(self):
|
|
||||||
self._test_compromising_ssh(rc=-1, check=False)
|
|
||||||
|
|
||||||
|
|
||||||
class PrlimitTestCase(test_base.BaseTestCase):
|
|
||||||
# Simply program that does nothing and returns an exit code 0.
|
|
||||||
# Use Python to be portable.
|
|
||||||
SIMPLE_PROGRAM = [sys.executable, '-c', 'pass']
|
|
||||||
|
|
||||||
def soft_limit(self, res, substract, default_limit):
|
|
||||||
# Create a new soft limit for a resource, lower than the current
|
|
||||||
# soft limit.
|
|
||||||
soft_limit, hard_limit = resource.getrlimit(res)
|
|
||||||
if soft_limit <= 0:
|
|
||||||
soft_limit = default_limit
|
|
||||||
else:
|
|
||||||
soft_limit -= substract
|
|
||||||
return soft_limit
|
|
||||||
|
|
||||||
def memory_limit(self, res):
|
|
||||||
# Substract 1 kB just to get a different limit. Don't substract too
|
|
||||||
# much to avoid memory allocation issues.
|
|
||||||
#
|
|
||||||
# Use 1 GB by default. Limit high enough to be able to load shared
|
|
||||||
# libraries. Limit low enough to be work on 32-bit platforms.
|
|
||||||
return self.soft_limit(res, 1024, 1024 ** 3)
|
|
||||||
|
|
||||||
def limit_address_space(self):
|
|
||||||
max_memory = self.memory_limit(resource.RLIMIT_AS)
|
|
||||||
return processutils.ProcessLimits(address_space=max_memory)
|
|
||||||
|
|
||||||
def test_simple(self):
|
|
||||||
# Simple test running a program (/bin/true) with no parameter
|
|
||||||
prlimit = self.limit_address_space()
|
|
||||||
stdout, stderr = processutils.execute(*self.SIMPLE_PROGRAM,
|
|
||||||
prlimit=prlimit)
|
|
||||||
self.assertEqual('', stdout.rstrip())
|
|
||||||
self.assertEqual(stderr.rstrip(), '')
|
|
||||||
|
|
||||||
def check_limit(self, prlimit, resource, value):
|
|
||||||
code = ';'.join(('import resource',
|
|
||||||
'print(resource.getrlimit(resource.%s))' % resource))
|
|
||||||
args = [sys.executable, '-c', code]
|
|
||||||
stdout, stderr = processutils.execute(*args, prlimit=prlimit)
|
|
||||||
expected = (value, value)
|
|
||||||
self.assertEqual(str(expected), stdout.rstrip())
|
|
||||||
|
|
||||||
def test_address_space(self):
|
|
||||||
prlimit = self.limit_address_space()
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_AS', prlimit.address_space)
|
|
||||||
|
|
||||||
def test_core_size(self):
|
|
||||||
size = self.soft_limit(resource.RLIMIT_CORE, 1, 1024)
|
|
||||||
prlimit = processutils.ProcessLimits(core_file_size=size)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_CORE', prlimit.core_file_size)
|
|
||||||
|
|
||||||
def test_cpu_time(self):
|
|
||||||
time = self.soft_limit(resource.RLIMIT_CPU, 1, 1024)
|
|
||||||
prlimit = processutils.ProcessLimits(cpu_time=time)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_CPU', prlimit.cpu_time)
|
|
||||||
|
|
||||||
def test_data_size(self):
|
|
||||||
max_memory = self.memory_limit(resource.RLIMIT_DATA)
|
|
||||||
prlimit = processutils.ProcessLimits(data_size=max_memory)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_DATA', max_memory)
|
|
||||||
|
|
||||||
def test_file_size(self):
|
|
||||||
size = self.soft_limit(resource.RLIMIT_FSIZE, 1, 1024)
|
|
||||||
prlimit = processutils.ProcessLimits(file_size=size)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_FSIZE', prlimit.file_size)
|
|
||||||
|
|
||||||
def test_memory_locked(self):
|
|
||||||
max_memory = self.memory_limit(resource.RLIMIT_MEMLOCK)
|
|
||||||
prlimit = processutils.ProcessLimits(memory_locked=max_memory)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_MEMLOCK', max_memory)
|
|
||||||
|
|
||||||
def test_resident_set_size(self):
|
|
||||||
max_memory = self.memory_limit(resource.RLIMIT_RSS)
|
|
||||||
prlimit = processutils.ProcessLimits(resident_set_size=max_memory)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_RSS', max_memory)
|
|
||||||
|
|
||||||
def test_number_files(self):
|
|
||||||
nfiles = self.soft_limit(resource.RLIMIT_NOFILE, 1, 1024)
|
|
||||||
prlimit = processutils.ProcessLimits(number_files=nfiles)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_NOFILE', nfiles)
|
|
||||||
|
|
||||||
def test_number_processes(self):
|
|
||||||
nprocs = self.soft_limit(resource.RLIMIT_NPROC, 1, 65535)
|
|
||||||
prlimit = processutils.ProcessLimits(number_processes=nprocs)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_NPROC', nprocs)
|
|
||||||
|
|
||||||
def test_stack_size(self):
|
|
||||||
max_memory = self.memory_limit(resource.RLIMIT_STACK)
|
|
||||||
prlimit = processutils.ProcessLimits(stack_size=max_memory)
|
|
||||||
self.check_limit(prlimit, 'RLIMIT_STACK', max_memory)
|
|
||||||
|
|
||||||
def test_unsupported_prlimit(self):
|
|
||||||
self.assertRaises(ValueError, processutils.ProcessLimits, xxx=33)
|
|
||||||
|
|
||||||
def test_relative_path(self):
|
|
||||||
prlimit = self.limit_address_space()
|
|
||||||
program = sys.executable
|
|
||||||
|
|
||||||
env = dict(os.environ)
|
|
||||||
env['PATH'] = os.path.dirname(program)
|
|
||||||
args = [os.path.basename(program), '-c', 'pass']
|
|
||||||
processutils.execute(*args, prlimit=prlimit, env_variables=env)
|
|
||||||
|
|
||||||
def test_execv_error(self):
|
|
||||||
prlimit = self.limit_address_space()
|
|
||||||
args = ['/missing_path/dont_exist/program']
|
|
||||||
try:
|
|
||||||
processutils.execute(*args, prlimit=prlimit)
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
self.assertEqual(1, exc.exit_code)
|
|
||||||
self.assertEqual('', exc.stdout)
|
|
||||||
expected = ('%s -m oslo_concurrency.prlimit: '
|
|
||||||
'failed to execute /missing_path/dont_exist/program: '
|
|
||||||
% os.path.basename(sys.executable))
|
|
||||||
self.assertIn(expected, exc.stderr)
|
|
||||||
else:
|
|
||||||
self.fail("ProcessExecutionError not raised")
|
|
||||||
|
|
||||||
def test_setrlimit_error(self):
|
|
||||||
prlimit = self.limit_address_space()
|
|
||||||
|
|
||||||
# trying to set a limit higher than the current hard limit
|
|
||||||
# with setrlimit() should fail.
|
|
||||||
higher_limit = prlimit.address_space + 1024
|
|
||||||
|
|
||||||
args = [sys.executable, '-m', 'oslo_concurrency.prlimit',
|
|
||||||
'--as=%s' % higher_limit,
|
|
||||||
'--']
|
|
||||||
args.extend(self.SIMPLE_PROGRAM)
|
|
||||||
try:
|
|
||||||
processutils.execute(*args, prlimit=prlimit)
|
|
||||||
except processutils.ProcessExecutionError as exc:
|
|
||||||
self.assertEqual(1, exc.exit_code)
|
|
||||||
self.assertEqual('', exc.stdout)
|
|
||||||
expected = ('%s -m oslo_concurrency.prlimit: '
|
|
||||||
'failed to set the AS resource limit: '
|
|
||||||
% os.path.basename(sys.executable))
|
|
||||||
self.assertIn(expected, exc.stderr)
|
|
||||||
else:
|
|
||||||
self.fail("ProcessExecutionError not raised")
|
|
@ -1,18 +0,0 @@
|
|||||||
# Copyright 2016 OpenStack Foundation
|
|
||||||
#
|
|
||||||
# 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 pbr.version
|
|
||||||
|
|
||||||
version_info = pbr.version.VersionInfo('oslo_concurrency')
|
|
@ -1,76 +0,0 @@
|
|||||||
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""
|
|
||||||
Watchdog module.
|
|
||||||
|
|
||||||
.. versionadded:: 0.4
|
|
||||||
"""
|
|
||||||
|
|
||||||
import contextlib
|
|
||||||
import logging
|
|
||||||
import threading
|
|
||||||
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
|
|
||||||
@contextlib.contextmanager
|
|
||||||
def watch(logger, action, level=logging.DEBUG, after=5.0):
|
|
||||||
"""Log a message if an operation exceeds a time threshold.
|
|
||||||
|
|
||||||
This context manager is expected to be used when you are going to
|
|
||||||
do an operation in code which might either deadlock or take an
|
|
||||||
extraordinary amount of time, and you'd like to emit a status
|
|
||||||
message back to the user that the operation is still ongoing but
|
|
||||||
has not completed in an expected amount of time. This is more user
|
|
||||||
friendly than logging 'start' and 'end' events and making users
|
|
||||||
correlate the events to figure out they ended up in a deadlock.
|
|
||||||
|
|
||||||
:param logger: an object that complies to the logger definition
|
|
||||||
(has a .log method).
|
|
||||||
|
|
||||||
:param action: a meaningful string that describes the thing you
|
|
||||||
are about to do.
|
|
||||||
|
|
||||||
:param level: the logging level the message should be emitted
|
|
||||||
at. Defaults to logging.DEBUG.
|
|
||||||
|
|
||||||
:param after: the duration in seconds before the message is
|
|
||||||
emitted. Defaults to 5.0 seconds.
|
|
||||||
|
|
||||||
Example usage::
|
|
||||||
|
|
||||||
FORMAT = '%(asctime)-15s %(message)s'
|
|
||||||
logging.basicConfig(format=FORMAT)
|
|
||||||
LOG = logging.getLogger('mylogger')
|
|
||||||
|
|
||||||
with watchdog.watch(LOG, "subprocess call", logging.ERROR):
|
|
||||||
subprocess.call("sleep 10", shell=True)
|
|
||||||
print "done"
|
|
||||||
|
|
||||||
"""
|
|
||||||
watch = timeutils.StopWatch()
|
|
||||||
watch.start()
|
|
||||||
|
|
||||||
def log():
|
|
||||||
msg = "%s not completed after %0.3fs" % (action, watch.elapsed())
|
|
||||||
logger.log(level, msg)
|
|
||||||
|
|
||||||
timer = threading.Timer(after, log)
|
|
||||||
timer.start()
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
timer.cancel()
|
|
||||||
timer.join()
|
|
@ -1,3 +0,0 @@
|
|||||||
---
|
|
||||||
other:
|
|
||||||
- Switch to reno for managing release notes.
|
|
@ -1,273 +0,0 @@
|
|||||||
# -*- coding: utf-8 -*-
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# This file is execfile()d with the current directory set to its
|
|
||||||
# containing dir.
|
|
||||||
#
|
|
||||||
# Note that not all possible configuration values are present in this
|
|
||||||
# autogenerated file.
|
|
||||||
#
|
|
||||||
# All configuration values have a default; values that are commented out
|
|
||||||
# serve to show the default.
|
|
||||||
|
|
||||||
# If extensions (or modules to document with autodoc) are in another directory,
|
|
||||||
# add these directories to sys.path here. If the directory is relative to the
|
|
||||||
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
|
||||||
# sys.path.insert(0, os.path.abspath('.'))
|
|
||||||
|
|
||||||
# -- General configuration ------------------------------------------------
|
|
||||||
|
|
||||||
# If your documentation needs a minimal Sphinx version, state it here.
|
|
||||||
# needs_sphinx = '1.0'
|
|
||||||
|
|
||||||
# Add any Sphinx extension module names here, as strings. They can be
|
|
||||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
|
||||||
# ones.
|
|
||||||
extensions = [
|
|
||||||
'oslosphinx',
|
|
||||||
'reno.sphinxext',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
|
||||||
templates_path = ['_templates']
|
|
||||||
|
|
||||||
# The suffix of source filenames.
|
|
||||||
source_suffix = '.rst'
|
|
||||||
|
|
||||||
# The encoding of source files.
|
|
||||||
# source_encoding = 'utf-8-sig'
|
|
||||||
|
|
||||||
# The master toctree document.
|
|
||||||
master_doc = 'index'
|
|
||||||
|
|
||||||
# General information about the project.
|
|
||||||
project = u'oslo.concurrency Release Notes'
|
|
||||||
copyright = u'2016, oslo.concurrency Developers'
|
|
||||||
|
|
||||||
# The version info for the project you're documenting, acts as replacement for
|
|
||||||
# |version| and |release|, also used in various other places throughout the
|
|
||||||
# built documents.
|
|
||||||
#
|
|
||||||
# The short X.Y version.
|
|
||||||
from oslo_concurrency.version import version_info as oslo_concurrency_version
|
|
||||||
# The full version, including alpha/beta/rc tags.
|
|
||||||
release = oslo_concurrency_version.version_string_with_vcs()
|
|
||||||
# The short X.Y version.
|
|
||||||
version = oslo_concurrency_version.canonical_version_string()
|
|
||||||
|
|
||||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
|
||||||
# for a list of supported languages.
|
|
||||||
# language = None
|
|
||||||
|
|
||||||
# There are two options for replacing |today|: either, you set today to some
|
|
||||||
# non-false value, then it is used:
|
|
||||||
# today = ''
|
|
||||||
# Else, today_fmt is used as the format for a strftime call.
|
|
||||||
# today_fmt = '%B %d, %Y'
|
|
||||||
|
|
||||||
# List of patterns, relative to source directory, that match files and
|
|
||||||
# directories to ignore when looking for source files.
|
|
||||||
exclude_patterns = []
|
|
||||||
|
|
||||||
# The reST default role (used for this markup: `text`) to use for all
|
|
||||||
# documents.
|
|
||||||
# default_role = None
|
|
||||||
|
|
||||||
# If true, '()' will be appended to :func: etc. cross-reference text.
|
|
||||||
# add_function_parentheses = True
|
|
||||||
|
|
||||||
# If true, the current module name will be prepended to all description
|
|
||||||
# unit titles (such as .. function::).
|
|
||||||
# add_module_names = True
|
|
||||||
|
|
||||||
# If true, sectionauthor and moduleauthor directives will be shown in the
|
|
||||||
# output. They are ignored by default.
|
|
||||||
# show_authors = False
|
|
||||||
|
|
||||||
# The name of the Pygments (syntax highlighting) style to use.
|
|
||||||
pygments_style = 'sphinx'
|
|
||||||
|
|
||||||
# A list of ignored prefixes for module index sorting.
|
|
||||||
# modindex_common_prefix = []
|
|
||||||
|
|
||||||
# If true, keep warnings as "system message" paragraphs in the built documents.
|
|
||||||
# keep_warnings = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for HTML output ----------------------------------------------
|
|
||||||
|
|
||||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
|
||||||
# a list of builtin themes.
|
|
||||||
html_theme = 'default'
|
|
||||||
|
|
||||||
# Theme options are theme-specific and customize the look and feel of a theme
|
|
||||||
# further. For a list of options available for each theme, see the
|
|
||||||
# documentation.
|
|
||||||
# html_theme_options = {}
|
|
||||||
|
|
||||||
# Add any paths that contain custom themes here, relative to this directory.
|
|
||||||
# html_theme_path = []
|
|
||||||
|
|
||||||
# The name for this set of Sphinx documents. If None, it defaults to
|
|
||||||
# "<project> v<release> documentation".
|
|
||||||
# html_title = None
|
|
||||||
|
|
||||||
# A shorter title for the navigation bar. Default is the same as html_title.
|
|
||||||
# html_short_title = None
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top
|
|
||||||
# of the sidebar.
|
|
||||||
# html_logo = None
|
|
||||||
|
|
||||||
# The name of an image file (within the static path) to use as favicon of the
|
|
||||||
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
|
||||||
# pixels large.
|
|
||||||
# html_favicon = None
|
|
||||||
|
|
||||||
# Add any paths that contain custom static files (such as style sheets) here,
|
|
||||||
# relative to this directory. They are copied after the builtin static files,
|
|
||||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
|
||||||
html_static_path = ['_static']
|
|
||||||
|
|
||||||
# Add any extra paths that contain custom files (such as robots.txt or
|
|
||||||
# .htaccess) here, relative to this directory. These files are copied
|
|
||||||
# directly to the root of the documentation.
|
|
||||||
# html_extra_path = []
|
|
||||||
|
|
||||||
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
|
||||||
# using the given strftime format.
|
|
||||||
# html_last_updated_fmt = '%b %d, %Y'
|
|
||||||
|
|
||||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
|
||||||
# typographically correct entities.
|
|
||||||
# html_use_smartypants = True
|
|
||||||
|
|
||||||
# Custom sidebar templates, maps document names to template names.
|
|
||||||
# html_sidebars = {}
|
|
||||||
|
|
||||||
# Additional templates that should be rendered to pages, maps page names to
|
|
||||||
# template names.
|
|
||||||
# html_additional_pages = {}
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# html_domain_indices = True
|
|
||||||
|
|
||||||
# If false, no index is generated.
|
|
||||||
# html_use_index = True
|
|
||||||
|
|
||||||
# If true, the index is split into individual pages for each letter.
|
|
||||||
# html_split_index = False
|
|
||||||
|
|
||||||
# If true, links to the reST sources are added to the pages.
|
|
||||||
# html_show_sourcelink = True
|
|
||||||
|
|
||||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
|
||||||
# html_show_sphinx = True
|
|
||||||
|
|
||||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
|
||||||
# html_show_copyright = True
|
|
||||||
|
|
||||||
# If true, an OpenSearch description file will be output, and all pages will
|
|
||||||
# contain a <link> tag referring to it. The value of this option must be the
|
|
||||||
# base URL from which the finished HTML is served.
|
|
||||||
# html_use_opensearch = ''
|
|
||||||
|
|
||||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
|
||||||
# html_file_suffix = None
|
|
||||||
|
|
||||||
# Output file base name for HTML help builder.
|
|
||||||
htmlhelp_basename = 'oslo.concurrencyReleaseNotesDoc'
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for LaTeX output ---------------------------------------------
|
|
||||||
|
|
||||||
latex_elements = {
|
|
||||||
# The paper size ('letterpaper' or 'a4paper').
|
|
||||||
# 'papersize': 'letterpaper',
|
|
||||||
|
|
||||||
# The font size ('10pt', '11pt' or '12pt').
|
|
||||||
# 'pointsize': '10pt',
|
|
||||||
|
|
||||||
# Additional stuff for the LaTeX preamble.
|
|
||||||
# 'preamble': '',
|
|
||||||
}
|
|
||||||
|
|
||||||
# Grouping the document tree into LaTeX files. List of tuples
|
|
||||||
# (source start file, target name, title,
|
|
||||||
# author, documentclass [howto, manual, or own class]).
|
|
||||||
latex_documents = [
|
|
||||||
('index', 'oslo.concurrencyReleaseNotes.tex',
|
|
||||||
u'oslo.concurrency Release Notes Documentation',
|
|
||||||
u'oslo.concurrency Developers', 'manual'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# The name of an image file (relative to this directory) to place at the top of
|
|
||||||
# the title page.
|
|
||||||
# latex_logo = None
|
|
||||||
|
|
||||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
|
||||||
# not chapters.
|
|
||||||
# latex_use_parts = False
|
|
||||||
|
|
||||||
# If true, show page references after internal links.
|
|
||||||
# latex_show_pagerefs = False
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
# latex_show_urls = False
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
# latex_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# latex_domain_indices = True
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for manual page output ---------------------------------------
|
|
||||||
|
|
||||||
# One entry per manual page. List of tuples
|
|
||||||
# (source start file, name, description, authors, manual section).
|
|
||||||
man_pages = [
|
|
||||||
('index', 'oslo.concurrencyReleaseNotes',
|
|
||||||
u'oslo.concurrency Release Notes Documentation',
|
|
||||||
[u'oslo.concurrency Developers'], 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
# If true, show URL addresses after external links.
|
|
||||||
# man_show_urls = False
|
|
||||||
|
|
||||||
|
|
||||||
# -- Options for Texinfo output -------------------------------------------
|
|
||||||
|
|
||||||
# Grouping the document tree into Texinfo files. List of tuples
|
|
||||||
# (source start file, target name, title, author,
|
|
||||||
# dir menu entry, description, category)
|
|
||||||
texinfo_documents = [
|
|
||||||
('index', 'oslo.concurrencyReleaseNotes',
|
|
||||||
u'oslo.concurrency Release Notes Documentation',
|
|
||||||
u'oslo.concurrency Developers', 'oslo.concurrencyReleaseNotes',
|
|
||||||
'One line description of project.',
|
|
||||||
'Miscellaneous'),
|
|
||||||
]
|
|
||||||
|
|
||||||
# Documents to append as an appendix to all manuals.
|
|
||||||
# texinfo_appendices = []
|
|
||||||
|
|
||||||
# If false, no module index is generated.
|
|
||||||
# texinfo_domain_indices = True
|
|
||||||
|
|
||||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
|
||||||
# texinfo_show_urls = 'footnote'
|
|
||||||
|
|
||||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
|
||||||
# texinfo_no_detailmenu = False
|
|
@ -1,8 +0,0 @@
|
|||||||
===============================
|
|
||||||
oslo.concurrency Release Notes
|
|
||||||
===============================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 1
|
|
||||||
|
|
||||||
unreleased
|
|
@ -1,30 +0,0 @@
|
|||||||
# Andi Chandler <andi@gowling.com>, 2016. #zanata
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: oslo.concurrency Release Notes 3.11.1\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2016-07-01 03:44+0000\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"PO-Revision-Date: 2016-06-28 05:54+0000\n"
|
|
||||||
"Last-Translator: Andi Chandler <andi@gowling.com>\n"
|
|
||||||
"Language-Team: English (United Kingdom)\n"
|
|
||||||
"Language: en-GB\n"
|
|
||||||
"X-Generator: Zanata 3.7.3\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
|
||||||
|
|
||||||
msgid "3.10.0"
|
|
||||||
msgstr "3.10.0"
|
|
||||||
|
|
||||||
msgid "Other Notes"
|
|
||||||
msgstr "Other Notes"
|
|
||||||
|
|
||||||
msgid "Switch to reno for managing release notes."
|
|
||||||
msgstr "Switch to reno for managing release notes."
|
|
||||||
|
|
||||||
msgid "Unreleased Release Notes"
|
|
||||||
msgstr "Unreleased Release Notes"
|
|
||||||
|
|
||||||
msgid "oslo.concurrency Release Notes"
|
|
||||||
msgstr "oslo.concurrency Release Notes"
|
|
@ -1,5 +0,0 @@
|
|||||||
==========================
|
|
||||||
Unreleased Release Notes
|
|
||||||
==========================
|
|
||||||
|
|
||||||
.. release-notes::
|
|
@ -1,13 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
pbr>=1.6 # Apache-2.0
|
|
||||||
enum34;python_version=='2.7' or python_version=='2.6' or python_version=='3.3' # BSD
|
|
||||||
iso8601>=0.1.11 # MIT
|
|
||||||
oslo.config>=3.12.0 # Apache-2.0
|
|
||||||
oslo.i18n>=2.1.0 # Apache-2.0
|
|
||||||
oslo.utils>=3.16.0 # Apache-2.0
|
|
||||||
six>=1.9.0 # MIT
|
|
||||||
fasteners>=0.7 # Apache-2.0
|
|
||||||
retrying!=1.3.0,>=1.2.3 # Apache-2.0
|
|
58
setup.cfg
58
setup.cfg
@ -1,58 +0,0 @@
|
|||||||
[metadata]
|
|
||||||
name = oslo.concurrency
|
|
||||||
summary = Oslo Concurrency library
|
|
||||||
description-file =
|
|
||||||
README.rst
|
|
||||||
author = OpenStack
|
|
||||||
author-email = openstack-dev@lists.openstack.org
|
|
||||||
home-page = http://launchpad.net/oslo
|
|
||||||
classifier =
|
|
||||||
Environment :: OpenStack
|
|
||||||
Intended Audience :: Information Technology
|
|
||||||
Intended Audience :: System Administrators
|
|
||||||
License :: OSI Approved :: Apache Software License
|
|
||||||
Operating System :: POSIX :: Linux
|
|
||||||
Programming Language :: Python
|
|
||||||
Programming Language :: Python :: 2
|
|
||||||
Programming Language :: Python :: 2.7
|
|
||||||
Programming Language :: Python :: 3
|
|
||||||
Programming Language :: Python :: 3.4
|
|
||||||
Programming Language :: Python :: 3.5
|
|
||||||
|
|
||||||
[files]
|
|
||||||
packages =
|
|
||||||
oslo_concurrency
|
|
||||||
|
|
||||||
[entry_points]
|
|
||||||
oslo.config.opts =
|
|
||||||
oslo.concurrency = oslo_concurrency.opts:list_opts
|
|
||||||
console_scripts =
|
|
||||||
lockutils-wrapper = oslo_concurrency.lockutils:main
|
|
||||||
|
|
||||||
[build_sphinx]
|
|
||||||
source-dir = doc/source
|
|
||||||
build-dir = doc/build
|
|
||||||
all_files = 1
|
|
||||||
|
|
||||||
[upload_sphinx]
|
|
||||||
upload-dir = doc/build/html
|
|
||||||
|
|
||||||
[compile_catalog]
|
|
||||||
directory = oslo_concurrency/locale
|
|
||||||
domain = oslo_concurrency
|
|
||||||
|
|
||||||
[update_catalog]
|
|
||||||
domain = oslo_concurrency
|
|
||||||
output_dir = oslo_concurrency/locale
|
|
||||||
input_file = oslo_concurrency/locale/oslo_concurrency.pot
|
|
||||||
|
|
||||||
[extract_messages]
|
|
||||||
keywords = _ gettext ngettext l_ lazy_gettext
|
|
||||||
mapping_file = babel.cfg
|
|
||||||
output_file = oslo_concurrency/locale/oslo_concurrency.pot
|
|
||||||
|
|
||||||
[pbr]
|
|
||||||
warnerrors = True
|
|
||||||
|
|
||||||
[wheel]
|
|
||||||
universal = 1
|
|
29
setup.py
29
setup.py
@ -1,29 +0,0 @@
|
|||||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
|
|
||||||
import setuptools
|
|
||||||
|
|
||||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
|
||||||
# setuptools if some other modules registered functions in `atexit`.
|
|
||||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
|
||||||
try:
|
|
||||||
import multiprocessing # noqa
|
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
setuptools.setup(
|
|
||||||
setup_requires=['pbr>=1.8'],
|
|
||||||
pbr=True)
|
|
@ -1,16 +0,0 @@
|
|||||||
# The order of packages is significant, because pip processes them in the order
|
|
||||||
# of appearance. Changing the order has an impact on the overall integration
|
|
||||||
# process, which may cause wedges in the gate later.
|
|
||||||
|
|
||||||
hacking<0.11,>=0.10.0
|
|
||||||
oslotest>=1.10.0 # Apache-2.0
|
|
||||||
coverage>=3.6 # Apache-2.0
|
|
||||||
futures>=3.0;python_version=='2.7' or python_version=='2.6' # BSD
|
|
||||||
fixtures>=3.0.0 # Apache-2.0/BSD
|
|
||||||
|
|
||||||
# These are needed for docs generation
|
|
||||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
|
||||||
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
|
|
||||||
reno>=1.8.0 # Apache2
|
|
||||||
|
|
||||||
eventlet!=0.18.3,>=0.18.2 # MIT
|
|
42
tox.ini
42
tox.ini
@ -1,42 +0,0 @@
|
|||||||
[tox]
|
|
||||||
minversion = 1.6
|
|
||||||
envlist = py35,py34,py27,pep8
|
|
||||||
|
|
||||||
[testenv]
|
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
|
||||||
# We want to support both vanilla stdlib and eventlet monkey patched
|
|
||||||
commands =
|
|
||||||
lockutils-wrapper python setup.py testr --slowest --testr-args='{posargs}'
|
|
||||||
env TEST_EVENTLET=1 lockutils-wrapper python setup.py testr --slowest --testr-args='{posargs}'
|
|
||||||
|
|
||||||
[testenv:pep8]
|
|
||||||
commands = flake8
|
|
||||||
|
|
||||||
[testenv:venv]
|
|
||||||
commands = {posargs}
|
|
||||||
|
|
||||||
[testenv:docs]
|
|
||||||
commands = python setup.py build_sphinx
|
|
||||||
|
|
||||||
[testenv:cover]
|
|
||||||
commands = python setup.py test --coverage --coverage-package-name=oslo_concurrency --testr-args='{posargs}'
|
|
||||||
|
|
||||||
[flake8]
|
|
||||||
show-source = True
|
|
||||||
ignore = H405
|
|
||||||
exclude=.venv,.git,.tox,dist,*lib/python*,*egg,build
|
|
||||||
|
|
||||||
[hacking]
|
|
||||||
import_exceptions =
|
|
||||||
oslo_concurrency._i18n
|
|
||||||
|
|
||||||
[testenv:pip-missing-reqs]
|
|
||||||
# do not install test-requirements as that will pollute the virtualenv for
|
|
||||||
# determining missing packages
|
|
||||||
# this also means that pip-missing-reqs must be installed separately, outside
|
|
||||||
# of the requirements.txt files
|
|
||||||
deps = pip_missing_reqs
|
|
||||||
commands = pip-missing-reqs -d --ignore-module=oslo_concurrency* --ignore-file=oslo_concurrency/tests/* --ignore-file=tests/ oslo_concurrency
|
|
||||||
|
|
||||||
[testenv:releasenotes]
|
|
||||||
commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
|
Loading…
Reference in New Issue
Block a user