Skip to content

Commit f8c6ab5

Browse files
committed
Adds debug tracing of curl commands
This patch adds support to the icontrol library to trace API calls and provides an easy to read representation of these API calls (in curl format). This tracing is a necessary component of debugging more complicated API usage (such as when done in the f5-sdk).
1 parent 0cfcf5a commit f8c6ab5

5 files changed

Lines changed: 113 additions & 85 deletions

File tree

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ deploy:
4141
on:
4242
repo: F5Networks/f5-icontrol-rest-python
4343
tags: true
44+
python: 2.7
4445
- provider: pypi
4546
user: $PYPI_USER
4647
password: $PYPI_PASSWORD

icontrol/session.py

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,17 @@
6666
from icontrol.exceptions import InvalidScheme
6767
from icontrol.exceptions import InvalidSuffixCollection
6868
from icontrol.exceptions import InvalidURIComponentPart
69+
from six import iteritems
6970

7071
import functools
7172
import logging
7273
import requests
7374

75+
try:
76+
import json
77+
except ImportError:
78+
import simplejson as json
79+
7480
try:
7581
# Python 3
7682
from urllib.parse import urlsplit
@@ -369,6 +375,10 @@ def __init__(self, username, password, **kwargs):
369375
On BIG-IQ systems, the value 'local' can be used to refer to
370376
local user authentication.
371377
"""
378+
379+
# Used for holding debug information
380+
self._debug = []
381+
372382
verify = kwargs.pop('verify', False)
373383
timeout = kwargs.pop('timeout', 30)
374384
token_auth = kwargs.pop('token', None)
@@ -437,7 +447,10 @@ def delete(self, uri, **kwargs):
437447
:type partition: str
438448
:param \**kwargs: The :meth:`reqeusts.Session.delete` optional params
439449
"""
440-
return self.session.delete(uri, **kwargs)
450+
req = requests.Request('DELETE', uri, **kwargs)
451+
prepared = self.session.prepare_request(req)
452+
self._debug.append(debug_prepared_request(prepared))
453+
return self.session.send(prepared)
441454

442455
@decorate_HTTP_verb_method
443456
def get(self, uri, **kwargs):
@@ -459,7 +472,10 @@ def get(self, uri, **kwargs):
459472
:type partition: str
460473
:param \**kwargs: The :meth:`reqeusts.Session.get` optional params
461474
"""
462-
return self.session.get(uri, **kwargs)
475+
req = requests.Request('GET', uri, **kwargs)
476+
prepared = self.session.prepare_request(req)
477+
self._debug.append(debug_prepared_request(prepared))
478+
return self.session.send(prepared)
463479

464480
@decorate_HTTP_verb_method
465481
def patch(self, uri, data=None, **kwargs):
@@ -483,7 +499,10 @@ def patch(self, uri, data=None, **kwargs):
483499
:type partition: str
484500
:param \**kwargs: The :meth:`reqeusts.Session.patch` optional params
485501
"""
486-
return self.session.patch(uri, data=data, **kwargs)
502+
req = requests.Request('PATCH', uri, data=data, **kwargs)
503+
prepared = self.session.prepare_request(req)
504+
self._debug.append(debug_prepared_request(prepared))
505+
return self.session.send(prepared)
487506

488507
@decorate_HTTP_verb_method
489508
def post(self, uri, data=None, json=None, **kwargs):
@@ -509,7 +528,10 @@ def post(self, uri, data=None, json=None, **kwargs):
509528
:type partition: str
510529
:param \**kwargs: The :meth:`reqeusts.Session.post` optional params
511530
"""
512-
return self.session.post(uri, data=data, json=json, **kwargs)
531+
req = requests.Request('POST', uri, data=data, json=json, **kwargs)
532+
prepared = self.session.prepare_request(req)
533+
self._debug.append(debug_prepared_request(prepared))
534+
return self.session.send(prepared)
513535

514536
@decorate_HTTP_verb_method
515537
def put(self, uri, data=None, **kwargs):
@@ -535,7 +557,10 @@ def put(self, uri, data=None, **kwargs):
535557
:type partition: str
536558
:param **kwargs: The :meth:`reqeusts.Session.put` optional params
537559
"""
538-
return self.session.put(uri, data=data, **kwargs)
560+
req = requests.Request('PUT', uri, data=data, **kwargs)
561+
prepared = self.session.prepare_request(req)
562+
self._debug.append(debug_prepared_request(prepared))
563+
return self.session.send(prepared)
539564

