Hello community,

here is the log from the commit of package python-oslo.service for 
openSUSE:Factory checked in at 2015-12-21 12:03:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-oslo.service (Old)
 and      /work/SRC/openSUSE:Factory/.python-oslo.service.new (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-oslo.service"

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-oslo.service/python-oslo.service.changes  
2015-10-30 13:44:00.000000000 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-oslo.service.new/python-oslo.service.changes 
    2015-12-21 12:03:55.000000000 +0100
@@ -1,0 +2,11 @@
+Thu Nov 19 18:25:51 UTC 2015 - [email protected]
+
+- update to 0.12.0:
+  * Document graceful_shutdown_timeout config option
+  * Remove py26 env from test list
+  * Added config option graceful_shutdown_timeout
+  * Add docstring for LoopingCallBase._start()
+  * Add doc8 to py27 tox env and fix raised issues
+  * Document termination of children on SIGHUP
+
+-------------------------------------------------------------------

Old:
----
  oslo.service-0.10.0.tar.gz

New:
----
  oslo.service-0.12.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-oslo.service.spec ++++++
--- /var/tmp/diff_new_pack.RGMNnS/_old  2015-12-21 12:03:56.000000000 +0100
+++ /var/tmp/diff_new_pack.RGMNnS/_new  2015-12-21 12:03:56.000000000 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           python-oslo.service
-Version:        0.10.0
+Version:        0.12.0
 Release:        0
 Summary:        Oslo service library
 License:        Apache-2.0
@@ -36,10 +36,10 @@
 Requires:       python-greenlet >= 0.3.2
 Requires:       python-monotonic >= 0.3
 Requires:       python-oslo.concurrency >= 2.3.0
-Requires:       python-oslo.config >= 2.3.0
+Requires:       python-oslo.config >= 2.6.0
 Requires:       python-oslo.i18n >= 1.5.0
 Requires:       python-oslo.log >= 1.8.0
-Requires:       python-oslo.utils >= 2.0.0
+Requires:       python-oslo.utils >= 2.4.0
 Requires:       python-six >= 1.9.0
 
 BuildRoot:      %{_tmppath}/%{name}-%{version}-build

++++++ oslo.service-0.10.0.tar.gz -> oslo.service-0.12.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/AUTHORS 
new/oslo.service-0.12.0/AUTHORS
--- old/oslo.service-0.10.0/AUTHORS     2015-10-12 17:38:47.000000000 +0200
+++ new/oslo.service-0.12.0/AUTHORS     2015-11-10 00:02:48.000000000 +0100
@@ -46,6 +46,7 @@
 Jeff Peeler <[email protected]>
 Joe Gordon <[email protected]>
 Joe Heck <[email protected]>
+John L. Villalovos <[email protected]>
 Joshua Harlow <[email protected]>
 Joshua Harlow <[email protected]>
 Julien Danjou <[email protected]>
@@ -84,6 +85,7 @@
 apporc <[email protected]>
 fujioka yuuichi <[email protected]>
 gongysh <[email protected]>
+lin-hua-cheng <[email protected]>
 liu-sheng <[email protected]>
 liyingjun <[email protected]>
 ravikumar-venkatesan <[email protected]>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/ChangeLog 
new/oslo.service-0.12.0/ChangeLog
--- old/oslo.service-0.10.0/ChangeLog   2015-10-12 17:38:47.000000000 +0200
+++ new/oslo.service-0.12.0/ChangeLog   2015-11-10 00:02:48.000000000 +0100
@@ -1,6 +1,25 @@
 CHANGES
 =======
 
+0.12.0
+------
+
+* Document graceful_shutdown_timeout config option
+* Remove py26 env from test list
+* Added config option graceful_shutdown_timeout
+* Updated from global requirements
+* Add docstring for LoopingCallBase._start()
+* Updated from global requirements
+
+0.11.0
+------
+
+* Updated from global requirements
+* Add doc8 to py27 tox env and fix raised issues
+* Document termination of children on SIGHUP
+* Updated from global requirements
+* Updated from global requirements
+
 0.10.0
 ------
 
@@ -16,6 +35,7 @@
 * Expand README and clean up intro to sphinx docs
 * Add shields.io version/downloads links/badges into README.rst
 * add auto-generated docs for config options
+* Move backoff looping call from IPA to oslo.service
 * Change ignore-errors to ignore_errors
 * Fix the home-page value in setup.cfg
 * WSGI module was corrected
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/PKG-INFO 
new/oslo.service-0.12.0/PKG-INFO
--- old/oslo.service-0.10.0/PKG-INFO    2015-10-12 17:38:47.000000000 +0200
+++ new/oslo.service-0.12.0/PKG-INFO    2015-11-10 00:02:49.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: oslo.service
-Version: 0.10.0
+Version: 0.12.0
 Summary: oslo.service library
 Home-page: http://wiki.openstack.org/wiki/Oslo#oslo.service
 Author: OpenStack
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/doc/source/usage.rst 
new/oslo.service-0.12.0/doc/source/usage.rst
--- old/oslo.service-0.10.0/doc/source/usage.rst        2015-10-12 
17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/doc/source/usage.rst        2015-11-10 
00:02:06.000000000 +0100
@@ -72,23 +72,28 @@
 Launching and controlling services
 ==================================
 
-oslo_service.service module provides tools for launching OpenStack services 
and controlling their lifecycles.
+oslo_service.service module provides tools for launching OpenStack
+services and controlling their lifecycles.
 
-A service is an instance of any class that subclasses 
:py:class:`oslo_service.service.ServiceBase`.
-:py:class:`ServiceBase <oslo_service.service.ServiceBase>` is an abstract 
class that defines an interface every
-service should implement. :py:class:`oslo_service.service.Service` can serve 
as a base for constructing new services.
+A service is an instance of any class that
+subclasses :py:class:`oslo_service.service.ServiceBase`.
+:py:class:`ServiceBase <oslo_service.service.ServiceBase>` is an
+abstract class that defines an interface every
+service should implement. :py:class:`oslo_service.service.Service` can
+serve as a base for constructing new services.
 
 Launchers
 ~~~~~~~~~
 
 oslo_service.service module provides two launchers for running services:
 
-    * :py:class:`oslo_service.service.ServiceLauncher` - used for running one 
or more service in
-      a parent process.
-    * :py:class:`oslo_service.service.ProcessLauncher` - forks a given number 
of workers in which
-      service(s) are then started.
+    * :py:class:`oslo_service.service.ServiceLauncher` - used for
+      running one or more service in a parent process.
+    * :py:class:`oslo_service.service.ProcessLauncher` - forks a given
+      number of workers in which service(s) are then started.
 
-It is possible to initialize whatever launcher is needed and then launch a 
service using it.
+It is possible to initialize whatever launcher is needed and then
+launch a service using it.
 
 ::
 
@@ -104,9 +109,10 @@
     process_launcher = service.ProcessLauncher(CONF, wait_interval=1.0)
     process_launcher.launch_service(service.Service(), workers=2)
 
-Or one can simply call :func:`oslo_service.service.launch` which will 
automatically pick an appropriate launcher
-based on a number of workers that are passed to it (ServiceLauncher in case 
workers=1 or None and ProcessLauncher in
-other case).
+Or one can simply call :func:`oslo_service.service.launch` which will
+automatically pick an appropriate launcher based on a number of workers that
+are passed to it (ServiceLauncher in case workers=1 or None and
+ProcessLauncher in other case).
 
 ::
 
@@ -117,24 +123,36 @@
 
     launcher = service.launch(CONF, service.Service(), workers=3)
 
-*NOTE:* Please be informed that it is highly recommended to use no more than 
one instance of ServiceLauncher and
-ProcessLauncher classes per process.
+*NOTE:* Please be informed that it is highly recommended to use no
+more than one instance of ServiceLauncher and ProcessLauncher classes
+per process.
 
 Signal handling
 ~~~~~~~~~~~~~~~
 
-oslo_service.service provides handlers for such signals as SIGTERM, SIGINT and 
SIGHUP.
+oslo_service.service provides handlers for such signals as SIGTERM, SIGINT
+and SIGHUP.
 
-SIGTERM is used for graceful termination of services. This can allow a server 
to wait for all clients to close
-connections while rejecting new incoming requests. To force instantaneous 
termination SIGINT signal must be sent.
-
-On receiving SIGHUP configuration files are reloaded and a service is being 
reset and started again.Thus, SIGHUP
-can be used for changing config options on the go. To achieve this each 
service should implement a *reset* method
-which actually enforces changes to config options values.
+SIGTERM is used for graceful termination of services. This can allow a
+server to wait for all clients to close connections while rejecting new
+incoming requests. Config option graceful_shutdown_timeout specifies how
+many seconds after receiving a SIGTERM signal, a server should continue
+to run, handling the existing connections. Setting graceful_shutdown_timeout
+to zero means that the server will wait indefinitely until all remaining
+requests have been fully served.
+
+To force instantaneous termination SIGINT signal must be sent.
+
+On receiving SIGHUP configuration files are reloaded and a service
+is being reset and started again. Then all child workers are gracefully
+stopped using SIGTERM and workers with new configuration are
+spawned. Thus, SIGHUP can be used for changing config options on the go.
 
 *NOTE:* SIGHUP is not supported on Windows.
+*NOTE:* Config option graceful_shutdown_timeout is not supported on Windows.
 
-Below is the example of a service with a reset method that allows reloading 
logging options by sending a SIGHUP.
+Below is the example of a service with a reset method that allows reloading
+logging options by sending a SIGHUP.
 
 ::
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/oslo.service.egg-info/PKG-INFO 
new/oslo.service-0.12.0/oslo.service.egg-info/PKG-INFO
--- old/oslo.service-0.10.0/oslo.service.egg-info/PKG-INFO      2015-10-12 
17:38:47.000000000 +0200
+++ new/oslo.service-0.12.0/oslo.service.egg-info/PKG-INFO      2015-11-10 
00:02:48.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: oslo.service
-Version: 0.10.0
+Version: 0.12.0
 Summary: oslo.service library
 Home-page: http://wiki.openstack.org/wiki/Oslo#oslo.service
 Author: OpenStack
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/oslo.service.egg-info/pbr.json 
new/oslo.service-0.12.0/oslo.service.egg-info/pbr.json
--- old/oslo.service-0.10.0/oslo.service.egg-info/pbr.json      2015-10-12 
17:38:47.000000000 +0200
+++ new/oslo.service-0.12.0/oslo.service.egg-info/pbr.json      2015-11-10 
00:02:48.000000000 +0100
@@ -1 +1 @@
-{"git_version": "7ecf3be", "is_release": true}
\ No newline at end of file
+{"is_release": true, "git_version": "ca82d5f"}
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo.service-0.10.0/oslo.service.egg-info/requires.txt 
new/oslo.service-0.12.0/oslo.service.egg-info/requires.txt
--- old/oslo.service-0.10.0/oslo.service.egg-info/requires.txt  2015-10-12 
17:38:47.000000000 +0200
+++ new/oslo.service-0.12.0/oslo.service.egg-info/requires.txt  2015-11-10 
00:02:48.000000000 +0100
@@ -3,9 +3,9 @@
 eventlet>=0.17.4
 greenlet>=0.3.2
 monotonic>=0.3
-oslo.utils>=2.0.0
+oslo.utils!=2.6.0,>=2.4.0
 oslo.concurrency>=2.3.0
-oslo.config>=2.3.0
+oslo.config>=2.6.0
 oslo.log>=1.8.0
 six>=1.9.0
 oslo.i18n>=1.5.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/oslo_service/_options.py 
new/oslo.service-0.12.0/oslo_service/_options.py
--- old/oslo.service-0.10.0/oslo_service/_options.py    2015-10-12 
17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/oslo_service/_options.py    2015-11-10 
00:02:06.000000000 +0100
@@ -39,6 +39,10 @@
                 default=True,
                 help='Enables or disables logging values of all registered '
                      'options when starting a service (at DEBUG level).'),
