The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/pylxd/pull/181
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Tests that require ws4py are skipped in environments that don't have ws4py available. Fixes #180
From f58d24ed199f38b1e40ba0bc8c26b54b06ef9068 Mon Sep 17 00:00:00 2001 From: Paul Hummer <paul.hum...@canonical.com> Date: Fri, 16 Sep 2016 14:12:23 -0600 Subject: [PATCH] Make ws4py an optional library. Tests that require ws4py are skipped in environments that don't have ws4py available. Fixes #180 --- pylxd/client.py | 10 ++++- pylxd/deprecated/connection.py | 83 +++++++++++++++++++----------------- pylxd/models/container.py | 12 +++++- pylxd/tests/models/test_container.py | 2 + pylxd/tests/test_client.py | 7 +++ pylxd/tests/testing.py | 14 ++++++ 6 files changed, 87 insertions(+), 41 deletions(-) diff --git a/pylxd/client.py b/pylxd/client.py index e18028c..a4ee4f0 100644 --- a/pylxd/client.py +++ b/pylxd/client.py @@ -18,7 +18,12 @@ import requests import requests_unixsocket from six.moves.urllib import parse -from ws4py.client import WebSocketBaseClient +try: + from ws4py.client import WebSocketBaseClient + _ws4py_installed = True +except ImportError: + WebSocketBaseClient = object + _ws4py_installed = True from pylxd import exceptions, managers @@ -238,6 +243,9 @@ def events(self, websocket_client=None): specified for implementation-specific handling of events as they occur. """ + if not _ws4py_installed: + raise ValueError( + 'This feature requires the optional ws4py library.') if websocket_client is None: websocket_client = _WebsocketClient diff --git a/pylxd/deprecated/connection.py b/pylxd/deprecated/connection.py index 65adbf0..b86ad94 100644 --- a/pylxd/deprecated/connection.py +++ b/pylxd/deprecated/connection.py @@ -24,7 +24,10 @@ from six.moves import http_client from six.moves import queue -from ws4py import client as websocket +try: + from ws4py import client as websocket +except ImportError: + websocket = None from pylxd.deprecated import exceptions from pylxd.deprecated import utils @@ -88,43 +91,44 @@ def _get_ssl_certs(): _LXDResponse = namedtuple('LXDResponse', ['status', 'body', 'json']) -class WebSocketClient(websocket.WebSocketBaseClient): - def __init__(self, url, protocols=None, extensions=None, ssl_options=None, - headers=None): - """WebSocket client that executes into a eventlet green thread.""" - websocket.WebSocketBaseClient.__init__(self, url, protocols, - extensions, - ssl_options=ssl_options, - headers=headers) - self._th = threading.Thread(target=self.run, name='WebSocketClient') - self._th.daemon = True - - self.messages = queue.Queue() - - def handshake_ok(self): - """Starts the client's thread.""" - self._th.start() - - def received_message(self, message): - """Override the base class to store the incoming message.""" - self.messages.put(copy.deepcopy(message)) - - def closed(self, code, reason=None): - # When the connection is closed, put a StopIteration - # on the message queue to signal there's nothing left - # to wait for - self.messages.put(StopIteration) - - def receive(self): - # If the websocket was terminated and there are no messages - # left in the queue, return None immediately otherwise the client - # will block forever - if self.terminated and self.messages.empty(): - return None - message = self.messages.get() - if message is StopIteration: - return None - return message +if websocket is not None: + class WebSocketClient(websocket.WebSocketBaseClient): + def __init__(self, url, protocols=None, extensions=None, ssl_options=None, + headers=None): + """WebSocket client that executes into a eventlet green thread.""" + websocket.WebSocketBaseClient.__init__(self, url, protocols, + extensions, + ssl_options=ssl_options, + headers=headers) + self._th = threading.Thread(target=self.run, name='WebSocketClient') + self._th.daemon = True + + self.messages = queue.Queue() + + def handshake_ok(self): + """Starts the client's thread.""" + self._th.start() + + def received_message(self, message): + """Override the base class to store the incoming message.""" + self.messages.put(copy.deepcopy(message)) + + def closed(self, code, reason=None): + # When the connection is closed, put a StopIteration + # on the message queue to signal there's nothing left + # to wait for + self.messages.put(StopIteration) + + def receive(self): + # If the websocket was terminated and there are no messages + # left in the queue, return None immediately otherwise the client + # will block forever + if self.terminated and self.messages.empty(): + return None + message = self.messages.get() + if message is StopIteration: + return None + return message class LXDConnection(object): @@ -202,6 +206,9 @@ def get_raw(self, *args, **kwargs): raise exceptions.PyLXDException('Failed to get raw response') def get_ws(self, path): + if websocket is None: + raise ValueError( + 'This feature requires the optional ws4py library.') if self.unix_socket: connection_string = 'ws+unix://%s' % self.unix_socket else: diff --git a/pylxd/models/container.py b/pylxd/models/container.py index f7ef5d3..f7e3bd1 100644 --- a/pylxd/models/container.py +++ b/pylxd/models/container.py @@ -15,8 +15,13 @@ import six from six.moves.urllib import parse -from ws4py.client import WebSocketBaseClient -from ws4py.manager import WebSocketManager +try: + from ws4py.client import WebSocketBaseClient + from ws4py.manager import WebSocketManager + _ws4py_installed = True +except ImportError: + WebSocketBaseClient = object + _ws4py_installed = False from pylxd import managers from pylxd.models import _model as model @@ -183,6 +188,9 @@ def unfreeze(self, timeout=30, force=True, wait=False): def execute(self, commands, environment={}): """Execute a command on the container.""" + if not _ws4py_installed: + raise ValueError( + 'This feature requires the optional ws4py library.') if isinstance(commands, six.string_types): raise TypeError("First argument must be a list.") response = self.api['exec'].post(json={ diff --git a/pylxd/tests/models/test_container.py b/pylxd/tests/models/test_container.py index e2044a5..e1ae3ca 100644 --- a/pylxd/tests/models/test_container.py +++ b/pylxd/tests/models/test_container.py @@ -155,6 +155,7 @@ def test_delete(self): an_container.delete(wait=True) + @testing.requires_ws4py @mock.patch('pylxd.models.container._StdinWebsocket') @mock.patch('pylxd.models.container._CommandWebsocketClient') def test_execute(self, _CommandWebsocketClient, _StdinWebsocket): @@ -171,6 +172,7 @@ def test_execute(self, _CommandWebsocketClient, _StdinWebsocket): self.assertEqual('test\n', stdout) + @testing.requires_ws4py def test_execute_string(self): """A command passed as string raises a TypeError.""" an_container = models.Container( diff --git a/pylxd/tests/test_client.py b/pylxd/tests/test_client.py index 2b1fbb2..cb68e5e 100644 --- a/pylxd/tests/test_client.py +++ b/pylxd/tests/test_client.py @@ -21,6 +21,7 @@ import requests_unixsocket from pylxd import client, exceptions +from pylxd.tests.testing import requires_ws4py class TestClient(unittest.TestCase): @@ -164,6 +165,7 @@ def test_host_info(self): an_client = client.Client() self.assertEqual('zfs', an_client.host_info['environment']['storage']) + @requires_ws4py def test_events(self): """The default websocket client is returned.""" an_client = client.Client() @@ -172,6 +174,7 @@ def test_events(self): self.assertEqual('/1.0/events', ws_client.resource) + @requires_ws4py def test_events_unix_socket(self): """A unix socket compatible websocket client is returned.""" websocket_client = mock.Mock(resource=None) @@ -183,6 +186,7 @@ def test_events_unix_socket(self): WebsocketClient.assert_called_once_with('ws+unix:///lxd/unix.socket') + @requires_ws4py def test_events_htt(self): """An http compatible websocket client is returned.""" websocket_client = mock.Mock(resource=None) @@ -194,6 +198,7 @@ def test_events_htt(self): WebsocketClient.assert_called_once_with('ws://lxd.local') + @requires_ws4py def test_events_https(self): """An https compatible websocket client is returned.""" websocket_client = mock.Mock(resource=None) @@ -339,6 +344,7 @@ def test_delete(self, Session): class TestWebsocketClient(unittest.TestCase): """Tests for pylxd.client.WebsocketClient.""" + @requires_ws4py def test_handshake_ok(self): """A `message` attribute of an empty list is created.""" ws_client = client._WebsocketClient('ws://an/fake/path') @@ -347,6 +353,7 @@ def test_handshake_ok(self): self.assertEqual([], ws_client.messages) + @requires_ws4py def test_received_message(self): """A json dict is added to the messages attribute.""" message = mock.Mock(data=json.dumps({'test': 'data'}).encode('utf-8')) diff --git a/pylxd/tests/testing.py b/pylxd/tests/testing.py index 9adfb37..e60912c 100644 --- a/pylxd/tests/testing.py +++ b/pylxd/tests/testing.py @@ -6,6 +6,20 @@ from pylxd.tests import mock_lxd +def requires_ws4py(f): + """Marks a test as requiring ws4py. + + As ws4py is an optional dependency, some tests should + be skipped, as they won't run properly if ws4py is not + installed. + """ + try: + import ws4py # NOQA + return f + except ImportError: + return unittest.skip('ws4py is not installed')(f) + + class PyLXDTestCase(unittest.TestCase): """A test case for handling mocking of LXD services."""
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel