Skip to content

Commit 7132112

Browse files
committed
Adds XML-RPC proxy server to be used for tests
This allows the bindings to be used without a special transport in order to run tests. * Setting compression off would still send compression headers. That is now fixed. * Fixes minor testing errors.
1 parent 05ca114 commit 7132112

10 files changed

Lines changed: 172 additions & 44 deletions

File tree

SoftLayer/API.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,11 +192,12 @@ def call(self, service, method, *args, **kwargs):
192192
if self._prefix and not service.startswith(self._prefix):
193193
service = self._prefix + service
194194

195-
http_headers = {}
195+
http_headers = {'Accept': '*/*'}
196196

197197
if kwargs.get('compress', True):
198-
http_headers['Accept'] = '*/*'
199198
http_headers['Accept-Encoding'] = 'gzip, deflate, compress'
199+
else:
200+
http_headers['Accept-Encoding'] = None
200201

201202
if kwargs.get('raw_headers'):
202203
http_headers.update(kwargs.get('raw_headers'))

SoftLayer/testing/__init__.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,14 @@
55
:license: MIT, see LICENSE for more details.
66
"""
77
# Disable pylint import error and too many methods error
8-
# pylint: disable=F0401,R0904
8+
# pylint: disable=invalid-name
99
import logging
1010
import os.path
1111

1212
import SoftLayer
1313
from SoftLayer.CLI import core
1414
from SoftLayer.CLI import environment
15+
from SoftLayer.testing import xmlrpc
1516

1617
from click import testing
1718
import mock
@@ -49,6 +50,11 @@ def set_mock(self, service, method):
4950
self.mocked[_mock_key(service, method)] = _mock
5051
return _mock
5152

53+
def clear(self):
54+
"""Clear out mocks and call history."""
55+
self.calls = []
56+
self.mocked = {}
57+
5258
def _record_call(self, call):
5359
"""Record and log the API call (for later assertions)."""
5460
self.calls.append(call)
@@ -74,6 +80,17 @@ def _mock_key(service, method):
7480
class TestCase(testtools.TestCase):
7581
"""Testcase class with PEP-8 compatable method names."""
7682

83+
@classmethod
84+
def setUpClass(cls):
85+
"""Stand up fixtured/mockable XML-RPC server."""
86+
cls.mocks = MockableTransport(SoftLayer.FixtureTransport())
87+
cls.server = xmlrpc.create_test_server(cls.mocks)
88+
89+
@classmethod
90+
def tearDownClass(cls):
91+
"""Clean up the http server."""
92+
cls.server.shutdown()
93+
7794
def set_up(self):
7895
"""Aliased from setUp."""
7996
pass
@@ -85,18 +102,23 @@ def tear_down(self):
85102
def setUp(self): # NOQA
86103
testtools.TestCase.setUp(self)
87104

88-
# Create a crazy mockable, fixture client
89-
self.mocks = MockableTransport(SoftLayer.FixtureTransport())
90-
self.transport = SoftLayer.TimingTransport(self.mocks)
91-
self.client = SoftLayer.BaseClient(transport=self.transport)
105+
self.mocks.clear()
106+
107+
host, port = self.server.socket.getsockname()[:2]
108+
endpoint_url = "http://%s:%s" % (host, port)
109+
transport = SoftLayer.XmlRpcTransport(endpoint_url=endpoint_url)
110+
wrapped_transport = SoftLayer.TimingTransport(transport)
111+
112+
self.client = SoftLayer.BaseClient(transport=wrapped_transport)
92113

93114
self.env = environment.Environment()
94115
self.env.client = self.client
95-
return self.set_up()
116+
self.set_up()
96117

97118
def tearDown(self): # NOQA
98119
testtools.TestCase.tearDown(self)
99-
return self.tear_down()
120+
self.tear_down()
121+
self.mocks.clear()
100122

101123
def calls(self, service=None, method=None):
102124
"""Return all API calls made during the current test."""
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
getObject = {'id': id, 'billingItem': {'id': 1056}}
1+
getObject = {'id': 1234, 'billingItem': {'id': 1056}}

SoftLayer/testing/xmlrpc.py

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
"""
2+
SoftLayer.testing.xmprpc
3+
~~~~~~~~~~~~~~~~~~~~~~~~
4+
XMP-RPC server which can use a transport to proxy requests for testing.
5+
6+
:license: MIT, see LICENSE for more details.
7+
"""
8+
import logging
9+
import threading
10+
11+
import six
12+
13+
import SoftLayer
14+
from SoftLayer import transports
15+
from SoftLayer import utils
16+
17+
# pylint: disable=invalid-name, broad-except
18+
19+
20+
class TestServer(six.moves.BaseHTTPServer.HTTPServer):
21+
"""Test HTTP server which holds a given transport."""
22+
23+
def __init__(self, transport, *args, **kw):
24+
six.moves.BaseHTTPServer.HTTPServer.__init__(self, *args, **kw)
25+
self.transport = transport
26+
27+
28+
class TestHandler(six.moves.BaseHTTPServer.BaseHTTPRequestHandler):
29+
"""Test XML-RPC Handler which converts XML-RPC to transport requests."""
30+
31+
def do_POST(self):
32+
"""Handle XML-RPC POSTs."""
33+
try:
34+
length = int(self.headers['Content-Length'])
35+
data = self.rfile.read(length).decode('utf-8')
36+
args, method = utils.xmlrpc_client.loads(data)
37+
headers = args[0].get('headers', {})
38+
39+
# Form Request for the transport
40+
req = transports.Request()
41+
req.service = self.path.lstrip('/')
42+
req.method = method
43+
req.limit = utils.lookup(headers, 'resultLimit', 'limit')
44+
req.offset = utils.lookup(headers, 'resultLimit', 'offset')
45+
req.args = args[1:]
46+
req.filter = _item_by_key_postfix(headers, 'ObjectFilter') or None
47+
req.mask = _item_by_key_postfix(headers, 'ObjectMask').get('mask')
48+
req.identifier = _item_by_key_postfix(headers,
49+
'InitParameters').get('id')
50+
req.transport_headers = dict(((k.lower(), v)
51+
for k, v in self.headers.items()))
52+
53+
# Get response
54+
response = self.server.transport(req)
55+
56+
response_body = utils.xmlrpc_client.dumps((response,),
57+
allow_none=True,
58+
methodresponse=1)
59+
60+
self.send_response(200)
61+
self.send_header("Content-type", "application/xml")
62+
self.end_headers()
63+
self.wfile.write(response_body.encode('utf-8'))
64+
65+
except SoftLayer.SoftLayerAPIError as ex:
66+
self.send_response(ex.faultCode or 500)
67+
self.end_headers()
68+
response = utils.xmlrpc_client.Fault(ex.faultCode, str(ex.reason))
69+
response_body = utils.xmlrpc_client.dumps((response,),
70+
allow_none=True,
71+
methodresponse=1)
72+
self.wfile.write(response_body.encode('utf-8'))
73+
except Exception as ex:
74+
self.send_response(500)
75+
logging.exception("Error while handling request")
76+
77+
def log_message(self, fmt, *args):
78+
"""Override log_message."""
79+
pass
80+
81+
82+
def _item_by_key_postfix(dictionary, key_prefix):
83+
"""Get item from a dictionary which begins with the given prefix."""
84+
for key, value in dictionary.items():
85+
if key.endswith(key_prefix):
86+
return value
87+
88+
return {}
89+
90+
91+
def create_test_server(transport, host='localhost', port=0):
92+
"""Create a test XML-RPC server in a new thread."""
93+
server = TestServer(transport, (host, port), TestHandler)
94+
thread = threading.Thread(target=server.serve_forever,
95+
kwargs={'poll_interval': 0.05})
96+
thread.start()
97+
return server

SoftLayer/tests/CLI/modules/call_api_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def test_options(self):
2626
self.assertEqual(result.exit_code, 0)
2727
self.assertEqual(json.loads(result.output), 'test')
2828
self.assert_called_with('SoftLayer_Service', 'method',
29-
mask='some.mask',
29+
mask='mask[some.mask]',
3030
limit=20,
3131
offset=40,
3232
identifier='100')

SoftLayer/tests/api_tests.py

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -189,34 +189,41 @@ def test_call_invalid_arguments(self):
189189
self.client.call, 'SERVICE', 'METHOD', invalid_kwarg='invalid')
190190

191191
def test_call_compression_disabled(self):
192-
self.set_mock('SoftLayer_SERVICE', 'METHOD')
192+
mocked = self.set_mock('SoftLayer_SERVICE', 'METHOD')
193+
mocked.return_value = {}
194+
193195
self.client['SERVICE'].METHOD(compress=False)
194196

195-
self.assert_called_with('SoftLayer_SERVICE', 'METHOD')
197+
calls = self.calls('SoftLayer_SERVICE', 'METHOD')
198+
self.assertEqual(len(calls), 1)
199+
headers = calls[0].transport_headers
200+
self.assertEqual(headers.get('accept-encoding'), 'identity')
196201

197202
def test_call_compression_enabled(self):
198-
self.set_mock('SoftLayer_SERVICE', 'METHOD')
203+
mocked = self.set_mock('SoftLayer_SERVICE', 'METHOD')
204+
mocked.return_value = {}
205+
199206
self.client['SERVICE'].METHOD(compress=True)
200207

201-
expected_headers = {
202-
'Accept-Encoding': 'gzip, deflate, compress',
203-
'Accept': '*/*',
204-
}
205-
self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
206-
transport_headers=expected_headers)
208+
calls = self.calls('SoftLayer_SERVICE', 'METHOD')
209+
self.assertEqual(len(calls), 1)
210+
headers = calls[0].transport_headers
211+
self.assertEqual(headers.get('accept-encoding'),
212+
'gzip, deflate, compress')
207213

208214
def test_call_compression_override(self):
209215
# raw_headers should override compress=False
210-
self.set_mock('SoftLayer_SERVICE', 'METHOD')
216+
mocked = self.set_mock('SoftLayer_SERVICE', 'METHOD')
217+
mocked.return_value = {}
218+
211219
self.client['SERVICE'].METHOD(
212220
compress=False,
213221
raw_headers={'Accept-Encoding': 'gzip'})
214222

215-
expected_headers = {
216-
'Accept-Encoding': 'gzip',
217-
}
218-
self.assert_called_with('SoftLayer_SERVICE', 'METHOD',
219-
transport_headers=expected_headers)
223+
calls = self.calls('SoftLayer_SERVICE', 'METHOD')
224+
self.assertEqual(len(calls), 1)
225+
headers = calls[0].transport_headers
226+
self.assertEqual(headers.get('accept-encoding'), 'gzip')
220227

221228

222229
class UnauthenticatedAPIClient(testing.TestCase):

SoftLayer/tests/managers/dns_tests.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ def test_get_zone(self):
2727
self.assertEqual(res, fixtures.SoftLayer_Dns_Domain.getObject)
2828
self.assert_called_with('SoftLayer_Dns_Domain', 'getObject',
2929
identifier=12345,
30-
mask='resourceRecords')
30+
mask='mask[resourceRecords]')
3131

3232
def test_get_zone_without_records(self):
3333
self.dns_client.get_zone(12345, records=False)

SoftLayer/tests/managers/firewall_tests.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def test_get_standard_package_virtual_server(self):
5959

6060
self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject',
6161
identifier=1234,
62-
mask='primaryNetworkComponent[maxSpeed]')
62+
mask='mask[primaryNetworkComponent[maxSpeed]]')
6363

6464
_filter = {
6565
'items': {
@@ -77,7 +77,7 @@ def test_get_standard_package_bare_metal(self):
7777

7878
# we should ask for the frontEndNetworkComponents to get
7979
# the firewall port speed
80-
mask = 'id,maxSpeed,networkComponentGroup.networkComponents'
80+
mask = 'mask[id,maxSpeed,networkComponentGroup.networkComponents]'
8181
self.assert_called_with('SoftLayer_Hardware_Server',
8282
'getFrontendNetworkComponents',
8383
identifier=1234,
@@ -154,7 +154,7 @@ def test_add_standard_firewall_virtual_server(self):
154154
self.firewall.add_standard_firewall(6327, is_virt=True)
155155

156156
self.assert_called_with('SoftLayer_Virtual_Guest', 'getObject',
157-
mask='primaryNetworkComponent[maxSpeed]',
157+
mask='mask[primaryNetworkComponent[maxSpeed]]',
158158
identifier=6327)
159159

160160
_filter = {
@@ -195,7 +195,7 @@ def test_add_standard_firewall_server(self):
195195

196196
# we should ask for the frontEndNetworkComponents to get
197197
# the firewall port speed
198-
mask = 'id,maxSpeed,networkComponentGroup.networkComponents'
198+
mask = 'mask[id,maxSpeed,networkComponentGroup.networkComponents]'
199199
self.assert_called_with('SoftLayer_Hardware_Server',
200200
'getFrontendNetworkComponents',
201201
mask=mask,

SoftLayer/tests/managers/loadbal_tests.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -87,19 +87,20 @@ def test_get_local_lbs(self):
8787
result = self.lb_mgr.get_local_lbs()
8888

8989
self.assertEqual(len(result), 0)
90-
mask = 'loadBalancerHardware[datacenter],ipAddress'
90+
mask = 'mask[loadBalancerHardware[datacenter],ipAddress]'
9191
self.assert_called_with('SoftLayer_Account', 'getAdcLoadBalancers',
9292
mask=mask)
9393

9494
def test_get_local_lb(self):
9595
result = self.lb_mgr.get_local_lb(22348)
9696

9797
self.assertEqual(result['id'], 22348)
98-
mask = ('loadBalancerHardware[datacenter], '
98+
mask = ('mask['
99+
'loadBalancerHardware[datacenter], '
99100
'ipAddress, virtualServers[serviceGroups'
100101
'[routingMethod,routingType,services'
101102
'[healthChecks[type], groupReferences,'
102-
' ipAddress]]]')
103+
' ipAddress]]]]')
103104
self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
104105
identifier=22348,
105106
mask=mask)
@@ -142,7 +143,7 @@ def test_edit_service(self):
142143
}
143144
}
144145
}
145-
mask = 'serviceGroups[services[groupReferences,healthChecks]]'
146+
mask = 'mask[serviceGroups[services[groupReferences,healthChecks]]]'
146147
self.assert_called_with(VIRT_IP_SERVICE, 'getVirtualServers',
147148
identifier=12345,
148149
filter=_filter,
@@ -153,7 +154,7 @@ def test_edit_service(self):
153154
def test_add_service(self):
154155
self.lb_mgr.add_service(12345, 50718, 123, 80, True, 21, 1)
155156

156-
mask = 'virtualServers[serviceGroups[services[groupReferences]]]'
157+
mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]'
157158
self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
158159
mask=mask,
159160
identifier=12345)
@@ -173,7 +174,7 @@ def test_edit_service_group(self):
173174
routing_type=2,
174175
routing_method=10)
175176

176-
mask = 'virtualServers[serviceGroups[services[groupReferences]]]'
177+
mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]'
177178
self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
178179
identifier=12345,
179180
mask=mask)
@@ -183,7 +184,7 @@ def test_edit_service_group(self):
183184
def test_add_service_group(self):
184185
self.lb_mgr.add_service_group(12345, 100, 80, 2, 10)
185186

186-
mask = 'virtualServers[serviceGroups[services[groupReferences]]]'
187+
mask = 'mask[virtualServers[serviceGroups[services[groupReferences]]]]'
187188
self.assert_called_with(VIRT_IP_SERVICE, 'getObject',
188189
mask=mask,
189190
identifier=12345)
@@ -201,7 +202,7 @@ def test_reset_service_group(self):
201202
self.assert_called_with(VIRT_IP_SERVICE, 'getVirtualServers',
202203
identifier=12345,
203204
filter=_filter,
204-
mask='serviceGroups')
205+
mask='mask[serviceGroups]')
205206

206207
service = ('SoftLayer_Network_Application_Delivery_Controller_'
207208
'LoadBalancer_Service_Group')

0 commit comments

Comments
 (0)