+    cfg.IntOpt('graceful_shutdown_timeout',
+               default=0,
+               help='Specify a timeout after which a gracefully shutdown '
+                    'server will exit. Zero value means endless wait.'),
 ]
 
 wsgi_opts = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/oslo_service/loopingcall.py 
new/oslo.service-0.12.0/oslo_service/loopingcall.py
--- old/oslo.service-0.10.0/oslo_service/loopingcall.py 2015-10-12 
17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/oslo_service/loopingcall.py 2015-11-10 
00:02:06.000000000 +0100
@@ -15,6 +15,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import random
 import sys
 
 from eventlet import event
@@ -47,6 +48,15 @@
         self.retvalue = retvalue
 
 
+class LoopingCallTimeOut(Exception):
+    """Exception for a timed out LoopingCall.
+
+    The LoopingCall will raise this exception when a timeout is provided
+    and it is exceeded.
+    """
+    pass
+
+
 def _safe_wrapper(f, kind, func_name):
     """Wrapper that calls into wrapped function and logs errors as needed."""
 
@@ -89,6 +99,18 @@
         self._running = False
 
     def _start(self, idle_for, initial_delay=None, stop_on_exception=True):
+        """Start the looping
+
+        :param idle_for: Callable that takes two positional arguments, returns
+                         how long to idle for. The first positional argument is
+                         the last result from the function being looped and the
+                         second positional argument is the time it took to
+                         calculate that result.
+        :param initial_delay: How long to delay before starting the looping.
+                              Value is in seconds.
+        :param stop_on_exception: Whether to stop if an exception occurs.
+        :returns: eventlet event instance
+        """
         if self._thread is not None:
             raise RuntimeError(self._RUN_ONLY_ONE_MESSAGE)
         self._running = True
