From c2588f99724111b06bc1ddbb6a72e276489d0b3c Mon Sep 17 00:00:00 2001 From: Vincent Llorens Date: Fri, 18 Dec 2015 12:09:20 +0100 Subject: [PATCH] import project from launchpad git repo In addition to importing the project, this commit also includes fixes to pass PEP8 and unit tests on OpenStack CI. Commit history that was imported from the launchpad project: . The first commit's message is: first Synergy implementation . This is the 2nd commit message: added README file . This is the 3rd commit message: added new logging configuration support . This is the 4th commit message: added new logging configuration support . This is the 5th commit message: Synergy repository restructured . This is the 6th commit message: Synergy repository restructured . This is the 7th commit message: TimeManager . This is the 8th commit message: first service implementation . This is the 9th commit message: package synergy.managers.scheduler removed . This is the 10th commit message: Keystone and MYSQL configuration removed . This is the 11th commit message: Clean up setup.py - remove all directory creation, chmoding, etc. This will be handled by the system package. - rename `scripts` as `bin` to follow best practices. . This is the 12th commit message: Fix systemd unit file - remove all the directory creation since it will be done by the system package. . This is the 13th commit message: Move synergy source to parent directory Follow the standard of having the source package directory at the root. This also make it easier to work with setuptools. . This is the 14th commit message: Add dependencies in setup.py . This is the 15th commit message: Add RPM spec file for packaging . This is the 16th commit message: debian/ubuntu package . This is the 17th commit message: add oslo.log as a requirement . This is the 18th commit message: deb pkg: explicit naming for oslo.* . This is the 19th commit message: rename deb package to python-synergy-service . This is the 20th commit message: fix debian package configure step . This is the 21st commit message: remove unecessary init & upstart scripts We will target Ubuntu >= 15.04 and EL >= 7. Both use systemd by default, so we do not need initv and upstart scripts. . This is the 22nd commit message: changed license field: GPL->Apache 2 . This is the 23rd commit message: change license to Apache 2 for rpm & deb . This is the 24th commit message: rename python package to "synergy-service" This is the name that will be used on PyPI. . This is the 25th commit message: oslo_log dependence removed . This is the 26th commit message: remove oslo.log dependency from packaging . This is the 27th commit message: remove oslo package renaming Both python-oslo-* and python-oslo.* are supported on Ubuntu >= 15.04, but only python-oslo.* are supported on Ubuntu 14.04. . This is the 28th commit message: fix oslo imports for Ubuntu14.04 This concerns 2 python packages: - oslo config - oslo messaging Both packages cannot be imported using their "oslo_PKG" name in Ubuntu 14.04. These packages can be imported on both Ubuntu 14.04 and 15.04 using "oslo.PKG". . This is the 29th commit message: fix manager config setup We check for manager in the conf file using the oslo config package. Previously, we relied on a private variable to list the managers. However, this private variable is not present on the oslo.config packaged shipped with ubuntu 14.04. In this commit we change the way we list managers to be compatible with both Ubuntu 14.04 and 15.04. . This is the 30th commit message: Revert "fix oslo imports for Ubuntu14.04" This reverts commit 6ee3b4d54765993d165169a56d54efdfb2653c89. We are going to use try/except imports to deal with oslo{.,_}PKG . This is the 31st commit message: use try/except imports for oslo{.,_} packages The oslo packages are imported using "oslo.PKG" for versions < 2.0.0, but are imported using "oslo_PKG" for versions >= 2.0.0. We use try/except statements to try to import "oslo_PKG" first, and fail over to "oslo.PKG". . This is the 32nd commit message: add packaging README . This is the 33rd commit message: add cleaning functions in packaging scripts This way when a container as finished building, when can easily do a rebuild with: docker start -a -i container_id . This is the 34th commit message: various fixes on packaging - deb: postinst depends on "adduser" package - deb: add upstart & systemv init scripts in deb package - deb: postrm purge now removes init script - deb: override lintian error about systemd/init script - rpm: fix the cleaning stage - rpm: fix /var/lib/synergy not being created and preventing systemd from starting the synergy service . This is the 35th commit message: use entry points to discover managers Also add the TimerManager to the manager entry point. fix centos docker builder The .git repo would not get cleaned up after a failed build, resulting in incapacity of starting the container again. add base files for OpenStack CI Most of the files were created with cookiecutter. http://docs.openstack.org/infra/manual/creators.html#preparing-a-new-git-repository-using-cookiecutter fix centos docker builder The .git repo would not get cleaned up after a failed build, resulting in incapacity of starting the container again. fix setup.py failing to build due to pbr rename service to "synergy" Previously it was either "python-synergy-service" or "synergy" depending on the platform. Change-Id: Iebded1d0712a710d9f71913144bf82be31e6765b --- .coveragerc | 7 + .testr.conf | 7 + CONTRIBUTING.rst | 3 + HACKING.rst | 4 + LICENSE | 176 +++++++ MANIFEST.in | 6 + README.rst | 40 ++ babel.cfg | 2 + bin/synergy | 10 + config/synergy.conf | 23 + doc/source/conf.py | 74 +++ doc/source/contributing.rst | 4 + doc/source/index.rst | 24 + doc/source/installation.rst | 5 + doc/source/readme.rst | 1 + doc/source/usage.rst | 7 + packaging/README.md | 108 ++++ packaging/debian/changelog | 5 + packaging/debian/compat | 1 + packaging/debian/control | 25 + packaging/debian/copyright | 21 + packaging/debian/docs | 1 + packaging/debian/python-synergy-service.dirs | 5 + .../debian/python-synergy-service.install | 2 + .../python-synergy-service.lintian-overrides | 5 + .../debian/python-synergy-service.postinst | 48 ++ .../debian/python-synergy-service.postrm | 41 ++ .../debian/python-synergy-service.preinst | 34 ++ .../python-synergy-service.synergy.init | 166 ++++++ .../python-synergy-service.synergy.upstart | 19 + packaging/debian/rules | 31 ++ packaging/debian/source/format | 1 + packaging/docker/build_env.sh | 1 + packaging/docker/centos7/Dockerfile | 13 + packaging/docker/centos7/build.sh | 30 ++ packaging/docker/ubuntu-14.04/Dockerfile | 17 + packaging/docker/ubuntu-14.04/build.sh | 27 + packaging/rpm/python-synergy.spec | 96 ++++ requirements.txt | 9 + scripts/synergy.service | 12 + setup.cfg | 50 ++ setup.py | 27 + synergy/__init__.py | 0 synergy/common/__init__.py | 0 synergy/common/command.py | 60 +++ synergy/common/config.py | 103 ++++ synergy/common/context.py | 172 +++++++ synergy/common/log.py | 68 +++ synergy/common/manager.py | 147 ++++++ synergy/common/serializer.py | 165 ++++++ synergy/common/service.py | 68 +++ synergy/common/utils.py | 33 ++ synergy/common/wsgi.py | 299 +++++++++++ synergy/examples/__init__.py | 0 synergy/examples/timer_manager.py | 44 ++ synergy/service.py | 476 ++++++++++++++++++ synergy/tests/__init__.py | 0 synergy/tests/base.py | 18 + synergy/tests/test_manager.py | 27 + test-requirements.txt | 14 + tox.ini | 60 +++ 61 files changed, 2942 insertions(+) create mode 100644 .coveragerc create mode 100644 .testr.conf create mode 100644 CONTRIBUTING.rst create mode 100644 HACKING.rst create mode 100644 LICENSE create mode 100644 MANIFEST.in create mode 100644 README.rst create mode 100644 babel.cfg create mode 100644 bin/synergy create mode 100644 config/synergy.conf create mode 100755 doc/source/conf.py create mode 100644 doc/source/contributing.rst create mode 100644 doc/source/index.rst create mode 100644 doc/source/installation.rst create mode 100644 doc/source/readme.rst create mode 100644 doc/source/usage.rst create mode 100644 packaging/README.md create mode 100644 packaging/debian/changelog create mode 100644 packaging/debian/compat create mode 100644 packaging/debian/control create mode 100644 packaging/debian/copyright create mode 100644 packaging/debian/docs create mode 100644 packaging/debian/python-synergy-service.dirs create mode 100644 packaging/debian/python-synergy-service.install create mode 100644 packaging/debian/python-synergy-service.lintian-overrides create mode 100755 packaging/debian/python-synergy-service.postinst create mode 100755 packaging/debian/python-synergy-service.postrm create mode 100755 packaging/debian/python-synergy-service.preinst create mode 100644 packaging/debian/python-synergy-service.synergy.init create mode 100644 packaging/debian/python-synergy-service.synergy.upstart create mode 100755 packaging/debian/rules create mode 100644 packaging/debian/source/format create mode 100644 packaging/docker/build_env.sh create mode 100644 packaging/docker/centos7/Dockerfile create mode 100644 packaging/docker/centos7/build.sh create mode 100644 packaging/docker/ubuntu-14.04/Dockerfile create mode 100644 packaging/docker/ubuntu-14.04/build.sh create mode 100644 packaging/rpm/python-synergy.spec create mode 100644 requirements.txt create mode 100644 scripts/synergy.service create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 synergy/__init__.py create mode 100644 synergy/common/__init__.py create mode 100644 synergy/common/command.py create mode 100644 synergy/common/config.py create mode 100644 synergy/common/context.py create mode 100644 synergy/common/log.py create mode 100644 synergy/common/manager.py create mode 100644 synergy/common/serializer.py create mode 100644 synergy/common/service.py create mode 100644 synergy/common/utils.py create mode 100644 synergy/common/wsgi.py create mode 100644 synergy/examples/__init__.py create mode 100644 synergy/examples/timer_manager.py create mode 100644 synergy/service.py create mode 100644 synergy/tests/__init__.py create mode 100644 synergy/tests/base.py create mode 100644 synergy/tests/test_manager.py create mode 100644 test-requirements.txt create mode 100644 tox.ini diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..5674976 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,7 @@ +[run] +branch = True +source = synergy +omit = synergy/openstack/* + +[report] +ignore_errors = True diff --git a/.testr.conf b/.testr.conf new file mode 100644 index 0000000..6d83b3c --- /dev/null +++ b/.testr.conf @@ -0,0 +1,7 @@ +[DEFAULT] +test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ + OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \ + OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \ + ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..a1a7bcb --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,3 @@ +Bugs should be filed on Launchpad, not GitHub: + + https://bugs.launchpad.net/synergy-service diff --git a/HACKING.rst b/HACKING.rst new file mode 100644 index 0000000..42cb110 --- /dev/null +++ b/HACKING.rst @@ -0,0 +1,4 @@ +Synergy Style Commandments +=============================================== + +Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..68c771a --- /dev/null +++ b/LICENSE @@ -0,0 +1,176 @@ + + 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. + diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..c978a52 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,6 @@ +include AUTHORS +include ChangeLog +exclude .gitignore +exclude .gitreview + +global-exclude *.pyc diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a94e95c --- /dev/null +++ b/README.rst @@ -0,0 +1,40 @@ +------------------------------ + SYNERGY +------------------------------ + +Synergy is as a new extensible general purpose management OpenStack service. +Its capabilities are implemented by a collection of managers which are specific +and independent pluggable tasks, executed periodically or interactively. The +managers can interact with each other in a loosely coupled way. + +* Free software: Apache license +* Documentation: http://docs.openstack.org/developer/synergy-service +* Source: http://git.openstack.org/cgit/openstack/synergy-service +* Bugs: http://bugs.launchpad.net/synergy-service + +================ +1. INSTALLATION +================ + +--------------- +1.1 REQUISITES +--------------- + + +---------------------- +1.2 OPTIONAL PACKAGES +---------------------- + +--------------- +1.3 INSTALLING +--------------- + + +------------------ +1.4 CONFIGURATION +------------------ + + +--------------- +1.5 SECURITY +--------------- diff --git a/babel.cfg b/babel.cfg new file mode 100644 index 0000000..15cd6cb --- /dev/null +++ b/babel.cfg @@ -0,0 +1,2 @@ +[python: **.py] + diff --git a/bin/synergy b/bin/synergy new file mode 100644 index 0000000..d1cae6d --- /dev/null +++ b/bin/synergy @@ -0,0 +1,10 @@ +#!/usr/bin/python +# PBR Generated from u'console_scripts' + +import sys + +from synergy.service import main + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/config/synergy.conf b/config/synergy.conf new file mode 100644 index 0000000..56b1043 --- /dev/null +++ b/config/synergy.conf @@ -0,0 +1,23 @@ +[DEFAULT] + +[Logger] +filename=/var/log/synergy/synergy.log +level=INFO +formatter="%(asctime)s - %(name)s - %(levelname)s - %(message)s" +maxBytes=1048576 +backupCount=100 + + +[WSGI] +host=localhost +port=8051 +threads=2 +use_ssl=False +#ssl_ca_file= +#ssl_cert_file= +#ssl_key_file= +max_header_line=16384 +retry_until_window=30 +tcp_keepidle=600 +backlog=4096 + diff --git a/doc/source/conf.py b/doc/source/conf.py new file mode 100755 index 0000000..e2df1ae --- /dev/null +++ b/doc/source/conf.py @@ -0,0 +1,74 @@ +# -*- 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', + #'sphinx.ext.intersphinx', + 'oslosphinx' +] + +# 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'synergy-service' + +# If true, '()' will be appended to :func: etc. cross-reference text. +add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +add_module_names = True + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. Major themes that come with +# Sphinx are currently 'default' and 'sphinxdoc'. +# html_theme_path = ["."] +# html_theme = '_theme' +# html_static_path = ['static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = '%sdoc' % project + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass +# [howto/manual]). +latex_documents = [ + ('index', + '%s.tex' % project, + u'%s Documentation' % project, + u'OpenStack Foundation', 'manual'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +#intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/doc/source/contributing.rst b/doc/source/contributing.rst new file mode 100644 index 0000000..1728a61 --- /dev/null +++ b/doc/source/contributing.rst @@ -0,0 +1,4 @@ +============ +Contributing +============ +.. include:: ../../CONTRIBUTING.rst diff --git a/doc/source/index.rst b/doc/source/index.rst new file mode 100644 index 0000000..f0289e4 --- /dev/null +++ b/doc/source/index.rst @@ -0,0 +1,24 @@ +.. synergy documentation master file, created by + sphinx-quickstart on Tue Jul 9 22:26:36 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to synergy's documentation! +======================================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + readme + installation + usage + contributing + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/doc/source/installation.rst b/doc/source/installation.rst new file mode 100644 index 0000000..4927ba5 --- /dev/null +++ b/doc/source/installation.rst @@ -0,0 +1,5 @@ +============ +Installation +============ + +TODO diff --git a/doc/source/readme.rst b/doc/source/readme.rst new file mode 100644 index 0000000..a6210d3 --- /dev/null +++ b/doc/source/readme.rst @@ -0,0 +1 @@ +.. include:: ../../README.rst diff --git a/doc/source/usage.rst b/doc/source/usage.rst new file mode 100644 index 0000000..d86ea05 --- /dev/null +++ b/doc/source/usage.rst @@ -0,0 +1,7 @@ +======== +Usage +======== + +To use synergy in a project:: + + import synergy diff --git a/packaging/README.md b/packaging/README.md new file mode 100644 index 0000000..9d3f568 --- /dev/null +++ b/packaging/README.md @@ -0,0 +1,108 @@ +Packaging +========= + +Packaging for Ubuntu and CentOS using Docker +-------------------------------------------- + +We provide Dockerfiles for CentOS 7 and Ubuntu 14.04. A Dockerfile for Ubuntu > +14.04 should work by just changing the "FROM" statement of the Ubuntu 14.04 +Dockerfile. Using these, you can easily build rpm and deb packages for +synergy-service without having to setup the build system on your own system. + +The build process using Docker is made of 3 steps: + +1. Build the docker image + +2. Setup the build variables + +3. Run the build with docker + +If the build is successful, the package will be put in the build directory +inside the synergy-service directory. + + +### Example for CentOS 7 + +- go into the directory that contains the Dockerfile for CentOS 7 + + cd synergy-service/packaging/docker/centos7 + +- build the docker image and tag it + + docker build -t synergy-centos7-builder . + +- edit the file `synergy-service/packaging/docker/build_env.sh` to define environment variables. + +- launch the container + + docker run -i -v /path/to/synergy-service:/tmp/synergy-service \ + --env-file=/path/to/synergy-service/packaging/docker/build_env.sh \ + synergy-centos7-builder + + This actually mount the synergy-service directory to `/tmp/synergy-service` on + the guest. + It also loads environment variables from the `build_env.sh` file. + +- the resulting rpm should be in the build directory if successful + + +### Example for Ubuntu 14.04 + +- go into the directory that contains the Dockerfile for Ubuntu 14.04 + + cd synergy-service/packaging/docker/ubuntu-14.04 + +- build the docker image and tag it + + docker build -t synergy-ubuntu14.04-builder . + +- edit the file `synergy-service/packaging/docker/build_env.sh` to define environment variables. + +- launch the container + + docker run -i -v /path/to/synergy-service:/tmp/synergy-service \ + --env-file=/path/to/synergy-service/packaging/docker/build_env.sh \ + synergy-ubuntu14.04-builder + +- the resulting deb should be in the build directory if successful + + +Packaging for Ubuntu +-------------------- + +1. Install the necessary build packages: + - debhelper + - dh-systemd + - build-essential + - devscripts + - python-all + - python-setuptools + +2. Make a gzip archive of synergy-service named `python-synergy-service_VERSION.orig.tar.gz`. + +3. Copy `synergy-service/packaging/debian` to `synergy-service/debian`. + +4. Go in the `synergy-service` directory and build with `debuild -us -uc`. + + +Packaging for CentOS +-------------------- + +1. Install the necessary build packages: + - rpm-build + - python-devel + - python-setuptools + +2. Setup your rpmbuild environment if not already done. + + mkdir -p ~/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + +3. Move `synergy-service/packaging/rpm/python-synergy.spec` to + `~/rpmbuild/SPECS`. + +4. Create a source archive: + + cp -r /path/to/synergy-service ~/rpmbuild/SOURCES/python-synergy-service + tar cjf python-synergy-service python-synergy-service.tar.bz2 + +5. Go in `~/rpmbuild/SPECS` and buils with `rpmbuild -ba python-synergy.spec`. diff --git a/packaging/debian/changelog b/packaging/debian/changelog new file mode 100644 index 0000000..5844edf --- /dev/null +++ b/packaging/debian/changelog @@ -0,0 +1,5 @@ +python-synergy-service (0.1-1) unstable; urgency=low + + * Initial release + + -- Vincent Llorens Mon, 15 Feb 2016 14:02:30 +0000 diff --git a/packaging/debian/compat b/packaging/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/packaging/debian/compat @@ -0,0 +1 @@ +9 diff --git a/packaging/debian/control b/packaging/debian/control new file mode 100644 index 0000000..efc8cd8 --- /dev/null +++ b/packaging/debian/control @@ -0,0 +1,25 @@ +Source: python-synergy-service +Section: contrib/python +Priority: optional +Maintainer: Vincent Llorens +Build-Depends: debhelper (>= 9), + dh-python, + dh-systemd, + git-core, + python-all, + python-pbr, + python-setuptools +Standards-Version: 3.9.5 +Homepage: https://launchpad.net/synergy-service +Vcs-Git: git://git.launchpad.net/synergy-service +Vcs-Browser: https://git.launchpad.net/synergy-service + +Package: python-synergy-service +Architecture: all +Depends: ${python:Depends}, ${misc:Depends}, adduser +Description: Synergy service for OpenStack. + Synergy is as a new extensible general purpose management OpenStack service. + Its capabilities are implemented by a collection of managers which are + specific and independent pluggable tasks, executed periodically or + interactively. The managers can interact with each other in a loosely + coupled way. diff --git a/packaging/debian/copyright b/packaging/debian/copyright new file mode 100644 index 0000000..593a354 --- /dev/null +++ b/packaging/debian/copyright @@ -0,0 +1,21 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: synergy-service +Source: + +Files: * +Copyright: 2015, 2016 Lisa Zangrando +License: Apache-2 + 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. + . + On Debian-based systems the full text of the Apache version 2.0 license + can be found in `/usr/share/common-licenses/Apache-2.0' diff --git a/packaging/debian/docs b/packaging/debian/docs new file mode 100644 index 0000000..a1320b1 --- /dev/null +++ b/packaging/debian/docs @@ -0,0 +1 @@ +README.rst diff --git a/packaging/debian/python-synergy-service.dirs b/packaging/debian/python-synergy-service.dirs new file mode 100644 index 0000000..139641d --- /dev/null +++ b/packaging/debian/python-synergy-service.dirs @@ -0,0 +1,5 @@ +etc/synergy +var/lib/synergy +var/log/synergy +var/run/synergy +var/lock/synergy diff --git a/packaging/debian/python-synergy-service.install b/packaging/debian/python-synergy-service.install new file mode 100644 index 0000000..5052746 --- /dev/null +++ b/packaging/debian/python-synergy-service.install @@ -0,0 +1,2 @@ +config/synergy.conf /etc/synergy +scripts/synergy.service /lib/systemd/system diff --git a/packaging/debian/python-synergy-service.lintian-overrides b/packaging/debian/python-synergy-service.lintian-overrides new file mode 100644 index 0000000..5deaf8c --- /dev/null +++ b/packaging/debian/python-synergy-service.lintian-overrides @@ -0,0 +1,5 @@ +# Both systemv init and systemd scripts are provided. +# Systemd script is automatically provided with the name "synergy-service", +# whereas the init script is provided with the name "python-synergy-service". +# Lintian complains because they don't have the same name. +systemd-no-service-for-init-script diff --git a/packaging/debian/python-synergy-service.postinst b/packaging/debian/python-synergy-service.postinst new file mode 100755 index 0000000..428f8b1 --- /dev/null +++ b/packaging/debian/python-synergy-service.postinst @@ -0,0 +1,48 @@ +#!/bin/sh +# postinst script for python-synergy-service +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + configure) + # Add synergy group & user + adduser --quiet --system --group --home /var/lib/synergy synergy > /dev/null 2>&1 + # Change dirs and files permission/ownership + chown -R synergy:synergy /etc/synergy \ + /var/lib/synergy \ + /var/lock/synergy \ + /var/log/synergy \ + /var/run/synergy + chmod 0700 /var/lib/synergy/ + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/packaging/debian/python-synergy-service.postrm b/packaging/debian/python-synergy-service.postrm new file mode 100755 index 0000000..477804e --- /dev/null +++ b/packaging/debian/python-synergy-service.postrm @@ -0,0 +1,41 @@ +#!/bin/sh +# postrm script for python-synergy-service +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `remove' +# * `purge' +# * `upgrade' +# * `failed-upgrade' +# * `abort-install' +# * `abort-install' +# * `abort-upgrade' +# * `disappear' +# +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + + +case "$1" in + remove|upgrade|failed-upgrade|abort-install|abort-upgrade|disappear) + ;; + + purge) + update-rc.d python-synergy-service remove >/dev/null + ;; + + *) + echo "postrm called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/packaging/debian/python-synergy-service.preinst b/packaging/debian/python-synergy-service.preinst new file mode 100755 index 0000000..b4f864f --- /dev/null +++ b/packaging/debian/python-synergy-service.preinst @@ -0,0 +1,34 @@ +#!/bin/sh +# preinst script for python-synergy-service +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `install' +# * `install' +# * `upgrade' +# * `abort-upgrade' +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +case "$1" in + install|upgrade) + ;; + + abort-upgrade) + ;; + + *) + echo "preinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/packaging/debian/python-synergy-service.synergy.init b/packaging/debian/python-synergy-service.synergy.init new file mode 100644 index 0000000..4f8e513 --- /dev/null +++ b/packaging/debian/python-synergy-service.synergy.init @@ -0,0 +1,166 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: synergy +# Required-Start: $local_fs $network $remote_fs $syslog +# Required-Stop: $local_fs $network $remote_fs $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# Short-Description: +# Description: +# <...> +# <...> +### END INIT INFO + +# Author: Vincent Llorens + +# Do NOT "set -e" + +# PATH should only include /usr/* if it runs after the mountnfs.sh script +PATH=/sbin:/usr/sbin:/bin:/usr/bin +DESC="python-synergy-service" +NAME=synergy +DAEMON=/usr/sbin/synergy +DAEMON_ARGS="" +PIDFILE=/var/run/$NAME.pid +SCRIPTNAME=/etc/init.d/$NAME + +# Exit if the package is not installed +[ -x "$DAEMON" ] || exit 0 + +# Read configuration variable file if it is present +[ -r /etc/default/$NAME ] && . /etc/default/$NAME + +# Load the VERBOSE setting and other rcS variables +. /lib/init/vars.sh + +# Define LSB log_* functions. +# Depend on lsb-base (>= 3.2-14) to ensure that this file is present +# and status_of_proc is working. +. /lib/lsb/init-functions + +# +# Function that starts the daemon/service +# +do_start() +{ + # Return + # 0 if daemon has been started + # 1 if daemon was already running + # 2 if daemon could not be started + # start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ + # || return 1 + # start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \ + # $DAEMON_ARGS \ + # || return 2 + # The above code will not work for interpreted scripts, use the next + # six lines below instead (Ref: #643337, start-stop-daemon(8) ) + start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \ + --name $NAME --test > /dev/null \ + || return 1 + start-stop-daemon --start --quiet --pidfile $PIDFILE --startas $DAEMON \ + --name $NAME -- $DAEMON_ARGS \ + || return 2 + + # Add code here, if necessary, that waits for the process to be ready + # to handle requests from services started subsequently which depend + # on this one. As a last resort, sleep for some time. +} + +# +# Function that stops the daemon/service +# +do_stop() +{ + # Return + # 0 if daemon has been stopped + # 1 if daemon was already stopped + # 2 if daemon could not be stopped + # other if a failure occurred + start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME + RETVAL="$?" + [ "$RETVAL" = 2 ] && return 2 + # Wait for children to finish too if this is a daemon that forks + # and if the daemon is only ever run from this initscript. + # If the above conditions are not satisfied then add some other code + # that waits for the process to drop all resources that could be + # needed by services started subsequently. A last resort is to + # sleep for some time. + start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON + [ "$?" = 2 ] && return 2 + # Many daemons don't delete their pidfiles when they exit. + rm -f $PIDFILE + return "$RETVAL" +} + +# +# Function that sends a SIGHUP to the daemon/service +# +do_reload() { + # + # If the daemon can reload its configuration without + # restarting (for example, when it is sent a SIGHUP), + # then implement that here. + # + start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME + return 0 +} + +case "$1" in + start) + [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" + do_start + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + stop) + [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" + do_stop + case "$?" in + 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; + 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; + esac + ;; + status) + status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? + ;; + #reload|force-reload) + # + # If do_reload() is not implemented then leave this commented out + # and leave 'force-reload' as an alias for 'restart'. + # + #log_daemon_msg "Reloading $DESC" "$NAME" + #do_reload + #log_end_msg $? + #;; + restart|force-reload) + # + # If the "reload" option is implemented then remove the + # 'force-reload' alias + # + log_daemon_msg "Restarting $DESC" "$NAME" + do_stop + case "$?" in + 0|1) + do_start + case "$?" in + 0) log_end_msg 0 ;; + 1) log_end_msg 1 ;; # Old process is still running + *) log_end_msg 1 ;; # Failed to start + esac + ;; + *) + # Failed to stop + log_end_msg 1 + ;; + esac + ;; + *) + #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 + echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 + exit 3 + ;; +esac + +: diff --git a/packaging/debian/python-synergy-service.synergy.upstart b/packaging/debian/python-synergy-service.synergy.upstart new file mode 100644 index 0000000..597ea73 --- /dev/null +++ b/packaging/debian/python-synergy-service.synergy.upstart @@ -0,0 +1,19 @@ +description "Synergy service" +author "Lisa Zangrando " + +start on runlevel [2345] +stop on runlevel [!2345] + +pre-start script + for i in "lock run log lib"; do + mkdir -p /var/"$i"/synergy + chown synergy:synergy /var/"$i"/synergy + done +end script + +script + DAEMON_ARGS="" + exec start-stop-daemon --start --chdir /var/lib/synergy \ + --chuid synergy:synergy --make-pidfile --pidfile /var/run/synergy/synergy.pid \ + --exec /usr/bin/synergy -- --config-file=/etc/synergy/synergy.conf ${DAEMON_ARGS} +end script diff --git a/packaging/debian/rules b/packaging/debian/rules new file mode 100755 index 0000000..a2cf5f2 --- /dev/null +++ b/packaging/debian/rules @@ -0,0 +1,31 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#DH_VERBOSE = 1 + +# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed +export PYBUILD_NAME=python-synergy-service + +# main packaging script based on dh7 syntax +%: + dh $@ --with python2,systemd --buildsystem=pybuild + +override_dh_installinit: + dh_installinit --name synergy + +# debmake generated override targets +# This is example for Cmake (See http://bugs.debian.org/641051 ) +#override_dh_auto_configure: +# dh_auto_configure -- \ +# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) diff --git a/packaging/debian/source/format b/packaging/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/packaging/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/packaging/docker/build_env.sh b/packaging/docker/build_env.sh new file mode 100644 index 0000000..db714a4 --- /dev/null +++ b/packaging/docker/build_env.sh @@ -0,0 +1 @@ +PKG_VERSION=0.1 diff --git a/packaging/docker/centos7/Dockerfile b/packaging/docker/centos7/Dockerfile new file mode 100644 index 0000000..0f4efb9 --- /dev/null +++ b/packaging/docker/centos7/Dockerfile @@ -0,0 +1,13 @@ +FROM centos:7 +MAINTAINER Vincent Llorens +RUN yum install -y https://rdoproject.org/repos/rdo-release.rpm \ + rpm-build \ + python-devel \ + python-setuptools \ + && yum update -y +RUN mkdir /tmp/synergy-service +RUN useradd -m -p pkger pkger +USER pkger +COPY build.sh /home/pkger/ +WORKDIR /home/pkger/ +CMD bash build.sh diff --git a/packaging/docker/centos7/build.sh b/packaging/docker/centos7/build.sh new file mode 100644 index 0000000..f85d9d4 --- /dev/null +++ b/packaging/docker/centos7/build.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +set -e -x + +RPMBUILD=/home/pkger/rpmbuild +PKG_DIR=/tmp/synergy-service + +function setup() { + mkdir -p /home/pkger/rpmbuild/{BUILD,RPMS,SOURCES,SPECS,SRPMS} + cd $RPMBUILD/SOURCES/ + cp -r $PKG_DIR python-synergy-service-$PKG_VERSION + rm -r python-synergy-service-$PKG_VERSION/build || true + tar cjf python-synergy-service-${PKG_VERSION}.tar.bz2 python-synergy-service-$PKG_VERSION + cp $PKG_DIR/packaging/rpm/python-synergy.spec $RPMBUILD/SPECS/python-synergy.spec +} + +function build() { + cd $RPMBUILD/SPECS + export PBR_VERSION=$PKG_VERSION + rpmbuild -ba python-synergy.spec + mkdir -p $PKG_DIR/build/ + cp -i $RPMBUILD/RPMS/noarch/python-synergy-service-*.rpm $PKG_DIR/build/ +} + +function clean() { + rm -rf $RPMBUILD +} + +clean +setup +build diff --git a/packaging/docker/ubuntu-14.04/Dockerfile b/packaging/docker/ubuntu-14.04/Dockerfile new file mode 100644 index 0000000..5263747 --- /dev/null +++ b/packaging/docker/ubuntu-14.04/Dockerfile @@ -0,0 +1,17 @@ +FROM ubuntu:14.04 +MAINTAINER Vincent Llorens +RUN apt-get update \ + && apt-get install -y build-essential \ + debhelper \ + devscripts \ + dh-systemd \ + git-core \ + python-all \ + python-pbr \ + python-setuptools +RUN mkdir /tmp/synergy-service +RUN useradd -m -p pkger pkger +USER pkger +COPY build.sh /home/pkger/build.sh +WORKDIR /home/pkger +CMD bash build.sh diff --git a/packaging/docker/ubuntu-14.04/build.sh b/packaging/docker/ubuntu-14.04/build.sh new file mode 100644 index 0000000..df1b17b --- /dev/null +++ b/packaging/docker/ubuntu-14.04/build.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +set -e -x + +PKG_DIR=/tmp/synergy-service + +function setup() { + cd /home/pkger + cp -r $PKG_DIR synergy-service + tar cfz python-synergy-service_${PKG_VERSION}.orig.tar.gz synergy-service + mv synergy-service python-synergy-service + cp -r python-synergy-service/packaging/debian python-synergy-service/debian +} + +function build() { + cd /home/pkger/python-synergy-service + debuild -us -uc + mkdir -p $PKG_DIR/build + cp -i /home/pkger/*.deb $PKG_DIR/build +} + +function clean() { + rm -r /home/pkger/python-synergy-service* +} + +setup +build +clean diff --git a/packaging/rpm/python-synergy.spec b/packaging/rpm/python-synergy.spec new file mode 100644 index 0000000..2986b1e --- /dev/null +++ b/packaging/rpm/python-synergy.spec @@ -0,0 +1,96 @@ +%{!?python_sitelib: %global python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print(get_python_lib())")} + +Name: python-synergy-service +Version: 0.1 +Release: 1%{?dist} +Summary: Synergy service + +License: ASL 2.0 +URL: https://launchpad.net/synergy-service +Source0: https://launchpad.net/synergy-service/%{name}-%{version}.tar.bz2 + +BuildArch: noarch +BuildRequires: systemd +BuildRequires: python-devel +BuildRequires: python-setuptools +Requires(pre): shadow-utils +Requires(post): systemd +Requires(preun): systemd +Requires(postun): systemd +Requires: python-eventlet +Requires: python-oslo-config +Requires: python-oslo-messaging +Requires: python-oslo-log +Requires: python-dateutil + + +%description +Synergy is as a new extensible general purpose management OpenStack service. +Its capabilities are implemented by a collection of managers which are +specific and independent pluggable tasks, executed periodically or +interactively. The managers can interact with each other in a loosely coupled +way. + + +%prep +%setup -q + + +%build +%{__python} setup.py build + + +%install +rm -rf $RPM_BUILD_ROOT +%{__python} setup.py install -O1 --skip-build --root $RPM_BUILD_ROOT + +install -d -m0755 %{buildroot}%{_sysconfdir}/synergy +install -D -m0644 config/synergy.conf %{buildroot}%{_sysconfdir}/synergy/synergy.conf +install -D -m0644 scripts/synergy.service %{buildroot}%{_unitdir}/synergy.service +install -d -m0700 %{buildroot}%{_localstatedir}/lib/synergy +install -d -m0755 %{buildroot}%{_localstatedir}/log/synergy +touch %{buildroot}%{_localstatedir}/log/synergy/synergy.log +install -d -m0755 %{buildroot}%{_localstatedir}/run/synergy +install -d -m0755 %{buildroot}%{_localstatedir}/lock/synergy + + +%files +%doc README.rst +%{python_sitelib}/* +%config(noreplace) %{_sysconfdir}/synergy/synergy.conf +%{_bindir}/synergy +%{_unitdir}/synergy.service +%defattr(-, synergy, root, -) +%{_localstatedir}/lock/synergy/ +%{_localstatedir}/log/synergy/ +%{_localstatedir}/log/synergy/synergy.log +%{_localstatedir}/run/synergy/ +%attr(700, synergy, root) %{_localstatedir}/lib/synergy/ + + +%pre +getent group synergy > /dev/null || groupadd -r synergy +getent passwd synergy > /dev/null || \ + useradd -r -g synergy -s /sbin/nologin synergy +exit 0 + + +%post +%systemd_post synergy.service + + +%preun +%systemd_preun synergy.service + + +%postun +%systemd_postun_with_restart synergy.service +if [ "$1" = 0 ]; then + userdel -r synergy + groupdel synergy + true +fi + +%changelog +* Wed Jan 20 2016 Vincent Llorens +- WIP RPM release diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..d0daa7d --- /dev/null +++ b/requirements.txt @@ -0,0 +1,9 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +pbr>=1.6 +eventlet +oslo.config +oslo.messaging +python-dateutil diff --git a/scripts/synergy.service b/scripts/synergy.service new file mode 100644 index 0000000..1942345 --- /dev/null +++ b/scripts/synergy.service @@ -0,0 +1,12 @@ +[Unit] +Description=Synergy service +After=mysql.service postgresql.service slapd.service rabbitmq-server.service ntp.service + +[Service] +User=synergy +Group=synergy +WorkingDirectory=/var/lib/synergy +ExecStart=/usr/bin/synergy + +[Install] +WantedBy=multi-user.target diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..ba6ce42 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,50 @@ +[metadata] +name = synergy-service +version = 0.1.1 +summary = Synergy is as an extensible general purpose management OpenStack service. +description-file = + README.rst +author = Lisa Zangrando +author-email = lisa.zangrando@pd.infn.it +home-page = https://launchpad.net/synergy-service +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 + +[files] +packages = + synergy +scripts = + bin/synergy + +[entry_points] +synergy.managers = + timer = synergy.examples.timer_manager:TimerManager + +[build_sphinx] +source-dir = doc/source +build-dir = doc/build +all_files = 1 + +[upload_sphinx] +upload-dir = doc/build/html + +[compile_catalog] +directory = synergy/locale +domain = synergy + +[update_catalog] +domain = synergy +output_dir = synergy/locale +input_file = synergy/locale/synergy.pot + +[extract_messages] +keywords = _ gettext ngettext l_ lazy_gettext +mapping_file = babel.cfg +output_file = synergy/locale/synergy.pot diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..98b93eb --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +# 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'], + pbr=True) diff --git a/synergy/__init__.py b/synergy/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/synergy/common/__init__.py b/synergy/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/synergy/common/command.py b/synergy/common/command.py new file mode 100644 index 0000000..d60f23f --- /dev/null +++ b/synergy/common/command.py @@ -0,0 +1,60 @@ +from synergy.common import serializer + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Command(serializer.SynergyObject): + VERSION = "1.0" + + def __init__(self, name): + super(Command, self).__init__(name) + + def getParameters(self): + parameters = self.get("parameters") + + if parameters is None: + self.set("parameters", {}) + + return self.get("parameters") + + def addParameter(self, name, value): + self.getParameters()[name] = value + + def getParameter(self, name): + return self.getParameters().get(name, None) + + def setParameters(self, parameters): + self.set("parameters", parameters) + + def getResults(self): + result = self.get("result") + + if not result: + self.set("result", {}) + + return self.get("result") + + def addResult(self, name, value): + self.getResults()[name] = value + + def getResult(self, name): + return self.getResults().get(name, None) + + def setResults(self, data): + self.set("result", data) diff --git a/synergy/common/config.py b/synergy/common/config.py new file mode 100644 index 0000000..3cfdb67 --- /dev/null +++ b/synergy/common/config.py @@ -0,0 +1,103 @@ +try: + from oslo_config import cfg +except ImportError: + from oslo.config import cfg + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +CONF = cfg.CONF + +service_opts = [ + cfg.StrOpt("topic", default="synergy_topic", help="the topic"), + cfg.StrOpt("exchange", default="synergy_exchange", help="the exchange"), +] + +wsgi_opts = [ + cfg.StrOpt("host", default="localhost", + help="Address to bind the server"), + cfg.IntOpt("port", default=8051, + help="The port on which the server will listen"), + cfg.IntOpt("threads", default=1000), + cfg.IntOpt("backlog", default=4096, + help="Number of backlog requests to configure the socket with"), + 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("retry_until_window", default=30, + help="Number of seconds to keep retrying to listen"), + cfg.IntOpt("max_header_line", default=16384, + help="Max header line to accommodate large tokens"), + cfg.BoolOpt("use_ssl", default=False, + help="Enable SSL on the API server"), + cfg.StrOpt("ssl_ca_file", default=None, + help="CA certificate file to use to verify connecting clients"), + cfg.StrOpt("ssl_cert_file", default=None, + help="The certificate file"), + cfg.StrOpt("ssl_key_file", default=None, + help="The private key file") +] + +logger_opts = [ + cfg.StrOpt("filename", default="/var/log/synergy/synergy.log", + required=True), + cfg.StrOpt("level", default="INFO", required=False), + cfg.IntOpt("maxBytes", default=1048576), + cfg.IntOpt("backupCount", default=100), + cfg.StrOpt("formatter", + default="%(asctime)s - %(name)s - %(levelname)s - %(message)s", + required=False) +] + +manager_opts = [ + cfg.BoolOpt("autostart", default=False), + cfg.IntOpt("rate", default=60) +] + +""" +keystone_opts = [ + cfg.StrOpt("admin_user", required=True), + cfg.StrOpt("admin_password", required=True), + cfg.StrOpt("admin_project_name", required=True), + cfg.StrOpt("auth_url", required=True) +] + +mysql_opts = [ + cfg.StrOpt("host", required=True), + cfg.StrOpt("user", default="synergy"), + cfg.StrOpt("password", required=True), + cfg.StrOpt("db", default="synergy", required=True), + cfg.IntOpt("pool_size", default="10", required=False) +] +""" + +cfg.CONF.register_opts(service_opts) +cfg.CONF.register_opts(wsgi_opts, group="WSGI") +cfg.CONF.register_opts(logger_opts, group="Logger") +# cfg.CONF.register_opts(socket_opts) +# cfg.CONF.register_opts(keystone_opts, group="Keystone") +# cfg.CONF.register_opts(mysql_opts, group="MYSQL") + + +def parse_args(args=None, usage=None, default_config_files=None): + cfg.CONF(args=args, + project='synergy', + version="1.0", + usage=usage, + default_config_files=default_config_files) diff --git a/synergy/common/context.py b/synergy/common/context.py new file mode 100644 index 0000000..ceb2276 --- /dev/null +++ b/synergy/common/context.py @@ -0,0 +1,172 @@ +import copy +import datetime +import six +import uuid + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +def generate_request_id(): + return 'req-' + str(uuid.uuid4()) + + +class RequestContext(object): + """Security context and request information. + + Represents the user taking a given action within the system. + + """ + + def __init__(self, user_id, project_id, is_admin=None, read_deleted="no", + roles=None, remote_address=None, timestamp=None, + request_id=None, auth_token=None, overwrite=True, + quota_class=None, user_name=None, project_name=None, + service_catalog=None, instance_lock_checked=False, **kwargs): + self.user_id = user_id + self.project_id = project_id + self.roles = roles or [] + self.read_deleted = read_deleted + self.remote_address = remote_address + if not timestamp: + timestamp = datetime.datetime.utcnow() + if isinstance(timestamp, six.string_types): + timestamp = datetime.datetime.strptime(timestamp, + '%Y-%m-%dT%H:%M:%S.%f') + self.timestamp = timestamp + if not request_id: + request_id = generate_request_id() + self.request_id = request_id + self.auth_token = auth_token + + if service_catalog: + # Only include required parts of service_catalog + self.service_catalog = [ + s for s in service_catalog if s.get('type') in ('volume',)] + else: + # if list is empty or none + self.service_catalog = [] + + self.instance_lock_checked = instance_lock_checked + + # NOTE(markmc): this attribute is currently only used by the + # rs_limits turnstile pre-processor. + # See https://lists.launchpad.net/openstack/msg12200.html + self.quota_class = quota_class + self.user_name = user_name + self.project_name = project_name + self.is_admin = is_admin + # if self.is_admin is None: + # self.is_admin = policy.check_is_admin(self) + + """ + if overwrite or not hasattr(local.store, 'context'): + self.update_store() + """ + def _get_read_deleted(self): + return self._read_deleted + + def _set_read_deleted(self, read_deleted): + if read_deleted not in ('no', 'yes', 'only'): + raise ValueError("read_deleted can only be one of 'no', " + "'yes' or 'only', not %r" % read_deleted) + self._read_deleted = read_deleted + + def _del_read_deleted(self): + del self._read_deleted + + read_deleted = property(_get_read_deleted, _set_read_deleted, + _del_read_deleted) + + def update_store(self): + # local.store.context = self + pass + + def toDict(self): + date_format = "%Y-%m-%dT%H:%M:%S.%f" + + return {'user_id': self.user_id, + 'project_id': self.project_id, + 'is_admin': self.is_admin, + 'read_deleted': self.read_deleted, + 'roles': self.roles, + 'remote_address': self.remote_address, + 'timestamp': datetime.datetime.strptime(self.timestamp, + date_format), + 'request_id': self.request_id, + 'auth_token': self.auth_token, + 'quota_class': self.quota_class, + 'user_name': self.user_name, + 'service_catalog': self.service_catalog, + 'project_name': self.project_name, + 'instance_lock_checked': self.instance_lock_checked, + 'tenant': self.tenant, + 'user': self.user} + + @classmethod + def fromDict(cls, values): + values.pop('user', None) + values.pop('tenant', None) + + return cls(**values) + + def elevated(self, read_deleted=None, overwrite=False): + """Return a version of this context with admin flag set.""" + context = copy.copy(self) + context.is_admin = True + + if 'admin' not in context.roles: + context.roles.append('admin') + + if read_deleted is not None: + context.read_deleted = read_deleted + + return context + + # NOTE(sirp): the openstack/common version of RequestContext uses + # tenant/user whereas the Nova version uses project_id/user_id. We need + # this shim in order to use context-aware code from openstack/common, like + # logging, until we make the switch to using openstack/common's version of + # RequestContext. + @property + def tenant(self): + return self.project_id + + @property + def user(self): + return self.user_id + + +def get_admin_context(read_deleted="no"): + return RequestContext(user_id=None, + project_id=None, + is_admin=True, + read_deleted=read_deleted, + overwrite=False) + + +def is_user_context(context): + """Indicates if the request context is a normal user.""" + if not context: + return False + if context.is_admin: + return False + if not context.user_id or not context.project_id: + return False + return True diff --git a/synergy/common/log.py b/synergy/common/log.py new file mode 100644 index 0000000..e321420 --- /dev/null +++ b/synergy/common/log.py @@ -0,0 +1,68 @@ +import logging +import logging.handlers + +try: + from oslo_config import cfg +except ImportError: + from oslo.config import cfg + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + +CONF = cfg.CONF +loggers = {} + + +def getLogger(name="unknown"): + global loggers + + if loggers.get(name): + return loggers.get(name) + + else: + logger = logging.getLogger(name) + + if CONF.Logger.level == "DEBUG": + logger.setLevel(logging.DEBUG) + elif CONF.Logger.level == "INFO": + logger.setLevel(logging.INFO) + elif CONF.Logger.level == "WARNING": + logger.setLevel(logging.WARNING) + elif CONF.Logger.level == "ERROR": + logger.setLevel(logging.ERROR) + elif CONF.Logger.level == "CRITICAL": + logger.setLevel(logging.CRITICAL) + else: + logger.setLevel(logging.INFO) + + # create a logging format + formatter = logging.Formatter(CONF.Logger.formatter) + + # Add the log message handler to the logger + handler = logging.handlers.RotatingFileHandler( + CONF.Logger.filename, + maxBytes=CONF.Logger.maxBytes, + backupCount=CONF.Logger.backupCount) + + handler.setFormatter(formatter) + + logger.addHandler(handler) + + loggers[name] = logger + + return logger diff --git a/synergy/common/manager.py b/synergy/common/manager.py new file mode 100644 index 0000000..3110c2e --- /dev/null +++ b/synergy/common/manager.py @@ -0,0 +1,147 @@ +from threading import Condition +from threading import Timer + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +class Manager(object): + + def __init__(self, name): + self.config_opts = [] + self.condition = Condition() + self.name = name + self.status = "CREATED" + self.autostart = False + self.rate = -1 + self.timer = None + self.is_running = False + self.managers = {} + + def execute(self, command, *args, **kargs): + pass + + def task(self): + pass + + def doOnEvent(self, event_type, *args, **kargs): + pass + + def getManagers(self): + return self.managers + + def getManager(self, name): + return self.managers.get(name, None) + + def notify(self, event_type="DEFAULT", manager_name=None, *args, **kargs): + if manager_name is not None: + if manager_name in self.managers: + self.managers[manager_name].doOnEvent(event_type, + *args, **kargs) + else: + for manager in self.managers.values(): + if manager.getName() != manager_name: + manager.doOnEvent(event_type, *args, **kargs) + + def getName(self): + return self.name + + def getOptions(self): + return self.config_opts + + def isAutoStart(self): + return self.autostart + + def setAutoStart(self, autostart): + self.autostart = autostart + + def getRate(self): + return self.rate + + def setRate(self, rate): + if rate and rate > 0: + self.rate = rate + + def setup(self): + """Manager initialization + + Hook to do additional manager initialization when one requests + the service be started. This is called before any service record + is created. + Child classes should override this method. + """ + pass + + def destroy(self): + pass + + def getStatus(self): + return self.status + + def setStatus(self, status): + with self.condition: + self.status = status + + self.condition.notifyAll() + # if self.status == "RUNNING": + # self.__task() + + """ + def __task(self): + if self.rate: + if self.status == "RUNNING": + self.task() + self.timer = Timer(self.rate, self.__task) + self.timer.start() + else: + self.timer.cancel() + """ + + def start(self): + if not self.rate: + return + + if not self.is_running and self.rate > 0: + self.timer = Timer(self.rate * 60, self._run) + self.timer.start() + self.is_running = True + + def _run(self): + self.is_running = False + self.start() + + if self.status == "RUNNING": + self.task() + + def stop(self): + self.timer.cancel() + self.is_running = False + + def run(self): + if not self.rate: + return + + with self.condition: + while self.status != "DESTROYED" and self.status != "ERROR": + if self.status == "RUNNING": + self.task() + + self.condition.wait(self.rate) + else: + self.condition.wait() diff --git a/synergy/common/serializer.py b/synergy/common/serializer.py new file mode 100644 index 0000000..d7ca473 --- /dev/null +++ b/synergy/common/serializer.py @@ -0,0 +1,165 @@ +try: + import oslo_messaging +except ImportError: + import oslo.messaging as oslo_messaging + +from synergy.common import context as ctx +from synergy.common import log as logging +from synergy.common import utils + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +LOG = logging.getLogger(__name__) + + +class SynergyObject(object): + """Base class and object factory. + + This forms the base of all objects that can be remoted or instantiated + via RPC. Simply defining a class that inherits from this base class + will make it remotely instantiatable. Objects should implement the + necessary "get" classmethod routines as well as "set" object methods + as appropriate. + """ + VERSION = "1.0" + + def __init__(self, name=None): + self.attributes = {} + + if name: + self.attributes["name"] = name + + def getName(self): + return self.attributes["name"] + + def setName(self, name): + self.attributes["name"] = name + + def get(self, field=None): + return self.attributes.get(field, None) + + def set(self, field, value): + self.attributes[field] = value + + def setContext(self, context): + self.context = context + + def setAttributes(self, attributes): + if attributes: + self.attributes = attributes + + @classmethod + def deserialize(cls, context, entity): + if "synergy_object.namespace" not in entity: + raise Exception("synergy_object.namespace nof defined!") + + if "synergy_object.name" not in entity: + raise Exception("synergy_object.name nof defined!") + + if "synergy_object.version" not in entity: + raise Exception("synergy_object.version nof defined!") + + if entity["synergy_object.namespace"] != 'synergy': + raise Exception("unsupported object objtype='%s.%s" + % (entity["synergy_object.namespace"], + entity["synergy_object.name"])) + + objName = entity['synergy_object.name'] + # objVer = entity['synergy_object.version'] + objClass = utils.import_class(objName) + + # objInstance = objClass(context=context, data=entity) + + objInstance = objClass(name=None) + objInstance.setContext(context) + objInstance.setAttributes(entity) + + return objInstance + + def serialize(self): + name = self.__class__.__module__ + "." + self.__class__.__name__ + self.attributes['synergy_object.name'] = name + self.attributes['synergy_object.version'] = self.VERSION + self.attributes['synergy_object.namespace'] = 'synergy' + + return self.attributes + + def log(self): + for key, value in self.attributes.items(): + LOG.info("%s = %s" % (key, value)) + + +class SynergySerializer(oslo_messaging.Serializer): + def __init__(self): + super(oslo_messaging.Serializer, self).__init__() + + def serialize_entity(self, context, entity): + if not entity: + return entity + + if isinstance(entity, SynergyObject): + entity = entity.serialize() + elif isinstance(entity, dict): + result = {} + + for key, value in entity.items(): + result[key] = self.serialize_entity(context, value) + + entity = result + + return entity + + def deserialize_entity(self, context, entity): + if isinstance(entity, dict): + if 'synergy_object.name' in entity: + entity = SynergyObject.deserialize(context, entity) + else: + result = {} + + for key, value in entity.items(): + result[key] = self.deserialize_entity(context, value) + + entity = result + + return entity + + def serialize_context(self, context): + return context.toDict() + + def deserialize_context(self, context): + return ctx.RequestContext.fromDict(context) + + +class RequestContextSerializer(oslo_messaging.Serializer): + def __init__(self): + pass + + def serialize_entity(self, context, entity): + return entity + + def deserialize_entity(self, context, entity): + return entity + + def serialize_context(self, context): + return context.toDict() + + def deserialize_context(self, context): + return ctx.RequestContext.fromDict(context) diff --git a/synergy/common/service.py b/synergy/common/service.py new file mode 100644 index 0000000..5ee03b9 --- /dev/null +++ b/synergy/common/service.py @@ -0,0 +1,68 @@ +import os +import signal +import sys + +try: + from oslo_config import cfg +except ImportError: + from oslo.config import cfg + +from synergy.common import log as logging + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + +CONF = cfg.CONF +LOG = logging.getLogger(__name__) +SIGTERM_SENT = False + + +class Service(object): + def __init__(self, name): + self.name = name + signal.signal(signal.SIGTERM, self.sigterm_handler) + signal.signal(signal.SIGINT, self.sigterm_handler) + + def sigterm_handler(self, signum, frame): + global SIGTERM_SENT + if not SIGTERM_SENT: + LOG.info("Shutting down %s" % self.name) + SIGTERM_SENT = True + self.stop() + os.killpg(0, signal.SIGTERM) + + sys.exit() + + def getName(self): + return self.name + + def start(self): + pass + + def stop(self): + pass + + def wait(self): + pass + + def restart(self): + # Reload config files and restart service + CONF.reload_config_files() + self.stop() + self.start() diff --git a/synergy/common/utils.py b/synergy/common/utils.py new file mode 100644 index 0000000..2567894 --- /dev/null +++ b/synergy/common/utils.py @@ -0,0 +1,33 @@ +import sys +import traceback + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +def import_class(import_str): + """Returns a class from a string including module and class.""" + mod_str, _sep, class_str = import_str.rpartition('.') + __import__(mod_str) + + try: + return getattr(sys.modules[mod_str], class_str) + except AttributeError: + raise ImportError('Class %s cannot be found (%s)' % + (class_str, + traceback.format_exception(*sys.exc_info()))) diff --git a/synergy/common/wsgi.py b/synergy/common/wsgi.py new file mode 100644 index 0000000..6582728 --- /dev/null +++ b/synergy/common/wsgi.py @@ -0,0 +1,299 @@ +import errno +import eventlet +import os +import re +import socket +import ssl +import time + +from synergy.common import log as logging +from sys import exc_info +from traceback import format_tb + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + +LOG = logging.getLogger(__name__) + + +class Dispatcher(object): + """Dispatcher + + The main WSGI application. Dispatch the current request to + the functions from above and store the regular expression + captures in the WSGI environment as `myapp.url_args` so that + the functions from above can access the url placeholders. + If nothing matches call the `not_found` function. + """ + + def __init__(self): + self.actions = {} + + def register(self, action, callback): + self.actions[action] = callback + + def unregister(self, action): + del self.actions[action] + + def __call__(self, environ, start_response): + """Call the application can catch exceptions.""" + appiter = None + # just call the application and send the output back unchanged + # but catch exceptions + + path = environ.get('PATH_INFO', '').lstrip('/') + application = None + + for regex, callback in self.actions.items(): + match = re.search(regex, path) + if match is not None: + environ['myapp.url_args'] = match.groups() + application = callback + break + + if application is not None: + try: + self.appiter = callback(environ, start_response) + for item in self.appiter: + yield item + # if an exception occours we get the exception information and + # prepare a traceback we can render + except Exception: + e_type, e_value, tb = exc_info() + traceback = ['Traceback (most recent call last):'] + traceback += format_tb(tb) + traceback.append('%s: %s' % (e_type.__name__, e_value)) + # we might have not a stated response by now. + # Try to start one with the status + # code 500 or ignore an raised exception if the application + # already started one. + try: + start_response("500 INTERNAL SERVER ERROR", + [('Content-Type', 'text/plain')]) + except Exception: + pass + yield '\n'.join(traceback) + + # wsgi applications might have a close function. + # If it exists it *must* be called. + if hasattr(appiter, 'close'): + self.appiter.close() + else: + """Called if no applations matches.""" + try: + start_response("404 NOT FOUND", + [('Content-Type', 'text/plain')]) + except Exception: + pass + yield "Not Found" + + +class WSGILog(object): + """A thin wrapper that responds to `write` and logs.""" + + def __init__(self, logger, level=20): + self.logger = logger + self.level = level + + def write(self, msg): + self.logger.log(self.level, msg.rstrip()) + + +class Server(object): + """Server class to manage multiple WSGI sockets and applications.""" + + def __init__(self, name, host_name, host_port=8051, threads=1000, + application=None, use_ssl=False, ssl_ca_file=None, + ssl_cert_file=None, ssl_key_file=None, max_header_line=16384, + retry_until_window=30, tcp_keepidle=600, backlog=4096): + + """Parameters + + name: the server's name + host_name: the host's name + host_port: + application: + backlog: number of backlog requests to configure the socket with + tcp_keepidle: sets the value of TCP_KEEPIDLE in seconds for each server + socket. Not supported on OS X + retry_until_window: number of seconds to keep retrying to listen + max_header_line: max header line to accommodate large tokens + use_ssl: enable SSL on the API server + ssl_ca_file: CA certificate file to use to verify connecting clients + ssl_cert_file: the certificate file + ssl_key_file: the private key file + """ + + # Raise the default from 8192 to accommodate large tokens + eventlet.wsgi.MAX_HEADER_LINE = max_header_line + + self.name = name + self.host_name = host_name + self.host_port = host_port + self.application = application + self.threads = threads + self.socket = None + self.use_ssl = use_ssl + self.tcp_keepidle = tcp_keepidle + self.backlog = backlog + self.retry_until_window = retry_until_window + self.running = False + self.dispatcher = Dispatcher() + + if not application: + self.application = self.dispatcher + + if use_ssl: + if not os.path.exists(ssl_cert_file): + raise RuntimeError("Unable to find ssl_cert_file: %s" + % ssl_cert_file) + + if not os.path.exists(ssl_key_file): + raise RuntimeError("Unable to find ssl_key_file : %s" + % ssl_key_file) + + # ssl_ca_file is optional + if ssl_ca_file and not os.path.exists(ssl_ca_file): + raise RuntimeError("Unable to find ssl_ca_file: %s" + % ssl_ca_file) + + self.ssl_kwargs = { + 'server_side': True, + 'certfile': ssl_cert_file, + 'keyfile': ssl_key_file, + 'cert_reqs': ssl.CERT_NONE, + } + + if ssl_ca_file: + self.ssl_kwargs['ca_certs'] = ssl_ca_file + self.ssl_kwargs['cert_reqs'] = ssl.CERT_REQUIRED + + def register(self, action, callback): + self.dispatcher.register(action, callback) + + def unregister(self, action): + self.dispatcher.unregister(action) + + def start(self): + """Run a WSGI server with the given application. + + :param application: The application to be run in the WSGI server + :param port: Port to bind to if none is specified in conf + """ + + pgid = os.getpid() + try: + # NOTE(flaper87): Make sure this process + # runs in its own process group. + os.setpgid(pgid, pgid) + except OSError: + pgid = 0 + + try: + info = socket.getaddrinfo(self.host_name, + self.host_port, + socket.AF_UNSPEC, + socket.SOCK_STREAM)[0] + family = info[0] + bind_addr = info[-1] + except Exception as ex: + LOG.error("Unable to listen on %s:%s: %s" + % (self.host_name, self.host_port, ex)) + raise ex + + retry_until = time.time() + self.retry_until_window + exception = None + + while not self.socket and time.time() < retry_until: + try: + self.socket = eventlet.listen(bind_addr, + backlog=self.backlog, + family=family) + if self.use_ssl: + self.socket = ssl.wrap_socket(self.socket, + **self.ssl_kwargs) + + if self.use_ssl: + ssl.wrap_socket(self.sock, **self.ssl_kwarg) + + except socket.error as ex: + exception = ex + LOG.error("Unable to listen on %s:%s: %s" + % (self.host_name, self.host_port, ex)) + + if ex.errno == errno.EADDRINUSE: + retry_until = 0 + eventlet.sleep(0.1) + break + + if exception is not None: + raise exception + + if not self.socket: + raise RuntimeError("Could not bind to %s:%s after trying for %d s" + % (self.host_host, self.host_port, + self.retry_until_window)) + + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + + # sockets can hang around forever without keepalive + self.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'): + self.socket.setsockopt(socket.IPPROTO_TCP, + socket.TCP_KEEPIDLE, + self.tcp_keepidle) + + os.umask(0o27) # ensure files are created with the correct privileges + + self.pool = eventlet.GreenPool(self.threads) + self.pool.spawn_n(self._single_run, self.application, self.socket) + + self.running = True + + def isRunning(self): + return self.running + + def stop(self): + LOG.info("shutting down: requests left: %s", self.pool.running()) + self.running = False + self.pool.resize(0) + # self.pool.waitall() + + if self.socket: + eventlet.greenio.shutdown_safe(self.socket) + self.socket.close() + + self.running = False + + def wait(self): + """Wait until all servers have completed running""" + try: + self.pool.waitall() + except KeyboardInterrupt: + pass + + def _single_run(self, application, sock): + """Start a WSGI server in a new green thread.""" + LOG.info("Starting single process server") + eventlet.wsgi.server(sock, application, + custom_pool=self.pool, + log=WSGILog(LOG), + debug=False) diff --git a/synergy/examples/__init__.py b/synergy/examples/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/synergy/examples/timer_manager.py b/synergy/examples/timer_manager.py new file mode 100644 index 0000000..f6f28d4 --- /dev/null +++ b/synergy/examples/timer_manager.py @@ -0,0 +1,44 @@ +import time + +from synergy.common import log as logging +from synergy.common.manager import Manager + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + +LOG = logging.getLogger(__name__) + + +class TimerManager(Manager): + + def __init__(self): + Manager.__init__(self, name="TimerManager") + + def setup(self): + LOG.info("%s setup invoked!" % (self.name)) + + def execute(self, cmd): + LOG.info("%s execute invoked!" % (self.name)) + LOG.info("command name=%s" % (cmd.getName())) + + def destroy(self): + LOG.info("%s destroy invoked!" % (self.name)) + + def task(self): + localtime = time.asctime(time.localtime(time.time())) + LOG.info("Local current time: %s" % (localtime)) diff --git a/synergy/service.py b/synergy/service.py new file mode 100644 index 0000000..2a9b9f1 --- /dev/null +++ b/synergy/service.py @@ -0,0 +1,476 @@ +import eventlet +import json +import sys + +from cgi import escape +from cgi import parse_qs +from pkg_resources import iter_entry_points +from synergy.common import config +from synergy.common import log as logging +from synergy.common import serializer +from synergy.common import service +from synergy.common import wsgi + +try: + from oslo_config import cfg +except ImportError: + from oslo.config import cfg + + +__author__ = "Lisa Zangrando" +__email__ = "lisa.zangrando[AT]pd.infn.it" +__copyright__ = """Copyright (c) 2015 INFN - INDIGO-DataCloud +All Rights Reserved + +Licensed under the Apache License, Version 2.0; +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.""" + + +CONF = cfg.CONF +LOG = None +MANAGER_ENTRY_POINT = "synergy.managers" # used to discover Synergy managers + + +class ManagerRPC(object): + + def __init__(self, managers): + self.managers = managers + + def list(self, ctx, **args): + result = [] + + for name, manager in self.managers.items(): + result.append(name) + + return result + + def start(self, ctx, **args): + manager_name = args.get("arg").get("manager", None) + result = {} + + for name, manager in self.managers.items(): + if manager.getStatus() == "ACTIVE" \ + and (not manager_name or manager_name == name): + LOG.info("starting the %s manager" % (name)) + try: + # self.managers[name].start() + self.managers[name].setStatus("RUNNING") + LOG.info("%s manager started" % (name)) + + result[name] = manager.getStatus() + except Exception as ex: + self.managers[name].setStatus("ERROR") + LOG.error("error occurred during the manager start-up %s" + % (ex)) + + result[name] = manager.getStatus() + pass + + return result + + def stop(self, ctx, **args): + manager_name = args.get("arg").get("manager", None) + result = {} + + for name, manager in self.managers.items(): + if manager.getStatus() == "RUNNING" \ + and (not manager_name or manager_name == name): + LOG.info("stopping the %s manager" % (name)) + try: + # self.managers[name].stop() + self.managers[name].setStatus("ACTIVE") + LOG.info("%s manager stopped" % (name)) + + result[name] = manager.getStatus() + except Exception as ex: + self.managers[name].setStatus("ERROR") + LOG.error("error occurred during the manager stop %s" + % (ex)) + + result[name] = manager.getStatus() + pass + + return result + + def execute(self, ctx, **args): + manager_name = args.get("arg").get("manager", None) + command = args.get("arg").get("command", None) + result = {} + + if not manager_name: + result["error"] = "manager name not defined!" + + if not command: + result["error"] = "command not defined!" + + if manager_name in self.managers: + manager = self.managers[manager_name] + manager.execute(cmd=command) + result["command"] = "OK" + + return result + + def status(self, ctx, **args): + manager_name = args.get("arg").get("manager", None) + result = {} + + for name, manager in self.managers.items(): + if not manager_name or manager_name == name: + result[name] = manager.getStatus() + + return result + + +class Synergy(service.Service): + """Service object for binaries running on hosts. + + A service takes a manager and enables rpc by listening to queues based + on topic. It also periodically runs tasks on the manager and reports + it state to the database services table. + """ + + def __init__(self, *args, **kwargs): + super(Synergy, self).__init__("Synergy") + + self.managers = {} + + for entry in iter_entry_points(MANAGER_ENTRY_POINT): + LOG.info("loading manager %r", entry.name) + + try: + """ + found = False + + try: + CONF.get(entry.name) + found = True + except Exception as ex: + LOG.info("missing section [%s] in synergy.conf for manager" + " %r: using the default values" + % (entry.name, entry.name)) + """ + + CONF.register_opts(config.manager_opts, group=entry.name) + + manager_conf = CONF.get(entry.name) + manager_class = entry.load() + + manager_obj = manager_class(*args, **kwargs) + LOG.info("manager instance %r created!", entry.name) + + manager_obj.setAutoStart(manager_conf.autostart) + manager_obj.setRate(manager_conf.rate) + + self.managers[manager_obj.getName()] = manager_obj + + CONF.register_opts(manager_obj.getOptions(), group=entry.name) + except Exception as ex: + LOG.error("Exception has occured", exc_info=1) + + LOG.error("manager %r instantiation error: %s" + % (entry.name, ex)) + self.managers[manager_obj.getName()].setStatus("ERROR") + + raise Exception("manager %r instantiation error: %s" + % (entry.name, ex)) + + for name, manager in self.managers.items(): + manager.managers = self.managers + + try: + manager.setup() + manager.setStatus("ACTIVE") + + LOG.info("manager '%s' initialized!" % (manager.getName())) + except Exception as ex: + LOG.error("manager '%s' instantiation error: %s" % (name, ex)) + self.managers[manager.getName()].setStatus("ERROR") + raise ex + + self.saved_args, self.saved_kwargs = args, kwargs + + def listManagers(self, environ, start_response): + result = [] + + for name, manager in self.managers.items(): + result.append(name) + + start_response("200 OK", [("Content-Type", "text/html")]) + return ["%s" % json.dumps(result)] + + def getManagerStatus(self, environ, start_response): + manager_list = None + result = {} + + query = environ.get("QUERY_STRING", None) + + if query: + parameters = parse_qs(query) + + if "manager" in parameters: + if isinstance(parameters['manager'], (list, tuple)): + manager_list = parameters['manager'] + else: + manager_list = [parameters['manager']] + + for manager in manager_list: + escape(manager) + + for name, manager in self.managers.items(): + if not manager_list or name in manager_list: + result[name] = manager.getStatus() + + if manager_list and len(manager_list) == 1 and len(result) == 0: + start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) + return ["manager %r not found!" % manager_list[0]] + else: + start_response("200 OK", [("Content-Type", "text/html")]) + return ["%s" % json.dumps(result)] + + def executeCommand(self, environ, start_response): + manager_name = None + command = None + + synergySerializer = serializer.SynergySerializer() + query = environ.get("QUERY_STRING", None) + # LOG.info("QUERY_STRING %s" % query) + if query: + parameters = parse_qs(query) + + if "manager" in parameters: + manager_name = escape(parameters['manager'][0]) + + if "command" in parameters: + command_string = escape(parameters['command'][0]) + command_string = command_string.replace("'", "\"") + entity = json.loads(command_string) + command = synergySerializer.deserialize_entity(context=None, + entity=entity) + + if not query or not manager_name or not command: + start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) + return ["wrong query"] + + if manager_name in self.managers: + manager = self.managers[manager_name] + try: + manager.execute(cmd=command) + result = synergySerializer.serialize_entity(context=None, + entity=command) + # LOG.info("command result %s" % result) + + start_response("200 OK", [("Content-Type", "text/html")]) + return ["%s" % json.dumps(result)] + except Exception as ex: + LOG.info("executeCommand error: %s" % ex) + start_response("404 NOT FOUND", + [("Content-Type", "text/plain")]) + return ["error: %s" % ex] + else: + start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) + return ["manager %r not found!" % manager_name] + + def startManager(self, environ, start_response): + manager_list = None + result = {} + + # synergySerializer = serializer.SynergySerializer() + query = environ.get("QUERY_STRING", None) + + if query: + parameters = parse_qs(query) + + if "manager" in parameters: + if isinstance(parameters['manager'], (list, tuple)): + manager_list = parameters['manager'] + else: + manager_list = [parameters['manager']] + + for manager in manager_list: + escape(manager) + + for name, manager in self.managers.items(): + if not manager_list or name in manager_list: + result[name] = {} + + if manager.getStatus() == "ACTIVE": + LOG.info("starting the %r manager" % (name)) + try: + # self.managers[name].start() + self.managers[name].setStatus("RUNNING") + LOG.info("%r manager started!" % (name)) + + result[name]["message"] = "started successfully" + except Exception as ex: + self.managers[name].setStatus("ERROR") + LOG.error("error occurred during the manager start-up" + "%s" % (ex)) + + result[name]["message"] = "ERROR: %s" % ex + pass + else: + result[name]["message"] = "WARN: already started" + + result[name]["status"] = manager.getStatus() + + if manager_list and len(manager_list) == 1 and len(result) == 0: + start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) + return ["manager %r not found!" % manager_list[0]] + else: + start_response("200 OK", [("Content-Type", "text/html")]) + return ["%s" % json.dumps(result)] + + def stopManager(self, environ, start_response): + manager_list = None + result = {} + + query = environ.get("QUERY_STRING", None) + + if query: + parameters = parse_qs(query) + + if "manager" in parameters: + if isinstance(parameters['manager'], (list, tuple)): + manager_list = parameters['manager'] + else: + manager_list = [parameters['manager']] + + for manager in manager_list: + escape(manager) + + for name, manager in self.managers.items(): + if not manager_list or name in manager_list: + result[name] = {} + + if manager.getStatus() == "RUNNING": + LOG.info("stopping the %r manager" % (name)) + try: + # self.managers[name].stop() + self.managers[name].setStatus("ACTIVE") + LOG.info("%r manager stopped!" % (name)) + + result[name]["message"] = "stopped successfully" + except Exception as ex: + self.managers[name].setStatus("ERROR") + LOG.error("error occurred during the manager stop: %s" + % (ex)) + + result[name]["message"] = "ERROR: %s" % ex + pass + else: + result[name]["message"] = "WARN: already stopped" + + result[name]["status"] = manager.getStatus() + + if manager_list and len(manager_list) == 1 and len(result) == 0: + start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) + return ["manager %r not found!" % manager_list[0]] + else: + start_response("200 OK", [("Content-Type", "text/html")]) + return ["%s" % json.dumps(result)] + + def start(self): + self.model_disconnected = False + + for name, manager in self.managers.items(): + if manager.getStatus() != "ERROR" and manager.isAutoStart(): + try: + LOG.info("starting the %r manager" % (name)) + manager.start() + manager.setStatus("RUNNING") + LOG.info("%r manager started! (rate=%s min)" + % (name, manager.getRate())) + except Exception as ex: + LOG.error("error occurred during the manager start %s" + % (ex)) + manager.setStatus("ERROR") + raise ex + + self.wsgi_server = wsgi.Server( + name="WSGI server", + host_name=CONF.WSGI.host, + host_port=CONF.WSGI.port, + threads=CONF.WSGI.threads, + use_ssl=CONF.WSGI.use_ssl, + ssl_ca_file=CONF.WSGI.ssl_ca_file, + ssl_cert_file=CONF.WSGI.ssl_cert_file, + ssl_key_file=CONF.WSGI.ssl_key_file, + max_header_line=CONF.WSGI.max_header_line, + retry_until_window=CONF.WSGI.retry_until_window, + tcp_keepidle=CONF.WSGI.tcp_keepidle, + backlog=CONF.WSGI.backlog) + + self.wsgi_server.register(r'^$', wsgi.index) + self.wsgi_server.register(r'synergy/list', self.listManagers) + self.wsgi_server.register(r'synergy/status', self.getManagerStatus) + self.wsgi_server.register(r'synergy/execute', self.executeCommand) + self.wsgi_server.register(r'synergy/start', self.startManager) + self.wsgi_server.register(r'synergy/stop', self.stopManager) + self.wsgi_server.start() + + LOG.info("STARTED!") + self.wsgi_server.wait() + + def kill(self): + """Destroy the service object in the datastore.""" + LOG.warn("killing service") + self.stop() + LOG.warn("Service killed") + + def stop(self): + for name, manager in self.managers.items(): + LOG.info("destroying the %s manager" % (name)) + try: + manager.setStatus("DESTROYED") + manager.destroy() + # manager.join() + # LOG.info("%s manager destroyed" % (name)) + except Exception as ex: + manager.setStatus("ERROR") + LOG.error("error occurred during the manager destruction: %s" + % ex) + + if self.wsgi_server: + self.wsgi_server.stop() + + LOG.info("STOPPED!") + + +def main(): + try: + eventlet.monkey_patch(os=False) + + # the configuration will be into the cfg.CONF global data structure + config.parse_args(args=sys.argv[1:], + default_config_files=["/etc/synergy/synergy.conf"]) + + if not cfg.CONF.config_file: + sys.exit("ERROR: Unable to find configuration file via the " + "default search paths (~/.synergy/, ~/, /etc/synergy/" + ", /etc/) and the '--config-file' option!") + + global LOG + # LOG = logging.getLogger(None) + + LOG = logging.getLogger(__name__) + LOG.info("Starting Synergy...") + + # set session ID to this process so we can kill group in sigterm + # os.setsid() + + server = Synergy() + server.start() + + LOG.info("Synergy started") + except Exception as ex: + LOG.error("unrecoverable error: %s" % ex) diff --git a/synergy/tests/__init__.py b/synergy/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/synergy/tests/base.py b/synergy/tests/base.py new file mode 100644 index 0000000..bc2d9c8 --- /dev/null +++ b/synergy/tests/base.py @@ -0,0 +1,18 @@ +# 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 oslotest import base + + +class TestCase(base.BaseTestCase): + + """Test case base class for all unit tests.""" diff --git a/synergy/tests/test_manager.py b/synergy/tests/test_manager.py new file mode 100644 index 0000000..e02e79c --- /dev/null +++ b/synergy/tests/test_manager.py @@ -0,0 +1,27 @@ +# 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. + + +from synergy.common.manager import Manager +from synergy.tests import base + + +class TestManager(base.TestCase): + + def setUp(self): + super(TestManager, self).setUp() + self.manager = Manager(name="dummy_manager") + + def test_name(self): + self.assertEqual(self.manager.getName(), "dummy_manager") diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..21a7e3b --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,14 @@ +# The order of packages is significant, because pip processes them in the order +# of appearance. Changing the order has an impact on the overall integration +# process, which may cause wedges in the gate later. + +hacking<0.11,>=0.10.0 + +coverage>=3.6 +python-subunit>=0.0.18 +sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 +oslosphinx>=2.5.0 # Apache-2.0 +oslotest>=1.10.0 # Apache-2.0 +testrepository>=0.0.18 +testscenarios>=0.4 +testtools>=1.4.0 diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..a1cec90 --- /dev/null +++ b/tox.ini @@ -0,0 +1,60 @@ +[tox] +minversion = 2.0 +envlist = py27-constraints,pep8-constraints +skipsdist = True + +[testenv] +usedevelop = True +install_command = + constraints: {[testenv:common-constraints]install_command} + pip install -U {opts} {packages} +setenv = + VIRTUAL_ENV={envdir} +deps = -r{toxinidir}/test-requirements.txt +commands = python setup.py test --slowest --testr-args='{posargs}' + +[testenv:common-constraints] +install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages} + +[testenv:pep8] +commands = flake8 {posargs} + +[testenv:pep8-constraints] +install_command = {[testenv:common-constraints]install_command} +commands = flake8 {posargs} + +[testenv:venv] +commands = {posargs} + +[testenv:venv-constraints] +install_command = {[testenv:common-constraints]install_command} +commands = {posargs} + +[testenv:cover] +commands = python setup.py test --coverage --testr-args='{posargs}' + +[testenv:cover-constraints] +install_command = {[testenv:common-constraints]install_command} +commands = python setup.py test --coverage --testr-args='{posargs}' + +[testenv:docs] +commands = python setup.py build_sphinx + +[testenv:docs-constraints] +install_command = {[testenv:common-constraints]install_command} +commands = python setup.py build_sphinx + +[testenv:debug] +commands = oslo_debug_helper {posargs} + +[testenv:debug-constraints] +install_command = {[testenv:common-constraints]install_command} +commands = oslo_debug_helper {posargs} + +[flake8] +# E123, E125 skipped as they are invalid PEP-8. + +show-source = True +ignore = E123,E125 +builtins = _ +exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build