Skip to content

Commit

Permalink
Improvements for port in use handling and other cleanup in listener
Browse files Browse the repository at this point in the history
Details:
- Docs: Clarified that listener must be stopped when restarting it or creating
  a second listener on same port, to avoid port address in use error.
- Added support for context manager to WBEMListener class, which upon exit
  ensures that listener is stopped.
- Improved the exception message that occurs when listener port is in use.
- Added test cases for listener port in use.
- Cleaned up the test method `send_indications()` to remove the unused
  `https_port` argument.
- Moved printing of sent indications in test method to be done only when
  VERBOSE is set.
- Docs: Outdented comments in example to right level.

Signed-off-by: Andreas Maier <maiera@de.ibm.com>
  • Loading branch information
andy-maier committed Feb 21, 2018
1 parent 5f225a4 commit ebdee43
Show file tree
Hide file tree
Showing 2 changed files with 100 additions and 17 deletions.
70 changes: 63 additions & 7 deletions pywbem/_listener.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ def main():
my_listener.add_callback(process_indication)
listener.start()
# listener runs until executable terminated
# or listener.stop()
# listener runs until executable terminated
# or listener.stop()
See the example in section :ref:`WBEMSubscriptionManager` for an example of
using a listener in combination with a subscription manager.
Expand All @@ -73,8 +73,10 @@ def main():
creates a listener and displays any indications it receives, in MOF format.
"""

import sys
import re
import logging
import errno
try: # Python 2.7+
from logging import NullHandler
except ImportError:
Expand Down Expand Up @@ -549,6 +551,11 @@ class WBEMListener(object):
The listener supports starting and stopping threads that listen for
CIM-XML ExportIndication messages using HTTP and/or HTTPS, and that pass
any received indications on to registered callback functions.
The listener must be stopped before creating another listener object
in the same Python process, in order to free the TCP/IP port it
listens on. The WBEMListener class can be used as a context manager
to ensure the stopping of the listener.
"""

def __init__(self, host, http_port=None, https_port=None,
Expand Down Expand Up @@ -649,6 +656,27 @@ def __repr__(self):
self.https_port, self.certfile, self.keyfile, self.logger,
self._callbacks)

def __enter__(self):
"""
*New in pywbem 0.12.*
Enter method when the class is used as a context manager.
Returns the listener object.
"""
return self

def __exit__(self, exc_type, exc_value, traceback):
"""
*New in pywbem 0.12.*
Exit method when the class is used as a context manager.
It cleans up by calling
:meth:`~pywbem.WBEMListener.stop`.
"""
self.stop()
return False # re-raise any exceptions

@property
def host(self):
"""The IP address or host name this listener can be reached at,
Expand Down Expand Up @@ -743,13 +771,30 @@ def start(self):
These server threads can be stopped using the
:meth:`~pywbem.WBEMListener.stop` method.
They will be automatically stopped when the main thread terminates.
They will be automatically stopped when the main thread terminates
(i.e. when the Python process terminates), or when the WBEMListener
was used as a context manager and the context manager scope closes.
The listener must be stopped before creating another listener object
in the same Python process, in order to free the TCP/IP port it
listens on.
"""

if self._http_port:
if not self._http_server:
server = ThreadedHTTPServer((self._host, self._http_port),
ListenerRequestHandler)
try:
server = ThreadedHTTPServer((self._host, self._http_port),
ListenerRequestHandler)
except Exception as exc:
# In Python 2: socket.error; In Python 3: OSError
if getattr(exc, 'errno', None) == errno.EADDRINUSE:
# Reraise with improved error message
msg = "WBEM listener port %s already in use" % \
self._http_port
exc_type = type(exc)
six.reraise(exc_type, exc_type(errno.EADDRINUSE, msg),
sys.exc_info()[2])
raise

# pylint: disable=attribute-defined-outside-init
server.listener = self
Expand All @@ -765,8 +810,19 @@ def start(self):

if self._https_port:
if not self._https_server:
server = ThreadedHTTPServer((self._host, self._https_port),
ListenerRequestHandler)
try:
server = ThreadedHTTPServer((self._host, self._https_port),
ListenerRequestHandler)
except Exception as exc:
# In Python 2: socket.error; In Python 3: OSError
if getattr(exc, 'errno', None) == errno.EADDRINUSE:
# Reraise with improved error message
msg = "WBEM listener port %s already in use" % \
self._http_port
exc_type = type(exc)
six.reraise(exc_type, exc_type(errno.EADDRINUSE, msg),
sys.exc_info()[2])
raise

# pylint: disable=attribute-defined-outside-init
server.listener = self
Expand Down
47 changes: 37 additions & 10 deletions testsuite/test_indicationlistener.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
from time import time
import datetime
from random import randint
import socket
import errno
import requests
import six

from pywbem import WBEMListener

Expand Down Expand Up @@ -167,14 +170,16 @@ def createlistener(host, http_port=None, https_port=None,
LISTENER.start()

# pylint: disable=unused-argument
def send_indications(self, send_count, http_port, https_port):
def send_indications(self, send_count, http_port):
"""
Send the number of indications defined by the send_count attribute
using the specified listener HTTP port.
Creates the listener, starts the listener, creates the
indication XML and adds sequence number and time to the
indication instance and sends that instance using requests.
The indication instance is modified for each indication count so
that each carries its own sequence number
that each carries its own sequence number.
"""

# pylint: disable=global-variable-not-assigned
Expand Down Expand Up @@ -220,8 +225,9 @@ def send_indications(self, send_count, http_port, https_port):
self.fail('Error return from send. Terminating.')

endtime = timer.elapsed_sec()
print('Sent %s indications in %s sec or %.2f ind/sec' %
(send_count, endtime, (send_count / endtime)))
if VERBOSE:
print('Sent %s indications in %s sec or %.2f ind/sec' %
(send_count, endtime, (send_count / endtime)))

self.assertEqual(send_count, RCV_COUNT,
'Mismatch between sent and rcvd')
Expand All @@ -232,20 +238,41 @@ def send_indications(self, send_count, http_port, https_port):

def test_send_10(self):
"""Test with sending 10 indications"""
self.send_indications(10, 50000, None)
self.send_indications(10, 50000)

def test_send_100(self):
"""Test sending 100 indications"""
self.send_indications(100, 50000, None)
self.send_indications(100, 50000)

# Disabled the following tests, because in some environments it takes 30min.
# def test_send_1000(self):
# """Test sending 1000 indications"""
# self.send_indications(1000, 5002, None)
# self.send_indications(1000, 50000)

def test_port_in_use(self):
"""
Test starting the listener when port is in use by another listener.
"""

host = 'localhost'

# Don't use this port in other tests, to be on the safe side
# as far as port reuse is concerned.
http_port = '59999'

exp_exc_type = socket.error if six.PY2 else OSError

listener1 = WBEMListener(host, http_port)
listener1.start()

listener2 = WBEMListener(host, http_port)
try:
listener2.start()
except exp_exc_type as exc:
if exc.errno != errno.EADDRINUSE:
raise

# This test takes about 60 seconds and so is disabled for now
# def test_send_10000(self):
# self.send_indications(10000)
listener1.stop()


if __name__ == '__main__':
Expand Down

0 comments on commit ebdee43

Please sign in to comment.