540565
def append_user_agent(self, user_agent):
541566
"""Append text to the User-Agent header for the request.
@@ -568,3 +593,14 @@ def token(self, value):
568593
been read from a stored value for example.
569594
"""
570595
self.session.auth.token = value
596+
597+
598+
def debug_prepared_request(request):
599+
return
600+
result = "curl -k -X {0} {1}".format(request.method.upper(), request.url)
601+
for k, v in iteritems(request.headers):
602+
result = result + " -H '{0}: {1}'".format(k, v)
603+
if request.body:
604+
kwargs = json.loads(request.body)
605+
result = result + " -d '" + json.dumps(kwargs, sort_keys=True) + "'"
606+
return result

icontrol/test/unit/test_session.py

Lines changed: 66 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
import mock
1616
import pytest
17+
import requests
1718

1819
from icontrol import __version__ as VERSION
1920
from icontrol import session
@@ -27,11 +28,7 @@ def iCRS():
2728
fake_iCRS.session = mock.MagicMock()
2829
mock_response = mock.MagicMock()
2930
mock_response.status_code = 200
30-
fake_iCRS.session.delete.return_value = mock_response
31-
fake_iCRS.session.get.return_value = mock_response
32-
fake_iCRS.session.patch.return_value = mock_response
33-
fake_iCRS.session.post.return_value = mock_response
34-
fake_iCRS.session.put.return_value = mock_response
31+
fake_iCRS.session.send.return_value = mock_response
3532
return fake_iCRS
3633

3734

@@ -380,119 +377,109 @@ def test_correct_uri_construction_mgmt_cm(uparts_cm):
380377

381378
# Test exception handling
382379
def test_wrapped_delete_success(iCRS, uparts):
383-
iCRS.delete(uparts['base_uri'], partition='AFN', name='AIN',
384-
uri_as_parts=True)
385-
assert iCRS.session.delete.call_args ==\
386-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN')
380+
iCRS.delete(uparts['base_uri'], partition='AFN', name='AIN', uri_as_parts=True)
381+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
382+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
387383

388384

389385
def test_wrapped_delete_207_fail(iCRS, uparts):
390-
iCRS.session.delete.return_value.status_code = 207
391-
with pytest.raises(session.iControlUnexpectedHTTPError) as CHE:
392-
iCRS.delete(uparts['base_uri'], partition='A_FOLDER_NAME',
393-
name='AN_INSTANCE_NAME')
394-
assert str(CHE.value).startswith('207 Unexpected Error: ')
386+
iCRS.session.send.return_value.status_code = 207
387+
with pytest.raises(session.iControlUnexpectedHTTPError) as ex:
388+
iCRS.delete(uparts['base_uri'], partition='A_FOLDER_NAME', name='AN_INSTANCE_NAME')
389+
assert str(ex.value).startswith('207 Unexpected Error: ')
395390

396391

397392
def test_wrapped_get_success(iCRS, uparts):
398-
iCRS.get(uparts['base_uri'], partition='AFN', name='AIN',
399-
uri_as_parts=True)
400-
assert iCRS.session.get.call_args ==\
401-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN')
393+
iCRS.get(uparts['base_uri'], partition='AFN', name='AIN', uri_as_parts=True)
394+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
395+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
402396

403397

404398
def test_wrapped_get_success_with_suffix(iCRS, uparts):
405-
iCRS.get(uparts['base_uri'], partition='AFN', name='AIN',
406-
suffix=uparts['suffix'],
407-
uri_as_parts=True)
408-
assert iCRS.session.get.call_args ==\
409-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN/members/m1')
399+
iCRS.get(uparts['base_uri'], partition='AFN', name='AIN', suffix=uparts['suffix'], uri_as_parts=True)
400+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
401+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN/members/m1'
410402