@@ -195,6 +217,92 @@
                            stop_on_exception=stop_on_exception)
 
 
+class BackOffLoopingCall(LoopingCallBase):
+    """Run a method in a loop with backoff on error.
+
+    The passed in function should return True (no error, return to
+    initial_interval),
+    False (error, start backing off), or raise LoopingCallDone(retvalue=None)
+    (quit looping, return retvalue if set).
+
+    When there is an error, the call will backoff on each failure. The
+    backoff will be equal to double the previous base interval times some
+    jitter. If a backoff would put it over the timeout, it halts immediately,
+    so the call will never take more than timeout, but may and likely will
+    take less time.
+
+    When the function return value is True or False, the interval will be
+    multiplied by a random jitter. If min_jitter or max_jitter is None,
+    there will be no jitter (jitter=1). If min_jitter is below 0.5, the code
+    may not backoff and may increase its retry rate.
+
+    If func constantly returns True, this function will not return.
+
+    To run a func and wait for a call to finish (by raising a LoopingCallDone):
+
+        timer = BackOffLoopingCall(func)
+        response = timer.start().wait()
+
+    :param initial_delay: delay before first running of function
+    :param starting_interval: initial interval in seconds between calls to
+                              function. When an error occurs and then a
+                              success, the interval is returned to
+                              starting_interval
+    :param timeout: time in seconds before a LoopingCallTimeout is raised.
+                    The call will never take longer than timeout, but may quit
+                    before timeout.
+    :param max_interval: The maximum interval between calls during errors
+    :param jitter: Used to vary when calls are actually run to avoid group of
+                   calls all coming at the exact same time. Uses
+                   random.gauss(jitter, 0.1), with jitter as the mean for the
+                   distribution. If set below .5, it can cause the calls to
+                   come more rapidly after each failure.
+    :raises: LoopingCallTimeout if time spent doing error retries would exceed
+             timeout.
+    """
+
+    _RNG = random.SystemRandom()
+    _KIND = 'Dynamic backoff interval looping call'
+    _RUN_ONLY_ONE_MESSAGE = ("A dynamic backoff interval looping call can"
+                             " only run one function at a time")
+
+    def __init__(self, f=None, *args, **kw):
+        super(BackOffLoopingCall, self).__init__(f=f, *args, **kw)
+        self._error_time = 0
+        self._interval = 1
+
+    def start(self, initial_delay=None, starting_interval=1, timeout=300,
+              max_interval=300, jitter=0.75):
+        if self._thread is not None:
+            raise RuntimeError(self._RUN_ONLY_ONE_MESSAGE)
+
+        # Reset any prior state.
+        self._error_time = 0
+        self._interval = starting_interval
+
+        def _idle_for(success, _elapsed):
+            random_jitter = self._RNG.gauss(jitter, 0.1)
+            if success:
+                # Reset error state now that it didn't error...
+                self._interval = starting_interval
+                self._error_time = 0
+                return self._interval * random_jitter
+            else:
+                # Perform backoff
+                self._interval = idle = min(
+                    self._interval * 2 * random_jitter, max_interval)
+                # Don't go over timeout, end early if necessary. If
+                # timeout is 0, keep going.
+                if timeout > 0 and self._error_time + idle > timeout:
+                    raise LoopingCallTimeOut(
+                        _('Looping call timed out after %.02f seconds')
+                        % self._error_time)
+                self._error_time += idle
+                return idle
+
+        return self._start(_idle_for, initial_delay=initial_delay)
+
+
 class RetryDecorator(object):
     """Decorator for retrying a function upon suggested exceptions.
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/oslo_service/service.py 
new/oslo.service-0.12.0/oslo_service/service.py
--- old/oslo.service-0.10.0/oslo_service/service.py     2015-10-12 
17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/oslo_service/service.py     2015-11-10 
00:02:06.000000000 +0100
@@ -71,7 +71,8 @@
 
 
 def _is_sighup_and_daemon(signo):
-    if not (SignalHandler().is_sighup_supported and signo == signal.SIGHUP):
+    if not (SignalHandler().is_signal_supported('SIGHUP') and
+            signo == signal.SIGHUP):
         # Avoid checking if we are a daemon, because the signal isn't
         # SIGHUP.
         return False
@@ -135,7 +136,6 @@
         self.signals_to_name = dict(
             (sigval, name)
             for (name, sigval) in self._signals_by_name.items())
-        self.is_sighup_supported = 'SIGHUP' in self._signals_by_name
         self._signal_handlers = collections.defaultdict(set)
         self.clear()
 
@@ -149,7 +149,7 @@
             self.add_handler(sig, handler)
 
     def add_handler(self, sig, handler):
-        if sig == "SIGHUP" and not self.is_sighup_supported:
+        if not self.is_signal_supported(sig):
             return
         signo = self._signals_by_name[sig]
         self._signal_handlers[signo].add(handler)
@@ -159,6 +159,9 @@
         for handler in self._signal_handlers[signo]:
             handler(signo, frame)
 
+    def is_signal_supported(self, sig_name):
+        return sig_name in self._signals_by_name
+
 
 class Launcher(object):
     """Launch one or more services and wait for them to complete."""
@@ -319,6 +322,7 @@
         self.signal_handler.add_handlers(('SIGTERM', 'SIGHUP'),
                                          self._handle_signal)
         self.signal_handler.add_handler('SIGINT', self._fast_exit)
+        self.signal_handler.add_handler('SIGALRM', self._on_alarm_exit)
 
     def _handle_signal(self, signo, frame):
         """Set signal handlers.
