oslo.service-1.29.0/0000775000175100017510000000000013224676442014254 5ustar zuulzuul00000000000000oslo.service-1.29.0/HACKING.rst0000666000175100017510000000025113224676223016047 0ustar zuulzuul00000000000000oslo.service Style Commandments ====================================================== Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ oslo.service-1.29.0/test-requirements.txt0000666000175100017510000000111313224676223020510 0ustar zuulzuul00000000000000# 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. fixtures>=3.0.0 # Apache-2.0/BSD hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0 mock>=2.0.0 # BSD oslotest>=1.10.0 # Apache-2.0 # These are needed for docs generation/testing openstackdocstheme>=1.17.0 # Apache-2.0 sphinx>=1.6.2 # BSD doc8>=0.6.0 # Apache-2.0 reno>=2.5.0 # Apache-2.0 coverage!=4.4,>=4.0 # Apache-2.0 # Bandit security code scanner bandit>=1.1.0 # Apache-2.0 oslo.service-1.29.0/CONTRIBUTING.rst0000666000175100017510000000102313224676223016710 0ustar zuulzuul00000000000000If 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.service oslo.service-1.29.0/ChangeLog0000664000175100017510000004211013224676442016024 0ustar zuulzuul00000000000000CHANGES ======= 1.29.0 ------ * Maintain shared memory after fork in Python >=3.7 * Updated from global requirements * Revert "Permit aborting loopingcall while sleeping" 1.28.1 ------ 1.28.0 ------ * Remove -U from pip install * Avoid tox\_install.sh for constraints support * Updated from global requirements * Remove setting of version/release from releasenotes * Updated from global requirements 1.27.0 ------ * Updated from global requirements * change periodic\_task to catch all exceptions including BaseException * Fix bandit scan and make it voting * Imported Translations from Zanata 1.26.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Updated from global requirements * Updated from global requirements * Update reno for stable/pike * Updated from global requirements 1.25.0 ------ * Update URLs in documents according to document migration 1.24.1 ------ * rearrange existing documentation to fit the new standard layout * switch from oslosphinx to openstackdocstheme 1.24.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Permit aborting loopingcall while sleeping * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.23.0 ------ * Add min\_interval to BackOffLoopingCall 1.22.0 ------ * Updated from global requirements * Updated from global requirements 1.21.0 ------ * Remove log translations * Use Sphinx 1.5 warning-is-error * Fix some reST field lists in docstrings * Updated from global requirements 1.20.0 ------ * Updated from global requirements * [Fix gate]Update test requirement * Updated from global requirements * Updated from global requirements * Fix race condition with fast threads * pbr.version.VersionInfo needs package name (oslo.xyz and not oslo\_xyz) * Remove duplicated register\_opts call * Update reno for stable/ocata * Remove references to Python 3.4 1.19.0 ------ * Add FixedIntervalWithTimeoutLoopingCall * Add Constraints support * Show team and repo badges on README 1.18.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Imported Translations from Zanata * Update .coveragerc after the removal of respective directory * Delete python bytecode file 1.17.0 ------ * Changed the home-page link * Updated from global requirements * Replace 'MagicMock' with 'Mock' * Enable release notes translation * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.16.0 ------ * Updated from global requirements * Stay alive on double SIGHUP 1.15.0 ------ * Updated from global requirements 1.14.0 ------ * Updated from global requirements * Fix parameters of assertEqual are misplaced 1.13.0 ------ * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add reno for release notes management * Updated from global requirements 1.12.0 ------ * Imported Translations from Zanata * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements 1.11.0 ------ * Trivial: ignore openstack/common in flake8 exclude list 1.10.0 ------ * [Trivial] Remove executable privilege of doc/source/conf.py 1.9.0 ----- * Updated from global requirements * Offer mutate\_config\_files * Make \_spawn\_service more flexible * Remove direct dependency on babel * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix argument type for \_sd\_notify() on python3 * Use a timeutils.StopWatch for cancel timing * Add ability to cancel Threads and ThreadGroups * Exception: message need '\_' function * Fix Heartbeats stop when time is changed * Updated from global requirements 1.7.0 ----- * Updated from global requirements * Correct some help text * Fix typo in help text * wsgi: decrease the default number of greenthreads in pool * Updated from global requirements 1.6.0 ----- * Updated from global requirements * Allow the backdoor to serve from a local unix domain socket * Updated from global requirements 1.5.0 ----- * Use requests in TestWSGIServerWithSSL instead of raw socket client 1.4.0 ----- * Updated from global requirements * Updated from global requirements * Fix misspelling and rewrite sentence * Add a more useful/detailed frame dumping function * Updated from global requirements * Update translation setup * Fix race condition on handling signals * Updated from global requirements * Updated from global requirements * Updated from global requirements * Updated from global requirements * Fix artificial service.wait() 1.3.0 ----- * Graceful shutdown added to ServiceLauncher * Fix test execution on CentOS 7 * Updated from global requirements * Fix some inconsistency in docstrings * Refactoring of tests/eventlet\_service.py * Updated from global requirements * Remove argument ServiceLauncher.wait() method * fix a couple of assert issues * Run sslutils and wsgi tests for python3 * Updated from global requirements 1.2.0 ----- * Updated from global requirements * Fix a race condition in signal handlers * Enable py3 mock.patch of RuntimeError * Delete python bytecode before every test run * Trival: Remove 'MANIFEST.in' 1.1.0 ----- * Avoid warning when time taken is close to zero * Update the \_i18n.py file and fix the domain value * Add Bandit to tox for security static analysis * Code refactoring of ThreadGroup::stop\_timers() 1.0.0 ----- * Updated from global requirements * Updated from global requirements * Add functionality for creating Unix domain WSGI servers * Use reflection.get\_class\_name() from oslo.utils * Remove Python 2.6 classifier * Remove openstack-common.conf * cleanup tox.ini * Change "started child" messages to DEBUG * Support for SSL protocol and cipher controls 0.13.0 ------ * Default value of graceful\_shutdown\_timeout is set to 60sec * Updated from global requirements * Logger name argument was added into wsgi.Server constructor * Avoid the dual-naming confusion * Forbid launching services with 0 or negative number of workers 0.12.0 ------ * Document graceful\_shutdown\_timeout config option * Remove py26 env from test list * Added config option graceful\_shutdown\_timeout * Updated from global requirements * Add docstring for LoopingCallBase.\_start() * Updated from global requirements 0.11.0 ------ * Updated from global requirements * Add doc8 to py27 tox env and fix raised issues * Document termination of children on SIGHUP * Updated from global requirements * Updated from global requirements 0.10.0 ------ * RetryDecorator should not log warnings/errors for expected exceptions * Termination children on SIGHUP added * Fix coverage configuration and execution * Add register\_opts function to sslutils * Move the common thread manipulating routine to a shared routine * Update log string to correctly denote what it waits on * Avoid removing entries for timers that didn't stop * Cleanup thread on thread done callback * Move 'history' -> release notes section * Add unit tests for sslutils * Expand README and clean up intro to sphinx docs * Add shields.io version/downloads links/badges into README.rst * add auto-generated docs for config options * Move backoff looping call from IPA to oslo.service * Change ignore-errors to ignore\_errors * Fix the home-page value in setup.cfg * WSGI module was corrected * Updated from global requirements * ThreadGroup's stop didn't recognise the current thread correctly * doing monkey\_patch for unittest 0.9.0 ----- * Handling corner cases in dynamic looping call * Change DEBUG log in loopingcall to TRACE level log * Updated from global requirements 0.8.0 ----- * Added wsgi functionality 0.7.0 ----- * Updated from global requirements * Update "Signal handling" section of usage docs * Use oslo\_utils reflection to get 'f' callable name * Updated from global requirements * Prefix the 'safe\_wrapper' function to be '\_safe\_wrapper' * Setup translations * Check that sighup is supported before accessing signal.SIGHUP * Use contextlib.closing instead of try ... finally: sock.close * Avoid using the global lockutils semaphore collection * Updated from global requirements 0.6.0 ----- * Added newline at end of file * Added class SignalHandler * Updated from global requirements * Activate pep8 check that \_ is imported * Denote what happens when no exceptions are passed in * Allow LoopingCall to continue on exception in callee 0.5.0 ----- * Updated from global requirements * Updated from global requirements * Updated from global requirements * Add oslo\_debug\_helper to tox.ini * Add usage documentation for oslo\_service.service module 0.4.0 ----- * Updated from global requirements * save docstring, name etc using six.wraps * Move backdoor-related tests from test\_service * Add mock to test\_requirements * Remove usage of mox in test\_eventlet\_backdoor 0.3.0 ----- * Copy RetryDecorator from oslo.vmware * Increase test coverage of systemd * Ensure we set the event and wait on the timer in the test * Make it easier to use the eventlet backdoor locally * Track created thread and disallow more than one start being active 0.2.0 ----- * Documentation on the use of the oslo-config-generator * Add greenlet to requirements * Add tox target to find missing requirements * Enforce H405 check * Enforce H301 check * Return timer after adding it to internal list * Updated from global requirements * Have all the looping calls share a common run loop * Move service abstract base class check to launch\_service methods * Fix a typo in a comment * Updated from global requirements * Use a signal name->sigval and sigval->signal name mapping 0.1.0 ----- * Test for instantaneous shutdown fixed * Graceful shutdown WSGI/RPC server * Use monotonic.monotonic and stopwatches instead of time.time * Updated from global requirements * Eventlet service fixed * Add documentation for the service module * Improve test coverage for loopingcall module * Add oslo.service documentation * Remove usage of global CONF * Make logging option values configurable * Introduce abstract base class for services * Add entrypoints for option discovery * Updated from global requirements * Move the option definitions into a private file * Fix unit tests * Fix pep8 * exported from oslo-incubator by graduate.sh * Clean up logging to conform to guidelines * Port service to Python 3 * Test for shutting down eventlet server on signal * service child process normal SIGTERM exit * Revert "Revert "Revert "Optimization of waiting subprocesses in ProcessLauncher""" * Revert "Revert "Optimization of waiting subprocesses in ProcessLauncher"" * Revert "Optimization of waiting subprocesses in ProcessLauncher" * ProcessLauncher: reload config file in parent process on SIGHUP * Add check to test\_\_signal\_handlers\_set * Store ProcessLauncher signal handlers on class level * Remove unused validate\_ssl\_version * Update tests for optional sslv3 * Fixed ssl.PROTOCOL\_SSLv3 not supported by Python 2.7.9 * Optimization of waiting subprocesses in ProcessLauncher * Switch from oslo.config to oslo\_config * Change oslo.config to oslo\_config * Remove oslo.log code and clean up versionutils API * Replace mox by mox3 * Allow overriding name for periodic tasks * Separate add\_periodic\_task from the metaclass \_\_init\_\_ * Upgrade to hacking 0.10 * Remove unnecessary import of eventlet * Added graceful argument on Service.stop method * Remove extra white space in log message * Prefer delayed %r formatting over explicit repr use * ServiceRestartTest: make it more resilient * threadgroup: don't log GreenletExit * add list\_opts to all modules with configuration options * Remove code that moved to oslo.i18n * Remove graduated test and fixtures libraries * rpc, notifier: remove deprecated modules * Let oslotest manage the six.move setting for mox * Remove usage of readlines() * Allow test\_service to run in isolation * Changes calcuation of variable delay * Use timestamp in loopingcall * Remove unnecessary setUp function * Log the function name of looping call * pep8: fixed multiple violations * Make periodic tasks run on regular spacing interval * Use moxstubout and mockpatch from oslotest * Implement stop method in ProcessLauncher * Fix parenthesis typo misunderstanding in periodic\_task * Fix docstring indentation in systemd * Remove redundant default=None for config options * Make unspecified periodic spaced tasks run on default interval * Make stop\_timers() method public * Remove deprecated LoopingCall * Fixed several typos * Add graceful stop function to ThreadGroup.stop * Use oslotest instead of common test module * Remove duplicated "caught" message * Move notification point to a better place * Remove rendundant parentheses of cfg help strings * Adds test condition in test\_periodic * Fixed spelling error - occured to occurred * Add missing \_LI for LOG.info in service module * notify calling process we are ready to serve * Reap child processes gracefully if greenlet thread gets killed * Improve help strings for sslutils module * Remove unnecessary usage of noqa * Removes use of timeutils.set\_time\_override * Update oslo log messages with translation domains * Refactor unnecessary arithmetic ops in periodic\_task * Refactor if logic in periodic\_task * Use timestamp in periodic tasks * Add basic Python 3 tests * Clear time override in test\_periodic * Don't share periodic\_task instance data in a class attr * Revert "service: replace eventlet event by threading" * Simplify launch method * Simple typo correction * Cleanup unused log related code * Utilizes assertIsNone and assertIsNotNone * Fix filter() usage due to python 3 compability * Use hacking import\_exceptions for gettextutils.\_ * threadgroup: use threading rather than greenthread * disable SIGHUP restart behavior in foreground * service: replace eventlet event by threading * Allow configurable ProcessLauncher liveness check * Make wait & stop methods work on all threads * Typos fix in db and periodic\_task module * Remove vim header * os.\_exit in \_start\_child may cause unexpected exception * Adjust import order according to PEP8 imports rule * Add a link method to Thread * Use multiprocessing.Event to ensure services have started * Apply six for metaclass * Removed calls to locals() * Move comment in service.py to correct location * Fixes issue with SUGHUP in services on Windows * Replace using tests.utils part2 * Bump hacking to 0.7.0 * Replace using tests.utils with openstack.common.test * Refactors boolean returns * Add service restart function in oslo-incubator * Fix stylistic problems with help text * Enable H302 hacking check * Convert kombu SSL version string into integer * Allow launchers to be stopped multiple times * Ignore any exceptions from rpc.cleanup() * Add graceful service shutdown support to Launcher * Improve usability when backdoor\_port is nonzero * Enable hacking H404 test * Enable hacking H402 test * Enable hacking H401 test * Fixes import order nits * Add DynamicLoopCall timers to ThreadGroups * Pass backdoor\_port to services being launched * Improve python3 compatibility * Use print\_function \_\_future\_\_ import * Improve Python 3.x compatibility * Import nova's looping call * Copy recent changes in periodic tasks from nova * Fix IBM copyright strings * Removes unused imports in the tests module * update OpenStack, LLC to OpenStack Foundation * Add function for listing native threads to eventlet backdoor * Use oslo-config-2013.1b3 * Support for SSL in wsgi.Service * Replace direct use of testtools BaseTestCase * Use testtools as test base class * ThreadGroup remove unused name parameters * Implement importutils.try\_import * Fix test cases in tests.unit.test\_service * Don't rely on os.wait() blocking * Use Service thread group for WSGI request handling * Make project pyflakes clean * Replace try: import with extras.try\_import * raise\_on\_error parameter shouldn't be passed to task function * Account for tasks duration in LoopingCall delay * updating sphinx documentation * Enable eventlet\_backdoor to return port * Use the ThreadGroup for the Launcher * Change RPC cleanup ordering * threadgroup : greethread.cancel() should be kill() * Use spawn\_n when not capturing return value * Make ThreadGroup derived from object to make mocking possible * Don't log exceptions for GreenletExit and thread\_done * Log CONF from ProcessLauncher.wait, like ServiceLauncher * Import order clean-up * Added a missing \`cfg\` import in service.py * Log config on startup * Integrate eventlet backdoor * Add the rpc service and delete manager * Use pep8 v1.3.3 * Add threadgroup to manage timers and greenthreads * Add basic periodic task infrastructure * Add multiprocess service launcher * Add signal handling to service launcher * Basic service launching infrastructure * Move manager.py and service.py into common * Copy eventlet\_backdoor into common from nova * Copy LoopingCall from nova for service.py * initial commit * Initial skeleton project oslo.service-1.29.0/requirements.txt0000666000175100017510000000107713224676223017544 0ustar zuulzuul00000000000000# 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. WebOb>=1.7.1 # MIT eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 # MIT greenlet>=0.4.10 # MIT monotonic>=0.6 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 oslo.concurrency>=3.20.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0 oslo.log>=3.30.0 # Apache-2.0 six>=1.10.0 # MIT oslo.i18n>=3.15.3 # Apache-2.0 PasteDeploy>=1.5.0 # MIT Routes>=2.3.1 # MIT Paste>=2.0.2 # MIT oslo.service-1.29.0/LICENSE0000666000175100017510000002363713224676223015273 0ustar zuulzuul00000000000000 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. oslo.service-1.29.0/AUTHORS0000664000175100017510000001114413224676442015325 0ustar zuulzuul00000000000000Accela Zhao Akihiro Motoki Alan Pevec Alberto Murillo Alessandro Pilotti Alex Gaynor Alex Holden Alexander Gorodnev Alexis Lee Allain Legacy Andreas Jaeger Andreas Jaeger Angus Salkeld Ann Kamyshnikova Ben Nemec Brant Knudson Brian Elliott Carl Baldwin Cedric Brandily Chang Bo Guo ChangBo Guo(gcb) ChangBo Guo(gcb) Christian Berendt Christopher Lefelhocz Chuck Short Clif Houck Corey Bryant Dan Prince Daniel P. Berrange Davanum Srinivas Davanum Srinivas David Ripton DennyZhang Dina Belova Dirk Mueller Dmitry Tantsur Doug Hellmann Doug Hellmann Duan Jiong Elena Ezhova Eoghan Glynn Eric Brown Eric Guo Eric Windisch Fengqian.Gao Flavio Percoco Gary Kotton Hengqing Hu Ian Wienand Ihar Hrachyshka Ilya Shakhat Iswarya_Vakati James Carey Jason Dunsmore Jason Kölker Javeme Javier Pena Jay Pipes Jeff Peeler Joe Gordon Joe Heck John L. Villalovos Joshua Harlow Joshua Harlow Joshua Harlow Julien Danjou Kevin L. Mitchell Kiall Mac Innes Kirill Bespalov Kurt Taylor Marian Horban Mark McClain Mark McLoughlin Maru Newby Matt Riedemann Matthew Treinish Michael Still Mitsuru Kanabuchi Monty Taylor OpenStack Release Bot Pavlo Shchelokovskyy Qin Zhao Raymond Pekowski Rohit Jaiswal Roman Podoliaka Ronald Bradford Russell Bryant Sean Dague Sean McGinnis Sergey Kraynev Sergey Lukjanov Sergey Vilgelm Soren Hansen Stephen Finucane Steve Martinelli Steven Hardy Surojit Pathak Thomas Herve Thomas Herve Tianhua Huang Tom Cammann Tony Breeds Victor Sergeyev Victor Stinner Wenzhi Yu Zane Bitter ZhiQiang Fan Zhongyue Luo Zuul apporc fujioka yuuichi gecong1973 gongysh lei zhang lin-hua-cheng liu-sheng liyingjun melanie witt melissaml ravikumar-venkatesan ricolin sonu.kumar stanzgy venkatamahesh xhzhf yan.haifeng zwei oslo.service-1.29.0/PKG-INFO0000664000175100017510000000431413224676442015353 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: oslo.service Version: 1.29.0 Summary: oslo.service library Home-page: https://docs.openstack.org/oslo.service/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/oslo.service.svg :target: http://governance.openstack.org/reference/tags/index.html .. Change things from this point on ======================================================== oslo.service -- Library for running OpenStack services ======================================================== .. image:: https://img.shields.io/pypi/v/oslo.service.svg :target: https://pypi.python.org/pypi/oslo.service/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.service.svg :target: https://pypi.python.org/pypi/oslo.service/ :alt: Downloads oslo.service provides a framework for defining new long-running services using the patterns established by other OpenStack applications. It also includes utilities long-running applications might need for working with SSL or WSGI, performing periodic operations, interacting with systemd, etc. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.service/latest/ * Source: https://git.openstack.org/cgit/openstack/oslo.service * Bugs: https://bugs.launchpad.net/oslo.service Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 oslo.service-1.29.0/oslo_service/0000775000175100017510000000000013224676442016750 5ustar zuulzuul00000000000000oslo.service-1.29.0/oslo_service/sslutils.py0000666000175100017510000000627213224676223021212 0ustar zuulzuul00000000000000# Copyright 2013 IBM Corp. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import copy import os import ssl from oslo_service._i18n import _ from oslo_service import _options config_section = 'ssl' _SSL_PROTOCOLS = { "tlsv1": ssl.PROTOCOL_TLSv1, "sslv23": ssl.PROTOCOL_SSLv23 } _OPTIONAL_PROTOCOLS = { 'sslv2': 'PROTOCOL_SSLv2', 'sslv3': 'PROTOCOL_SSLv3', 'tlsv1_1': 'PROTOCOL_TLSv1_1', 'tlsv1_2': 'PROTOCOL_TLSv1_2', } for protocol in _OPTIONAL_PROTOCOLS: try: _SSL_PROTOCOLS[protocol] = getattr(ssl, _OPTIONAL_PROTOCOLS[protocol]) except AttributeError: # nosec pass def list_opts(): """Entry point for oslo-config-generator.""" return [(config_section, copy.deepcopy(_options.ssl_opts))] def register_opts(conf): """Registers sslutils config options.""" return conf.register_opts(_options.ssl_opts, config_section) def is_enabled(conf): conf.register_opts(_options.ssl_opts, config_section) cert_file = conf.ssl.cert_file key_file = conf.ssl.key_file ca_file = conf.ssl.ca_file use_ssl = cert_file or key_file if cert_file and not os.path.exists(cert_file): raise RuntimeError(_("Unable to find cert_file : %s") % cert_file) if ca_file and not os.path.exists(ca_file): raise RuntimeError(_("Unable to find ca_file : %s") % ca_file) if key_file and not os.path.exists(key_file): raise RuntimeError(_("Unable to find key_file : %s") % key_file) if use_ssl and (not cert_file or not key_file): raise RuntimeError(_("When running server in SSL mode, you must " "specify both a cert_file and key_file " "option value in your configuration file")) return use_ssl def wrap(conf, sock): conf.register_opts(_options.ssl_opts, config_section) ssl_kwargs = { 'server_side': True, 'certfile': conf.ssl.cert_file, 'keyfile': conf.ssl.key_file, 'cert_reqs': ssl.CERT_NONE, } if conf.ssl.ca_file: ssl_kwargs['ca_certs'] = conf.ssl.ca_file ssl_kwargs['cert_reqs'] = ssl.CERT_REQUIRED if conf.ssl.version: key = conf.ssl.version.lower() try: ssl_kwargs['ssl_version'] = _SSL_PROTOCOLS[key] except KeyError: raise RuntimeError( _("Invalid SSL version : %s") % conf.ssl.version) if conf.ssl.ciphers: ssl_kwargs['ciphers'] = conf.ssl.ciphers # NOTE(eezhova): SSL/TLS protocol version is injected in ssl_kwargs above, # so skipping bandit check return ssl.wrap_socket(sock, **ssl_kwargs) # nosec oslo.service-1.29.0/oslo_service/systemd.py0000666000175100017510000000603513224676223021015 0ustar zuulzuul00000000000000# Copyright 2012-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. """ Helper module for systemd service readiness notification. """ import contextlib import logging import os import socket import sys LOG = logging.getLogger(__name__) def _abstractify(socket_name): if socket_name.startswith('@'): # abstract namespace socket socket_name = '\0%s' % socket_name[1:] return socket_name def _sd_notify(unset_env, msg): notify_socket = os.getenv('NOTIFY_SOCKET') if notify_socket: sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) with contextlib.closing(sock): try: sock.connect(_abstractify(notify_socket)) sock.sendall(msg) if unset_env: del os.environ['NOTIFY_SOCKET'] except EnvironmentError: LOG.debug("Systemd notification failed", exc_info=True) def notify(): """Send notification to Systemd that service is ready. For details see http://www.freedesktop.org/software/systemd/man/sd_notify.html """ _sd_notify(False, b'READY=1') def notify_once(): """Send notification once to Systemd that service is ready. Systemd sets NOTIFY_SOCKET environment variable with the name of the socket listening for notifications from services. This method removes the NOTIFY_SOCKET environment variable to ensure notification is sent only once. """ _sd_notify(True, b'READY=1') def onready(notify_socket, timeout): """Wait for systemd style notification on the socket. :param notify_socket: local socket address :type notify_socket: string :param timeout: socket timeout :type timeout: float :returns: 0 service ready 1 service not ready 2 timeout occurred """ sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) sock.settimeout(timeout) sock.bind(_abstractify(notify_socket)) with contextlib.closing(sock): try: msg = sock.recv(512) except socket.timeout: return 2 if b'READY=1' == msg: return 0 else: return 1 if __name__ == '__main__': # simple CLI for testing if len(sys.argv) == 1: notify() elif len(sys.argv) >= 2: timeout = float(sys.argv[1]) notify_socket = os.getenv('NOTIFY_SOCKET') if notify_socket: retval = onready(notify_socket, timeout) sys.exit(retval) oslo.service-1.29.0/oslo_service/tests/0000775000175100017510000000000013224676442020112 5ustar zuulzuul00000000000000oslo.service-1.29.0/oslo_service/tests/test_wsgi.py0000666000175100017510000003300213224676223022471 0ustar zuulzuul00000000000000# Copyright 2011 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # 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. """Unit tests for `wsgi`.""" import os import platform import six import socket import tempfile import testtools import eventlet import eventlet.wsgi import mock import requests import webob from oslo_config import cfg from oslo_service import sslutils from oslo_service.tests import base from oslo_service import wsgi from oslo_utils import netutils from oslotest import moxstubout SSL_CERT_DIR = os.path.normpath(os.path.join( os.path.dirname(os.path.abspath(__file__)), 'ssl_cert')) CONF = cfg.CONF class WsgiTestCase(base.ServiceBaseTestCase): """Base class for WSGI tests.""" def setUp(self): super(WsgiTestCase, self).setUp() self.conf(args=[], default_config_files=[]) class TestLoaderNothingExists(WsgiTestCase): """Loader tests where os.path.exists always returns False.""" def setUp(self): super(TestLoaderNothingExists, self).setUp() mox_fixture = self.useFixture(moxstubout.MoxStubout()) self.stubs = mox_fixture.stubs self.stubs.Set(os.path, 'exists', lambda _: False) def test_relpath_config_not_found(self): self.config(api_paste_config='api-paste.ini') self.assertRaises( wsgi.ConfigNotFound, wsgi.Loader, self.conf ) def test_asbpath_config_not_found(self): self.config(api_paste_config='/etc/openstack-srv/api-paste.ini') self.assertRaises( wsgi.ConfigNotFound, wsgi.Loader, self.conf ) class TestLoaderNormalFilesystem(WsgiTestCase): """Loader tests with normal filesystem (unmodified os.path module).""" _paste_config = """ [app:test_app] use = egg:Paste#static document_root = /tmp """ def setUp(self): super(TestLoaderNormalFilesystem, self).setUp() self.paste_config = tempfile.NamedTemporaryFile(mode="w+t") self.paste_config.write(self._paste_config.lstrip()) self.paste_config.seek(0) self.paste_config.flush() self.config(api_paste_config=self.paste_config.name) self.loader = wsgi.Loader(CONF) def test_config_found(self): self.assertEqual(self.paste_config.name, self.loader.config_path) def test_app_not_found(self): self.assertRaises( wsgi.PasteAppNotFound, self.loader.load_app, "nonexistent app", ) def test_app_found(self): url_parser = self.loader.load_app("test_app") self.assertEqual("/tmp", url_parser.directory) def tearDown(self): self.paste_config.close() super(TestLoaderNormalFilesystem, self).tearDown() class TestWSGIServer(WsgiTestCase): """WSGI server tests.""" def setUp(self): super(TestWSGIServer, self).setUp() def test_no_app(self): server = wsgi.Server(self.conf, "test_app", None) self.assertEqual("test_app", server.name) def test_custom_max_header_line(self): self.config(max_header_line=4096) # Default value is 16384 wsgi.Server(self.conf, "test_custom_max_header_line", None) self.assertEqual(eventlet.wsgi.MAX_HEADER_LINE, self.conf.max_header_line) def test_start_random_port(self): server = wsgi.Server(self.conf, "test_random_port", None, host="127.0.0.1", port=0) server.start() self.assertNotEqual(0, server.port) server.stop() server.wait() @testtools.skipIf(not netutils.is_ipv6_enabled(), "no ipv6 support") def test_start_random_port_with_ipv6(self): server = wsgi.Server(self.conf, "test_random_port", None, host="::1", port=0) server.start() self.assertEqual("::1", server.host) self.assertNotEqual(0, server.port) server.stop() server.wait() @testtools.skipIf(platform.mac_ver()[0] != '', 'SO_REUSEADDR behaves differently ' 'on OSX, see bug 1436895') def test_socket_options_for_simple_server(self): # test normal socket options has set properly self.config(tcp_keepidle=500) server = wsgi.Server(self.conf, "test_socket_options", None, host="127.0.0.1", port=0) server.start() sock = server.socket self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)) self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE)) if hasattr(socket, 'TCP_KEEPIDLE'): self.assertEqual(self.conf.tcp_keepidle, sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE)) self.assertFalse(server._server.dead) server.stop() server.wait() self.assertTrue(server._server.dead) @testtools.skipIf(not hasattr(socket, "AF_UNIX"), 'UNIX sockets not supported') def test_server_with_unix_socket(self): socket_file = self.get_temp_file_path('sock') socket_mode = 0o644 server = wsgi.Server(self.conf, "test_socket_options", None, socket_family=socket.AF_UNIX, socket_mode=socket_mode, socket_file=socket_file) self.assertEqual(socket_file, server.socket.getsockname()) self.assertEqual(socket_mode, os.stat(socket_file).st_mode & 0o777) server.start() self.assertFalse(server._server.dead) server.stop() server.wait() self.assertTrue(server._server.dead) def test_server_pool_waitall(self): # test pools waitall method gets called while stopping server server = wsgi.Server(self.conf, "test_server", None, host="127.0.0.1") server.start() with mock.patch.object(server._pool, 'waitall') as mock_waitall: server.stop() server.wait() mock_waitall.assert_called_once_with() def test_uri_length_limit(self): eventlet.monkey_patch(os=False, thread=False) server = wsgi.Server(self.conf, "test_uri_length_limit", None, host="127.0.0.1", max_url_len=16384, port=33337) server.start() self.assertFalse(server._server.dead) uri = "http://127.0.0.1:%d/%s" % (server.port, 10000 * 'x') resp = requests.get(uri, proxies={"http": ""}) eventlet.sleep(0) self.assertNotEqual(requests.codes.REQUEST_URI_TOO_LARGE, resp.status_code) uri = "http://127.0.0.1:%d/%s" % (server.port, 20000 * 'x') resp = requests.get(uri, proxies={"http": ""}) eventlet.sleep(0) self.assertEqual(requests.codes.REQUEST_URI_TOO_LARGE, resp.status_code) server.stop() server.wait() def test_reset_pool_size_to_default(self): server = wsgi.Server(self.conf, "test_resize", None, host="127.0.0.1", max_url_len=16384) server.start() # Stopping the server, which in turn sets pool size to 0 server.stop() self.assertEqual(0, server._pool.size) # Resetting pool size to default server.reset() server.start() self.assertEqual(CONF.wsgi_default_pool_size, server._pool.size) def test_client_socket_timeout(self): self.config(client_socket_timeout=5) # mocking eventlet spawn method to check it is called with # configured 'client_socket_timeout' value. with mock.patch.object(eventlet, 'spawn') as mock_spawn: server = wsgi.Server(self.conf, "test_app", None, host="127.0.0.1", port=0) server.start() _, kwargs = mock_spawn.call_args self.assertEqual(self.conf.client_socket_timeout, kwargs['socket_timeout']) server.stop() def test_wsgi_keep_alive(self): self.config(wsgi_keep_alive=False) # mocking eventlet spawn method to check it is called with # configured 'wsgi_keep_alive' value. with mock.patch.object(eventlet, 'spawn') as mock_spawn: server = wsgi.Server(self.conf, "test_app", None, host="127.0.0.1", port=0) server.start() _, kwargs = mock_spawn.call_args self.assertEqual(self.conf.wsgi_keep_alive, kwargs['keepalive']) server.stop() class TestWSGIServerWithSSL(WsgiTestCase): """WSGI server with SSL tests.""" def setUp(self): super(TestWSGIServerWithSSL, self).setUp() cert_file_name = os.path.join(SSL_CERT_DIR, 'certificate.crt') key_file_name = os.path.join(SSL_CERT_DIR, 'privatekey.key') eventlet.monkey_patch(os=False, thread=False) self.config(cert_file=cert_file_name, key_file=key_file_name, group=sslutils.config_section) @testtools.skipIf(six.PY3, "bug/1482633: test hangs on Python 3") def test_ssl_server(self): def test_app(env, start_response): start_response('200 OK', {}) return ['PONG'] fake_ssl_server = wsgi.Server(self.conf, "fake_ssl", test_app, host="127.0.0.1", port=0, use_ssl=True) fake_ssl_server.start() self.assertNotEqual(0, fake_ssl_server.port) response = requests.post( 'https://127.0.0.1:%s/' % fake_ssl_server.port, verify=os.path.join(SSL_CERT_DIR, 'ca.crt'), data='PING') self.assertEqual('PONG', response.text) fake_ssl_server.stop() fake_ssl_server.wait() @testtools.skipIf(six.PY3, "bug/1482633: test hangs on Python 3") def test_two_servers(self): def test_app(env, start_response): start_response('200 OK', {}) return ['PONG'] fake_ssl_server = wsgi.Server(self.conf, "fake_ssl", test_app, host="127.0.0.1", port=0, use_ssl=True) fake_ssl_server.start() self.assertNotEqual(0, fake_ssl_server.port) fake_server = wsgi.Server(self.conf, "fake", test_app, host="127.0.0.1", port=0) fake_server.start() self.assertNotEqual(0, fake_server.port) response = requests.post( 'https://127.0.0.1:%s/' % fake_ssl_server.port, verify=os.path.join(SSL_CERT_DIR, 'ca.crt'), data='PING') self.assertEqual('PONG', response.text) response = requests.post( 'http://127.0.0.1:%s/' % fake_server.port, data='PING') self.assertEqual('PONG', response.text) fake_ssl_server.stop() fake_ssl_server.wait() fake_server.stop() fake_server.wait() @testtools.skipIf(platform.mac_ver()[0] != '', 'SO_REUSEADDR behaves differently ' 'on OSX, see bug 1436895') @testtools.skipIf(six.PY3, "bug/1482633: test hangs on Python 3") def test_socket_options_for_ssl_server(self): # test normal socket options has set properly self.config(tcp_keepidle=500) server = wsgi.Server(self.conf, "test_socket_options", None, host="127.0.0.1", port=0, use_ssl=True) server.start() sock = server.socket self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)) self.assertEqual(1, sock.getsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE)) if hasattr(socket, 'TCP_KEEPIDLE'): self.assertEqual(CONF.tcp_keepidle, sock.getsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE)) server.stop() server.wait() @testtools.skipIf(not netutils.is_ipv6_enabled(), "no ipv6 support") @testtools.skipIf(six.PY3, "bug/1482633: test hangs on Python 3") def test_app_using_ipv6_and_ssl(self): greetings = 'Hello, World!!!' @webob.dec.wsgify def hello_world(req): return greetings server = wsgi.Server(self.conf, "fake_ssl", hello_world, host="::1", port=0, use_ssl=True) server.start() response = requests.get('https://[::1]:%d/' % server.port, verify=os.path.join(SSL_CERT_DIR, 'ca.crt')) self.assertEqual(greetings, response.text) server.stop() server.wait() oslo.service-1.29.0/oslo_service/tests/ssl_cert/0000775000175100017510000000000013224676442021730 5ustar zuulzuul00000000000000oslo.service-1.29.0/oslo_service/tests/ssl_cert/ca.key0000666000175100017510000000625313224676223023032 0ustar zuulzuul00000000000000-----BEGIN RSA PRIVATE KEY----- MIIJJwIBAAKCAgEAwILIMebpHYK1E1zhyi6713GGTQ9DFeLOE1T25+XTJqAkO7ef QzZfB8QwCXy/8bmbhmKgQQ7APuuDci8SKCkYeWCxqJRGmg0tZVlj5gCfrV2u+olw S+XyaOGCFkYScs6D34BaE2rGD2GDryoSPc2feAt6X4+ZkDPZnvaHQP6j9Ofq/4Wm sECEas0IO5X8SDF8afA47U9ZXFkcgQK6HCHDcokLaaZxEyZFSaPex6ZAESNthkGO xEThRPxAkJhqYCeMl3Hff98XEUcFNzuAOmcnQJJgRemwJO2hS5KS3Y3p9/nBRlh3 tSAG1nbY5kXSpyaq296D9x/esnXlt+9JUmn1rKyvmaFBC/SbzyyQoO3MT5r8rKte 0bulLw1bZOZNlhxSv2KCg5RD6vlNrnpsZszw4nj28fBroeFp0JMeT8jcqGs3qdm8 sXLcBgiTalLYtiCNV9wZjOduQotuFN6mDwZvfa6hzZjcBNfqeLyTEnFb5k6pIla0 wydWx/jvBAzoxOkEcVjak747A+p/rriD5hVUBH0BuNaWcEgKe9jcHnLvU8hUxFtg PxUHOOR+eMa+FS3ApKf9sJ/zVUq0uxyA9hUnsvnqv/CywLSvaNKBiKQTL0QLEXnw 6EQb7g/XuwC5mmt+l30wGh9M1U/QMaU/+YzT4sVLTXIHJ7ExRTbEecbNbjsCAwEA AQKCAgA0ySd/l2NANkDUaFl5CMt0zaoXoyGv9Jqw7lEtUPVO2AZXYYgH8/amuIK7 dztiWpRsisqKTDMmjYljW8jMvkf5sCvGn7GkOAzEh3g+7tjZvqBmDh1+kjSf0YXL +bbBSCMcu6L3RAW+3ewvsYeC7sjVL8CER2nCApWfYtW/WpM2agkju0/zcB1e841Y WU3ttbP5kGbrmyBTlBOexFKnuBJRa4Z3l63VpF7HTGmfsNRMXrx/XaZ55rEmK0zA 2SoB55ZDSHQSKee3UxP5CxWj7fjzWa+QO/2Sgp4BjNU8btdCqXb3hPZ98aQuVjQv H+Ic9xtOYnso3dJAeNdeUfx23psAHhUqYruD+xrjwTJV5viGO05AHjp/i4dKjOaD CMFKP/AGUcGAsL/Mjq5oMbWovbqhGaaOw4I0Xl/JuB0XQXWwr5D2cLUjMaCS9bLq WV8lfEitoCVihAi21s8MIyQWHvl4m4d/aD5KNh0MJYo3vYCrs6A256dhbmlEmGBr DY1++4yxz4YkY07jYbQYkDlCtwu51g+YE8lKAE9+Mz+PDgbRB7dgw7K3Q9SsXp1P ui7/vnrgqppnYm4aaHvXEZ1qwwt2hpoumhQo/k1xrSzVKQ83vjzjXoDc9o84Vsv2 dmcLGKPpu+cm2ks8q6x2EI09dfkJjb/7N9SpU0AOjU7CgDye0QKCAQEA5/mosLuC vXwh5FkJuV/wpipwqkS4vu+KNQiN83wdz+Yxw6siAz6/SIjr0sRmROop6CNCaBNq 887+mgm62rEe5eU4vHRlBOlYQD0qa+il09uwYPU0JunSOabxUCBhSuW/LXZyq7rA ywGB7OVSTWwgb6Y0X1pUcOXK5qYaWJUdUEi2oVrU160phbDAcZNH+vAyl+IRJmVJ LP7f1QwVrnIvIBgpIvPLRigagn84ecXPITClq4KjGNy2Qq/iarEwY7llFG10xHmK xbzQ8v5XfPZ4Swmp+35kwNhfp6HRVWV3RftX4ftFArcFGYEIActItIz10rbLJ+42 fc8oZKq/MB9NlwKCAQEA1HLOuODXrFsKtLaQQzupPLpdyfYWR7A6tbghH5paKkIg A+BSO/b91xOVx0jN2lkxe0Ns1QCpHZU8BXZ9MFCaZgr75z0+vhIRjrMTXXirlray 1mptar018j79sDJLFBF8VQFfi7Edd3OwB2dbdDFJhzNUbNJIVkVo+bXYfuWGlotG EVWxX/CnPgnKknl6vX/8YSg6qJCwcUTmQRoqermd02VtrMrGgytcOG6QdKYTT/ct b3zDNXdeLOJKyLZS1eW4V2Pcl4Njbaxq/U7KYkjWWZzVVsiCjWA8H0RXGf+Uk9Gu cUg5hm5zxXcOGdI6yRVxHEU7CKc25Ks5xw4xPkhA/QKCAQBd7yC6ABQe+qcWul9P q2PdRY49xHozBvimJQKmN/oyd3prS18IhV4b1yX3QQRQn6m8kJqRXluOwqEiaxI5 AEQMv9dLqK5HYN4VlS8aZyjPM0Sm3mPx5fj0038f/RyooYPauv4QQB1VlxSvguTi 6QfxbhIDEqbi2Ipi/5vnhupJ2kfp6sgJVdtcgYhL9WHOYXl7O1XKgHUzPToSIUSe USp4CpCN0L7dd9vUQAP0e382Z2aOnuXAaY98TZCXt4xqtWYS8Ye5D6Z8D8tkuk1f Esb/S7iDWFkgJf4F+Wa099NmiTK7FW6KfOYZv8AoSdL1GadpXg/B6ZozM7Gdoe6t Y9+dAoIBABH2Rv4gnHuJEwWmbdoRYESvKSDbOpUDFGOq1roaTcdG4fgR7kH9pwaZ NE+uGyF76xAV6ky0CphitrlrhDgiiHtaMGQjrHtbgbqD7342pqNOfR5dzzR4HOiH ZOGRzwE6XT2+qPphljE0SczGc1gGlsXklB3DRbRtl+uM8WoBM/jke58ZlK6c5Tb8 kvEBblw5Rvhb82GvIgvhnGoisTbBHNPzvmseldwfPWPUDUifhgB70I6diM+rcP3w gAwqRiSpkIVq/wqcZDqwmjcigz/+EolvFiaJO2iCm3K1T3v2PPSmhM41Ig/4pLcs UrfiK3A27OJMBCq+IIkC5RasX4N5jm0CggEAXT9oyIO+a7ggpfijuba0xuhFwf+r NY49hx3YshWXX5T3LfKZpTh+A1vjGcj57MZacRcTkFQgHVcyu+haA9lI4vsFMesU 9GqenrJNvxsV4i3avIxGjjx7d0Ok/7UuawTDuRea8m13se/oJOl5ftQK+ZoVqtO8 SzeNNpakiuCxmIEqaD8HUwWvgfA6n0HPJNc0vFAqu6Y5oOr8GDHd5JoKA8Sb15N9 AdFqwCbW9SqUVsvHDuiOKXy8lCr3OiuyjgBfbIyuuWbaU0PqIiKW++lTluXkl7Uz vUawgfgX85sY6A35g1O/ydEQw2+h2tzDvQdhhyTYpMZjZwzIIPjCQMgHPA== -----END RSA PRIVATE KEY----- oslo.service-1.29.0/oslo_service/tests/ssl_cert/ca.crt0000666000175100017510000000467013224676223023033 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIHADCCBOigAwIBAgIJAOjPGLL9VDhjMA0GCSqGSIb3DQEBDQUAMIGwMQswCQYD VQQGEwJVUzEOMAwGA1UECBMFVGV4YXMxDzANBgNVBAcTBkF1c3RpbjEdMBsGA1UE ChMUT3BlblN0YWNrIEZvdW5kYXRpb24xHTAbBgNVBAsTFE9wZW5TdGFjayBEZXZl bG9wZXJzMRAwDgYDVQQDEwdUZXN0IENBMTAwLgYJKoZIhvcNAQkBFiFvcGVuc3Rh Y2stZGV2QGxpc3RzLm9wZW5zdGFjay5vcmcwHhcNMTUwMTA4MDIyOTEzWhcNMjUw MTA4MDIyOTEzWjCBsDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYD VQQHEwZBdXN0aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9uMR0wGwYD VQQLExRPcGVuU3RhY2sgRGV2ZWxvcGVyczEQMA4GA1UEAxMHVGVzdCBDQTEwMC4G CSqGSIb3DQEJARYhb3BlbnN0YWNrLWRldkBsaXN0cy5vcGVuc3RhY2sub3JnMIIC IjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwILIMebpHYK1E1zhyi6713GG TQ9DFeLOE1T25+XTJqAkO7efQzZfB8QwCXy/8bmbhmKgQQ7APuuDci8SKCkYeWCx qJRGmg0tZVlj5gCfrV2u+olwS+XyaOGCFkYScs6D34BaE2rGD2GDryoSPc2feAt6 X4+ZkDPZnvaHQP6j9Ofq/4WmsECEas0IO5X8SDF8afA47U9ZXFkcgQK6HCHDcokL aaZxEyZFSaPex6ZAESNthkGOxEThRPxAkJhqYCeMl3Hff98XEUcFNzuAOmcnQJJg RemwJO2hS5KS3Y3p9/nBRlh3tSAG1nbY5kXSpyaq296D9x/esnXlt+9JUmn1rKyv maFBC/SbzyyQoO3MT5r8rKte0bulLw1bZOZNlhxSv2KCg5RD6vlNrnpsZszw4nj2 8fBroeFp0JMeT8jcqGs3qdm8sXLcBgiTalLYtiCNV9wZjOduQotuFN6mDwZvfa6h zZjcBNfqeLyTEnFb5k6pIla0wydWx/jvBAzoxOkEcVjak747A+p/rriD5hVUBH0B uNaWcEgKe9jcHnLvU8hUxFtgPxUHOOR+eMa+FS3ApKf9sJ/zVUq0uxyA9hUnsvnq v/CywLSvaNKBiKQTL0QLEXnw6EQb7g/XuwC5mmt+l30wGh9M1U/QMaU/+YzT4sVL TXIHJ7ExRTbEecbNbjsCAwEAAaOCARkwggEVMB0GA1UdDgQWBBQTWz2WEB0sJg9c xfM5JeJMIAJq0jCB5QYDVR0jBIHdMIHagBQTWz2WEB0sJg9cxfM5JeJMIAJq0qGB tqSBszCBsDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZB dXN0aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9uMR0wGwYDVQQLExRP cGVuU3RhY2sgRGV2ZWxvcGVyczEQMA4GA1UEAxMHVGVzdCBDQTEwMC4GCSqGSIb3 DQEJARYhb3BlbnN0YWNrLWRldkBsaXN0cy5vcGVuc3RhY2sub3JnggkA6M8Ysv1U OGMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOCAgEAIfAD6uVorT5WomG1 2DWRm3kuwa+EDimgVF6VRvxCzyHx7e/6KJQj149KpMQ6e0ZPjqQw+pZ+jJSgq6TP MEjCHgIDwdKhi9LmQWIlo8xdzgfZW2VQkVLvwkqAnWWhCy9oGc/Ypk8pjiZfCx+/ DSJBbFnopI9f8epAKMq7N3jJyEMoTctzmI0KckrZnJ1Gq4MZpoxGmkJiGhWoUk8p r8apXZ6B1DzO1XxpGw2BIcrUC3bQS/vPrg5/XbyaAu2BSgu6iF7ULqkBsEd0yK/L i2gO9eTacaX3zJBQOlMJFsIAgIiVw6Rq6BuhU9zxDoopY4feta/NDOpk1OjY3MV7 4rcLTU6XYaItMDRe+dmjBOK+xspsaCU4kHEkA7mHL5YZhEEWLHj6QY8tAiIQMVQZ RuTpQIbNkjLW8Ls+CbwL2LkUFB19rKu9tFpzEJ1IIeFmt5HZsL5ri6W2qkSPIbIe Qq15kl/a45jgBbgn2VNA5ecjW20hhXyaS9AKWXK+AeFBaFIFDUrB2UP4YSDbJWUJ 0LKe+QuumXdl+iRdkgb1Tll7qme8gXAeyzVGHK2AsaBg+gkEeSyVLRKIixceyy+3 6yqlKJhk2qeV3ceOfVm9ZdvRlzWyVctaTcGIpDFqf4y8YyVhL1e2KGKcmYtbLq+m rtku4CM3HldxcM4wqSB1VcaTX8o= -----END CERTIFICATE----- oslo.service-1.29.0/oslo_service/tests/ssl_cert/privatekey.key0000666000175100017510000000625313224676223024632 0ustar zuulzuul00000000000000-----BEGIN RSA PRIVATE KEY----- MIIJKAIBAAKCAgEA0Em2/KEwoGff3uETvH0J783L7gQ1B+v1Wse68Y0JHdb+5RtB p9011kJg0AY+urZPcIRgrQgnFmOvYe5NxIr1K0EwVq5N4g+pVRWQ7dOOrVGS/4ue n97oOEeSMJToSr+MjDeJzRCjEvKBPJsbFeAmBWgHMawm5/M5DXi1+TMZ4bITcvnP kOkI1CpYtYXuygzHeQzmEGb4tZ6iIE+OawRdBj5RiIOVItb/ojznlf/DANbn460/ r/WVZR9zIDIiqPe3SmbFl5jvFCdptaOFdPZ1Mr52XBGNvvvDrrkCB4XVHmWlO9TJ PVpDCAfWR30Z5BuQEPBZJUsf1ug8+B0wu1faKlSBAAASEg5RZc5hlUdU1eFXoKuM CC990Xf1z2Ga5ei88/biKRbgv53lE8TtWr0onhTOKPD0aG7iVBAJFnxII1khgmdP Pnf7p+z3dbCecQ+2j20VEp41a3ePH/COW4MWGEFNk7iyc0ir3GcJhIRAAixEFVYC G/supYuKBTLtr0cwjKLe1ARk300IPLgE24kO7fddYstUzA2siJHmZ0JQWhEq38T5 g+Pts2XNkxNIYugEU8zvUud/Kb0drMcMTUrG9PiDNgy5srhjTuZ7JuIvhA29TTwh OAVToMAahv7L8YerD5uZ7PnLQX/oWXJX5KJKttUQS3On0TDKvFvzk4kUrS0CAwEA AQKCAgAkdpMrPMi3fBfL+9kpqTYhHgTyYRgrj9o/DzIh8U/EQowS7aebzHUNUkeC g2Vd6GaVywblo8S7/a2JVl+U5cKv1NSyiAcoaRd6xrC9gci7fMlgJUAauroqiBUG njrgQxJGxb5BAQWbXorTYk/mj3v4fFKuFnYlKwY03on020ZPpY4UFbmJo9Ig2lz3 QkAgbQZKocBw5KXrnZ7CS0siXvwuCKDbZjWoiLzt2P2t2712myizSfQZSMPjlRLh cwVwURVsV/uFY4ePHqs52iuV40N3I7KywXvwEEEciFTbnklF7gN0Kvcj33ZWpJCV qUfsEAsze/APQEyNodBymyGZ2nJdn9PqaQYnVhE9xpjiXejQHZsuMnrA3jYr8Mtx j0EZiX4ICI4Njt9oI/EtWhQtcDt86hTEtBlyFRU6jhW8O5Ai7hzxCYgUJ7onWVOE PtCC9FoOwumXWgdZNz/hMqQSn91O8trferccdUGIfx8N/G4QkyzOLI0Hc6Mubby7 +GGRwVXnLsIGxpFc+VBHY/J6offCkXx3MPbfn57x0LGZu1GtHoep391yLUrBs9jx nJrUI9OuwaeOG0iesTuGT+PbZWxDrJEtA7DRM1FBMNMvn5BTTg7yx8EqUM35hnFf 5J1XEf0DW5nUPH1Qadgi1LZjCAhiD5OuNooFsTmN7dSdleF+PQKCAQEA7jq7drTu O1ePCO+dQeECauy1qv9SO2LIHfLZ/L4OwcEtEnE8xBbvrZfUqkbUITCS6rR8UITp 6ru0MyhUEsRsk4FHIJV2P1pB2Zy+8tV4Dm3aHh4bCoECqAPHMgXUkP+9kIOn2QsE uRXnsEiQAl0SxSTcduy5F+WIWLVl4A72ry3cSvrEGwMEz0sjaEMmCZ2B8X8EJt64 uWUSHDaAMSg80bADy3p+OhmWMGZTDl/KRCz9pJLyICMxsotfbvE0BadAZr+UowSe ldqKlgRYlYL3pAhwjeMO/QxmMfRxjvG09romqe0Bcs8BDNII/ShAjjHQUwxcEszQ P14g8QwmTQVm5wKCAQEA39M3GveyIhX6vmyR4DUlxE5+yloTACdlCZu6wvFlRka8 3FEw8DWKVfnmYYFt/RPukYeBRmXwqLciGSly7PnaBXeNFqNXiykKETzS2UISZoqT Dur06GmcI+Lk1my9v5gLB1LT/D8XWjwmjA5hNO1J1UYmp+X4dgaYxWzOKBsTTJ8j SVaEaxBUwLHy58ehoQm+G5+QqL5yU/n1hPwXx1XYvd33OscSGQRbALrH2ZxsqxMZ yvNa2NYt3TnihXcF36Df5861DTNI7NDqpY72C4U8RwaqgTdDkD+t8zrk/r3LUa5d NGkGQF+59spBcb64IPZ4DuJ9//GaEsyj0jPF/FTMywKCAQEA1DiB83eumjKf+yfq AVv/GV2RYKleigSvnO5QfrSY1MXP7xPtPAnqrcwJ6T57jq2E04zBCcG92BwqpUAR 1T4iMy0BPeenlTxEWSUnfY/pCYGWwymykSLoSOBEvS0wdZM9PdXq2pDUPkVjRkj9 8P0U0YbK1y5+nOkfE1dVT8pEuz2xdyH5PM7to/SdsC3RXtNvhMDP5AiYqp99CKEM hb4AoBOa7dNLS1qrzqX4618uApnJwqgdBcAUb6d09pHs8/RQjLeyI57j3z72Ijnw 6A/pp7jU+7EAEzDOgUXvO5Xazch61PmLRsldeBxLYapQB9wcZz8lbqICCdFCqzlV jVt4lQKCAQA9CYxtfj7FrNjENTdSvSufbQiGhinIUPXsuNslbk7/6yp1qm5+Exu2 dn+s927XJShZ52oJmKMYX1idJACDP1+FPiTrl3+4I2jranrVZH9AF2ojF0/SUXqT Drz4/I6CQSRAywWkNFBZ+y1H5GP92vfXgVnpT32CMipXLGTL6xZIPt2QkldqGvoB 0oU7T+Vz1QRS5CC+47Cp1fBuY5DYe0CwBmf1T3RP/jAS8tytK0s3G+5cuiB8IWxA eBid7OddJLHqtSQKhYHNkutqWqIeYicd92Nn+XojTDpTqivojDl1/ObN9BYQWAqO knlmW2w7EPuMk5doxKoPll7WY+gJ99YhAoIBAHf5HYRh4ZuYkx+R1ow8/Ahp7N4u BGFRNnCpMG358Zws95wvBg5dkW8VU0M3256M0kFkw2AOyyyNsHqIhMNakzHesGo/ TWhqCh23p1xBLY5p14K8K6iOc1Jfa1LqGsL2TZ06TeNNyONMGqq0yOyD62CdLRDj 0ACL/z2j494LmfqhV45hYuqjQbrLizjrr6ln75g2WJ32U+zwl7KUHnBL7IEwb4Be KOl1bfVwZAs0GtHuaiScBYRLUaSC/Qq7YPjTh1nmg48DQC/HUCNGMqhoZ950kp9k 76HX+MpwUi5y49moFmn/3qDvefGFpX1td8vYMokx+eyKTXGFtxBUwPnMUSQ= -----END RSA PRIVATE KEY----- oslo.service-1.29.0/oslo_service/tests/ssl_cert/certificate.crt0000666000175100017510000000474113224676223024731 0ustar zuulzuul00000000000000-----BEGIN CERTIFICATE----- MIIHHjCCBQagAwIBAgIBATANBgkqhkiG9w0BAQ0FADCBsDELMAkGA1UEBhMCVVMx DjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0aW4xHTAbBgNVBAoTFE9wZW5T dGFjayBGb3VuZGF0aW9uMR0wGwYDVQQLExRPcGVuU3RhY2sgRGV2ZWxvcGVyczEQ MA4GA1UEAxMHVGVzdCBDQTEwMC4GCSqGSIb3DQEJARYhb3BlbnN0YWNrLWRldkBs aXN0cy5vcGVuc3RhY2sub3JnMB4XDTE1MDEwODAyNTQzNVoXDTI1MDEwODAyNTQz NVoweDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFzMQ8wDQYDVQQHEwZBdXN0 aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9uMR0wGwYDVQQLExRPcGVu U3RhY2sgRGV2ZWxvcGVyczEKMAgGA1UEAxQBKjCCAiIwDQYJKoZIhvcNAQEBBQAD ggIPADCCAgoCggIBANBJtvyhMKBn397hE7x9Ce/Ny+4ENQfr9VrHuvGNCR3W/uUb QafdNdZCYNAGPrq2T3CEYK0IJxZjr2HuTcSK9StBMFauTeIPqVUVkO3Tjq1Rkv+L np/e6DhHkjCU6Eq/jIw3ic0QoxLygTybGxXgJgVoBzGsJufzOQ14tfkzGeGyE3L5 z5DpCNQqWLWF7soMx3kM5hBm+LWeoiBPjmsEXQY+UYiDlSLW/6I855X/wwDW5+Ot P6/1lWUfcyAyIqj3t0pmxZeY7xQnabWjhXT2dTK+dlwRjb77w665AgeF1R5lpTvU yT1aQwgH1kd9GeQbkBDwWSVLH9boPPgdMLtX2ipUgQAAEhIOUWXOYZVHVNXhV6Cr jAgvfdF39c9hmuXovPP24ikW4L+d5RPE7Vq9KJ4Uzijw9Ghu4lQQCRZ8SCNZIYJn Tz53+6fs93WwnnEPto9tFRKeNWt3jx/wjluDFhhBTZO4snNIq9xnCYSEQAIsRBVW Ahv7LqWLigUy7a9HMIyi3tQEZN9NCDy4BNuJDu33XWLLVMwNrIiR5mdCUFoRKt/E +YPj7bNlzZMTSGLoBFPM71Lnfym9HazHDE1KxvT4gzYMubK4Y07meybiL4QNvU08 ITgFU6DAGob+y/GHqw+bmez5y0F/6FlyV+SiSrbVEEtzp9Ewyrxb85OJFK0tAgMB AAGjggF4MIIBdDBLBgNVHREERDBCgglsb2NhbGhvc3SCDWlwNi1sb2NhbGhvc3SC CTEyNy4wLjAuMYIDOjoxhwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMB0GA1UdDgQW BBSjWxD0qedj9eeGUWyGphy5PU67dDCB5QYDVR0jBIHdMIHagBQTWz2WEB0sJg9c xfM5JeJMIAJq0qGBtqSBszCBsDELMAkGA1UEBhMCVVMxDjAMBgNVBAgTBVRleGFz MQ8wDQYDVQQHEwZBdXN0aW4xHTAbBgNVBAoTFE9wZW5TdGFjayBGb3VuZGF0aW9u MR0wGwYDVQQLExRPcGVuU3RhY2sgRGV2ZWxvcGVyczEQMA4GA1UEAxMHVGVzdCBD QTEwMC4GCSqGSIb3DQEJARYhb3BlbnN0YWNrLWRldkBsaXN0cy5vcGVuc3RhY2su b3JnggkA6M8Ysv1UOGMwCQYDVR0TBAIwADATBgNVHSUEDDAKBggrBgEFBQcDATAN BgkqhkiG9w0BAQ0FAAOCAgEAIGx/acXQEiGYFBJUduE6/Y6LBuHEVMcj0yfbLzja Eb35xKWHuX7tgQPwXy6UGlYM8oKIptIp/9eEuYXte6u5ncvD7e/JldCUVd0fW8hm fBOhfqVstcTmlfZ6WqTJD6Bp/FjUH+8qf8E+lsjNy7i0EsmcQOeQm4mkocHG1AA4 MEeuDg33lV6XCjW450BoZ/FTfwZSuTlGgFlEzUUrAe/ETdajF9G9aJ+0OvXzE1tU pvbvkU8eg4pLXxrzboOhyQMEmCikdkMYjo/0ZQrXrrJ1W8mCinkJdz6CToc7nUkU F8tdAY0rKMEM8SYHngMJU2943lpGbQhE5B4oms8I+SMTyCVz2Vu5I43Px68Y0GUN Bn5qu0w2Vj8eradoPF8pEAIVICIvlbiRepPbNZ7FieSsY2TEfLtxBd2DLE1YWeE5 p/RDBxqcDrGQuSg6gFSoLEhYgQcGnYgD75EIE8f/LrHFOAeSYEOhibFbK5G8p/2h EHcKZ9lvTgqwHn0FiTqZ3LWxVFsZiTsiyXErpJ2Nu2WTzo0k1xJMUpJqHuUZraei N5fA5YuDp2ShXRoZyVieRvp0TCmm6sHL8Pn0K8weJchYrvV1yvPKeuISN/fVCQev 88yih5Rh5R2szwoY3uVImpd99bMm0e1bXrQug43ZUz9rC4ABN6+lZvuorDWRVI7U I1M= -----END CERTIFICATE----- oslo.service-1.29.0/oslo_service/tests/test_systemd.py0000666000175100017510000000500613224676223023213 0ustar zuulzuul00000000000000# 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 import socket import mock from oslotest import base as test_base from oslo_service import systemd class SystemdTestCase(test_base.BaseTestCase): """Test case for Systemd service readiness.""" def test__abstractify(self): sock_name = '@fake_socket' res = systemd._abstractify(sock_name) self.assertEqual('\0{0}'.format(sock_name[1:]), res) @mock.patch.object(os, 'getenv', return_value='@fake_socket') def _test__sd_notify(self, getenv_mock, unset_env=False): self.ready = False self.closed = False class FakeSocket(object): def __init__(self, family, type): pass def connect(fs, socket): pass def close(fs): self.closed = True def sendall(fs, data): if data == b'READY=1': self.ready = True with mock.patch.object(socket, 'socket', new=FakeSocket): if unset_env: systemd.notify_once() else: systemd.notify() self.assertTrue(self.ready) self.assertTrue(self.closed) def test_notify(self): self._test__sd_notify() def test_notify_once(self): os.environ['NOTIFY_SOCKET'] = '@fake_socket' self._test__sd_notify(unset_env=True) self.assertRaises(KeyError, os.environ.__getitem__, 'NOTIFY_SOCKET') @mock.patch("socket.socket") def test_onready(self, sock_mock): recv_results = [b'READY=1', '', socket.timeout] expected_results = [0, 1, 2] for recv, expected in zip(recv_results, expected_results): if recv == socket.timeout: sock_mock.return_value.recv.side_effect = recv else: sock_mock.return_value.recv.return_value = recv actual = systemd.onready('@fake_socket', 1) self.assertEqual(expected, actual) oslo.service-1.29.0/oslo_service/tests/test_eventlet_backdoor.py0000666000175100017510000001445213224676223025222 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # 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. """ Unit Tests for eventlet backdoor """ import errno import os import socket import eventlet import mock from oslo_service import eventlet_backdoor from oslo_service.tests import base class BackdoorSocketPathTest(base.ServiceBaseTestCase): @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_path(self, listen_mock, spawn_mock): self.config(backdoor_socket="/tmp/my_special_socket") listen_mock.side_effect = mock.Mock() path = eventlet_backdoor.initialize_if_enabled(self.conf) self.assertEqual("/tmp/my_special_socket", path) @mock.patch.object(os, 'unlink') @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_path_already_exists(self, listen_mock, spawn_mock, unlink_mock): self.config(backdoor_socket="/tmp/my_special_socket") sock = mock.Mock() listen_mock.side_effect = [socket.error(errno.EADDRINUSE, ''), sock] path = eventlet_backdoor.initialize_if_enabled(self.conf) self.assertEqual("/tmp/my_special_socket", path) unlink_mock.assert_called_with("/tmp/my_special_socket") @mock.patch.object(os, 'unlink') @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_path_already_exists_and_gone(self, listen_mock, spawn_mock, unlink_mock): self.config(backdoor_socket="/tmp/my_special_socket") sock = mock.Mock() listen_mock.side_effect = [socket.error(errno.EADDRINUSE, ''), sock] unlink_mock.side_effect = OSError(errno.ENOENT, '') path = eventlet_backdoor.initialize_if_enabled(self.conf) self.assertEqual("/tmp/my_special_socket", path) unlink_mock.assert_called_with("/tmp/my_special_socket") @mock.patch.object(os, 'unlink') @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_path_already_exists_and_not_gone(self, listen_mock, spawn_mock, unlink_mock): self.config(backdoor_socket="/tmp/my_special_socket") listen_mock.side_effect = socket.error(errno.EADDRINUSE, '') unlink_mock.side_effect = OSError(errno.EPERM, '') self.assertRaises(OSError, eventlet_backdoor.initialize_if_enabled, self.conf) @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_path_no_perms(self, listen_mock, spawn_mock): self.config(backdoor_socket="/tmp/my_special_socket") listen_mock.side_effect = socket.error(errno.EPERM, '') self.assertRaises(socket.error, eventlet_backdoor.initialize_if_enabled, self.conf) class BackdoorPortTest(base.ServiceBaseTestCase): @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_port(self, listen_mock, spawn_mock): self.config(backdoor_port=1234) sock = mock.Mock() sock.getsockname.return_value = ('127.0.0.1', 1234) listen_mock.return_value = sock port = eventlet_backdoor.initialize_if_enabled(self.conf) self.assertEqual(1234, port) @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_port_inuse(self, listen_mock, spawn_mock): self.config(backdoor_port=2345) listen_mock.side_effect = socket.error(errno.EADDRINUSE, '') self.assertRaises(socket.error, eventlet_backdoor.initialize_if_enabled, self.conf) @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_port_range(self, listen_mock, spawn_mock): self.config(backdoor_port='8800:8899') sock = mock.Mock() sock.getsockname.return_value = ('127.0.0.1', 8800) listen_mock.return_value = sock port = eventlet_backdoor.initialize_if_enabled(self.conf) self.assertEqual(8800, port) @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_port_range_one_inuse(self, listen_mock, spawn_mock): self.config(backdoor_port='8800:8900') sock = mock.Mock() sock.getsockname.return_value = ('127.0.0.1', 8801) listen_mock.side_effect = [socket.error(errno.EADDRINUSE, ''), sock] port = eventlet_backdoor.initialize_if_enabled(self.conf) self.assertEqual(8801, port) @mock.patch.object(eventlet, 'spawn') @mock.patch.object(eventlet, 'listen') def test_backdoor_port_range_all_inuse(self, listen_mock, spawn_mock): self.config(backdoor_port='8800:8899') side_effects = [] for i in range(8800, 8900): side_effects.append(socket.error(errno.EADDRINUSE, '')) listen_mock.side_effect = side_effects self.assertRaises(socket.error, eventlet_backdoor.initialize_if_enabled, self.conf) def test_backdoor_port_reverse_range(self): self.config(backdoor_port='8888:7777') self.assertRaises(eventlet_backdoor.EventletBackdoorConfigValueError, eventlet_backdoor.initialize_if_enabled, self.conf) def test_backdoor_port_bad(self): self.config(backdoor_port='abc') self.assertRaises(eventlet_backdoor.EventletBackdoorConfigValueError, eventlet_backdoor.initialize_if_enabled, self.conf) oslo.service-1.29.0/oslo_service/tests/test_threadgroup.py0000666000175100017510000001107213224676223024047 0ustar zuulzuul00000000000000# Copyright (c) 2012 Rackspace Hosting # 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. """ Unit Tests for thread groups """ import time from eventlet import event from oslotest import base as test_base from oslo_service import threadgroup class ThreadGroupTestCase(test_base.BaseTestCase): """Test cases for thread group.""" def setUp(self): super(ThreadGroupTestCase, self).setUp() self.tg = threadgroup.ThreadGroup() self.addCleanup(self.tg.stop) def test_add_dynamic_timer(self): def foo(*args, **kwargs): pass initial_delay = 1 periodic_interval_max = 2 self.tg.add_dynamic_timer(foo, initial_delay, periodic_interval_max, 'arg', kwarg='kwarg') self.assertEqual(1, len(self.tg.timers)) timer = self.tg.timers[0] self.assertTrue(timer._running) self.assertEqual(('arg',), timer.args) self.assertEqual({'kwarg': 'kwarg'}, timer.kw) def test_stop_current_thread(self): stop_event = event.Event() quit_event = event.Event() def stop_self(*args, **kwargs): if args[0] == 1: time.sleep(1) self.tg.stop() stop_event.send('stop_event') quit_event.wait() for i in range(0, 4): self.tg.add_thread(stop_self, i, kwargs='kwargs') stop_event.wait() self.assertEqual(1, len(self.tg.threads)) quit_event.send('quit_event') def test_stop_immediately(self): def foo(*args, **kwargs): time.sleep(1) start_time = time.time() self.tg.add_thread(foo, 'arg', kwarg='kwarg') time.sleep(0) self.tg.stop() end_time = time.time() self.assertEqual(0, len(self.tg.threads)) self.assertTrue(end_time - start_time < 1) def test_stop_gracefully(self): def foo(*args, **kwargs): time.sleep(1) start_time = time.time() self.tg.add_thread(foo, 'arg', kwarg='kwarg') self.tg.stop(True) end_time = time.time() self.assertEqual(0, len(self.tg.threads)) self.assertTrue(end_time - start_time >= 1) def test_cancel_early(self): def foo(*args, **kwargs): time.sleep(1) self.tg.add_thread(foo, 'arg', kwarg='kwarg') self.tg.cancel() self.assertEqual(0, len(self.tg.threads)) def test_cancel_late(self): def foo(*args, **kwargs): time.sleep(0.3) self.tg.add_thread(foo, 'arg', kwarg='kwarg') time.sleep(0) self.tg.cancel() self.assertEqual(1, len(self.tg.threads)) def test_cancel_timeout(self): def foo(*args, **kwargs): time.sleep(0.3) self.tg.add_thread(foo, 'arg', kwarg='kwarg') time.sleep(0) self.tg.cancel(timeout=0.2, wait_time=0.1) self.assertEqual(0, len(self.tg.threads)) def test_stop_timers(self): def foo(*args, **kwargs): pass self.tg.add_timer('1234', foo) self.assertEqual(1, len(self.tg.timers)) self.tg.stop_timers() self.assertEqual(0, len(self.tg.timers)) def test_add_and_remove_timer(self): def foo(*args, **kwargs): pass timer = self.tg.add_timer('1234', foo) self.assertEqual(1, len(self.tg.timers)) timer.stop() self.assertEqual(1, len(self.tg.timers)) self.tg.timer_done(timer) self.assertEqual(0, len(self.tg.timers)) def test_add_and_remove_dynamic_timer(self): def foo(*args, **kwargs): pass initial_delay = 1 periodic_interval_max = 2 timer = self.tg.add_dynamic_timer(foo, initial_delay, periodic_interval_max) self.assertEqual(1, len(self.tg.timers)) self.assertTrue(timer._running) timer.stop() self.assertEqual(1, len(self.tg.timers)) self.tg.timer_done(timer) self.assertEqual(0, len(self.tg.timers)) oslo.service-1.29.0/oslo_service/tests/eventlet_service.py0000666000175100017510000001074213224676223024035 0ustar zuulzuul00000000000000 # 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. # An eventlet server that runs a service.py pool. # Opens listens on a random port. The port # is printed to stdout. import socket import sys import eventlet.wsgi import greenlet from oslo_config import cfg from oslo_service import service POOL_SIZE = 1 class Server(service.ServiceBase): """Server class to manage multiple WSGI sockets and applications.""" def __init__(self, application, host=None, port=None, keepalive=False, keepidle=None): self.application = application self.host = host or '0.0.0.0' self.port = port or 0 # Pool for a green thread in which wsgi server will be running self.pool = eventlet.GreenPool(POOL_SIZE) self.socket_info = {} self.greenthread = None self.keepalive = keepalive self.keepidle = keepidle self.socket = None def listen(self, key=None, backlog=128): """Create and start listening on socket. Call before forking worker processes. Raises Exception if this has already been called. """ # TODO(dims): eventlet's green dns/socket module does not actually # support IPv6 in getaddrinfo(). We need to get around this in the # future or monitor upstream for a fix. # Please refer below link # (https://bitbucket.org/eventlet/eventlet/ # src/e0f578180d7d82d2ed3d8a96d520103503c524ec/eventlet/support/ # greendns.py?at=0.12#cl-163) info = socket.getaddrinfo(self.host, self.port, socket.AF_UNSPEC, socket.SOCK_STREAM)[0] self.socket = eventlet.listen(info[-1], family=info[0], backlog=backlog) def start(self, key=None, backlog=128): """Run a WSGI server with the given application.""" if self.socket is None: self.listen(key=key, backlog=backlog) dup_socket = self.socket.dup() if key: self.socket_info[key] = self.socket.getsockname() # Optionally enable keepalive on the wsgi socket. if self.keepalive: dup_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) if self.keepidle is not None: dup_socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, self.keepidle) self.greenthread = self.pool.spawn(self._run, self.application, dup_socket) def stop(self): if self.greenthread is not None: self.greenthread.kill() def wait(self): """Wait until all servers have completed running.""" try: self.pool.waitall() except KeyboardInterrupt: pass except greenlet.GreenletExit: pass def reset(self): """Required by the service interface. The service interface is used by the launcher when receiving a SIGHUP. The service interface is defined in oslo_service.Service. Test server does not need to do anything here. """ pass def _run(self, application, socket): """Start a WSGI server with a new green thread pool.""" try: eventlet.wsgi.server(socket, application, debug=False) except greenlet.GreenletExit: # Wait until all servers have completed running pass def run(port_queue, workers=3): eventlet.patcher.monkey_patch() def hi_app(environ, start_response): start_response('200 OK', [('Content-Type', 'application/json')]) yield 'hi' server = Server(hi_app) server.listen() launcher = service.launch(cfg.CONF, server, workers) port = server.socket.getsockname()[1] port_queue.put(port) sys.stdout.flush() launcher.wait() if __name__ == '__main__': run() oslo.service-1.29.0/oslo_service/tests/__init__.py0000666000175100017510000000173513224676223022230 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import eventlet if os.name == 'nt': # eventlet monkey patching the os and thread modules causes # subprocess.Popen to fail on Windows when using pipes due # to missing non-blocking IO support. # # bug report on eventlet: # https://bitbucket.org/eventlet/eventlet/issue/132/ # eventletmonkey_patch-breaks eventlet.monkey_patch(os=False, thread=False) else: eventlet.monkey_patch() oslo.service-1.29.0/oslo_service/tests/base.py0000666000175100017510000000514313224676223021400 0ustar zuulzuul00000000000000# Copyright 2015 Mirantis 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 fixtures from oslo_config import fixture as config from oslotest import base as test_base from oslo_service import _options from oslo_service import sslutils class ServiceBaseTestCase(test_base.BaseTestCase): def setUp(self): super(ServiceBaseTestCase, self).setUp() self.conf_fixture = self.useFixture(config.Config()) self.conf_fixture.register_opts(_options.eventlet_backdoor_opts) self.conf_fixture.register_opts(_options.service_opts) self.conf_fixture.register_opts(_options.ssl_opts, sslutils.config_section) self.conf_fixture.register_opts(_options.periodic_opts) self.conf_fixture.register_opts(_options.wsgi_opts) self.conf = self.conf_fixture.conf self.config = self.conf_fixture.config self.conf(args=[], default_config_files=[]) def get_new_temp_dir(self): """Create a new temporary directory. :returns: fixtures.TempDir """ return self.useFixture(fixtures.TempDir()) def get_default_temp_dir(self): """Create a default temporary directory. Returns the same directory during the whole test case. :returns: fixtures.TempDir """ if not hasattr(self, '_temp_dir'): self._temp_dir = self.get_new_temp_dir() return self._temp_dir def get_temp_file_path(self, filename, root=None): """Returns an absolute path for a temporary file. If root is None, the file is created in default temporary directory. It also creates the directory if it's not initialized yet. If root is not None, the file is created inside the directory passed as root= argument. :param filename: filename :type filename: string :param root: temporary directory to create a new file in :type root: fixtures.TempDir :returns: absolute file path string """ root = root or self.get_default_temp_dir() return root.join(filename) oslo.service-1.29.0/oslo_service/tests/test_periodic.py0000666000175100017510000003275513224676223023334 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # 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. """ Unit Tests for periodic_task decorator and PeriodicTasks class. """ import mock from testtools import matchers from oslo_service import periodic_task from oslo_service.tests import base class AnException(Exception): pass class PeriodicTasksTestCase(base.ServiceBaseTestCase): """Test cases for PeriodicTasks.""" @mock.patch('oslo_service.periodic_task.now') def test_called_thrice(self, mock_now): time = 340 mock_now.return_value = time # Class inside test def to mock 'now' in # the periodic task decorator class AService(periodic_task.PeriodicTasks): def __init__(self, conf): super(AService, self).__init__(conf) self.called = {'doit': 0, 'urg': 0, 'ticks': 0, 'tocks': 0} @periodic_task.periodic_task def doit(self, context): self.called['doit'] += 1 @periodic_task.periodic_task def crashit(self, context): self.called['urg'] += 1 raise AnException('urg') @periodic_task.periodic_task( spacing=10 + periodic_task.DEFAULT_INTERVAL, run_immediately=True) def doit_with_ticks(self, context): self.called['ticks'] += 1 @periodic_task.periodic_task( spacing=10 + periodic_task.DEFAULT_INTERVAL) def doit_with_tocks(self, context): self.called['tocks'] += 1 external_called = {'ext1': 0, 'ext2': 0} @periodic_task.periodic_task def ext1(self, context): external_called['ext1'] += 1 @periodic_task.periodic_task( spacing=10 + periodic_task.DEFAULT_INTERVAL) def ext2(self, context): external_called['ext2'] += 1 serv = AService(self.conf) serv.add_periodic_task(ext1) serv.add_periodic_task(ext2) serv.run_periodic_tasks(None) # Time: 340 self.assertEqual(0, serv.called['doit']) self.assertEqual(0, serv.called['urg']) # New last run will be 350 self.assertEqual(1, serv.called['ticks']) self.assertEqual(0, serv.called['tocks']) self.assertEqual(0, external_called['ext1']) self.assertEqual(0, external_called['ext2']) time = time + periodic_task.DEFAULT_INTERVAL mock_now.return_value = time serv.run_periodic_tasks(None) # Time:400 # New Last run: 420 self.assertEqual(1, serv.called['doit']) self.assertEqual(1, serv.called['urg']) # Closest multiple of 70 is 420 self.assertEqual(1, serv.called['ticks']) self.assertEqual(0, serv.called['tocks']) self.assertEqual(1, external_called['ext1']) self.assertEqual(0, external_called['ext2']) time = time + periodic_task.DEFAULT_INTERVAL / 2 mock_now.return_value = time serv.run_periodic_tasks(None) self.assertEqual(1, serv.called['doit']) self.assertEqual(1, serv.called['urg']) self.assertEqual(2, serv.called['ticks']) self.assertEqual(1, serv.called['tocks']) self.assertEqual(1, external_called['ext1']) self.assertEqual(1, external_called['ext2']) time = time + periodic_task.DEFAULT_INTERVAL mock_now.return_value = time serv.run_periodic_tasks(None) self.assertEqual(2, serv.called['doit']) self.assertEqual(2, serv.called['urg']) self.assertEqual(3, serv.called['ticks']) self.assertEqual(2, serv.called['tocks']) self.assertEqual(2, external_called['ext1']) self.assertEqual(2, external_called['ext2']) @mock.patch('oslo_service.periodic_task.now') def test_called_correct(self, mock_now): time = 360444 mock_now.return_value = time test_spacing = 9 # Class inside test def to mock 'now' in # the periodic task decorator class AService(periodic_task.PeriodicTasks): def __init__(self, conf): super(AService, self).__init__(conf) self.called = {'ticks': 0} @periodic_task.periodic_task(spacing=test_spacing) def tick(self, context): self.called['ticks'] += 1 serv = AService(self.conf) for i in range(200): serv.run_periodic_tasks(None) self.assertEqual(int(i / test_spacing), serv.called['ticks']) time += 1 mock_now.return_value = time @mock.patch('oslo_service.periodic_task.now') def test_raises(self, mock_now): time = 230000 mock_now.return_value = time class AService(periodic_task.PeriodicTasks): def __init__(self, conf): super(AService, self).__init__(conf) self.called = {'urg': 0, } @periodic_task.periodic_task def crashit(self, context): self.called['urg'] += 1 raise AnException('urg') serv = AService(self.conf) now = serv._periodic_last_run['crashit'] mock_now.return_value = now + periodic_task.DEFAULT_INTERVAL self.assertRaises(AnException, serv.run_periodic_tasks, None, raise_on_error=True) def test_name(self): class AService(periodic_task.PeriodicTasks): def __init__(self, conf): super(AService, self).__init__(conf) @periodic_task.periodic_task(name='better-name') def tick(self, context): pass @periodic_task.periodic_task def tack(self, context): pass @periodic_task.periodic_task(name='another-name') def foo(self, context): pass serv = AService(self.conf) serv.add_periodic_task(foo) self.assertIn('better-name', serv._periodic_last_run) self.assertIn('another-name', serv._periodic_last_run) self.assertIn('tack', serv._periodic_last_run) class ManagerMetaTestCase(base.ServiceBaseTestCase): """Tests for the meta class which manages creation of periodic tasks.""" def test_meta(self): class Manager(periodic_task.PeriodicTasks): @periodic_task.periodic_task def foo(self): return 'foo' @periodic_task.periodic_task(spacing=4) def bar(self): return 'bar' @periodic_task.periodic_task(enabled=False) def baz(self): return 'baz' m = Manager(self.conf) self.assertThat(m._periodic_tasks, matchers.HasLength(2)) self.assertEqual(periodic_task.DEFAULT_INTERVAL, m._periodic_spacing['foo']) self.assertEqual(4, m._periodic_spacing['bar']) self.assertThat( m._periodic_spacing, matchers.Not(matchers.Contains('baz'))) @periodic_task.periodic_task def external(): return 42 m.add_periodic_task(external) self.assertThat(m._periodic_tasks, matchers.HasLength(3)) self.assertEqual(periodic_task.DEFAULT_INTERVAL, m._periodic_spacing['external']) class ManagerTestCase(base.ServiceBaseTestCase): """Tests the periodic tasks portion of the manager class.""" def setUp(self): super(ManagerTestCase, self).setUp() def test_periodic_tasks_with_idle(self): class Manager(periodic_task.PeriodicTasks): @periodic_task.periodic_task(spacing=200) def bar(self): return 'bar' m = Manager(self.conf) self.assertThat(m._periodic_tasks, matchers.HasLength(1)) self.assertEqual(200, m._periodic_spacing['bar']) # Now a single pass of the periodic tasks idle = m.run_periodic_tasks(None) self.assertAlmostEqual(60, idle, 1) def test_periodic_tasks_constant(self): class Manager(periodic_task.PeriodicTasks): @periodic_task.periodic_task(spacing=0) def bar(self): return 'bar' m = Manager(self.conf) idle = m.run_periodic_tasks(None) self.assertAlmostEqual(60, idle, 1) @mock.patch('oslo_service.periodic_task.now') def test_periodic_tasks_idle_calculation(self, mock_now): fake_time = 32503680000.0 mock_now.return_value = fake_time class Manager(periodic_task.PeriodicTasks): @periodic_task.periodic_task(spacing=10) def bar(self, context): return 'bar' m = Manager(self.conf) # Ensure initial values are correct self.assertEqual(1, len(m._periodic_tasks)) task_name, task = m._periodic_tasks[0] # Test task values self.assertEqual('bar', task_name) self.assertEqual(10, task._periodic_spacing) self.assertTrue(task._periodic_enabled) self.assertFalse(task._periodic_external_ok) self.assertFalse(task._periodic_immediate) self.assertAlmostEqual(32503680000.0, task._periodic_last_run) # Test the manager's representation of those values self.assertEqual(10, m._periodic_spacing[task_name]) self.assertAlmostEqual(32503680000.0, m._periodic_last_run[task_name]) mock_now.return_value = fake_time + 5 idle = m.run_periodic_tasks(None) self.assertAlmostEqual(5, idle, 1) self.assertAlmostEqual(32503680000.0, m._periodic_last_run[task_name]) mock_now.return_value = fake_time + 10 idle = m.run_periodic_tasks(None) self.assertAlmostEqual(10, idle, 1) self.assertAlmostEqual(32503680010.0, m._periodic_last_run[task_name]) @mock.patch('oslo_service.periodic_task.now') def test_periodic_tasks_immediate_runs_now(self, mock_now): fake_time = 32503680000.0 mock_now.return_value = fake_time class Manager(periodic_task.PeriodicTasks): @periodic_task.periodic_task(spacing=10, run_immediately=True) def bar(self, context): return 'bar' m = Manager(self.conf) # Ensure initial values are correct self.assertEqual(1, len(m._periodic_tasks)) task_name, task = m._periodic_tasks[0] # Test task values self.assertEqual('bar', task_name) self.assertEqual(10, task._periodic_spacing) self.assertTrue(task._periodic_enabled) self.assertFalse(task._periodic_external_ok) self.assertTrue(task._periodic_immediate) self.assertIsNone(task._periodic_last_run) # Test the manager's representation of those values self.assertEqual(10, m._periodic_spacing[task_name]) self.assertIsNone(m._periodic_last_run[task_name]) idle = m.run_periodic_tasks(None) self.assertAlmostEqual(32503680000.0, m._periodic_last_run[task_name]) self.assertAlmostEqual(10, idle, 1) mock_now.return_value = fake_time + 5 idle = m.run_periodic_tasks(None) self.assertAlmostEqual(5, idle, 1) def test_periodic_tasks_disabled(self): class Manager(periodic_task.PeriodicTasks): @periodic_task.periodic_task(spacing=-1) def bar(self): return 'bar' m = Manager(self.conf) idle = m.run_periodic_tasks(None) self.assertAlmostEqual(60, idle, 1) def test_external_running_here(self): self.config(run_external_periodic_tasks=True) class Manager(periodic_task.PeriodicTasks): @periodic_task.periodic_task(spacing=200, external_process_ok=True) def bar(self): return 'bar' m = Manager(self.conf) self.assertThat(m._periodic_tasks, matchers.HasLength(1)) @mock.patch('oslo_service.periodic_task.now') @mock.patch('random.random') def test_nearest_boundary(self, mock_random, mock_now): mock_now.return_value = 19 mock_random.return_value = 0 self.assertEqual(17, periodic_task._nearest_boundary(10, 7)) mock_now.return_value = 28 self.assertEqual(27, periodic_task._nearest_boundary(13, 7)) mock_now.return_value = 1841 self.assertEqual(1837, periodic_task._nearest_boundary(781, 88)) mock_now.return_value = 1835 self.assertEqual(mock_now.return_value, periodic_task._nearest_boundary(None, 88)) # Add 5% jitter mock_random.return_value = 1.0 mock_now.return_value = 1300 self.assertEqual(1200 + 10, periodic_task._nearest_boundary(1000, 200)) # Add 2.5% jitter mock_random.return_value = 0.5 mock_now.return_value = 1300 self.assertEqual(1200 + 5, periodic_task._nearest_boundary(1000, 200)) oslo.service-1.29.0/oslo_service/tests/test_loopingcall.py0000666000175100017510000004140613224676223024032 0ustar zuulzuul00000000000000# Copyright 2012 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 eventlet from eventlet.green import threading as greenthreading import mock from oslotest import base as test_base import oslo_service from oslo_service import loopingcall threading = eventlet.patcher.original('threading') time = eventlet.patcher.original('time') class LoopingCallTestCase(test_base.BaseTestCase): def setUp(self): super(LoopingCallTestCase, self).setUp() self.num_runs = 0 def test_return_true(self): def _raise_it(): raise loopingcall.LoopingCallDone(True) timer = loopingcall.FixedIntervalLoopingCall(_raise_it) self.assertTrue(timer.start(interval=0.5).wait()) def test_monotonic_timer(self): def _raise_it(): clock = eventlet.hubs.get_hub().clock ok = (clock == oslo_service._monotonic) raise loopingcall.LoopingCallDone(ok) timer = loopingcall.FixedIntervalLoopingCall(_raise_it) self.assertTrue(timer.start(interval=0.5).wait()) def test_eventlet_clock(self): # Make sure that by default the oslo_service.service_hub() kicks in, # test in the main thread hub = eventlet.hubs.get_hub() self.assertEqual(oslo_service._monotonic, hub.clock) def test_eventlet_use_hub_override(self): ns = {} def task(): try: self._test_eventlet_use_hub_override() except Exception as exc: ns['result'] = exc else: ns['result'] = 'ok' # test overriding the hub of in a new thread to not modify the hub # of the main thread thread = threading.Thread(target=task) thread.start() thread.join() self.assertEqual('ok', ns['result']) def _test_eventlet_use_hub_override(self): # Make sure that by default the # oslo_service.service_hub() kicks in old_clock = eventlet.hubs.get_hub().clock self.assertEqual(oslo_service._monotonic, old_clock) # eventlet will use time.monotonic() by default, same clock than # oslo.service_hub(): # https://github.com/eventlet/eventlet/pull/303 if not hasattr(time, 'monotonic'): # If anyone wants to override it try: eventlet.hubs.use_hub('poll') except Exception: eventlet.hubs.use_hub('selects') # then we get a new clock and the override works fine too! clock = eventlet.hubs.get_hub().clock self.assertNotEqual(old_clock, clock) def test_return_false(self): def _raise_it(): raise loopingcall.LoopingCallDone(False) timer = loopingcall.FixedIntervalLoopingCall(_raise_it) self.assertFalse(timer.start(interval=0.5).wait()) def test_terminate_on_exception(self): def _raise_it(): raise RuntimeError() timer = loopingcall.FixedIntervalLoopingCall(_raise_it) self.assertRaises(RuntimeError, timer.start(interval=0.5).wait) def _raise_and_then_done(self): if self.num_runs == 0: raise loopingcall.LoopingCallDone(False) else: self.num_runs = self.num_runs - 1 raise RuntimeError() def test_do_not_stop_on_exception(self): self.num_runs = 2 timer = loopingcall.FixedIntervalLoopingCall(self._raise_and_then_done) res = timer.start(interval=0.5, stop_on_exception=False).wait() self.assertFalse(res) def _wait_for_zero(self): """Called at an interval until num_runs == 0.""" if self.num_runs == 0: raise loopingcall.LoopingCallDone(False) else: self.num_runs = self.num_runs - 1 def test_no_double_start(self): wait_ev = greenthreading.Event() def _run_forever_until_set(): if wait_ev.is_set(): raise loopingcall.LoopingCallDone(True) timer = loopingcall.FixedIntervalLoopingCall(_run_forever_until_set) timer.start(interval=0.01) self.assertRaises(RuntimeError, timer.start, interval=0.01) wait_ev.set() timer.wait() def test_repeat(self): self.num_runs = 2 timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_zero) self.assertFalse(timer.start(interval=0.5).wait()) def assertAlmostEqual(self, expected, actual, precision=7, message=None): self.assertEqual(0, round(actual - expected, precision), message) @mock.patch('eventlet.greenthread.sleep') @mock.patch('oslo_utils.timeutils.now') def test_interval_adjustment(self, time_mock, sleep_mock): """Ensure the interval is adjusted to account for task duration.""" self.num_runs = 3 now = 1234567890 second = 1 smidgen = 0.01 time_mock.side_effect = [now, # restart now + second - smidgen, # end now, # restart now + second + second, # end now, # restart now + second + smidgen, # end now] # restart timer = loopingcall.FixedIntervalLoopingCall(self._wait_for_zero) timer.start(interval=1.01).wait() expected_calls = [0.02, 0.00, 0.00] for i, call in enumerate(sleep_mock.call_args_list): expected = expected_calls[i] args, kwargs = call actual = args[0] message = ('Call #%d, expected: %s, actual: %s' % (i, expected, actual)) self.assertAlmostEqual(expected, actual, message=message) def test_looping_call_timed_out(self): def _fake_task(): pass timer = loopingcall.FixedIntervalWithTimeoutLoopingCall(_fake_task) self.assertRaises(loopingcall.LoopingCallTimeOut, timer.start(interval=0.1, timeout=0.3).wait) class DynamicLoopingCallTestCase(test_base.BaseTestCase): def setUp(self): super(DynamicLoopingCallTestCase, self).setUp() self.num_runs = 0 def test_return_true(self): def _raise_it(): raise loopingcall.LoopingCallDone(True) timer = loopingcall.DynamicLoopingCall(_raise_it) self.assertTrue(timer.start().wait()) def test_monotonic_timer(self): def _raise_it(): clock = eventlet.hubs.get_hub().clock ok = (clock == oslo_service._monotonic) raise loopingcall.LoopingCallDone(ok) timer = loopingcall.DynamicLoopingCall(_raise_it) self.assertTrue(timer.start().wait()) def test_no_double_start(self): wait_ev = greenthreading.Event() def _run_forever_until_set(): if wait_ev.is_set(): raise loopingcall.LoopingCallDone(True) else: return 0.01 timer = loopingcall.DynamicLoopingCall(_run_forever_until_set) timer.start() self.assertRaises(RuntimeError, timer.start) wait_ev.set() timer.wait() def test_return_false(self): def _raise_it(): raise loopingcall.LoopingCallDone(False) timer = loopingcall.DynamicLoopingCall(_raise_it) self.assertFalse(timer.start().wait()) def test_terminate_on_exception(self): def _raise_it(): raise RuntimeError() timer = loopingcall.DynamicLoopingCall(_raise_it) self.assertRaises(RuntimeError, timer.start().wait) def _raise_and_then_done(self): if self.num_runs == 0: raise loopingcall.LoopingCallDone(False) else: self.num_runs = self.num_runs - 1 raise RuntimeError() def test_do_not_stop_on_exception(self): self.num_runs = 2 timer = loopingcall.DynamicLoopingCall(self._raise_and_then_done) timer.start(stop_on_exception=False).wait() def _wait_for_zero(self): """Called at an interval until num_runs == 0.""" if self.num_runs == 0: raise loopingcall.LoopingCallDone(False) else: self.num_runs = self.num_runs - 1 sleep_for = self.num_runs * 10 + 1 # dynamic duration return sleep_for def test_repeat(self): self.num_runs = 2 timer = loopingcall.DynamicLoopingCall(self._wait_for_zero) self.assertFalse(timer.start().wait()) def _timeout_task_without_any_return(self): pass def test_timeout_task_without_return_and_max_periodic(self): timer = loopingcall.DynamicLoopingCall( self._timeout_task_without_any_return ) self.assertRaises(RuntimeError, timer.start().wait) def _timeout_task_without_return_but_with_done(self): if self.num_runs == 0: raise loopingcall.LoopingCallDone(False) else: self.num_runs = self.num_runs - 1 @mock.patch('eventlet.greenthread.sleep') def test_timeout_task_without_return(self, sleep_mock): self.num_runs = 1 timer = loopingcall.DynamicLoopingCall( self._timeout_task_without_return_but_with_done ) timer.start(periodic_interval_max=5).wait() sleep_mock.assert_has_calls([mock.call(5)]) @mock.patch('eventlet.greenthread.sleep') def test_interval_adjustment(self, sleep_mock): self.num_runs = 2 timer = loopingcall.DynamicLoopingCall(self._wait_for_zero) timer.start(periodic_interval_max=5).wait() sleep_mock.assert_has_calls([mock.call(5), mock.call(1)]) @mock.patch('eventlet.greenthread.sleep') def test_initial_delay(self, sleep_mock): self.num_runs = 1 timer = loopingcall.DynamicLoopingCall(self._wait_for_zero) timer.start(initial_delay=3).wait() sleep_mock.assert_has_calls([mock.call(3), mock.call(1)]) class TestBackOffLoopingCall(test_base.BaseTestCase): @mock.patch('random.SystemRandom.gauss') @mock.patch('eventlet.greenthread.sleep') def test_exponential_backoff(self, sleep_mock, random_mock): def false(): return False random_mock.return_value = .8 self.assertRaises(loopingcall.LoopingCallTimeOut, loopingcall.BackOffLoopingCall(false).start() .wait) expected_times = [mock.call(1.6), mock.call(2.4000000000000004), mock.call(3.6), mock.call(5.4), mock.call(8.1), mock.call(12.15), mock.call(18.225), mock.call(27.337500000000002), mock.call(41.00625), mock.call(61.509375000000006), mock.call(92.26406250000001)] self.assertEqual(expected_times, sleep_mock.call_args_list) @mock.patch('random.SystemRandom.gauss') @mock.patch('eventlet.greenthread.sleep') def test_exponential_backoff_negative_value(self, sleep_mock, random_mock): def false(): return False # random.gauss() can return negative values random_mock.return_value = -.8 self.assertRaises(loopingcall.LoopingCallTimeOut, loopingcall.BackOffLoopingCall(false).start() .wait) expected_times = [mock.call(1.6), mock.call(2.4000000000000004), mock.call(3.6), mock.call(5.4), mock.call(8.1), mock.call(12.15), mock.call(18.225), mock.call(27.337500000000002), mock.call(41.00625), mock.call(61.509375000000006), mock.call(92.26406250000001)] self.assertEqual(expected_times, sleep_mock.call_args_list) @mock.patch('random.SystemRandom.gauss') @mock.patch('eventlet.greenthread.sleep') def test_no_backoff(self, sleep_mock, random_mock): random_mock.return_value = 1 func = mock.Mock() # func.side_effect func.side_effect = [True, True, True, loopingcall.LoopingCallDone( retvalue='return value')] retvalue = loopingcall.BackOffLoopingCall(func).start().wait() expected_times = [mock.call(1), mock.call(1), mock.call(1)] self.assertEqual(expected_times, sleep_mock.call_args_list) self.assertTrue(retvalue, 'return value') @mock.patch('random.SystemRandom.gauss') @mock.patch('eventlet.greenthread.sleep') def test_no_sleep(self, sleep_mock, random_mock): # Any call that executes properly the first time shouldn't sleep random_mock.return_value = 1 func = mock.Mock() # func.side_effect func.side_effect = loopingcall.LoopingCallDone(retvalue='return value') retvalue = loopingcall.BackOffLoopingCall(func).start().wait() self.assertFalse(sleep_mock.called) self.assertTrue(retvalue, 'return value') @mock.patch('random.SystemRandom.gauss') @mock.patch('eventlet.greenthread.sleep') def test_max_interval(self, sleep_mock, random_mock): def false(): return False random_mock.return_value = .8 self.assertRaises(loopingcall.LoopingCallTimeOut, loopingcall.BackOffLoopingCall(false).start( max_interval=60) .wait) expected_times = [mock.call(1.6), mock.call(2.4000000000000004), mock.call(3.6), mock.call(5.4), mock.call(8.1), mock.call(12.15), mock.call(18.225), mock.call(27.337500000000002), mock.call(41.00625), mock.call(60), mock.call(60), mock.call(60)] self.assertEqual(expected_times, sleep_mock.call_args_list) class AnException(Exception): pass class UnknownException(Exception): pass class RetryDecoratorTest(test_base.BaseTestCase): """Tests for retry decorator class.""" def test_retry(self): result = "RESULT" @loopingcall.RetryDecorator() def func(*args, **kwargs): return result self.assertEqual(result, func()) def func2(*args, **kwargs): return result retry = loopingcall.RetryDecorator() self.assertEqual(result, retry(func2)()) self.assertTrue(retry._retry_count == 0) def test_retry_with_expected_exceptions(self): result = "RESULT" responses = [AnException(None), AnException(None), result] def func(*args, **kwargs): response = responses.pop(0) if isinstance(response, Exception): raise response return response sleep_time_incr = 0.01 retry_count = 2 retry = loopingcall.RetryDecorator(10, sleep_time_incr, 10, (AnException,)) self.assertEqual(result, retry(func)()) self.assertTrue(retry._retry_count == retry_count) self.assertEqual(retry_count * sleep_time_incr, retry._sleep_time) def test_retry_with_max_retries(self): responses = [AnException(None), AnException(None), AnException(None)] def func(*args, **kwargs): response = responses.pop(0) if isinstance(response, Exception): raise response return response retry = loopingcall.RetryDecorator(2, 0, 0, (AnException,)) self.assertRaises(AnException, retry(func)) self.assertTrue(retry._retry_count == 2) def test_retry_with_unexpected_exception(self): def func(*args, **kwargs): raise UnknownException(None) retry = loopingcall.RetryDecorator() self.assertRaises(UnknownException, retry(func)) self.assertTrue(retry._retry_count == 0) oslo.service-1.29.0/oslo_service/tests/test_service.py0000666000175100017510000005765313224676223023202 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # 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. """ Unit Tests for service class """ from __future__ import print_function import logging import multiprocessing import os import signal import socket import time import traceback import eventlet from eventlet import event import mock from oslotest import base as test_base from oslo_service import service from oslo_service.tests import base from oslo_service.tests import eventlet_service LOG = logging.getLogger(__name__) class ExtendedService(service.Service): def test_method(self): return 'service' class ServiceManagerTestCase(test_base.BaseTestCase): """Test cases for Services.""" def test_override_manager_method(self): serv = ExtendedService() serv.start() self.assertEqual('service', serv.test_method()) class ServiceWithTimer(service.Service): def __init__(self, ready_event=None): super(ServiceWithTimer, self).__init__() self.ready_event = ready_event def start(self): super(ServiceWithTimer, self).start() self.timer_fired = 0 self.tg.add_timer(1, self.timer_expired) def wait(self): if self.ready_event: self.ready_event.set() super(ServiceWithTimer, self).wait() def timer_expired(self): self.timer_fired = self.timer_fired + 1 class ServiceCrashOnStart(ServiceWithTimer): def start(self): super(ServiceCrashOnStart, self).start() raise ValueError class ServiceTestBase(base.ServiceBaseTestCase): """A base class for ServiceLauncherTest and ServiceRestartTest.""" def _spawn_service(self, workers=1, service_maker=None, launcher_maker=None): self.workers = workers pid = os.fork() if pid == 0: os.setsid() # NOTE(johannes): We can't let the child processes exit back # into the unit test framework since then we'll have multiple # processes running the same tests (and possibly forking more # processes that end up in the same situation). So we need # to catch all exceptions and make sure nothing leaks out, in # particular SystemExit, which is raised by sys.exit(). We use # os._exit() which doesn't have this problem. status = 0 try: serv = service_maker() if service_maker else ServiceWithTimer() if launcher_maker: launcher = launcher_maker() launcher.launch_service(serv, workers=workers) else: launcher = service.launch(self.conf, serv, workers=workers) status = launcher.wait() except SystemExit as exc: status = exc.code except BaseException: # We need to be defensive here too try: traceback.print_exc() except BaseException: print("Couldn't print traceback") status = 2 # Really exit os._exit(status or 0) return pid def _wait(self, cond, timeout): start = time.time() while not cond(): if time.time() - start > timeout: break time.sleep(.1) def setUp(self): super(ServiceTestBase, self).setUp() # NOTE(markmc): ConfigOpts.log_opt_values() uses CONF.config-file self.conf(args=[], default_config_files=[]) self.addCleanup(self.conf.reset) self.addCleanup(self._reap_pid) self.pid = 0 def _reap_pid(self): if self.pid: # Make sure all processes are stopped os.kill(self.pid, signal.SIGTERM) # Make sure we reap our test process self._reap_test() def _reap_test(self): pid, status = os.waitpid(self.pid, 0) self.pid = None return status class ServiceLauncherTest(ServiceTestBase): """Originally from nova/tests/integrated/test_multiprocess_api.py.""" def _spawn(self): self.pid = self._spawn_service(workers=2) # Wait at most 10 seconds to spawn workers cond = lambda: self.workers == len(self._get_workers()) timeout = 10 self._wait(cond, timeout) workers = self._get_workers() self.assertEqual(len(workers), self.workers) return workers def _get_workers(self): f = os.popen('ps ax -o pid,ppid,command') # Skip ps header f.readline() processes = [tuple(int(p) for p in l.strip().split()[:2]) for l in f] return [p for p, pp in processes if pp == self.pid] def test_killed_worker_recover(self): start_workers = self._spawn() # kill one worker and check if new worker can come up LOG.info('pid of first child is %s' % start_workers[0]) os.kill(start_workers[0], signal.SIGTERM) # Wait at most 5 seconds to respawn a worker cond = lambda: start_workers != self._get_workers() timeout = 5 self._wait(cond, timeout) # Make sure worker pids don't match end_workers = self._get_workers() LOG.info('workers: %r' % end_workers) self.assertNotEqual(start_workers, end_workers) def _terminate_with_signal(self, sig): self._spawn() os.kill(self.pid, sig) # Wait at most 5 seconds to kill all workers cond = lambda: not self._get_workers() timeout = 5 self._wait(cond, timeout) workers = self._get_workers() LOG.info('workers: %r' % workers) self.assertFalse(workers, 'No OS processes left.') def test_terminate_sigkill(self): self._terminate_with_signal(signal.SIGKILL) status = self._reap_test() self.assertTrue(os.WIFSIGNALED(status)) self.assertEqual(signal.SIGKILL, os.WTERMSIG(status)) def test_terminate_sigterm(self): self._terminate_with_signal(signal.SIGTERM) status = self._reap_test() self.assertTrue(os.WIFEXITED(status)) self.assertEqual(0, os.WEXITSTATUS(status)) def test_crashed_service(self): service_maker = lambda: ServiceCrashOnStart() self.pid = self._spawn_service(service_maker=service_maker) status = self._reap_test() self.assertTrue(os.WIFEXITED(status)) self.assertEqual(1, os.WEXITSTATUS(status)) def test_child_signal_sighup(self): start_workers = self._spawn() os.kill(start_workers[0], signal.SIGHUP) # Wait at most 5 seconds to respawn a worker cond = lambda: start_workers == self._get_workers() timeout = 5 self._wait(cond, timeout) # Make sure worker pids match end_workers = self._get_workers() LOG.info('workers: %r' % end_workers) self.assertEqual(start_workers, end_workers) def test_parent_signal_sighup(self): start_workers = self._spawn() os.kill(self.pid, signal.SIGHUP) def cond(): workers = self._get_workers() return (len(workers) == len(start_workers) and not set(start_workers).intersection(workers)) # Wait at most 5 seconds to respawn a worker timeout = 10 self._wait(cond, timeout) self.assertTrue(cond()) class ServiceRestartTest(ServiceTestBase): def _spawn(self): ready_event = multiprocessing.Event() service_maker = lambda: ServiceWithTimer(ready_event=ready_event) self.pid = self._spawn_service(service_maker=service_maker) return ready_event def test_service_restart(self): ready = self._spawn() timeout = 5 ready.wait(timeout) self.assertTrue(ready.is_set(), 'Service never became ready') ready.clear() os.kill(self.pid, signal.SIGHUP) ready.wait(timeout) self.assertTrue(ready.is_set(), 'Service never back after SIGHUP') def test_terminate_sigterm(self): ready = self._spawn() timeout = 5 ready.wait(timeout) self.assertTrue(ready.is_set(), 'Service never became ready') os.kill(self.pid, signal.SIGTERM) status = self._reap_test() self.assertTrue(os.WIFEXITED(status)) self.assertEqual(0, os.WEXITSTATUS(status)) def test_mutate_hook_service_launcher(self): """Test mutate_config_files is called by ServiceLauncher on SIGHUP. Not using _spawn_service because ServiceLauncher doesn't fork and it's simplest to stay all in one process. """ mutate = multiprocessing.Event() self.conf.register_mutate_hook(lambda c, f: mutate.set()) launcher = service.launch( self.conf, ServiceWithTimer(), restart_method='mutate') self.assertFalse(mutate.is_set(), "Hook was called too early") launcher.restart() self.assertTrue(mutate.is_set(), "Hook wasn't called") def test_mutate_hook_process_launcher(self): """Test mutate_config_files is called by ProcessLauncher on SIGHUP. Forks happen in _spawn_service and ProcessLauncher. So we get three tiers of processes, the top tier being the test process. self.pid refers to the middle tier, which represents our application. Both service_maker and launcher_maker execute in the middle tier. The bottom tier is the workers. The behavior we want is that when the application (middle tier) receives a SIGHUP, it catches that, calls mutate_config_files and relaunches all the workers. This causes them to inherit the mutated config. """ mutate = multiprocessing.Event() ready = multiprocessing.Event() def service_maker(): self.conf.register_mutate_hook(lambda c, f: mutate.set()) return ServiceWithTimer(ready) def launcher_maker(): return service.ProcessLauncher(self.conf, restart_method='mutate') self.pid = self._spawn_service(1, service_maker, launcher_maker) timeout = 5 ready.wait(timeout) self.assertTrue(ready.is_set(), 'Service never became ready') ready.clear() self.assertFalse(mutate.is_set(), "Hook was called too early") os.kill(self.pid, signal.SIGHUP) ready.wait(timeout) self.assertTrue(ready.is_set(), 'Service never back after SIGHUP') self.assertTrue(mutate.is_set(), "Hook wasn't called") class _Service(service.Service): def __init__(self): super(_Service, self).__init__() self.init = event.Event() self.cleaned_up = False def start(self): self.init.send() def stop(self): self.cleaned_up = True super(_Service, self).stop() class LauncherTest(base.ServiceBaseTestCase): def test_graceful_shutdown(self): # test that services are given a chance to clean up: svc = _Service() launcher = service.launch(self.conf, svc) # wait on 'init' so we know the service had time to start: svc.init.wait() launcher.stop() self.assertTrue(svc.cleaned_up) # make sure stop can be called more than once. (i.e. play nice with # unit test fixtures in nova bug #1199315) launcher.stop() @mock.patch('oslo_service.service.ServiceLauncher.launch_service') def _test_launch_single(self, workers, mock_launch): svc = service.Service() service.launch(self.conf, svc, workers=workers) mock_launch.assert_called_with(svc, workers=workers) def test_launch_none(self): self._test_launch_single(None) def test_launch_one_worker(self): self._test_launch_single(1) def test_launch_invalid_workers_number(self): svc = service.Service() for num_workers in [0, -1]: self.assertRaises(ValueError, service.launch, self.conf, svc, num_workers) @mock.patch('signal.alarm') @mock.patch('oslo_service.service.ProcessLauncher.launch_service') def test_multiple_worker(self, mock_launch, alarm_mock): svc = service.Service() service.launch(self.conf, svc, workers=3) mock_launch.assert_called_with(svc, workers=3) def test_launch_wrong_service_base_class(self): # check that services that do not subclass service.ServiceBase # can not be launched. svc = mock.Mock() self.assertRaises(TypeError, service.launch, self.conf, svc) @mock.patch('signal.alarm') @mock.patch("oslo_service.service.Services.add") @mock.patch("oslo_service.eventlet_backdoor.initialize_if_enabled") def test_check_service_base(self, initialize_if_enabled_mock, services_mock, alarm_mock): initialize_if_enabled_mock.return_value = None launcher = service.Launcher(self.conf) serv = _Service() launcher.launch_service(serv) @mock.patch('signal.alarm') @mock.patch("oslo_service.service.Services.add") @mock.patch("oslo_service.eventlet_backdoor.initialize_if_enabled") def test_check_service_base_fails(self, initialize_if_enabled_mock, services_mock, alarm_mock): initialize_if_enabled_mock.return_value = None launcher = service.Launcher(self.conf) class FooService(object): def __init__(self): pass serv = FooService() self.assertRaises(TypeError, launcher.launch_service, serv) class ProcessLauncherTest(base.ServiceBaseTestCase): @mock.patch('signal.alarm') @mock.patch("signal.signal") def test_stop(self, signal_mock, alarm_mock): signal_mock.SIGTERM = 15 launcher = service.ProcessLauncher(self.conf) self.assertTrue(launcher.running) pid_nums = [22, 222] fakeServiceWrapper = service.ServiceWrapper(service.Service(), 1) launcher.children = {pid_nums[0]: fakeServiceWrapper, pid_nums[1]: fakeServiceWrapper} with mock.patch('oslo_service.service.os.kill') as mock_kill: with mock.patch.object(launcher, '_wait_child') as _wait_child: def fake_wait_child(): pid = pid_nums.pop() return launcher.children.pop(pid) _wait_child.side_effect = fake_wait_child with mock.patch('oslo_service.service.Service.stop') as \ mock_service_stop: mock_service_stop.side_effect = lambda: None launcher.stop() self.assertFalse(launcher.running) self.assertFalse(launcher.children) self.assertEqual([mock.call(222, signal_mock.SIGTERM), mock.call(22, signal_mock.SIGTERM)], mock_kill.mock_calls) mock_service_stop.assert_called_once_with() def test__handle_signal(self): signal_handler = service.SignalHandler() signal_handler.clear() self.assertEqual(0, len(signal_handler._signal_handlers[signal.SIGTERM])) call_1, call_2 = mock.Mock(), mock.Mock() signal_handler.add_handler('SIGTERM', call_1) signal_handler.add_handler('SIGTERM', call_2) self.assertEqual(2, len(signal_handler._signal_handlers[signal.SIGTERM])) signal_handler._handle_signal(signal.SIGTERM, 'test') # execute pending eventlet callbacks time.sleep(0) for m in signal_handler._signal_handlers[signal.SIGTERM]: m.assert_called_once_with(signal.SIGTERM, 'test') signal_handler.clear() @mock.patch('signal.alarm') @mock.patch("os.kill") @mock.patch("oslo_service.service.ProcessLauncher.stop") @mock.patch("oslo_service.service.ProcessLauncher._respawn_children") @mock.patch("oslo_service.service.ProcessLauncher.handle_signal") @mock.patch("oslo_config.cfg.CONF.log_opt_values") @mock.patch("oslo_service.systemd.notify_once") @mock.patch("oslo_config.cfg.CONF.reload_config_files") @mock.patch("oslo_service.service._is_sighup_and_daemon") def test_parent_process_reload_config(self, is_sighup_and_daemon_mock, reload_config_files_mock, notify_once_mock, log_opt_values_mock, handle_signal_mock, respawn_children_mock, stop_mock, kill_mock, alarm_mock): is_sighup_and_daemon_mock.return_value = True respawn_children_mock.side_effect = [None, eventlet.greenlet.GreenletExit()] launcher = service.ProcessLauncher(self.conf) launcher.sigcaught = 1 launcher.children = {} wrap_mock = mock.Mock() launcher.children[222] = wrap_mock launcher.wait() reload_config_files_mock.assert_called_once_with() wrap_mock.service.reset.assert_called_once_with() @mock.patch("oslo_service.service.ProcessLauncher._start_child") @mock.patch("oslo_service.service.ProcessLauncher.handle_signal") @mock.patch("eventlet.greenio.GreenPipe") @mock.patch("os.pipe") def test_check_service_base(self, pipe_mock, green_pipe_mock, handle_signal_mock, start_child_mock): pipe_mock.return_value = [None, None] launcher = service.ProcessLauncher(self.conf) serv = _Service() launcher.launch_service(serv, workers=0) @mock.patch("oslo_service.service.ProcessLauncher._start_child") @mock.patch("oslo_service.service.ProcessLauncher.handle_signal") @mock.patch("eventlet.greenio.GreenPipe") @mock.patch("os.pipe") def test_check_service_base_fails(self, pipe_mock, green_pipe_mock, handle_signal_mock, start_child_mock): pipe_mock.return_value = [None, None] launcher = service.ProcessLauncher(self.conf) class FooService(object): def __init__(self): pass serv = FooService() self.assertRaises(TypeError, launcher.launch_service, serv, 0) @mock.patch("oslo_service.service.ProcessLauncher._start_child") @mock.patch("oslo_service.service.ProcessLauncher.handle_signal") @mock.patch("eventlet.greenio.GreenPipe") @mock.patch("os.pipe") def test_double_sighup(self, pipe_mock, green_pipe_mock, handle_signal_mock, start_child_mock): # Test that issuing two SIGHUPs in a row does not exit; then send a # TERM that does cause an exit. pipe_mock.return_value = [None, None] launcher = service.ProcessLauncher(self.conf) serv = _Service() launcher.launch_service(serv, workers=0) def stager(): # -1: start state # 0: post-init # 1: first HUP sent # 2: second HUP sent # 3: TERM sent stager.stage += 1 if stager.stage < 3: launcher._handle_hup(1, mock.sentinel.frame) elif stager.stage == 3: launcher._handle_term(15, mock.sentinel.frame) else: self.fail("TERM did not kill launcher") stager.stage = -1 handle_signal_mock.side_effect = stager launcher.wait() self.assertEqual(3, stager.stage) class GracefulShutdownTestService(service.Service): def __init__(self): super(GracefulShutdownTestService, self).__init__() self.finished_task = event.Event() def start(self, sleep_amount): def sleep_and_send(finish_event): time.sleep(sleep_amount) finish_event.send() self.tg.add_thread(sleep_and_send, self.finished_task) def exercise_graceful_test_service(sleep_amount, time_to_wait, graceful): svc = GracefulShutdownTestService() svc.start(sleep_amount) svc.stop(graceful) def wait_for_task(svc): svc.finished_task.wait() return eventlet.timeout.with_timeout(time_to_wait, wait_for_task, svc=svc, timeout_value="Timeout!") class ServiceTest(test_base.BaseTestCase): def test_graceful_stop(self): # Here we wait long enough for the task to gracefully finish. self.assertIsNone(exercise_graceful_test_service(1, 2, True)) def test_ungraceful_stop(self): # Here we stop ungracefully, and will never see the task finish. self.assertEqual("Timeout!", exercise_graceful_test_service(1, 2, False)) class EventletServerProcessLauncherTest(base.ServiceBaseTestCase): def setUp(self): super(EventletServerProcessLauncherTest, self).setUp() self.conf(args=[], default_config_files=[]) self.addCleanup(self.conf.reset) self.workers = 3 def run_server(self): queue = multiprocessing.Queue() proc = multiprocessing.Process(target=eventlet_service.run, args=(queue,), kwargs={'workers': self.workers}) proc.start() port = queue.get() conn = socket.create_connection(('127.0.0.1', port)) # NOTE(blk-u): The sleep shouldn't be necessary. There must be a bug in # the server implementation where it takes some time to set up the # server or signal handlers. time.sleep(1) return (proc, conn) def test_shuts_down_on_sigint_when_client_connected(self): proc, conn = self.run_server() # check that server is live self.assertTrue(proc.is_alive()) # send SIGINT to the server and wait for it to exit while client still # connected. os.kill(proc.pid, signal.SIGINT) proc.join() conn.close() def test_graceful_shuts_down_on_sigterm_when_client_connected(self): self.config(graceful_shutdown_timeout=7) proc, conn = self.run_server() # send SIGTERM to the server and wait for it to exit while client still # connected. os.kill(proc.pid, signal.SIGTERM) # server with graceful shutdown must wait forewer if # option graceful_shutdown_timeout is not specified. # we can not wait forever ... so 3 seconds are enough time.sleep(3) self.assertTrue(proc.is_alive()) conn.close() proc.join() def test_graceful_stop_with_exceeded_graceful_shutdown_timeout(self): # Server must exit if graceful_shutdown_timeout exceeded graceful_shutdown_timeout = 4 self.config(graceful_shutdown_timeout=graceful_shutdown_timeout) proc, conn = self.run_server() time_before = time.time() os.kill(proc.pid, signal.SIGTERM) self.assertTrue(proc.is_alive()) proc.join() self.assertFalse(proc.is_alive()) time_after = time.time() self.assertTrue(time_after - time_before > graceful_shutdown_timeout) class EventletServerServiceLauncherTest(EventletServerProcessLauncherTest): def setUp(self): super(EventletServerServiceLauncherTest, self).setUp() self.workers = 1 oslo.service-1.29.0/oslo_service/tests/test_sslutils.py0000666000175100017510000001317313224676223023411 0ustar zuulzuul00000000000000# Copyright 2015 Mirantis, 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 mock import os import ssl from oslo_config import cfg from oslo_service import sslutils from oslo_service.tests import base CONF = cfg.CONF SSL_CERT_DIR = os.path.normpath(os.path.join( os.path.dirname(os.path.abspath(__file__)), 'ssl_cert')) class SslutilsTestCase(base.ServiceBaseTestCase): """Test cases for sslutils.""" def setUp(self): super(SslutilsTestCase, self).setUp() self.cert_file_name = os.path.join(SSL_CERT_DIR, 'certificate.crt') self.key_file_name = os.path.join(SSL_CERT_DIR, 'privatekey.key') self.ca_file_name = os.path.join(SSL_CERT_DIR, 'ca.crt') @mock.patch("%s.RuntimeError" % RuntimeError.__module__) @mock.patch("os.path.exists") def test_is_enabled(self, exists_mock, runtime_error_mock): exists_mock.return_value = True self.conf.set_default("cert_file", self.cert_file_name, group=sslutils.config_section) self.conf.set_default("key_file", self.key_file_name, group=sslutils.config_section) self.conf.set_default("ca_file", self.ca_file_name, group=sslutils.config_section) sslutils.is_enabled(self.conf) self.assertFalse(runtime_error_mock.called) @mock.patch("os.path.exists") def test_is_enabled_no_ssl_cert_file_fails(self, exists_mock): exists_mock.side_effect = [False] self.conf.set_default("cert_file", "/no/such/file", group=sslutils.config_section) self.assertRaises(RuntimeError, sslutils.is_enabled, self.conf) @mock.patch("os.path.exists") def test_is_enabled_no_ssl_key_file_fails(self, exists_mock): exists_mock.side_effect = [True, False] self.conf.set_default("cert_file", self.cert_file_name, group=sslutils.config_section) self.conf.set_default("key_file", "/no/such/file", group=sslutils.config_section) self.assertRaises(RuntimeError, sslutils.is_enabled, self.conf) @mock.patch("os.path.exists") def test_is_enabled_no_ssl_ca_file_fails(self, exists_mock): exists_mock.side_effect = [True, True, False] self.conf.set_default("cert_file", self.cert_file_name, group=sslutils.config_section) self.conf.set_default("key_file", self.key_file_name, group=sslutils.config_section) self.conf.set_default("ca_file", "/no/such/file", group=sslutils.config_section) self.assertRaises(RuntimeError, sslutils.is_enabled, self.conf) @mock.patch("ssl.wrap_socket") @mock.patch("os.path.exists") def _test_wrap(self, exists_mock, wrap_socket_mock, **kwargs): exists_mock.return_value = True sock = mock.Mock() self.conf.set_default("cert_file", self.cert_file_name, group=sslutils.config_section) self.conf.set_default("key_file", self.key_file_name, group=sslutils.config_section) ssl_kwargs = {'server_side': True, 'certfile': self.conf.ssl.cert_file, 'keyfile': self.conf.ssl.key_file, 'cert_reqs': ssl.CERT_NONE, } if kwargs: ssl_kwargs.update(**kwargs) sslutils.wrap(self.conf, sock) wrap_socket_mock.assert_called_once_with(sock, **ssl_kwargs) def test_wrap(self): self._test_wrap() def test_wrap_ca_file(self): self.conf.set_default("ca_file", self.ca_file_name, group=sslutils.config_section) ssl_kwargs = {'ca_certs': self.conf.ssl.ca_file, 'cert_reqs': ssl.CERT_REQUIRED } self._test_wrap(**ssl_kwargs) def test_wrap_ciphers(self): self.conf.set_default("ca_file", self.ca_file_name, group=sslutils.config_section) ciphers = ( 'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+' 'AES:ECDH+HIGH:DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:' 'RSA+HIGH:RSA+3DES:!aNULL:!eNULL:!MD5:!DSS:!RC4' ) self.conf.set_default("ciphers", ciphers, group=sslutils.config_section) ssl_kwargs = {'ca_certs': self.conf.ssl.ca_file, 'cert_reqs': ssl.CERT_REQUIRED, 'ciphers': ciphers} self._test_wrap(**ssl_kwargs) def test_wrap_ssl_version(self): self.conf.set_default("ca_file", self.ca_file_name, group=sslutils.config_section) self.conf.set_default("version", "tlsv1", group=sslutils.config_section) ssl_kwargs = {'ca_certs': self.conf.ssl.ca_file, 'cert_reqs': ssl.CERT_REQUIRED, 'ssl_version': ssl.PROTOCOL_TLSv1} self._test_wrap(**ssl_kwargs) oslo.service-1.29.0/oslo_service/locale/0000775000175100017510000000000013224676442020207 5ustar zuulzuul00000000000000oslo.service-1.29.0/oslo_service/locale/en_GB/0000775000175100017510000000000013224676442021161 5ustar zuulzuul00000000000000oslo.service-1.29.0/oslo_service/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013224676442022746 5ustar zuulzuul00000000000000oslo.service-1.29.0/oslo_service/locale/en_GB/LC_MESSAGES/oslo_service.po0000666000175100017510000001040013224676223025774 0ustar zuulzuul00000000000000# Andi Chandler , 2016. #zanata # Andreas Jaeger , 2016. #zanata # Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.service 1.25.1.dev9\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2017-09-18 13:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-10-07 09:30+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en-GB\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "" "A dynamic backoff interval looping call can only run one function at a time" msgstr "" "A dynamic backoff interval looping call can only run one function at a time" msgid "A dynamic interval looping call can only run one function at a time" msgstr "A dynamic interval looping call can only run one function at a time" msgid "" "A dynamic interval looping call should supply either an interval or " "periodic_interval_max" msgstr "" "A dynamic interval looping call should supply either an interval or " "periodic_interval_max" msgid "A fixed interval looping call can only run one function at a time" msgstr "A fixed interval looping call can only run one function at a time" msgid "" "A fixed interval looping call with timeout checking and can only run one " "function at at a time" msgstr "" "A fixed interval looping call with timeout checking and can only run one " "function at at a time" msgid "A looping call can only run one function at a time" msgstr "A looping call can only run one function at a time" #, python-format msgid "Could not find config at %(path)s" msgstr "Could not find config at %(path)s" #, python-format msgid "Could not load paste app '%(name)s' from %(path)s" msgstr "Could not load paste app '%(name)s' from %(path)s" msgid "Did not create backdoor at requested location" msgstr "Did not create backdoor at requested location" msgid "Dynamic backoff interval looping call" msgstr "Dynamic backoff interval looping call" msgid "Dynamic interval looping call" msgstr "Dynamic interval looping call" msgid "Fixed interval looping call" msgstr "Fixed interval looping call" msgid "Fixed interval looping call with timeout checking." msgstr "Fixed interval looping call with timeout checking." #, python-format msgid "Invalid SSL version : %s" msgstr "Invalid SSL version : %s" #, python-format msgid "Invalid backdoor_port configuration %(range)s: %(ex)s. %(help)s" msgstr "Invalid backdoor_port configuration %(range)s: %(ex)s. %(help)s" #, python-format msgid "" "Invalid input received: Unexpected argument for periodic task creation: " "%(arg)s." msgstr "" "Invalid input received: Unexpected argument for periodic task creation: " "%(arg)s." #, python-format msgid "Invalid restart_method: %s" msgstr "Invalid restart_method: %s" msgid "Launcher asked to start multiple workers" msgstr "Launcher asked to start multiple workers" #, python-format msgid "Looping call timed out after %.02f seconds" msgstr "Looping call timed out after %.02f seconds" msgid "Number of workers should be positive!" msgstr "Number of workers should be positive!" #, python-format msgid "Service %(service)s must an instance of %(base)s!" msgstr "Service %(service)s must an instance of %(base)s!" msgid "The backlog must be more than 0" msgstr "The backlog must be more than 0" #, python-format msgid "Unable to find ca_file : %s" msgstr "Unable to find ca_file : %s" #, python-format msgid "Unable to find cert_file : %s" msgstr "Unable to find cert_file : %s" #, python-format msgid "Unable to find key_file : %s" msgstr "Unable to find key_file : %s" #, python-format msgid "Unexpected argument for periodic task creation: %(arg)s." msgstr "Unexpected argument for periodic task creation: %(arg)s." msgid "Unknown looping call" msgstr "Unknown looping call" #, python-format msgid "Unsupported socket family: %s" msgstr "Unsupported socket family: %s" msgid "" "When running server in SSL mode, you must specify both a cert_file and " "key_file option value in your configuration file" msgstr "" "When running server in SSL mode, you must specify both a cert_file and " "key_file option value in your configuration file" oslo.service-1.29.0/oslo_service/_i18n.py0000666000175100017510000000212313224676223020235 0ustar zuulzuul00000000000000# 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. """oslo.i18n integration module. See https://docs.openstack.org/oslo.i18n/latest/user/index.html . """ import oslo_i18n DOMAIN = "oslo_service" _translators = oslo_i18n.TranslatorFactory(domain=DOMAIN) # The primary translation function using the well-known name "_" _ = _translators.primary # The contextual translation function using the name "_C" _C = _translators.contextual_form # The plural translation function using the name "_P" _P = _translators.plural_form def get_available_languages(): return oslo_i18n.get_available_languages(DOMAIN) oslo.service-1.29.0/oslo_service/__init__.py0000666000175100017510000000240313224676223021057 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import eventlet.patcher import monotonic from oslo_log import log as logging time = eventlet.patcher.original('time') LOG = logging.getLogger(__name__) if hasattr(time, 'monotonic'): # Use builtin monotonic clock, Python 3.3+ _monotonic = time.monotonic else: _monotonic = monotonic.monotonic def service_hub(): # NOTE(dims): Add a custom impl for EVENTLET_HUB, so we can # override the clock used in the eventlet hubs. The default # uses time.time() and we need to use a monotonic timer # to ensure that things like loopingcall work properly. hub = eventlet.hubs.get_default_hub().Hub() hub.clock = _monotonic return hub os.environ['EVENTLET_HUB'] = 'oslo_service:service_hub' oslo.service-1.29.0/oslo_service/eventlet_backdoor.py0000666000175100017510000001627213224676223023023 0ustar zuulzuul00000000000000# Copyright (c) 2012 OpenStack Foundation. # Administrator of the National Aeronautics and Space Administration. # 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 gc import logging import os import pprint import socket import sys import traceback import eventlet.backdoor import greenlet from oslo_service._i18n import _ from oslo_service import _options LOG = logging.getLogger(__name__) class EventletBackdoorConfigValueError(Exception): def __init__(self, port_range, help_msg, ex): msg = (_('Invalid backdoor_port configuration %(range)s: %(ex)s. ' '%(help)s') % {'range': port_range, 'ex': ex, 'help': help_msg}) super(EventletBackdoorConfigValueError, self).__init__(msg) self.port_range = port_range def _dont_use_this(): print("Don't use this, just disconnect instead") def _dump_frame(f, frame_chapter): co = f.f_code print(" %s Frame: %s" % (frame_chapter, co.co_name)) print(" File: %s" % (co.co_filename)) print(" Captured at line number: %s" % (f.f_lineno)) co_locals = set(co.co_varnames) if len(co_locals): not_set = co_locals.copy() set_locals = {} for var_name in f.f_locals.keys(): if var_name in co_locals: set_locals[var_name] = f.f_locals[var_name] not_set.discard(var_name) if set_locals: print(" %s set local variables:" % (len(set_locals))) for var_name in sorted(set_locals.keys()): print(" %s => %r" % (var_name, f.f_locals[var_name])) else: print(" 0 set local variables.") if not_set: print(" %s not set local variables:" % (len(not_set))) for var_name in sorted(not_set): print(" %s" % (var_name)) else: print(" 0 not set local variables.") else: print(" 0 Local variables.") def _detailed_dump_frames(f, thread_index): i = 0 while f is not None: _dump_frame(f, "%s.%s" % (thread_index, i + 1)) f = f.f_back i += 1 def _find_objects(t): return [o for o in gc.get_objects() if isinstance(o, t)] def _print_greenthreads(simple=True): for i, gt in enumerate(_find_objects(greenlet.greenlet)): print(i, gt) if simple: traceback.print_stack(gt.gr_frame) else: _detailed_dump_frames(gt.gr_frame, i) print() def _print_nativethreads(): for threadId, stack in sys._current_frames().items(): print(threadId) traceback.print_stack(stack) print() def _parse_port_range(port_range): if ':' not in port_range: start, end = port_range, port_range else: start, end = port_range.split(':', 1) try: start, end = int(start), int(end) if end < start: raise ValueError return start, end except ValueError as ex: raise EventletBackdoorConfigValueError( port_range, ex, _options.help_for_backdoor_port) def _listen(host, start_port, end_port, listen_func): try_port = start_port while True: try: return listen_func((host, try_port)) except socket.error as exc: if (exc.errno != errno.EADDRINUSE or try_port >= end_port): raise try_port += 1 def _try_open_unix_domain_socket(socket_path): try: return eventlet.listen(socket_path, socket.AF_UNIX) except socket.error as e: if e.errno != errno.EADDRINUSE: # NOTE(harlowja): Some other non-address in use error # occurred, since we aren't handling those, re-raise # and give up... raise else: # Attempt to remove the file before opening it again. try: os.unlink(socket_path) except OSError as e: if e.errno != errno.ENOENT: # NOTE(harlowja): File existed, but we couldn't # delete it, give up... raise return eventlet.listen(socket_path, socket.AF_UNIX) def _initialize_if_enabled(conf): conf.register_opts(_options.eventlet_backdoor_opts) backdoor_locals = { 'exit': _dont_use_this, # So we don't exit the entire process 'quit': _dont_use_this, # So we don't exit the entire process 'fo': _find_objects, 'pgt': _print_greenthreads, 'pnt': _print_nativethreads, } if conf.backdoor_port is None and conf.backdoor_socket is None: return None if conf.backdoor_socket is None: start_port, end_port = _parse_port_range(str(conf.backdoor_port)) sock = _listen('localhost', start_port, end_port, eventlet.listen) # In the case of backdoor port being zero, a port number is assigned by # listen(). In any case, pull the port number out here. where_running = sock.getsockname()[1] else: sock = _try_open_unix_domain_socket(conf.backdoor_socket) where_running = conf.backdoor_socket # NOTE(johannes): The standard sys.displayhook will print the value of # the last expression and set it to __builtin__._, which overwrites # the __builtin__._ that gettext sets. Let's switch to using pprint # since it won't interact poorly with gettext, and it's easier to # read the output too. def displayhook(val): if val is not None: pprint.pprint(val) sys.displayhook = displayhook LOG.info( 'Eventlet backdoor listening on %(where_running)s for' ' process %(pid)d', {'where_running': where_running, 'pid': os.getpid()} ) thread = eventlet.spawn(eventlet.backdoor.backdoor_server, sock, locals=backdoor_locals) return (where_running, thread) def initialize_if_enabled(conf): where_running_thread = _initialize_if_enabled(conf) if not where_running_thread: return None else: where_running, _thread = where_running_thread return where_running def _main(): import eventlet eventlet.monkey_patch(all=True) from oslo_config import cfg logging.basicConfig(level=logging.DEBUG) conf = cfg.ConfigOpts() conf.register_cli_opts(_options.eventlet_backdoor_opts) conf(sys.argv[1:]) where_running_thread = _initialize_if_enabled(conf) if not where_running_thread: raise RuntimeError(_("Did not create backdoor at requested location")) else: _where_running, thread = where_running_thread thread.wait() if __name__ == '__main__': # simple CLI for testing _main() oslo.service-1.29.0/oslo_service/loopingcall.py0000666000175100017510000004250513224676223021632 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011 Justin Santa Barbara # 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 random import sys import time from eventlet import event from eventlet import greenthread from oslo_log import log as logging from oslo_utils import excutils from oslo_utils import reflection from oslo_utils import timeutils import six from oslo_service._i18n import _ LOG = logging.getLogger(__name__) class LoopingCallDone(Exception): """Exception to break out and stop a LoopingCallBase. The poll-function passed to LoopingCallBase can raise this exception to break out of the loop normally. This is somewhat analogous to StopIteration. An optional return-value can be included as the argument to the exception; this return-value will be returned by LoopingCallBase.wait() """ def __init__(self, retvalue=True): """:param retvalue: Value that LoopingCallBase.wait() should return.""" self.retvalue = retvalue class LoopingCallTimeOut(Exception): """Exception for a timed out LoopingCall. The LoopingCall will raise this exception when a timeout is provided and it is exceeded. """ pass def _safe_wrapper(f, kind, func_name): """Wrapper that calls into wrapped function and logs errors as needed.""" def func(*args, **kwargs): try: return f(*args, **kwargs) except LoopingCallDone: raise # let the outer handler process this except Exception: LOG.error('%(kind)s %(func_name)r failed', {'kind': kind, 'func_name': func_name}, exc_info=True) return 0 return func class LoopingCallBase(object): _KIND = _("Unknown looping call") _RUN_ONLY_ONE_MESSAGE = _("A looping call can only run one function" " at a time") def __init__(self, f=None, *args, **kw): self.args = args self.kw = kw self.f = f self._running = False self._thread = None self.done = None def stop(self): self._running = False def wait(self): return self.done.wait() def _on_done(self, gt, *args, **kwargs): self._thread = None self._running = False def _start(self, idle_for, initial_delay=None, stop_on_exception=True): """Start the looping :param idle_for: Callable that takes two positional arguments, returns how long to idle for. The first positional argument is the last result from the function being looped and the second positional argument is the time it took to calculate that result. :param initial_delay: How long to delay before starting the looping. Value is in seconds. :param stop_on_exception: Whether to stop if an exception occurs. :returns: eventlet event instance """ if self._thread is not None: raise RuntimeError(self._RUN_ONLY_ONE_MESSAGE) self._running = True self.done = event.Event() self._thread = greenthread.spawn( self._run_loop, idle_for, initial_delay=initial_delay, stop_on_exception=stop_on_exception) self._thread.link(self._on_done) return self.done def _run_loop(self, idle_for_func, initial_delay=None, stop_on_exception=True): kind = self._KIND func_name = reflection.get_callable_name(self.f) func = self.f if stop_on_exception else _safe_wrapper(self.f, kind, func_name) if initial_delay: greenthread.sleep(initial_delay) try: watch = timeutils.StopWatch() while self._running: watch.restart() result = func(*self.args, **self.kw) watch.stop() if not self._running: break idle = idle_for_func(result, watch.elapsed()) LOG.trace('%(kind)s %(func_name)r sleeping ' 'for %(idle).02f seconds', {'func_name': func_name, 'idle': idle, 'kind': kind}) greenthread.sleep(idle) except LoopingCallDone as e: self.done.send(e.retvalue) except Exception: exc_info = sys.exc_info() try: LOG.error('%(kind)s %(func_name)r failed', {'kind': kind, 'func_name': func_name}, exc_info=exc_info) self.done.send_exception(*exc_info) finally: del exc_info return else: self.done.send(True) class FixedIntervalLoopingCall(LoopingCallBase): """A fixed interval looping call.""" _RUN_ONLY_ONE_MESSAGE = _("A fixed interval looping call can only run" " one function at a time") _KIND = _('Fixed interval looping call') def start(self, interval, initial_delay=None, stop_on_exception=True): def _idle_for(result, elapsed): delay = round(elapsed - interval, 2) if delay > 0: func_name = reflection.get_callable_name(self.f) LOG.warning('Function %(func_name)r run outlasted ' 'interval by %(delay).2f sec', {'func_name': func_name, 'delay': delay}) return -delay if delay < 0 else 0 return self._start(_idle_for, initial_delay=initial_delay, stop_on_exception=stop_on_exception) class FixedIntervalWithTimeoutLoopingCall(LoopingCallBase): """A fixed interval looping call with timeout checking mechanism.""" _RUN_ONLY_ONE_MESSAGE = _("A fixed interval looping call with timeout" " checking and can only run one function at" " at a time") _KIND = _('Fixed interval looping call with timeout checking.') def start(self, interval, initial_delay=None, stop_on_exception=True, timeout=0): start_time = time.time() def _idle_for(result, elapsed): delay = round(elapsed - interval, 2) if delay > 0: func_name = reflection.get_callable_name(self.f) LOG.warning('Function %(func_name)r run outlasted ' 'interval by %(delay).2f sec', {'func_name': func_name, 'delay': delay}) elapsed_time = time.time() - start_time if timeout > 0 and elapsed_time > timeout: raise LoopingCallTimeOut( _('Looping call timed out after %.02f seconds') % elapsed_time) return -delay if delay < 0 else 0 return self._start(_idle_for, initial_delay=initial_delay, stop_on_exception=stop_on_exception) class DynamicLoopingCall(LoopingCallBase): """A looping call which sleeps until the next known event. The function called should return how long to sleep for before being called again. """ _RUN_ONLY_ONE_MESSAGE = _("A dynamic interval looping call can only run" " one function at a time") _TASK_MISSING_SLEEP_VALUE_MESSAGE = _( "A dynamic interval looping call should supply either an" " interval or periodic_interval_max" ) _KIND = _('Dynamic interval looping call') def start(self, initial_delay=None, periodic_interval_max=None, stop_on_exception=True): def _idle_for(suggested_delay, elapsed): delay = suggested_delay if delay is None: if periodic_interval_max is not None: delay = periodic_interval_max else: # Note(suro-patz): An application used to receive a # TypeError thrown from eventlet layer, before # this RuntimeError was introduced. raise RuntimeError( self._TASK_MISSING_SLEEP_VALUE_MESSAGE) else: if periodic_interval_max is not None: delay = min(delay, periodic_interval_max) return delay return self._start(_idle_for, initial_delay=initial_delay, stop_on_exception=stop_on_exception) class BackOffLoopingCall(LoopingCallBase): """Run a method in a loop with backoff on error. The passed in function should return True (no error, return to initial_interval), False (error, start backing off), or raise LoopingCallDone(retvalue=None) (quit looping, return retvalue if set). When there is an error, the call will backoff on each failure. The backoff will be equal to double the previous base interval times some jitter. If a backoff would put it over the timeout, it halts immediately, so the call will never take more than timeout, but may and likely will take less time. When the function return value is True or False, the interval will be multiplied by a random jitter. If min_jitter or max_jitter is None, there will be no jitter (jitter=1). If min_jitter is below 0.5, the code may not backoff and may increase its retry rate. If func constantly returns True, this function will not return. To run a func and wait for a call to finish (by raising a LoopingCallDone): timer = BackOffLoopingCall(func) response = timer.start().wait() :param initial_delay: delay before first running of function :param starting_interval: initial interval in seconds between calls to function. When an error occurs and then a success, the interval is returned to starting_interval :param timeout: time in seconds before a LoopingCallTimeout is raised. The call will never take longer than timeout, but may quit before timeout. :param max_interval: The maximum interval between calls during errors :param jitter: Used to vary when calls are actually run to avoid group of calls all coming at the exact same time. Uses random.gauss(jitter, 0.1), with jitter as the mean for the distribution. If set below .5, it can cause the calls to come more rapidly after each failure. :param min_interval: The minimum interval in seconds between calls to function. :raises: LoopingCallTimeout if time spent doing error retries would exceed timeout. """ _RNG = random.SystemRandom() _KIND = _('Dynamic backoff interval looping call') _RUN_ONLY_ONE_MESSAGE = _("A dynamic backoff interval looping call can" " only run one function at a time") def __init__(self, f=None, *args, **kw): super(BackOffLoopingCall, self).__init__(f=f, *args, **kw) self._error_time = 0 self._interval = 1 def start(self, initial_delay=None, starting_interval=1, timeout=300, max_interval=300, jitter=0.75, min_interval=0.001): if self._thread is not None: raise RuntimeError(self._RUN_ONLY_ONE_MESSAGE) # Reset any prior state. self._error_time = 0 self._interval = starting_interval def _idle_for(success, _elapsed): random_jitter = abs(self._RNG.gauss(jitter, 0.1)) if success: # Reset error state now that it didn't error... self._interval = starting_interval self._error_time = 0 return self._interval * random_jitter else: # Perform backoff, random jitter around the next interval # bounded by min_interval and max_interval. idle = max(self._interval * 2 * random_jitter, min_interval) idle = min(idle, max_interval) # Calculate the next interval based on the mean, so that the # backoff grows at the desired rate. self._interval = max(self._interval * 2 * jitter, min_interval) # Don't go over timeout, end early if necessary. If # timeout is 0, keep going. if timeout > 0 and self._error_time + idle > timeout: raise LoopingCallTimeOut( _('Looping call timed out after %.02f seconds') % self._error_time) self._error_time += idle return idle return self._start(_idle_for, initial_delay=initial_delay) class RetryDecorator(object): """Decorator for retrying a function upon suggested exceptions. The decorated function is retried for the given number of times, and the sleep time between the retries is incremented until max sleep time is reached. If the max retry count is set to -1, then the decorated function is invoked indefinitely until an exception is thrown, and the caught exception is not in the list of suggested exceptions. """ def __init__(self, max_retry_count=-1, inc_sleep_time=10, max_sleep_time=60, exceptions=()): """Configure the retry object using the input params. :param max_retry_count: maximum number of times the given function must be retried when one of the input 'exceptions' is caught. When set to -1, it will be retried indefinitely until an exception is thrown and the caught exception is not in param exceptions. :param inc_sleep_time: incremental time in seconds for sleep time between retries :param max_sleep_time: max sleep time in seconds beyond which the sleep time will not be incremented using param inc_sleep_time. On reaching this threshold, max_sleep_time will be used as the sleep time. :param exceptions: suggested exceptions for which the function must be retried, if no exceptions are provided (the default) then all exceptions will be reraised, and no retrying will be triggered. """ self._max_retry_count = max_retry_count self._inc_sleep_time = inc_sleep_time self._max_sleep_time = max_sleep_time self._exceptions = exceptions self._retry_count = 0 self._sleep_time = 0 def __call__(self, f): func_name = reflection.get_callable_name(f) def _func(*args, **kwargs): result = None try: if self._retry_count: LOG.debug("Invoking %(func_name)s; retry count is " "%(retry_count)d.", {'func_name': func_name, 'retry_count': self._retry_count}) result = f(*args, **kwargs) except self._exceptions: with excutils.save_and_reraise_exception() as ctxt: LOG.debug("Exception which is in the suggested list of " "exceptions occurred while invoking function:" " %s.", func_name) if (self._max_retry_count != -1 and self._retry_count >= self._max_retry_count): LOG.debug("Cannot retry %(func_name)s upon " "suggested exception " "since retry count (%(retry_count)d) " "reached max retry count " "(%(max_retry_count)d).", {'retry_count': self._retry_count, 'max_retry_count': self._max_retry_count, 'func_name': func_name}) else: ctxt.reraise = False self._retry_count += 1 self._sleep_time += self._inc_sleep_time return self._sleep_time raise LoopingCallDone(result) @six.wraps(f) def func(*args, **kwargs): loop = DynamicLoopingCall(_func, *args, **kwargs) evt = loop.start(periodic_interval_max=self._max_sleep_time) LOG.debug("Waiting for function %s to return.", func_name) return evt.wait() return func oslo.service-1.29.0/oslo_service/wsgi.py0000666000175100017510000003034113224676223020273 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2010 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. """Utility methods for working with WSGI servers.""" from __future__ import print_function import copy import os import socket import eventlet import eventlet.wsgi import greenlet from paste import deploy import routes.middleware import webob.dec import webob.exc from oslo_log import log as logging from oslo_service._i18n import _ from oslo_service import _options from oslo_service import service from oslo_service import sslutils LOG = logging.getLogger(__name__) def list_opts(): """Entry point for oslo-config-generator.""" return [(None, copy.deepcopy(_options.wsgi_opts))] def register_opts(conf): """Registers WSGI config options.""" return conf.register_opts(_options.wsgi_opts) class InvalidInput(Exception): message = _("Invalid input received: " "Unexpected argument for periodic task creation: %(arg)s.") class Server(service.ServiceBase): """Server class to manage a WSGI server, serving a WSGI application.""" # TODO(eezhova): Consider changing the default host value to prevent # possible binding to all interfaces. The most appropriate value seems # to be 127.0.0.1, but it has to be verified that the change wouldn't # break any consuming project. def __init__(self, conf, name, app, host='0.0.0.0', port=0, # nosec pool_size=None, protocol=eventlet.wsgi.HttpProtocol, backlog=128, use_ssl=False, max_url_len=None, logger_name='eventlet.wsgi.server', socket_family=None, socket_file=None, socket_mode=None): """Initialize, but do not start, a WSGI server. :param conf: Instance of ConfigOpts. :param name: Pretty name for logging. :param app: The WSGI application to serve. :param host: IP address to serve the application. :param port: Port number to server the application. :param pool_size: Maximum number of eventlets to spawn concurrently. :param protocol: Protocol class. :param backlog: Maximum number of queued connections. :param use_ssl: Wraps the socket in an SSL context if True. :param max_url_len: Maximum length of permitted URLs. :param logger_name: The name for the logger. :param socket_family: Socket family. :param socket_file: location of UNIX socket. :param socket_mode: UNIX socket mode. :returns: None :raises: InvalidInput :raises: EnvironmentError """ self.conf = conf self.conf.register_opts(_options.wsgi_opts) self.default_pool_size = self.conf.wsgi_default_pool_size # Allow operators to customize http requests max header line size. eventlet.wsgi.MAX_HEADER_LINE = conf.max_header_line self.name = name self.app = app self._server = None self._protocol = protocol self.pool_size = pool_size or self.default_pool_size self._pool = eventlet.GreenPool(self.pool_size) self._logger = logging.getLogger(logger_name) self._use_ssl = use_ssl self._max_url_len = max_url_len self.client_socket_timeout = conf.client_socket_timeout or None if backlog < 1: raise InvalidInput(reason=_('The backlog must be more than 0')) if not socket_family or socket_family in [socket.AF_INET, socket.AF_INET6]: self.socket = self._get_socket(host, port, backlog) elif hasattr(socket, "AF_UNIX") and socket_family == socket.AF_UNIX: self.socket = self._get_unix_socket(socket_file, socket_mode, backlog) else: raise ValueError(_("Unsupported socket family: %s"), socket_family) (self.host, self.port) = self.socket.getsockname()[0:2] if self._use_ssl: sslutils.is_enabled(conf) def _get_socket(self, host, port, backlog): bind_addr = (host, port) # TODO(dims): eventlet's green dns/socket module does not actually # support IPv6 in getaddrinfo(). We need to get around this in the # future or monitor upstream for a fix try: info = socket.getaddrinfo(bind_addr[0], bind_addr[1], socket.AF_UNSPEC, socket.SOCK_STREAM)[0] family = info[0] bind_addr = info[-1] except Exception: family = socket.AF_INET try: sock = eventlet.listen(bind_addr, family, backlog=backlog) except EnvironmentError: LOG.error("Could not bind to %(host)s:%(port)s", {'host': host, 'port': port}) raise sock = self._set_socket_opts(sock) LOG.info("%(name)s listening on %(host)s:%(port)s", {'name': self.name, 'host': host, 'port': port}) return sock def _get_unix_socket(self, socket_file, socket_mode, backlog): sock = eventlet.listen(socket_file, family=socket.AF_UNIX, backlog=backlog) if socket_mode is not None: os.chmod(socket_file, socket_mode) LOG.info("%(name)s listening on %(socket_file)s:", {'name': self.name, 'socket_file': socket_file}) return sock def start(self): """Start serving a WSGI application. :returns: None """ # The server socket object will be closed after server exits, # but the underlying file descriptor will remain open, and will # give bad file descriptor error. So duplicating the socket object, # to keep file descriptor usable. self.dup_socket = self.socket.dup() if self._use_ssl: self.dup_socket = sslutils.wrap(self.conf, self.dup_socket) wsgi_kwargs = { 'func': eventlet.wsgi.server, 'sock': self.dup_socket, 'site': self.app, 'protocol': self._protocol, 'custom_pool': self._pool, 'log': self._logger, 'log_format': self.conf.wsgi_log_format, 'debug': False, 'keepalive': self.conf.wsgi_keep_alive, 'socket_timeout': self.client_socket_timeout } if self._max_url_len: wsgi_kwargs['url_length_limit'] = self._max_url_len self._server = eventlet.spawn(**wsgi_kwargs) def _set_socket_opts(self, _socket): _socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # sockets can hang around forever without keepalive _socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) # This option isn't available in the OS X version of eventlet if hasattr(socket, 'TCP_KEEPIDLE'): _socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, self.conf.tcp_keepidle) return _socket def reset(self): """Reset server greenpool size to default. :returns: None """ self._pool.resize(self.pool_size) def stop(self): """Stops eventlet server. Doesn't allow accept new connecting. :returns: None """ LOG.info("Stopping WSGI server.") if self._server is not None: # let eventlet close socket self._pool.resize(0) self._server.kill() def wait(self): """Block, until the server has stopped. Waits on the server's eventlet to finish, then returns. :returns: None """ try: if self._server is not None: num = self._pool.running() LOG.debug("Waiting WSGI server to finish %d requests.", num) self._pool.waitall() except greenlet.GreenletExit: LOG.info("WSGI server has stopped.") class Request(webob.Request): pass class Router(object): """WSGI middleware that maps incoming requests to WSGI apps.""" def __init__(self, mapper): """Create a router for the given routes.Mapper. Each route in `mapper` must specify a 'controller', which is a WSGI app to call. You'll probably want to specify an 'action' as well and have your controller be an object that can route the request to the action-specific method. Examples: mapper = routes.Mapper() sc = ServerController() # Explicit mapping of one route to a controller+action mapper.connect(None, '/svrlist', controller=sc, action='list') # Actions are all implicitly defined mapper.resource('server', 'servers', controller=sc) # Pointing to an arbitrary WSGI app. You can specify the # {path_info:.*} parameter so the target app can be handed just that # section of the URL. mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp()) """ self.map = mapper self._router = routes.middleware.RoutesMiddleware(self._dispatch, self.map) @webob.dec.wsgify(RequestClass=Request) def __call__(self, req): """Route the incoming request to a controller based on self.map. If no match, return a 404. """ return self._router @staticmethod @webob.dec.wsgify(RequestClass=Request) def _dispatch(req): """Dispatch the request to the appropriate controller. Called by self._router after matching the incoming request to a route and putting the information into req.environ. Either returns 404 or the routed WSGI app's response. """ match = req.environ['wsgiorg.routing_args'][1] if not match: return webob.exc.HTTPNotFound() app = match['controller'] return app class ConfigNotFound(Exception): def __init__(self, path): msg = _('Could not find config at %(path)s') % {'path': path} super(ConfigNotFound, self).__init__(msg) class PasteAppNotFound(Exception): def __init__(self, name, path): msg = (_("Could not load paste app '%(name)s' from %(path)s") % {'name': name, 'path': path}) super(PasteAppNotFound, self).__init__(msg) class Loader(object): """Used to load WSGI applications from paste configurations.""" def __init__(self, conf): """Initialize the loader, and attempt to find the config. :param conf: Application config :returns: None """ conf.register_opts(_options.wsgi_opts) self.config_path = None config_path = conf.api_paste_config if not os.path.isabs(config_path): self.config_path = conf.find_file(config_path) elif os.path.exists(config_path): self.config_path = config_path if not self.config_path: raise ConfigNotFound(path=config_path) def load_app(self, name): """Return the paste URLMap wrapped WSGI application. :param name: Name of the application to load. :returns: Paste URLMap object wrapping the requested application. :raises: PasteAppNotFound """ try: LOG.debug("Loading app %(name)s from %(path)s", {'name': name, 'path': self.config_path}) return deploy.loadapp("config:%s" % self.config_path, name=name) except LookupError: LOG.exception("Couldn't lookup app: %s", name) raise PasteAppNotFound(name=name, path=self.config_path) oslo.service-1.29.0/oslo_service/version.py0000666000175100017510000000126413224676223021011 0ustar zuulzuul00000000000000# 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.service') oslo.service-1.29.0/oslo_service/periodic_task.py0000666000175100017510000002003213224676223022136 0ustar zuulzuul00000000000000# # 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 copy import logging import random import time from monotonic import monotonic as now # noqa from oslo_utils import reflection import six from oslo_service._i18n import _ from oslo_service import _options LOG = logging.getLogger(__name__) DEFAULT_INTERVAL = 60.0 def list_opts(): """Entry point for oslo-config-generator.""" return [(None, copy.deepcopy(_options.periodic_opts))] class InvalidPeriodicTaskArg(Exception): message = _("Unexpected argument for periodic task creation: %(arg)s.") def periodic_task(*args, **kwargs): """Decorator to indicate that a method is a periodic task. This decorator can be used in two ways: 1. Without arguments '@periodic_task', this will be run on the default interval of 60 seconds. 2. With arguments: @periodic_task(spacing=N [, run_immediately=[True|False]] [, name=[None|"string"]) this will be run on approximately every N seconds. If this number is negative the periodic task will be disabled. If the run_immediately argument is provided and has a value of 'True', the first run of the task will be shortly after task scheduler starts. If run_immediately is omitted or set to 'False', the first time the task runs will be approximately N seconds after the task scheduler starts. If name is not provided, __name__ of function is used. """ def decorator(f): # Test for old style invocation if 'ticks_between_runs' in kwargs: raise InvalidPeriodicTaskArg(arg='ticks_between_runs') # Control if run at all f._periodic_task = True f._periodic_external_ok = kwargs.pop('external_process_ok', False) f._periodic_enabled = kwargs.pop('enabled', True) f._periodic_name = kwargs.pop('name', f.__name__) # Control frequency f._periodic_spacing = kwargs.pop('spacing', 0) f._periodic_immediate = kwargs.pop('run_immediately', False) if f._periodic_immediate: f._periodic_last_run = None else: f._periodic_last_run = now() return f # NOTE(sirp): The `if` is necessary to allow the decorator to be used with # and without parenthesis. # # In the 'with-parenthesis' case (with kwargs present), this function needs # to return a decorator function since the interpreter will invoke it like: # # periodic_task(*args, **kwargs)(f) # # In the 'without-parenthesis' case, the original function will be passed # in as the first argument, like: # # periodic_task(f) if kwargs: return decorator else: return decorator(args[0]) class _PeriodicTasksMeta(type): def _add_periodic_task(cls, task): """Add a periodic task to the list of periodic tasks. The task should already be decorated by @periodic_task. :return: whether task was actually enabled """ name = task._periodic_name if task._periodic_spacing < 0: LOG.info('Skipping periodic task %(task)s because ' 'its interval is negative', {'task': name}) return False if not task._periodic_enabled: LOG.info('Skipping periodic task %(task)s because ' 'it is disabled', {'task': name}) return False # A periodic spacing of zero indicates that this task should # be run on the default interval to avoid running too # frequently. if task._periodic_spacing == 0: task._periodic_spacing = DEFAULT_INTERVAL cls._periodic_tasks.append((name, task)) cls._periodic_spacing[name] = task._periodic_spacing return True def __init__(cls, names, bases, dict_): """Metaclass that allows us to collect decorated periodic tasks.""" super(_PeriodicTasksMeta, cls).__init__(names, bases, dict_) # NOTE(sirp): if the attribute is not present then we must be the base # class, so, go ahead an initialize it. If the attribute is present, # then we're a subclass so make a copy of it so we don't step on our # parent's toes. try: cls._periodic_tasks = cls._periodic_tasks[:] except AttributeError: cls._periodic_tasks = [] try: cls._periodic_spacing = cls._periodic_spacing.copy() except AttributeError: cls._periodic_spacing = {} for value in cls.__dict__.values(): if getattr(value, '_periodic_task', False): cls._add_periodic_task(value) def _nearest_boundary(last_run, spacing): """Find the nearest boundary in the past. The boundary is a multiple of the spacing with the last run as an offset. Eg if last run was 10 and spacing was 7, the new last run could be: 17, 24, 31, 38... 0% to 5% of the spacing value will be added to this value to ensure tasks do not synchronize. This jitter is rounded to the nearest second, this means that spacings smaller than 20 seconds will not have jitter. """ current_time = now() if last_run is None: return current_time delta = current_time - last_run offset = delta % spacing # Add up to 5% jitter jitter = int(spacing * (random.random() / 20)) # nosec return current_time - offset + jitter @six.add_metaclass(_PeriodicTasksMeta) class PeriodicTasks(object): def __init__(self, conf): super(PeriodicTasks, self).__init__() self.conf = conf self.conf.register_opts(_options.periodic_opts) self._periodic_last_run = {} for name, task in self._periodic_tasks: self._periodic_last_run[name] = task._periodic_last_run def add_periodic_task(self, task): """Add a periodic task to the list of periodic tasks. The task should already be decorated by @periodic_task. """ if self.__class__._add_periodic_task(task): self._periodic_last_run[task._periodic_name] = ( task._periodic_last_run) def run_periodic_tasks(self, context, raise_on_error=False): """Tasks to be run at a periodic interval.""" idle_for = DEFAULT_INTERVAL for task_name, task in self._periodic_tasks: if (task._periodic_external_ok and not self.conf.run_external_periodic_tasks): continue cls_name = reflection.get_class_name(self, fully_qualified=False) full_task_name = '.'.join([cls_name, task_name]) spacing = self._periodic_spacing[task_name] last_run = self._periodic_last_run[task_name] # Check if due, if not skip idle_for = min(idle_for, spacing) if last_run is not None: delta = last_run + spacing - now() if delta > 0: idle_for = min(idle_for, delta) continue LOG.debug("Running periodic task %(full_task_name)s", {"full_task_name": full_task_name}) self._periodic_last_run[task_name] = _nearest_boundary( last_run, spacing) try: task(self, context) except BaseException: if raise_on_error: raise LOG.exception("Error during %(full_task_name)s", {"full_task_name": full_task_name}) time.sleep(0) return idle_for oslo.service-1.29.0/oslo_service/threadgroup.py0000666000175100017510000001407313224676223021652 0ustar zuulzuul00000000000000# Copyright 2012 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 logging import threading import eventlet from eventlet import greenpool from oslo_service import loopingcall from oslo_utils import timeutils LOG = logging.getLogger(__name__) def _on_thread_done(_greenthread, group, thread): """Callback function to be passed to GreenThread.link() when we spawn(). Calls the :class:`ThreadGroup` to notify it to remove this thread from the associated group. """ group.thread_done(thread) class Thread(object): """Wrapper around a greenthread. Holds a reference to the :class:`ThreadGroup`. The Thread will notify the :class:`ThreadGroup` when it has done so it can be removed from the threads list. """ def __init__(self, thread, group, link=True): self.thread = thread if link: self.thread.link(_on_thread_done, group, self) self._ident = id(thread) @property def ident(self): return self._ident def stop(self): self.thread.kill() def wait(self): return self.thread.wait() def link(self, func, *args, **kwargs): self.thread.link(func, *args, **kwargs) def cancel(self, *throw_args): self.thread.cancel(*throw_args) class ThreadGroup(object): """The point of the ThreadGroup class is to: * keep track of timers and greenthreads (making it easier to stop them when need be). * provide an easy API to add timers. """ def __init__(self, thread_pool_size=10): self.pool = greenpool.GreenPool(thread_pool_size) self.threads = [] self.timers = [] def add_dynamic_timer(self, callback, initial_delay=None, periodic_interval_max=None, *args, **kwargs): timer = loopingcall.DynamicLoopingCall(callback, *args, **kwargs) timer.start(initial_delay=initial_delay, periodic_interval_max=periodic_interval_max) self.timers.append(timer) return timer def add_timer(self, interval, callback, initial_delay=None, *args, **kwargs): pulse = loopingcall.FixedIntervalLoopingCall(callback, *args, **kwargs) pulse.start(interval=interval, initial_delay=initial_delay) self.timers.append(pulse) return pulse def add_thread(self, callback, *args, **kwargs): gt = self.pool.spawn(callback, *args, **kwargs) th = Thread(gt, self, link=False) self.threads.append(th) gt.link(_on_thread_done, self, th) return th def thread_done(self, thread): self.threads.remove(thread) def timer_done(self, timer): self.timers.remove(timer) def _perform_action_on_threads(self, action_func, on_error_func): current = threading.current_thread() # Iterate over a copy of self.threads so thread_done doesn't # modify the list while we're iterating for x in self.threads[:]: if x.ident == current.ident: # Don't perform actions on the current thread. continue try: action_func(x) except eventlet.greenlet.GreenletExit: # nosec # greenlet exited successfully pass except Exception: on_error_func(x) def _stop_threads(self): self._perform_action_on_threads( lambda x: x.stop(), lambda x: LOG.exception('Error stopping thread.')) def stop_timers(self): for timer in self.timers: timer.stop() self.timers = [] def stop(self, graceful=False): """stop function has the option of graceful=True/False. * In case of graceful=True, wait for all threads to be finished. Never kill threads. * In case of graceful=False, kill threads immediately. """ self.stop_timers() if graceful: # In case of graceful=True, wait for all threads to be # finished, never kill threads self.wait() else: # In case of graceful=False(Default), kill threads # immediately self._stop_threads() def wait(self): for x in self.timers: try: x.wait() except eventlet.greenlet.GreenletExit: # nosec # greenlet exited successfully pass except Exception: LOG.exception('Error waiting on timer.') self._perform_action_on_threads( lambda x: x.wait(), lambda x: LOG.exception('Error waiting on thread.')) def _any_threads_alive(self): current = threading.current_thread() for x in self.threads[:]: if x.ident == current.ident: # Don't check current thread. continue if not x.thread.dead: return True return False def cancel(self, *throw_args, **kwargs): self._perform_action_on_threads( lambda x: x.cancel(*throw_args), lambda x: LOG.exception('Error canceling thread.')) timeout = kwargs.get('timeout', None) if timeout is None: return wait_time = kwargs.get('wait_time', 1) watch = timeutils.StopWatch(duration=timeout) watch.start() while self._any_threads_alive(): if not watch.expired(): eventlet.sleep(wait_time) continue LOG.debug("Cancel timeout reached, stopping threads.") self.stop() oslo.service-1.29.0/oslo_service/_options.py0000666000175100017510000001246313224676223021161 0ustar zuulzuul00000000000000# Copyright 2015 Mirantis 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. from oslo_config import cfg help_for_backdoor_port = ( "Acceptable values are 0, , and :, where 0 results " "in listening on a random tcp port number; results in listening " "on the specified port number (and not enabling backdoor if that port " "is in use); and : results in listening on the smallest " "unused port number within the specified range of port numbers. The " "chosen port is displayed in the service's log file.") eventlet_backdoor_opts = [ cfg.StrOpt('backdoor_port', help="Enable eventlet backdoor. %s" % help_for_backdoor_port), cfg.StrOpt('backdoor_socket', help="Enable eventlet backdoor, using the provided path" " as a unix socket that can receive connections. This" " option is mutually exclusive with 'backdoor_port' in" " that only one should be provided. If both are provided" " then the existence of this option overrides the usage of" " that option.") ] periodic_opts = [ cfg.BoolOpt('run_external_periodic_tasks', default=True, help='Some periodic tasks can be run in a separate process. ' 'Should we run them here?'), ] service_opts = [ cfg.BoolOpt('log_options', default=True, help='Enables or disables logging values of all registered ' 'options when starting a service (at DEBUG level).'), cfg.IntOpt('graceful_shutdown_timeout', default=60, help='Specify a timeout after which a gracefully shutdown ' 'server will exit. Zero value means endless wait.'), ] wsgi_opts = [ cfg.StrOpt('api_paste_config', default="api-paste.ini", help='File name for the paste.deploy config for api service'), cfg.StrOpt('wsgi_log_format', default='%(client_ip)s "%(request_line)s" status: ' '%(status_code)s len: %(body_length)s time:' ' %(wall_seconds).7f', help='A python format string that is used as the template to ' 'generate log lines. The following values can be' 'formatted into it: client_ip, date_time, request_line, ' 'status_code, body_length, wall_seconds.'), cfg.IntOpt('tcp_keepidle', default=600, help="Sets the value of TCP_KEEPIDLE in seconds for each " "server socket. Not supported on OS X."), cfg.IntOpt('wsgi_default_pool_size', default=100, help="Size of the pool of greenthreads used by wsgi"), cfg.IntOpt('max_header_line', default=16384, help="Maximum line size of message headers to be accepted. " "max_header_line may need to be increased when using " "large tokens (typically those generated when keystone " "is configured to use PKI tokens with big service " "catalogs)."), cfg.BoolOpt('wsgi_keep_alive', default=True, help="If False, closes the client socket connection " "explicitly."), cfg.IntOpt('client_socket_timeout', default=900, help="Timeout for client connections' socket operations. " "If an incoming connection is idle for this number of " "seconds it will be closed. A value of '0' means " "wait forever."), ] ssl_opts = [ cfg.StrOpt('ca_file', help="CA certificate file to use to verify " "connecting clients.", deprecated_group='DEFAULT', deprecated_name='ssl_ca_file'), cfg.StrOpt('cert_file', help="Certificate file to use when starting " "the server securely.", deprecated_group='DEFAULT', deprecated_name='ssl_cert_file'), cfg.StrOpt('key_file', help="Private key file to use when starting " "the server securely.", deprecated_group='DEFAULT', deprecated_name='ssl_key_file'), cfg.StrOpt('version', help='SSL version to use (valid only if SSL enabled). ' 'Valid values are TLSv1 and SSLv23. SSLv2, SSLv3, ' 'TLSv1_1, and TLSv1_2 may be available on some ' 'distributions.' ), cfg.StrOpt('ciphers', help='Sets the list of available ciphers. value should be a ' 'string in the OpenSSL cipher list format.' ), ] oslo.service-1.29.0/oslo_service/service.py0000666000175100017510000006175613224676235021003 0ustar zuulzuul00000000000000# Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # Copyright 2011 Justin Santa Barbara # 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. """Generic Node base class for all workers that run on hosts.""" import abc import collections import copy import errno import gc import io import logging import os import random import signal import six import sys import time import eventlet from eventlet import event from oslo_concurrency import lockutils from oslo_service._i18n import _ from oslo_service import _options from oslo_service import eventlet_backdoor from oslo_service import systemd from oslo_service import threadgroup LOG = logging.getLogger(__name__) _LAUNCHER_RESTART_METHODS = ['reload', 'mutate'] def list_opts(): """Entry point for oslo-config-generator.""" return [(None, copy.deepcopy(_options.eventlet_backdoor_opts + _options.service_opts))] def _is_daemon(): # The process group for a foreground process will match the # process group of the controlling terminal. If those values do # not match, or ioctl() fails on the stdout file handle, we assume # the process is running in the background as a daemon. # http://www.gnu.org/software/bash/manual/bashref.html#Job-Control-Basics try: is_daemon = os.getpgrp() != os.tcgetpgrp(sys.stdout.fileno()) except io.UnsupportedOperation: # Could not get the fileno for stdout, so we must be a daemon. is_daemon = True except OSError as err: if err.errno == errno.ENOTTY: # Assume we are a daemon because there is no terminal. is_daemon = True else: raise return is_daemon def _is_sighup_and_daemon(signo): if not (SignalHandler().is_signal_supported('SIGHUP') and signo == signal.SIGHUP): # Avoid checking if we are a daemon, because the signal isn't # SIGHUP. return False return _is_daemon() def _check_service_base(service): if not isinstance(service, ServiceBase): raise TypeError(_("Service %(service)s must an instance of %(base)s!") % {'service': service, 'base': ServiceBase}) @six.add_metaclass(abc.ABCMeta) class ServiceBase(object): """Base class for all services.""" @abc.abstractmethod def start(self): """Start service.""" @abc.abstractmethod def stop(self): """Stop service.""" @abc.abstractmethod def wait(self): """Wait for service to complete.""" @abc.abstractmethod def reset(self): """Reset service. Called in case service running in daemon mode receives SIGHUP. """ class Singleton(type): _instances = {} _semaphores = lockutils.Semaphores() def __call__(cls, *args, **kwargs): with lockutils.lock('singleton_lock', semaphores=cls._semaphores): if cls not in cls._instances: cls._instances[cls] = super(Singleton, cls).__call__( *args, **kwargs) return cls._instances[cls] @six.add_metaclass(Singleton) class SignalHandler(object): def __init__(self, *args, **kwargs): super(SignalHandler, self).__init__(*args, **kwargs) # Map all signal names to signal integer values and create a # reverse mapping (for easier + quick lookup). self._ignore_signals = ('SIG_DFL', 'SIG_IGN') self._signals_by_name = dict((name, getattr(signal, name)) for name in dir(signal) if name.startswith("SIG") and name not in self._ignore_signals) self.signals_to_name = dict( (sigval, name) for (name, sigval) in self._signals_by_name.items()) self._signal_handlers = collections.defaultdict(set) self.clear() def clear(self): for sig in self._signal_handlers: signal.signal(sig, signal.SIG_DFL) self._signal_handlers.clear() def add_handlers(self, signals, handler): for sig in signals: self.add_handler(sig, handler) def add_handler(self, sig, handler): if not self.is_signal_supported(sig): return signo = self._signals_by_name[sig] self._signal_handlers[signo].add(handler) signal.signal(signo, self._handle_signal) def _handle_signal(self, signo, frame): # This method can be called anytime, even between two Python # instructions. It's scheduled by the C signal handler of Python using # Py_AddPendingCall(). # # We only do one thing: schedule a call to _handle_signal_cb() later. # eventlet.spawn() is not signal-safe: _handle_signal() can be called # during a call to eventlet.spawn(). This case is supported, it is # ok to schedule multiple calls to _handle_signal() with the same # signal number. # # To call to _handle_signal_cb() is delayed to avoid reentrant calls to # _handle_signal_cb(). It avoids race conditions like reentrant call to # clear(): clear() is not reentrant (bug #1538204). eventlet.spawn(self._handle_signal_cb, signo, frame) def _handle_signal_cb(self, signo, frame): for handler in self._signal_handlers[signo]: handler(signo, frame) def is_signal_supported(self, sig_name): return sig_name in self._signals_by_name class Launcher(object): """Launch one or more services and wait for them to complete.""" def __init__(self, conf, restart_method='reload'): """Initialize the service launcher. :param restart_method: If 'reload', calls reload_config_files on SIGHUP. If 'mutate', calls mutate_config_files on SIGHUP. Other values produce a ValueError. :returns: None """ self.conf = conf conf.register_opts(_options.service_opts) self.services = Services() self.backdoor_port = ( eventlet_backdoor.initialize_if_enabled(self.conf)) self.restart_method = restart_method if restart_method not in _LAUNCHER_RESTART_METHODS: raise ValueError(_("Invalid restart_method: %s") % restart_method) def launch_service(self, service, workers=1): """Load and start the given service. :param service: The service you would like to start, must be an instance of :class:`oslo_service.service.ServiceBase` :param workers: This param makes this method compatible with ProcessLauncher.launch_service. It must be None, 1 or omitted. :returns: None """ if workers is not None and workers != 1: raise ValueError(_("Launcher asked to start multiple workers")) _check_service_base(service) service.backdoor_port = self.backdoor_port self.services.add(service) def stop(self): """Stop all services which are currently running. :returns: None """ self.services.stop() def wait(self): """Wait until all services have been stopped, and then return. :returns: None """ self.services.wait() def restart(self): """Reload config files and restart service. :returns: The return value from reload_config_files or mutate_config_files, according to the restart_method. """ if self.restart_method == 'reload': self.conf.reload_config_files() elif self.restart_method == 'mutate': self.conf.mutate_config_files() self.services.restart() class SignalExit(SystemExit): def __init__(self, signo, exccode=1): super(SignalExit, self).__init__(exccode) self.signo = signo class ServiceLauncher(Launcher): """Runs one or more service in a parent process.""" def __init__(self, conf, restart_method='reload'): """Constructor. :param conf: an instance of ConfigOpts :param restart_method: passed to super """ super(ServiceLauncher, self).__init__( conf, restart_method=restart_method) self.signal_handler = SignalHandler() def _graceful_shutdown(self, *args): self.signal_handler.clear() if (self.conf.graceful_shutdown_timeout and self.signal_handler.is_signal_supported('SIGALRM')): signal.alarm(self.conf.graceful_shutdown_timeout) self.stop() def _reload_service(self, *args): self.signal_handler.clear() raise SignalExit(signal.SIGHUP) def _fast_exit(self, *args): LOG.info('Caught SIGINT signal, instantaneous exiting') os._exit(1) def _on_timeout_exit(self, *args): LOG.info('Graceful shutdown timeout exceeded, ' 'instantaneous exiting') os._exit(1) def handle_signal(self): """Set self._handle_signal as a signal handler.""" self.signal_handler.clear() self.signal_handler.add_handler('SIGTERM', self._graceful_shutdown) self.signal_handler.add_handler('SIGINT', self._fast_exit) self.signal_handler.add_handler('SIGHUP', self._reload_service) self.signal_handler.add_handler('SIGALRM', self._on_timeout_exit) def _wait_for_exit_or_signal(self): status = None signo = 0 if self.conf.log_options: LOG.debug('Full set of CONF:') self.conf.log_opt_values(LOG, logging.DEBUG) try: super(ServiceLauncher, self).wait() except SignalExit as exc: signame = self.signal_handler.signals_to_name[exc.signo] LOG.info('Caught %s, exiting', signame) status = exc.code signo = exc.signo except SystemExit as exc: self.stop() status = exc.code except Exception: self.stop() return status, signo def wait(self): """Wait for a service to terminate and restart it on SIGHUP. :returns: termination status """ systemd.notify_once() self.signal_handler.clear() while True: self.handle_signal() status, signo = self._wait_for_exit_or_signal() if not _is_sighup_and_daemon(signo): break self.restart() super(ServiceLauncher, self).wait() return status class ServiceWrapper(object): def __init__(self, service, workers): self.service = service self.workers = workers self.children = set() self.forktimes = [] class ProcessLauncher(object): """Launch a service with a given number of workers.""" def __init__(self, conf, wait_interval=0.01, restart_method='reload'): """Constructor. :param conf: an instance of ConfigOpts :param wait_interval: The interval to sleep for between checks of child process exit. :param restart_method: If 'reload', calls reload_config_files on SIGHUP. If 'mutate', calls mutate_config_files on SIGHUP. Other values produce a ValueError. """ self.conf = conf conf.register_opts(_options.service_opts) self.children = {} self.sigcaught = None self.running = True self.wait_interval = wait_interval self.launcher = None rfd, self.writepipe = os.pipe() self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r') self.signal_handler = SignalHandler() self.handle_signal() self.restart_method = restart_method if restart_method not in _LAUNCHER_RESTART_METHODS: raise ValueError(_("Invalid restart_method: %s") % restart_method) def handle_signal(self): """Add instance's signal handlers to class handlers.""" self.signal_handler.add_handler('SIGTERM', self._handle_term) self.signal_handler.add_handler('SIGHUP', self._handle_hup) self.signal_handler.add_handler('SIGINT', self._fast_exit) self.signal_handler.add_handler('SIGALRM', self._on_alarm_exit) def _handle_term(self, signo, frame): """Handle a TERM event. :param signo: signal number :param frame: current stack frame """ self.sigcaught = signo self.running = False # Allow the process to be killed again and die from natural causes self.signal_handler.clear() def _handle_hup(self, signo, frame): """Handle a HUP event. :param signo: signal number :param frame: current stack frame """ self.sigcaught = signo self.running = False # Do NOT clear the signal_handler, allowing multiple SIGHUPs to be # received swiftly. If a non-HUP is received before #wait loops, the # second event will "overwrite" the HUP. This is fine. def _fast_exit(self, signo, frame): LOG.info('Caught SIGINT signal, instantaneous exiting') os._exit(1) def _on_alarm_exit(self, signo, frame): LOG.info('Graceful shutdown timeout exceeded, ' 'instantaneous exiting') os._exit(1) def _pipe_watcher(self): # This will block until the write end is closed when the parent # dies unexpectedly self.readpipe.read(1) LOG.info('Parent process has died unexpectedly, exiting') if self.launcher: self.launcher.stop() sys.exit(1) def _child_process_handle_signal(self): # Setup child signal handlers differently def _sigterm(*args): self.signal_handler.clear() self.launcher.stop() def _sighup(*args): self.signal_handler.clear() raise SignalExit(signal.SIGHUP) self.signal_handler.clear() # Parent signals with SIGTERM when it wants us to go away. self.signal_handler.add_handler('SIGTERM', _sigterm) self.signal_handler.add_handler('SIGHUP', _sighup) self.signal_handler.add_handler('SIGINT', self._fast_exit) def _child_wait_for_exit_or_signal(self, launcher): status = 0 signo = 0 # NOTE(johannes): All exceptions are caught to ensure this # doesn't fallback into the loop spawning children. It would # be bad for a child to spawn more children. try: launcher.wait() except SignalExit as exc: signame = self.signal_handler.signals_to_name[exc.signo] LOG.info('Child caught %s, exiting', signame) status = exc.code signo = exc.signo except SystemExit as exc: status = exc.code except BaseException: LOG.exception('Unhandled exception') status = 2 return status, signo def _child_process(self, service): self._child_process_handle_signal() # Reopen the eventlet hub to make sure we don't share an epoll # fd with parent and/or siblings, which would be bad eventlet.hubs.use_hub() # Close write to ensure only parent has it open os.close(self.writepipe) # Create greenthread to watch for parent to close pipe eventlet.spawn_n(self._pipe_watcher) # Reseed random number generator random.seed() launcher = Launcher(self.conf, restart_method=self.restart_method) launcher.launch_service(service) return launcher def _start_child(self, wrap): if len(wrap.forktimes) > wrap.workers: # Limit ourselves to one process a second (over the period of # number of workers * 1 second). This will allow workers to # start up quickly but ensure we don't fork off children that # die instantly too quickly. if time.time() - wrap.forktimes[0] < wrap.workers: LOG.info('Forking too fast, sleeping') time.sleep(1) wrap.forktimes.pop(0) wrap.forktimes.append(time.time()) pid = os.fork() if pid == 0: self.launcher = self._child_process(wrap.service) while True: self._child_process_handle_signal() status, signo = self._child_wait_for_exit_or_signal( self.launcher) if not _is_sighup_and_daemon(signo): self.launcher.wait() break self.launcher.restart() os._exit(status) LOG.debug('Started child %d', pid) wrap.children.add(pid) self.children[pid] = wrap return pid def launch_service(self, service, workers=1): """Launch a service with a given number of workers. :param service: a service to launch, must be an instance of :class:`oslo_service.service.ServiceBase` :param workers: a number of processes in which a service will be running """ _check_service_base(service) wrap = ServiceWrapper(service, workers) # Hide existing objects from the garbage collector, so that most # existing pages will remain in shared memory rather than being # duplicated between subprocesses in the GC mark-and-sweep. (Requires # Python 3.7 or later.) if hasattr(gc, 'freeze'): gc.freeze() LOG.info('Starting %d workers', wrap.workers) while self.running and len(wrap.children) < wrap.workers: self._start_child(wrap) def _wait_child(self): try: # Don't block if no child processes have exited pid, status = os.waitpid(0, os.WNOHANG) if not pid: return None except OSError as exc: if exc.errno not in (errno.EINTR, errno.ECHILD): raise return None if os.WIFSIGNALED(status): sig = os.WTERMSIG(status) LOG.info('Child %(pid)d killed by signal %(sig)d', dict(pid=pid, sig=sig)) else: code = os.WEXITSTATUS(status) LOG.info('Child %(pid)s exited with status %(code)d', dict(pid=pid, code=code)) if pid not in self.children: LOG.warning('pid %d not in child list', pid) return None wrap = self.children.pop(pid) wrap.children.remove(pid) return wrap def _respawn_children(self): while self.running: wrap = self._wait_child() if not wrap: # Yield to other threads if no children have exited # Sleep for a short time to avoid excessive CPU usage # (see bug #1095346) eventlet.greenthread.sleep(self.wait_interval) continue while self.running and len(wrap.children) < wrap.workers: self._start_child(wrap) def wait(self): """Loop waiting on children to die and respawning as necessary.""" systemd.notify_once() if self.conf.log_options: LOG.debug('Full set of CONF:') self.conf.log_opt_values(LOG, logging.DEBUG) try: while True: self.handle_signal() self._respawn_children() # No signal means that stop was called. Don't clean up here. if not self.sigcaught: return signame = self.signal_handler.signals_to_name[self.sigcaught] LOG.info('Caught %s, stopping children', signame) if not _is_sighup_and_daemon(self.sigcaught): break if self.restart_method == 'reload': self.conf.reload_config_files() elif self.restart_method == 'mutate': self.conf.mutate_config_files() for service in set( [wrap.service for wrap in self.children.values()]): service.reset() for pid in self.children: os.kill(pid, signal.SIGTERM) self.running = True self.sigcaught = None except eventlet.greenlet.GreenletExit: LOG.info("Wait called after thread killed. Cleaning up.") # if we are here it means that we are trying to do graceful shutdown. # add alarm watching that graceful_shutdown_timeout is not exceeded if (self.conf.graceful_shutdown_timeout and self.signal_handler.is_signal_supported('SIGALRM')): signal.alarm(self.conf.graceful_shutdown_timeout) self.stop() def stop(self): """Terminate child processes and wait on each.""" self.running = False LOG.debug("Stop services.") for service in set( [wrap.service for wrap in self.children.values()]): service.stop() LOG.debug("Killing children.") for pid in self.children: try: os.kill(pid, signal.SIGTERM) except OSError as exc: if exc.errno != errno.ESRCH: raise # Wait for children to die if self.children: LOG.info('Waiting on %d children to exit', len(self.children)) while self.children: self._wait_child() class Service(ServiceBase): """Service object for binaries running on hosts.""" def __init__(self, threads=1000): self.tg = threadgroup.ThreadGroup(threads) def reset(self): """Reset a service in case it received a SIGHUP.""" def start(self): """Start a service.""" def stop(self, graceful=False): """Stop a service. :param graceful: indicates whether to wait for all threads to finish or terminate them instantly """ self.tg.stop(graceful) def wait(self): """Wait for a service to shut down.""" self.tg.wait() class Services(object): def __init__(self): self.services = [] self.tg = threadgroup.ThreadGroup() self.done = event.Event() def add(self, service): """Add a service to a list and create a thread to run it. :param service: service to run """ self.services.append(service) self.tg.add_thread(self.run_service, service, self.done) def stop(self): """Wait for graceful shutdown of services and kill the threads.""" for service in self.services: service.stop() # Each service has performed cleanup, now signal that the run_service # wrapper threads can now die: if not self.done.ready(): self.done.send() # reap threads: self.tg.stop() def wait(self): """Wait for services to shut down.""" for service in self.services: service.wait() self.tg.wait() def restart(self): """Reset services and start them in new threads.""" self.stop() self.done = event.Event() for restart_service in self.services: restart_service.reset() self.tg.add_thread(self.run_service, restart_service, self.done) @staticmethod def run_service(service, done): """Service start wrapper. :param service: service to run :param done: event to wait on until a shutdown is triggered :returns: None """ try: service.start() except Exception: LOG.exception('Error starting thread.') raise SystemExit(1) else: done.wait() def launch(conf, service, workers=1, restart_method='reload'): """Launch a service with a given number of workers. :param conf: an instance of ConfigOpts :param service: a service to launch, must be an instance of :class:`oslo_service.service.ServiceBase` :param workers: a number of processes in which a service will be running :param restart_method: Passed to the constructed launcher. If 'reload', the launcher will call reload_config_files on SIGHUP. If 'mutate', it will call mutate_config_files on SIGHUP. Other values produce a ValueError. :returns: instance of a launcher that was used to launch the service """ if workers is not None and workers <= 0: raise ValueError(_("Number of workers should be positive!")) if workers is None or workers == 1: launcher = ServiceLauncher(conf, restart_method=restart_method) else: launcher = ProcessLauncher(conf, restart_method=restart_method) launcher.launch_service(service, workers=workers) return launcher oslo.service-1.29.0/oslo.service.egg-info/0000775000175100017510000000000013224676442020361 5ustar zuulzuul00000000000000oslo.service-1.29.0/oslo.service.egg-info/requires.txt0000664000175100017510000000036413224676442022764 0ustar zuulzuul00000000000000WebOb>=1.7.1 eventlet!=0.18.3,!=0.20.1,<0.21.0,>=0.18.2 greenlet>=0.4.10 monotonic>=0.6 oslo.utils>=3.33.0 oslo.concurrency>=3.20.0 oslo.config>=5.1.0 oslo.log>=3.30.0 six>=1.10.0 oslo.i18n>=3.15.3 PasteDeploy>=1.5.0 Routes>=2.3.1 Paste>=2.0.2 oslo.service-1.29.0/oslo.service.egg-info/entry_points.txt0000664000175100017510000000036413224676442023662 0ustar zuulzuul00000000000000[oslo.config.opts] oslo.service.periodic_task = oslo_service.periodic_task:list_opts oslo.service.service = oslo_service.service:list_opts oslo.service.sslutils = oslo_service.sslutils:list_opts oslo.service.wsgi = oslo_service.wsgi:list_opts oslo.service-1.29.0/oslo.service.egg-info/SOURCES.txt0000664000175100017510000000445513224676442022255 0ustar zuulzuul00000000000000.coveragerc .mailmap .testr.conf AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE README.rst babel.cfg requirements.txt setup.cfg setup.py test-requirements.txt tox.ini doc/source/conf.py doc/source/index.rst doc/source/configuration/index.rst doc/source/contributor/index.rst doc/source/install/index.rst doc/source/reference/eventlet_backdoor.rst doc/source/reference/index.rst doc/source/reference/loopingcall.rst doc/source/reference/periodic_task.rst doc/source/reference/service.rst doc/source/reference/sslutils.rst doc/source/reference/systemd.rst doc/source/reference/threadgroup.rst doc/source/user/history.rst doc/source/user/index.rst doc/source/user/usage.rst oslo.service.egg-info/PKG-INFO oslo.service.egg-info/SOURCES.txt oslo.service.egg-info/dependency_links.txt oslo.service.egg-info/entry_points.txt oslo.service.egg-info/not-zip-safe oslo.service.egg-info/pbr.json oslo.service.egg-info/requires.txt oslo.service.egg-info/top_level.txt oslo_service/__init__.py oslo_service/_i18n.py oslo_service/_options.py oslo_service/eventlet_backdoor.py oslo_service/loopingcall.py oslo_service/periodic_task.py oslo_service/service.py oslo_service/sslutils.py oslo_service/systemd.py oslo_service/threadgroup.py oslo_service/version.py oslo_service/wsgi.py oslo_service/locale/en_GB/LC_MESSAGES/oslo_service.po oslo_service/tests/__init__.py oslo_service/tests/base.py oslo_service/tests/eventlet_service.py oslo_service/tests/test_eventlet_backdoor.py oslo_service/tests/test_loopingcall.py oslo_service/tests/test_periodic.py oslo_service/tests/test_service.py oslo_service/tests/test_sslutils.py oslo_service/tests/test_systemd.py oslo_service/tests/test_threadgroup.py oslo_service/tests/test_wsgi.py oslo_service/tests/ssl_cert/ca.crt oslo_service/tests/ssl_cert/ca.key oslo_service/tests/ssl_cert/certificate.crt oslo_service/tests/ssl_cert/privatekey.key releasenotes/notes/add-timeout-looping-call-5cc396b75597c3c2.yaml releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml releasenotes/source/conf.py releasenotes/source/index.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.pooslo.service-1.29.0/oslo.service.egg-info/pbr.json0000664000175100017510000000005613224676442022040 0ustar zuulzuul00000000000000{"git_version": "e753070", "is_release": true}oslo.service-1.29.0/oslo.service.egg-info/dependency_links.txt0000664000175100017510000000000113224676442024427 0ustar zuulzuul00000000000000 oslo.service-1.29.0/oslo.service.egg-info/not-zip-safe0000664000175100017510000000000113224676373022612 0ustar zuulzuul00000000000000 oslo.service-1.29.0/oslo.service.egg-info/PKG-INFO0000664000175100017510000000431413224676442021460 0ustar zuulzuul00000000000000Metadata-Version: 1.1 Name: oslo.service Version: 1.29.0 Summary: oslo.service library Home-page: https://docs.openstack.org/oslo.service/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description-Content-Type: UNKNOWN Description: ======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/oslo.service.svg :target: http://governance.openstack.org/reference/tags/index.html .. Change things from this point on ======================================================== oslo.service -- Library for running OpenStack services ======================================================== .. image:: https://img.shields.io/pypi/v/oslo.service.svg :target: https://pypi.python.org/pypi/oslo.service/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.service.svg :target: https://pypi.python.org/pypi/oslo.service/ :alt: Downloads oslo.service provides a framework for defining new long-running services using the patterns established by other OpenStack applications. It also includes utilities long-running applications might need for working with SSL or WSGI, performing periodic operations, interacting with systemd, etc. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.service/latest/ * Source: https://git.openstack.org/cgit/openstack/oslo.service * Bugs: https://bugs.launchpad.net/oslo.service Platform: UNKNOWN Classifier: Environment :: OpenStack Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.5 oslo.service-1.29.0/oslo.service.egg-info/top_level.txt0000664000175100017510000000001513224676442023107 0ustar zuulzuul00000000000000oslo_service oslo.service-1.29.0/tox.ini0000666000175100017510000000411213224676223015564 0ustar zuulzuul00000000000000[tox] minversion = 2.0 envlist = py35,py27,pypy,pep8,bandit [testenv] setenv = VIRTUAL_ENV={envdir} BRANCH_NAME=master CLIENT_NAME=oslo.service install_command = pip install {opts} {packages} deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt whitelist_externals = find commands = find . -type f -name "*.py[co]" -delete python setup.py testr --slowest --testr-args='{posargs}' [testenv:pep8] commands = flake8 bandit -r oslo_service -n5 -x tests [testenv:py27] commands = find . -type f -name "*.py[co]" -delete python setup.py testr --slowest --testr-args='{posargs}' doc8 --ignore-path "doc/source/history.rst" doc/source [testenv:venv] commands = {posargs} [testenv:docs] commands = python setup.py build_sphinx [testenv:cover] commands = python setup.py test --coverage --coverage-package-name=oslo_service --testr-args='{posargs}' [flake8] # E123, E125 skipped as they are invalid PEP-8. show-source = True ignore = E123,E125 exclude=.venv,.git,.tox,dist,doc,*lib/python*,*egg,build [hacking] import_exceptions = oslo_service._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_service* --ignore-module=pkg_resources --ignore-file=oslo_service/tests/* oslo_service [testenv:debug] commands = oslo_debug_helper -t oslo_service/tests {posargs} [testenv:bandit] deps = -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = bandit -r oslo_service -n5 -x tests {posargs} [testenv:releasenotes] commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html oslo.service-1.29.0/.mailmap0000666000175100017510000000013113224676223015667 0ustar zuulzuul00000000000000# Format is: # # oslo.service-1.29.0/releasenotes/0000775000175100017510000000000013224676442016745 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/0000775000175100017510000000000013224676442020245 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/conf.py0000666000175100017510000002164013224676223021546 0ustar zuulzuul00000000000000# -*- 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 = [ 'openstackdocstheme', 'reno.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/oslo.service' bug_project = 'oslo.service' bug_tag = '' # 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.service Release Notes' copyright = u'2016, oslo.service Developers' # Release notes do not need a version in the title, they span # multiple versions. # The full version, including alpha/beta/rc tags. release = '' # The short X.Y version. version = '' # 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 = 'openstackdocs' # 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 # " v 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 = '%Y-%m-%d %H:%M' # 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 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.serviceReleaseNotesDoc' # -- 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.serviceReleaseNotes.tex', u'oslo.service Release Notes Documentation', u'oslo.service 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.serviceReleaseNotes', u'oslo.service Release Notes Documentation', [u'oslo.service 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.serviceReleaseNotes', u'oslo.service Release Notes Documentation', u'oslo.service Developers', 'oslo.serviceReleaseNotes', 'Provides a framework for defining new long-running services using the' ' patterns established by other OpenStack applications', '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 # -- Options for Internationalization output ------------------------------ locale_dirs = ['locale/'] oslo.service-1.29.0/releasenotes/source/locale/0000775000175100017510000000000013224676442021504 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/locale/en_GB/0000775000175100017510000000000013224676442022456 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/locale/en_GB/LC_MESSAGES/0000775000175100017510000000000013224676442024243 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po0000666000175100017510000000251313224676223027274 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.service Release Notes 1.25.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-09-18 13:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2017-10-07 09:31+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en-GB\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "1.17.0" msgstr "1.17.0" msgid "1.19.0" msgstr "1.19.0" msgid "" "Add a new type of looping call: FixedIntervalWithTimeoutLoopingCall. It is a " "FixedIntervalLoopingCall with timeout checking." msgstr "" "Add a new type of looping call: FixedIntervalWithTimeoutLoopingCall. It is a " "FixedIntervalLoopingCall with timeout checking." msgid "New Features" msgstr "New Features" msgid "Ocata Series Release Notes" msgstr "Ocata Series Release Notes" msgid "Other Notes" msgstr "Other Notes" msgid "Pike Series Release Notes" msgstr "Pike Series Release 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.service Release Notes" msgstr "oslo.service Release Notes" oslo.service-1.29.0/releasenotes/source/locale/fr/0000775000175100017510000000000013224676442022113 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/locale/fr/LC_MESSAGES/0000775000175100017510000000000013224676442023700 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po0000666000175100017510000000154113224676223026731 0ustar zuulzuul00000000000000# Gérald LONLAS , 2016. #zanata msgid "" msgstr "" "Project-Id-Version: oslo.service Release Notes 1.25.1\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2017-08-18 18:08+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2016-10-22 06:05+0000\n" "Last-Translator: Gérald LONLAS \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "Other Notes" msgstr "Autres notes" msgid "Switch to reno for managing release notes." msgstr "Commence à utiliser reno pour la gestion des notes de release" msgid "Unreleased Release Notes" msgstr "Note de release pour les changements non déployées" msgid "oslo.service Release Notes" msgstr "Note de release pour oslo.service" oslo.service-1.29.0/releasenotes/source/_static/0000775000175100017510000000000013224676442021673 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/_static/.placeholder0000666000175100017510000000000013224676223024143 0ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/unreleased.rst0000666000175100017510000000014413224676223023124 0ustar zuulzuul00000000000000========================== Unreleased Release Notes ========================== .. release-notes:: oslo.service-1.29.0/releasenotes/source/index.rst0000666000175100017510000000022413224676223022103 0ustar zuulzuul00000000000000============================ oslo.service Release Notes ============================ .. toctree:: :maxdepth: 1 unreleased pike ocata oslo.service-1.29.0/releasenotes/source/ocata.rst0000666000175100017510000000023013224676223022060 0ustar zuulzuul00000000000000=================================== Ocata Series Release Notes =================================== .. release-notes:: :branch: origin/stable/ocata oslo.service-1.29.0/releasenotes/source/_templates/0000775000175100017510000000000013224676442022402 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/_templates/.placeholder0000666000175100017510000000000013224676223024652 0ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/source/pike.rst0000666000175100017510000000021713224676223021726 0ustar zuulzuul00000000000000=================================== Pike Series Release Notes =================================== .. release-notes:: :branch: stable/pike oslo.service-1.29.0/releasenotes/notes/0000775000175100017510000000000013224676442020075 5ustar zuulzuul00000000000000oslo.service-1.29.0/releasenotes/notes/add-timeout-looping-call-5cc396b75597c3c2.yaml0000666000175100017510000000023113224676223027716 0ustar zuulzuul00000000000000--- features: - | Add a new type of looping call: FixedIntervalWithTimeoutLoopingCall. It is a FixedIntervalLoopingCall with timeout checking. oslo.service-1.29.0/releasenotes/notes/add_reno-3b4ae0789e9c45b4.yaml0000666000175100017510000000007113224676223024755 0ustar zuulzuul00000000000000--- other: - Switch to reno for managing release notes.oslo.service-1.29.0/.coveragerc0000666000175100017510000000016313224676223016374 0ustar zuulzuul00000000000000[run] branch = True source = oslo_service omit = oslo_service/tests/* [report] ignore_errors = True precision = 2 oslo.service-1.29.0/.testr.conf0000666000175100017510000000051413224676223016341 0ustar zuulzuul00000000000000[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 ./ ./oslo_service $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list oslo.service-1.29.0/README.rst0000666000175100017510000000226313224676223015745 0ustar zuulzuul00000000000000======================== Team and repository tags ======================== .. image:: http://governance.openstack.org/badges/oslo.service.svg :target: http://governance.openstack.org/reference/tags/index.html .. Change things from this point on ======================================================== oslo.service -- Library for running OpenStack services ======================================================== .. image:: https://img.shields.io/pypi/v/oslo.service.svg :target: https://pypi.python.org/pypi/oslo.service/ :alt: Latest Version .. image:: https://img.shields.io/pypi/dm/oslo.service.svg :target: https://pypi.python.org/pypi/oslo.service/ :alt: Downloads oslo.service provides a framework for defining new long-running services using the patterns established by other OpenStack applications. It also includes utilities long-running applications might need for working with SSL or WSGI, performing periodic operations, interacting with systemd, etc. * Free software: Apache license * Documentation: https://docs.openstack.org/oslo.service/latest/ * Source: https://git.openstack.org/cgit/openstack/oslo.service * Bugs: https://bugs.launchpad.net/oslo.service oslo.service-1.29.0/doc/0000775000175100017510000000000013224676442015021 5ustar zuulzuul00000000000000oslo.service-1.29.0/doc/source/0000775000175100017510000000000013224676442016321 5ustar zuulzuul00000000000000oslo.service-1.29.0/doc/source/user/0000775000175100017510000000000013224676442017277 5ustar zuulzuul00000000000000oslo.service-1.29.0/doc/source/user/usage.rst0000666000175100017510000001267713224676223021151 0ustar zuulzuul00000000000000===== Usage ===== To use oslo.service in a project:: import oslo_service Migrating to oslo.service ========================= The ``oslo.service`` library no longer assumes a global configuration object is available. Instead the following functions and classes have been changed to expect the consuming application to pass in an ``oslo.config`` configuration object: * :func:`~oslo_service.eventlet_backdoor.initialize_if_enabled` * :py:class:`oslo_service.periodic_task.PeriodicTasks` * :func:`~oslo_service.service.launch` * :py:class:`oslo_service.service.ProcessLauncher` * :py:class:`oslo_service.service.ServiceLauncher` * :func:`~oslo_service.sslutils.is_enabled` * :func:`~oslo_service.sslutils.wrap` When using service from oslo-incubator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ :: from foo.openstack.common import service launcher = service.launch(service, workers=2) When using oslo.service ~~~~~~~~~~~~~~~~~~~~~~~ :: from oslo_config import cfg from oslo_service import service CONF = cfg.CONF launcher = service.launch(CONF, service, workers=2) Using oslo.service with oslo-config-generator ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ``oslo.service`` provides several entry points to generate a configuration files. * :func:`oslo.service.service ` The options from the service and eventlet_backdoor modules for the [DEFAULT] section. * :func:`oslo.service.periodic_task ` The options from the periodic_task module for the [DEFAULT] section. * :func:`oslo.service.sslutils ` The options from the sslutils module for the [ssl] section. * :func:`oslo.service.wsgi ` The options from the wsgi module for the [DEFAULT] section. **ATTENTION:** The library doesn't provide an oslo.service entry point. .. code-block:: bash $ oslo-config-generator --namespace oslo.service.service \ --namespace oslo.service.periodic_task \ --namespace oslo.service.sslutils Launching and controlling services ================================== oslo_service.service module provides tools for launching OpenStack services and controlling their lifecycles. A service is an instance of any class that subclasses :py:class:`oslo_service.service.ServiceBase`. :py:class:`ServiceBase ` is an abstract class that defines an interface every service should implement. :py:class:`oslo_service.service.Service` can serve as a base for constructing new services. Launchers ~~~~~~~~~ oslo_service.service module provides two launchers for running services: * :py:class:`oslo_service.service.ServiceLauncher` - used for running one or more service in a parent process. * :py:class:`oslo_service.service.ProcessLauncher` - forks a given number of workers in which service(s) are then started. It is possible to initialize whatever launcher is needed and then launch a service using it. .. code-block:: python from oslo_config import cfg from oslo_service import service CONF = cfg.CONF service_launcher = service.ServiceLauncher(CONF) service_launcher.launch_service(service.Service()) process_launcher = service.ProcessLauncher(CONF, wait_interval=1.0) process_launcher.launch_service(service.Service(), workers=2) Or one can simply call :func:`oslo_service.service.launch` which will automatically pick an appropriate launcher based on a number of workers that are passed to it (ServiceLauncher in case workers=1 or None and ProcessLauncher in other case). .. code-block:: python from oslo_config import cfg from oslo_service import service CONF = cfg.CONF launcher = service.launch(CONF, service.Service(), workers=3) *NOTE:* Please be informed that it is highly recommended to use no more than one instance of ServiceLauncher and ProcessLauncher classes per process. Signal handling ~~~~~~~~~~~~~~~ oslo_service.service provides handlers for such signals as SIGTERM, SIGINT and SIGHUP. SIGTERM is used for graceful termination of services. This can allow a server to wait for all clients to close connections while rejecting new incoming requests. Config option graceful_shutdown_timeout specifies how many seconds after receiving a SIGTERM signal, a server should continue to run, handling the existing connections. Setting graceful_shutdown_timeout to zero means that the server will wait indefinitely until all remaining requests have been fully served. To force instantaneous termination SIGINT signal must be sent. On receiving SIGHUP configuration files are reloaded and a service is being reset and started again. Then all child workers are gracefully stopped using SIGTERM and workers with new configuration are spawned. Thus, SIGHUP can be used for changing config options on the go. *NOTE:* SIGHUP is not supported on Windows. *NOTE:* Config option graceful_shutdown_timeout is not supported on Windows. Below is the example of a service with a reset method that allows reloading logging options by sending a SIGHUP. .. code-block:: python from oslo_config import cfg from oslo_log import log as logging from oslo_service import service CONF = cfg.CONF LOG = logging.getLogger(__name__) class FooService(service.ServiceBase): def start(self): pass def wait(self): pass def stop(self): pass def reset(self): logging.setup(cfg.CONF, 'foo') oslo.service-1.29.0/doc/source/user/history.rst0000666000175100017510000000004013224676223021523 0ustar zuulzuul00000000000000.. include:: ../../../ChangeLog oslo.service-1.29.0/doc/source/user/index.rst0000666000175100017510000000032313224676223021135 0ustar zuulzuul00000000000000================== Using oslo.service ================== .. toctree:: :maxdepth: 2 usage .. history contains a lot of sections, toctree with maxdepth 1 is used. .. toctree:: :maxdepth: 1 history oslo.service-1.29.0/doc/source/conf.py0000666000175100017510000000513513224676223017623 0ustar zuulzuul00000000000000# -*- 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', 'openstackdocstheme', 'oslo_config.sphinxext', ] # openstackdocstheme options repository_name = 'openstack/oslo.service' bug_project = 'oslo.service' bug_tag = '' # 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 # The suffix of source filenames. source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = u'oslo.service' 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'] html_theme = 'openstackdocs' html_last_updated_fmt = '%Y-%m-%d %H:%M' # 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} oslo.service-1.29.0/doc/source/configuration/0000775000175100017510000000000013224676442021170 5ustar zuulzuul00000000000000oslo.service-1.29.0/doc/source/configuration/index.rst0000666000175100017510000000137013224676223023031 0ustar zuulzuul00000000000000======================= Configuration Options ======================= oslo.service uses oslo.config to define and manage configuration options to allow the deployer to control how an application uses this library. periodic_task ============= These options apply to services using the periodic task features of oslo.service. .. show-options:: oslo.service.periodic_task service ======= These options apply to services using the basic service framework. .. show-options:: oslo.service.service sslutils ======== These options apply to services using the SSL utilities module. .. show-options:: oslo.service.sslutils wsgi ==== These options apply to services using the WSGI (Web Service Gateway Interface) module. .. show-options:: oslo.service.wsgi oslo.service-1.29.0/doc/source/install/0000775000175100017510000000000013224676442017767 5ustar zuulzuul00000000000000oslo.service-1.29.0/doc/source/install/index.rst0000666000175100017510000000014313224676223021625 0ustar zuulzuul00000000000000============== Installation ============== At the command line:: $ pip install oslo.service oslo.service-1.29.0/doc/source/index.rst0000666000175100017510000000123613224676223020163 0ustar zuulzuul00000000000000====================================================== oslo.service -- Library for running OpenStack services ====================================================== oslo.service provides a framework for defining new long-running services using the patterns established by other OpenStack applications. It also includes utilities long-running applications might need for working with SSL or WSGI, performing periodic operations, interacting with systemd, etc. .. toctree:: :maxdepth: 2 install/index user/index configuration/index reference/index contributor/index .. rubric:: Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` oslo.service-1.29.0/doc/source/reference/0000775000175100017510000000000013224676442020257 5ustar zuulzuul00000000000000oslo.service-1.29.0/doc/source/reference/eventlet_backdoor.rst0000666000175100017510000000023713224676223024504 0ustar zuulzuul00000000000000================== eventlet_backdoor ================== .. automodule:: oslo_service.eventlet_backdoor :members: :undoc-members: :show-inheritance: oslo.service-1.29.0/doc/source/reference/loopingcall.rst0000666000175100017510000000021113224676223023305 0ustar zuulzuul00000000000000============= loopingcall ============= .. automodule:: oslo_service.loopingcall :members: :undoc-members: :show-inheritance: oslo.service-1.29.0/doc/source/reference/service.rst0000666000175100017510000000014613224676223022451 0ustar zuulzuul00000000000000========= service ========= .. automodule:: oslo_service.service :members: :show-inheritance: oslo.service-1.29.0/doc/source/reference/periodic_task.rst0000666000175100017510000000021713224676223023630 0ustar zuulzuul00000000000000============== periodic_task ============== .. automodule:: oslo_service.periodic_task :members: :undoc-members: :show-inheritance: oslo.service-1.29.0/doc/source/reference/systemd.rst0000666000175100017510000000017113224676223022477 0ustar zuulzuul00000000000000========= systemd ========= .. automodule:: oslo_service.systemd :members: :undoc-members: :show-inheritance: oslo.service-1.29.0/doc/source/reference/index.rst0000666000175100017510000000025713224676223022123 0ustar zuulzuul00000000000000============= API Reference ============= .. toctree:: :maxdepth: 1 eventlet_backdoor loopingcall periodic_task service sslutils systemd threadgroup oslo.service-1.29.0/doc/source/reference/threadgroup.rst0000666000175100017510000000021113224676223023326 0ustar zuulzuul00000000000000============= threadgroup ============= .. automodule:: oslo_service.threadgroup :members: :undoc-members: :show-inheritance: oslo.service-1.29.0/doc/source/reference/sslutils.rst0000666000175100017510000000017513224676223022675 0ustar zuulzuul00000000000000========== sslutils ========== .. automodule:: oslo_service.sslutils :members: :undoc-members: :show-inheritance: oslo.service-1.29.0/doc/source/contributor/0000775000175100017510000000000013224676442020673 5ustar zuulzuul00000000000000oslo.service-1.29.0/doc/source/contributor/index.rst0000666000175100017510000000011713224676223022532 0ustar zuulzuul00000000000000============ Contributing ============ .. include:: ../../../CONTRIBUTING.rst oslo.service-1.29.0/setup.cfg0000666000175100017510000000265513224676442016107 0ustar zuulzuul00000000000000[metadata] name = oslo.service summary = oslo.service library description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/oslo.service/latest/ 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.5 [files] packages = oslo_service [entry_points] oslo.config.opts = oslo.service.periodic_task = oslo_service.periodic_task:list_opts oslo.service.service = oslo_service.service:list_opts oslo.service.sslutils = oslo_service.sslutils:list_opts oslo.service.wsgi = oslo_service.wsgi:list_opts [build_sphinx] warning-is-error = 1 source-dir = doc/source build-dir = doc/build [upload_sphinx] upload-dir = doc/build/html [compile_catalog] directory = oslo_service/locale domain = oslo_service [update_catalog] domain = oslo_service output_dir = oslo_service/locale input_file = oslo_service/locale/oslo_service.pot [extract_messages] keywords = _ gettext ngettext l_ lazy_gettext mapping_file = babel.cfg output_file = oslo_service/locale/oslo_service.pot [wheel] universal = true [egg_info] tag_build = tag_date = 0 oslo.service-1.29.0/babel.cfg0000666000175100017510000000002113224676223015772 0ustar zuulzuul00000000000000[python: **.py] oslo.service-1.29.0/setup.py0000666000175100017510000000200613224676223015763 0ustar zuulzuul00000000000000# 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>=2.0.0'], pbr=True)