411403

412404
def test_wrapped_get_207_fail(iCRS, uparts):
413-
iCRS.session.get.return_value.status_code = 207
414-
with pytest.raises(session.iControlUnexpectedHTTPError) as CHE:
415-
iCRS.get(uparts['base_uri'], partition='A_FOLDER_NAME',
416-
name='AN_INSTANCE_NAME')
417-
assert str(CHE.value).startswith('207 Unexpected Error: ')
405+
iCRS.session.send.return_value.status_code = 207
406+
with pytest.raises(session.iControlUnexpectedHTTPError) as ex:
407+
iCRS.get(uparts['base_uri'], partition='A_FOLDER_NAME', name='AN_INSTANCE_NAME')
408+
assert str(ex.value).startswith('207 Unexpected Error: ')
418409

419410

420411
def test_wrapped_patch_success(iCRS, uparts):
421-
iCRS.patch(uparts['base_uri'], partition='AFN', name='AIN',
422-
uri_as_parts=True)
423-
assert iCRS.session.patch.call_args ==\
424-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN', data=None)
412+
iCRS.patch(uparts['base_uri'], partition='AFN', name='AIN', uri_as_parts=True)
413+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
414+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
415+
assert iCRS.session.prepare_request.call_args[0][0].data == []
425416

426417

427418
def test_wrapped_patch_207_fail(iCRS, uparts):
428-
iCRS.session.patch.return_value.status_code = 207
429-
with pytest.raises(session.iControlUnexpectedHTTPError) as CHE:
430-
iCRS.patch(uparts['base_uri'], partition='A_FOLDER_NAME',
431-
name='AN_INSTANCE_NAME')
432-
assert str(CHE.value).startswith('207 Unexpected Error: ')
419+
iCRS.session.send.return_value.status_code = 207
420+
with pytest.raises(session.iControlUnexpectedHTTPError) as ex:
421+
iCRS.patch(uparts['base_uri'], partition='A_FOLDER_NAME', name='AN_INSTANCE_NAME')
422+
assert str(ex.value).startswith('207 Unexpected Error: ')
433423

434424

435425
def test_wrapped_put_207_fail(iCRS, uparts):
436-
iCRS.session.put.return_value.status_code = 207
437-
with pytest.raises(session.iControlUnexpectedHTTPError) as CHE:
438-
iCRS.put(uparts['base_uri'], partition='A_FOLDER_NAME',
439-
name='AN_INSTANCE_NAME')
440-
assert str(CHE.value).startswith('207 Unexpected Error: ')
426+
iCRS.session.send.return_value.status_code = 207
427+
with pytest.raises(session.iControlUnexpectedHTTPError) as ex:
428+
iCRS.put(uparts['base_uri'], partition='A_FOLDER_NAME', name='AN_INSTANCE_NAME')
429+
assert str(ex.value).startswith('207 Unexpected Error: ')
441430

442431

443432
def test_wrapped_post_207_fail(iCRS, uparts):
444-
iCRS.session.post.return_value.status_code = 207
445-
with pytest.raises(session.iControlUnexpectedHTTPError) as CHE:
446-
iCRS.post(uparts['base_uri'], partition='A_FOLDER_NAME',
447-
name='AN_INSTANCE_NAME')
448-
assert str(CHE.value).startswith('207 Unexpected Error: ')
433+
iCRS.session.send.return_value.status_code = 207
434+
with pytest.raises(session.iControlUnexpectedHTTPError) as ex:
435+
iCRS.post(uparts['base_uri'], partition='A_FOLDER_NAME', name='AN_INSTANCE_NAME')
436+
assert str(ex.value).startswith('207 Unexpected Error: ')
449437

450438

451439
def test_wrapped_post_success(iCRS, uparts):
452-
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN',
453-
uri_as_parts=True)
454-
assert iCRS.session.post.call_args ==\
455-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN', data=None,
456-
json=None)
440+
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN', uri_as_parts=True)
441+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
442+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
443+
assert iCRS.session.prepare_request.call_args[0][0].data == []
444+
assert iCRS.session.prepare_request.call_args[0][0].json is None
457445

