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
This commit is contained in:
Vincent Llorens 2015-12-18 12:09:20 +01:00 committed by Lisa Zangrando
parent b7cc531069
commit c2588f9972
61 changed files with 2942 additions and 0 deletions

7
.coveragerc Normal file
View File

@ -0,0 +1,7 @@
[run]
branch = True
source = synergy
omit = synergy/openstack/*
[report]
ignore_errors = True

7
.testr.conf Normal file
View File

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

3
CONTRIBUTING.rst Normal file
View File

@ -0,0 +1,3 @@
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/synergy-service

4
HACKING.rst Normal file
View File

@ -0,0 +1,4 @@
Synergy Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

176
LICENSE Normal file
View File

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

6
MANIFEST.in Normal file
View File

@ -0,0 +1,6 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
global-exclude *.pyc

40
README.rst Normal file
View File

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

2
babel.cfg Normal file
View File

@ -0,0 +1,2 @@
[python: **.py]

10
bin/synergy Normal file
View File

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

23
config/synergy.conf Normal file
View File

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

74
doc/source/conf.py Executable file
View File

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

View File

@ -0,0 +1,4 @@
============
Contributing
============
.. include:: ../../CONTRIBUTING.rst

24
doc/source/index.rst Normal file
View File

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

View File

@ -0,0 +1,5 @@
============
Installation
============
TODO

1
doc/source/readme.rst Normal file
View File

@ -0,0 +1 @@
.. include:: ../../README.rst

7
doc/source/usage.rst Normal file
View File

@ -0,0 +1,7 @@
========
Usage
========
To use synergy in a project::
import synergy

108
packaging/README.md Normal file
View File

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

View File

@ -0,0 +1,5 @@
python-synergy-service (0.1-1) unstable; urgency=low
* Initial release
-- Vincent Llorens <vincent.llorens@cc.in2p3.fr> Mon, 15 Feb 2016 14:02:30 +0000

1
packaging/debian/compat Normal file
View File

@ -0,0 +1 @@
9

25
packaging/debian/control Normal file
View File

@ -0,0 +1,25 @@
Source: python-synergy-service
Section: contrib/python
Priority: optional
Maintainer: Vincent Llorens <vincent.llorens@cc.in2p3.fr>
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.

View File

@ -0,0 +1,21 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: synergy-service
Source: <https://git.launchpad.net/synergy-service>
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'

1
packaging/debian/docs Normal file
View File

@ -0,0 +1 @@
README.rst

View File

@ -0,0 +1,5 @@
etc/synergy
var/lib/synergy
var/log/synergy
var/run/synergy
var/lock/synergy

View File

@ -0,0 +1,2 @@
config/synergy.conf /etc/synergy
scripts/synergy.service /lib/systemd/system

View File

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

View File

@ -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:
# * <postinst> `configure' <most-recently-configured-version>
# * <old-postinst> `abort-upgrade' <new version>
# * <conflictor's-postinst> `abort-remove' `in-favour' <package>
# <new-version>
# * <postinst> `abort-remove'
# * <deconfigured's-postinst> `abort-deconfigure' `in-favour'
# <failed-install-package> <version> `removing'
# <conflicting-package> <version>
# 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

View File

@ -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:
# * <postrm> `remove'
# * <postrm> `purge'
# * <old-postrm> `upgrade' <new-version>
# * <new-postrm> `failed-upgrade' <old-version>
# * <new-postrm> `abort-install'
# * <new-postrm> `abort-install' <old-version>
# * <new-postrm> `abort-upgrade' <old-version>
# * <disappearer's-postrm> `disappear' <overwriter>
# <overwriter-version>
# 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

View File

@ -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:
# * <new-preinst> `install'
# * <new-preinst> `install' <old-version>
# * <new-preinst> `upgrade' <old-version>
# * <old-preinst> `abort-upgrade' <new-version>
# 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

View File

@ -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: <Enter a short description of the software>
# Description: <Enter a long description of the software>
# <...>
# <...>
### END INIT INFO
# Author: Vincent Llorens <vincent.llorens@cc.in2p3.fr>
# 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
:

View File

@ -0,0 +1,19 @@
description "Synergy service"
author "Lisa Zangrando <lisa.zangrando@pd.infn.it>"
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

31
packaging/debian/rules Executable file
View File

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

View File

@ -0,0 +1 @@
3.0 (quilt)

View File

@ -0,0 +1 @@
PKG_VERSION=0.1

View File

@ -0,0 +1,13 @@
FROM centos:7
MAINTAINER Vincent Llorens <vincent.llorens@cc.in2p3.fr>
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

View File

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

View File

@ -0,0 +1,17 @@
FROM ubuntu:14.04
MAINTAINER Vincent Llorens <vincent.llorens@cc.in2p3.fr>
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

View File

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

View File

@ -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 <vincent.llorens@cc.in2p3.fr>
- WIP RPM release

9
requirements.txt Normal file
View File

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

12
scripts/synergy.service Normal file
View File

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

50
setup.cfg Normal file
View File

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

27
setup.py Normal file
View File

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

0
synergy/__init__.py Normal file
View File

View File

60
synergy/common/command.py Normal file
View File

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

103
synergy/common/config.py Normal file
View File

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

172
synergy/common/context.py Normal file
View File

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

68
synergy/common/log.py Normal file
View File

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

147
synergy/common/manager.py Normal file
View File

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

View File

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

68
synergy/common/service.py Normal file
View File

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

33
synergy/common/utils.py Normal file
View File

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

299
synergy/common/wsgi.py Normal file
View File

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

View File

View File

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

476
synergy/service.py Normal file
View File

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

View File

18
synergy/tests/base.py Normal file
View File

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

View File

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

14
test-requirements.txt Normal file
View File

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

60
tox.ini Normal file
View File

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