@@ -336,6 +340,11 @@
         LOG.info(_LI('Caught SIGINT signal, instantaneous exiting'))
         os._exit(1)
 
+    def _on_alarm_exit(self, signo, frame):
+        LOG.info(_LI('Graceful shutdown timeout exceeded, '
+                     'instantaneous exiting'))
+        os._exit(1)
+
     def _pipe_watcher(self):
         # This will block until the write end is closed when the parent
         # dies unexpectedly
@@ -531,6 +540,12 @@
         except eventlet.greenlet.GreenletExit:
             LOG.info(_LI("Wait called after thread killed. Cleaning up."))
 
+        # if we are here it means that we try to do gracefull shutdown.
+        # add alarm watching that graceful_shutdown_timeout is not exceeded
+        if (self.conf.graceful_shutdown_timeout and
+                self.signal_handler.is_signal_supported('SIGALRM')):
+            signal.alarm(self.conf.graceful_shutdown_timeout)
+
         self.stop()
 
     def stop(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo.service-0.10.0/oslo_service/tests/eventlet_service.py 
new/oslo.service-0.12.0/oslo_service/tests/eventlet_service.py
--- old/oslo.service-0.10.0/oslo_service/tests/eventlet_service.py      
2015-10-12 17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/oslo_service/tests/eventlet_service.py      
2015-11-10 00:02:06.000000000 +0100
@@ -139,12 +139,7 @@
             launcher.launch_service(self.server)
 
 
-def run():
-    # ProcessLauncher uses cfg.CONF.log_opt_values()
-    # and cfg.CONF.log_opt_values() uses config_file option.
-    # We need to call CONF() to register the --config-file option
-    cfg.CONF()
-
+def run(port_queue=None):
     eventlet.patcher.monkey_patch()
 
     launcher = service.ProcessLauncher(cfg.CONF)
@@ -156,7 +151,8 @@
     server = ServerWrapper(Server(hi_app), workers=3)
     server.launch_with(launcher)
 
-    print('%s' % server.server.socket.getsockname()[1])
+    port = server.server.socket.getsockname()[1]
+    port_queue.put(port)
 
     sys.stdout.flush()
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo.service-0.10.0/oslo_service/tests/test_loopingcall.py 
new/oslo.service-0.12.0/oslo_service/tests/test_loopingcall.py
--- old/oslo.service-0.10.0/oslo_service/tests/test_loopingcall.py      
2015-10-12 17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/oslo_service/tests/test_loopingcall.py      
2015-11-10 00:02:06.000000000 +0100
@@ -235,6 +235,86 @@
         sleep_mock.assert_has_calls([mock.call(3), mock.call(1)])
 
 
+class TestBackOffLoopingCall(test_base.BaseTestCase):
+    @mock.patch('random.SystemRandom.gauss')
+    @mock.patch('eventlet.greenthread.sleep')
+    def test_exponential_backoff(self, sleep_mock, random_mock):
+        def false():
+            return False
+
+        random_mock.return_value = .8
+
+        self.assertRaises(loopingcall.LoopingCallTimeOut,
+                          loopingcall.BackOffLoopingCall(false).start()
+                          .wait)
+
+        expected_times = [mock.call(1.6000000000000001),
+                          mock.call(2.5600000000000005),
+                          mock.call(4.096000000000001),
+                          mock.call(6.5536000000000021),
+                          mock.call(10.485760000000004),
+                          mock.call(16.777216000000006),
+                          mock.call(26.843545600000013),
+                          mock.call(42.949672960000022),
+                          mock.call(68.719476736000033),
+                          mock.call(109.95116277760006)]
+        self.assertEqual(expected_times, sleep_mock.call_args_list)
+
+    @mock.patch('random.SystemRandom.gauss')
+    @mock.patch('eventlet.greenthread.sleep')
+    def test_no_backoff(self, sleep_mock, random_mock):
+        random_mock.return_value = 1
+        func = mock.Mock()
+        # func.side_effect
+        func.side_effect = [True, True, True, loopingcall.LoopingCallDone(
+            retvalue='return value')]
+
+        retvalue = loopingcall.BackOffLoopingCall(func).start().wait()
+
+        expected_times = [mock.call(1), mock.call(1), mock.call(1)]
+        self.assertEqual(expected_times, sleep_mock.call_args_list)
+        self.assertTrue(retvalue, 'return value')
+
+    @mock.patch('random.SystemRandom.gauss')
+    @mock.patch('eventlet.greenthread.sleep')
+    def test_no_sleep(self, sleep_mock, random_mock):
+        # Any call that executes properly the first time shouldn't sleep
+        random_mock.return_value = 1
+        func = mock.Mock()
+        # func.side_effect
+        func.side_effect = loopingcall.LoopingCallDone(retvalue='return value')
+
+        retvalue = loopingcall.BackOffLoopingCall(func).start().wait()
+        self.assertFalse(sleep_mock.called)
+        self.assertTrue(retvalue, 'return value')
+
+    @mock.patch('random.SystemRandom.gauss')
+    @mock.patch('eventlet.greenthread.sleep')
+    def test_max_interval(self, sleep_mock, random_mock):
+        def false():
+            return False
+
+        random_mock.return_value = .8
+
+        self.assertRaises(loopingcall.LoopingCallTimeOut,
+                          loopingcall.BackOffLoopingCall(false).start(
+                              max_interval=60)
+                          .wait)
+
+        expected_times = [mock.call(1.6000000000000001),
+                          mock.call(2.5600000000000005),
+                          mock.call(4.096000000000001),
+                          mock.call(6.5536000000000021),
+                          mock.call(10.485760000000004),
+                          mock.call(16.777216000000006),
+                          mock.call(26.843545600000013),
+                          mock.call(42.949672960000022),
+                          mock.call(60),
+                          mock.call(60),
+                          mock.call(60)]
+        self.assertEqual(expected_times, sleep_mock.call_args_list)
+
+
 class AnException(Exception):
     pass
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/oslo.service-0.10.0/oslo_service/tests/test_service.py 
new/oslo.service-0.12.0/oslo_service/tests/test_service.py
--- old/oslo.service-0.10.0/oslo_service/tests/test_service.py  2015-10-12 
17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/oslo_service/tests/test_service.py  2015-11-10 
00:02:06.000000000 +0100
@@ -20,26 +20,22 @@
 
 from __future__ import print_function
 
-import threading
-
 import logging
 import multiprocessing
 import os
 import signal
 import socket
-import sys
 import time
 import traceback
 
 import eventlet
 from eventlet import event
-from eventlet.green import subprocess
 import mock
 from oslotest import base as test_base
-from six.moves import queue
 
 from oslo_service import service
 from oslo_service.tests import base
+from oslo_service.tests import eventlet_service
 
 
 LOG = logging.getLogger(__name__)
@@ -304,8 +300,9 @@
     def test_launch_one_worker(self):
         self._test_launch_single(1)
 
+    @mock.patch('signal.alarm')
     @mock.patch('oslo_service.service.ProcessLauncher.launch_service')
-    def test_multiple_worker(self, mock_launch):
+    def test_multiple_worker(self, mock_launch, alarm_mock):
         svc = service.Service()
         service.launch(self.conf, svc, workers=3)
         mock_launch.assert_called_with(svc, workers=3)
@@ -316,19 +313,23 @@
         svc = mock.Mock()
         self.assertRaises(TypeError, service.launch, self.conf, svc)
 
+    @mock.patch('signal.alarm')
     @mock.patch("oslo_service.service.Services.add")
     @mock.patch("oslo_service.eventlet_backdoor.initialize_if_enabled")
     def test_check_service_base(self, initialize_if_enabled_mock,
-                                services_mock):
+                                services_mock,
+                                alarm_mock):
         initialize_if_enabled_mock.return_value = None
         launcher = service.Launcher(self.conf)
         serv = _Service()
         launcher.launch_service(serv)
 