458446

459447
def test_wrapped_post_success_with_data(iCRS, uparts):
460-
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN', data={'a': 1},
461-
uri_as_parts=True)
462-
assert iCRS.session.post.call_args ==\
463-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN',
464-
data={'a': 1}, json=None)
448+
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN', data={'a': 1}, uri_as_parts=True)
449+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
450+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
451+
assert iCRS.session.prepare_request.call_args[0][0].data == {'a': 1}
452+
assert iCRS.session.prepare_request.call_args[0][0].json is None
465453

466454

467455
def test_wrapped_post_success_with_json(iCRS, uparts):
468-
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN', json='{"a": 1}',
469-
uri_as_parts=True)
470-
assert iCRS.session.post.call_args ==\
471-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN', data=None,
472-
json='{"a": 1}')
456+
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN', json='{"a": 1}', uri_as_parts=True)
457+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
458+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
459+
assert iCRS.session.prepare_request.call_args[0][0].data == []
460+
assert iCRS.session.prepare_request.call_args[0][0].json == '{"a": 1}'
473461

474462

475463
def test_wrapped_post_success_with_json_and_data(iCRS, uparts):
476-
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN', data={'a': 1},
477-
json='{"a": 1}', uri_as_parts=True)
478-
assert iCRS.session.post.call_args ==\
479-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN',
480-
data={'a': 1}, json='{"a": 1}')
464+
iCRS.post(uparts['base_uri'], partition='AFN', name='AIN', data={'a': 1}, json='{"a": 1}', uri_as_parts=True)
465+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
466+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
467+
assert iCRS.session.prepare_request.call_args[0][0].data == {'a': 1}
468+
assert iCRS.session.prepare_request.call_args[0][0].json == '{"a": 1}'
481469

482470

483471
def test_wrapped_put_success(iCRS, uparts):
484-
iCRS.put(uparts['base_uri'], partition='AFN', name='AIN',
485-
uri_as_parts=True)
486-
assert iCRS.session.put.call_args ==\
487-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN', data=None)
472+
iCRS.put(uparts['base_uri'], partition='AFN', name='AIN', uri_as_parts=True)
473+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
474+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
475+
assert iCRS.session.prepare_request.call_args[0][0].data == []
488476

489477

490478
def test_wrapped_put_success_with_data(iCRS, uparts):
491-
iCRS.put(uparts['base_uri'], partition='AFN', name='AIN', data={'b': 2},
492-
uri_as_parts=True)
493-
assert iCRS.session.put.call_args ==\
494-
mock.call('https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN',
495-
data={'b': 2})
479+
iCRS.put(uparts['base_uri'], partition='AFN', name='AIN', data={'b': 2}, uri_as_parts=True)
480+
assert isinstance(iCRS.session.prepare_request.call_args[0][0], requests.Request)
481+
assert iCRS.session.prepare_request.call_args[0][0].url == 'https://0.0.0.0/mgmt/tm/root/RESTiface/~AFN~AIN'
482+
assert iCRS.session.prepare_request.call_args[0][0].data == {'b': 2}
496483

497484

498485
def test___init__user_agent():
@@ -531,7 +518,8 @@ def test__init__without_verify():
531518

532519

533520
def test__init__with_verify():
534-
icrs = session.iControlRESTSession('test_name', 'test_pw',
535-
token=True, verify='/path/to/cert')
521+
icrs = session.iControlRESTSession(
522+
'test_name', 'test_pw', token=True, verify='/path/to/cert'
523+
)
536524
assert icrs.session.verify is '/path/to/cert'
537525
assert icrs.session.auth.verify is '/path/to/cert'

requirements.test.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,5 @@ git+https://github.com/F5Networks/pytest-symbols.git
1313
python-coveralls==2.7.0
1414
pyOpenSSL==16.2.0
1515
requests-mock==1.1.0
16-
tox
16+
tox
17+
six

setup.cfg

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
[bdist_rpm]
2-
requires = python-requests >= 2.5.0
2+
requires = six>=1.9.0
3+
six<2.0.0
4+
python-requests >= 2.5.0

0 commit comments

Comments
 (0)