[lxc-devel] [pylxd/master] Make ws4py an optional library.
rockstar on Github
lxc-bot at linuxcontainers.org
Fri Sep 16 20:15:35 UTC 2016
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 402 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20160916/2c089fa6/attachment.bin>
-------------- next part --------------
From f58d24ed199f38b1e40ba0bc8c26b54b06ef9068 Mon Sep 17 00:00:00 2001
From: Paul Hummer <paul.hummer at 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."""
More information about the lxc-devel
mailing list