+    @mock.patch('signal.alarm')
     @mock.patch("oslo_service.service.Services.add")
     @mock.patch("oslo_service.eventlet_backdoor.initialize_if_enabled")
     def test_check_service_base_fails(self, initialize_if_enabled_mock,
-                                      services_mock):
+                                      services_mock,
+                                      alarm_mock):
         initialize_if_enabled_mock.return_value = None
         launcher = service.Launcher(self.conf)
 
@@ -341,8 +342,9 @@
 
 class ProcessLauncherTest(base.ServiceBaseTestCase):
 
+    @mock.patch('signal.alarm')
     @mock.patch("signal.signal")
-    def test_stop(self, signal_mock):
+    def test_stop(self, signal_mock, alarm_mock):
         signal_mock.SIGTERM = 15
         launcher = service.ProcessLauncher(self.conf)
         self.assertTrue(launcher.running)
@@ -386,6 +388,7 @@
             m.assert_called_once_with(signal.SIGTERM, 'test')
         signal_handler.clear()
 
+    @mock.patch('signal.alarm')
     @mock.patch("os.kill")
     @mock.patch("oslo_service.service.ProcessLauncher.stop")
     @mock.patch("oslo_service.service.ProcessLauncher._respawn_children")
@@ -402,7 +405,8 @@
                                           handle_signal_mock,
                                           respawn_children_mock,
                                           stop_mock,
-                                          kill_mock):
+                                          kill_mock,
+                                          alarm_mock):
         is_sighup_and_daemon_mock.return_value = True
         respawn_children_mock.side_effect = [None,
                                              eventlet.greenlet.GreenletExit()]
@@ -479,39 +483,19 @@
                          exercise_graceful_test_service(1, 2, False))
 
 
-class EventletServerTest(test_base.BaseTestCase):
-    def run_server(self):
-
-        server_path = os.path.join(os.path.dirname(os.path.abspath(__file__)),
-                                   'eventlet_service.py')
+class EventletServerTest(base.ServiceBaseTestCase):
+    def setUp(self):
+        super(EventletServerTest, self).setUp()
+        self.conf(args=[], default_config_files=[])
+        self.addCleanup(self.conf.reset)
 
-        # Start up an eventlet server.
-        server = subprocess.Popen([sys.executable, server_path],
-                                  stdout=subprocess.PIPE,
-                                  stderr=subprocess.PIPE,
-                                  bufsize=1000,
-                                  close_fds=True)
-
-        def enqueue_output(f, q):
-            while True:
-                line = f.readline()
-                if not line:
-                    break
-                q.put(line)
-            f.close()
-
-        # Start a thread to read stderr so the app doesn't block.
-        err_q = queue.Queue()
-        err_t = threading.Thread(target=enqueue_output,
-                                 args=(server.stderr, err_q))
-        err_t.daemon = True
-        err_t.start()
-
-        # The server's line of output is the port it picked.
-        port_str = server.stdout.readline()
-        port = int(port_str)
+    def run_server(self):
+        queue = multiprocessing.Queue()
+        proc = multiprocessing.Process(target=eventlet_service.run,
+                                       kwargs={'port_queue': queue})
+        proc.start()
 
-        # connect to the server.
+        port = queue.get()
         conn = socket.create_connection(('127.0.0.1', port))
 
         # NOTE(blk-u): The sleep shouldn't be necessary. There must be a bug in
@@ -519,38 +503,49 @@
         # server or signal handlers.
         time.sleep(1)
 
-        return (server, conn)
+        return (proc, conn)
 
     def test_shuts_down_on_sigint_when_client_connected(self):
-        server, conn = self.run_server()
+        proc, conn = self.run_server()
 
         # check that server is live
-        self.assertIsNone(server.poll())
+        self.assertTrue(proc.is_alive())
 
         # send SIGINT to the server and wait for it to exit while client still
         # connected.
-        server.send_signal(signal.SIGINT)
-        server.wait()
+        os.kill(proc.pid, signal.SIGINT)
+        proc.join()
         conn.close()
 
     def test_graceful_shuts_down_on_sigterm_when_client_connected(self):
-        server, conn = self.run_server()
+        self.config(graceful_shutdown_timeout=7)
+        proc, conn = self.run_server()
 
         # send SIGTERM to the server and wait for it to exit while client still
         # connected.
-        server.send_signal(signal.SIGTERM)
+        os.kill(proc.pid, signal.SIGTERM)
 
-        server_wait_thread = threading.Thread(
-            target=lambda server: server.wait(), args=(server,))
-        server_wait_thread.start()
-
-        # server with graceful shutdown must wait forewer
-        # for closing connection by client
-        # but for test 3 seconds is enough
+        # server with graceful shutdown must wait forewer if
+        # option graceful_shutdown_timeout is not specified.
+        # we can not wait forever ... so 3 seconds are enough
         time.sleep(3)
 
-        self.assertEqual(True, server_wait_thread.is_alive())
+        self.assertEqual(True, proc.is_alive())
 
         conn.close()
+        proc.join()
+
+    def test_graceful_stop_with_exceeded_graceful_shutdown_timeout(self):
+        # Server must exit if graceful_shutdown_timeout exceeded
+        graceful_shutdown_timeout = 4
+        self.config(graceful_shutdown_timeout=graceful_shutdown_timeout)
+        proc, conn = self.run_server()
+
+        time_before = time.time()
+        os.kill(proc.pid, signal.SIGTERM)
+        self.assertEqual(True, proc.is_alive())
+        proc.join()
+        self.assertFalse(False, proc.is_alive())
+        time_after = time.time()
 
-        server_wait_thread.join()
+        self.assertTrue(time_after - time_before > graceful_shutdown_timeout)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/requirements.txt 
new/oslo.service-0.12.0/requirements.txt
--- old/oslo.service-0.10.0/requirements.txt    2015-10-12 17:37:58.000000000 
+0200
+++ new/oslo.service-0.12.0/requirements.txt    2015-11-10 00:02:06.000000000 
+0100
@@ -7,9 +7,9 @@
 eventlet>=0.17.4
 greenlet>=0.3.2
 monotonic>=0.3 # Apache-2.0
-oslo.utils>=2.0.0 # Apache-2.0
+oslo.utils!=2.6.0,>=2.4.0 # Apache-2.0
 oslo.concurrency>=2.3.0 # Apache-2.0
-oslo.config>=2.3.0 # Apache-2.0
+oslo.config>=2.6.0 # Apache-2.0
 oslo.log>=1.8.0 # Apache-2.0
 six>=1.9.0
 oslo.i18n>=1.5.0 # Apache-2.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/setup.cfg 
new/oslo.service-0.12.0/setup.cfg
--- old/oslo.service-0.10.0/setup.cfg   2015-10-12 17:38:47.000000000 +0200
+++ new/oslo.service-0.12.0/setup.cfg   2015-11-10 00:02:49.000000000 +0100
@@ -60,7 +60,7 @@
 universal = true
 
 [egg_info]
+tag_build = 
 tag_date = 0
 tag_svn_revision = 0
-tag_build = 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/test-requirements.txt 
new/oslo.service-0.12.0/test-requirements.txt
--- old/oslo.service-0.10.0/test-requirements.txt       2015-10-12 
17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/test-requirements.txt       2015-11-10 
00:02:06.000000000 +0100
@@ -6,8 +6,9 @@
 mock>=1.2
 oslotest>=1.10.0 # Apache-2.0
 
-# These are needed for docs generation
+# These are needed for docs generation/testing
 oslosphinx>=2.5.0 # Apache-2.0
 sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
+doc8 # Apache-2.0
 
 coverage>=3.6
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/oslo.service-0.10.0/tox.ini 
new/oslo.service-0.12.0/tox.ini
--- old/oslo.service-0.10.0/tox.ini     2015-10-12 17:37:58.000000000 +0200
+++ new/oslo.service-0.12.0/tox.ini     2015-11-10 00:02:06.000000000 +0100
@@ -1,6 +1,6 @@
 [tox]
 minversion = 1.6
-envlist = py34,py26,py27,pypy,pep8
+envlist = py34,py27,pypy,pep8
 # NOTE(dhellmann): We cannot set skipdist=True
 # for oslo libraries because of the namespace package.
 #skipsdist = True
@@ -19,6 +19,11 @@
 [testenv:pep8]
 commands = flake8
 
+[testenv:py27]
+commands =
+    python setup.py testr --slowest --testr-args='{posargs}'
+    doc8 --ignore-path "doc/source/history.rst" doc/source
+
 [testenv:py34]
 commands =
   python -m testtools.run \


Reply via email to