Skip to content

Commit 037d244

Browse files
authored
Adds hard timeout for management initialization (#1333)
Issues: Fixes #541 Problem: The management root initialization may hang for longer than the specified timeout value Analysis: The requests library's underlying urllib3 code is doing retries when it fails to connect to a host or the connection is taking a long time. If you have a timeout set (for example 5), the default retries is 10 and therefore your timeout is actually 50 seconds. We can technically change this by providing a custom Retry() object to the `from requests.adapters import HTTPAdapter` adapter, but that would cause this retry to affect __all__ the API calls. We really only want to override the initial management root `_get_tmos_version` call. So we use the eventlet package to override this. If eventlet is not installed, the package will not be able to enforce this hard timeout and will pass on trying. By default, eventlet is installed with f5-sdk Tests: functional
1 parent 868360a commit 037d244

7 files changed

Lines changed: 45 additions & 7 deletions

File tree

devtools/bin/create-test-list.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ def determine_files_to_test(product, commit):
5959
results = []
6060
build_all = [
6161
'setup.py', 'f5/bigip/contexts.py', 'f5/bigip/mixins.py',
62-
'f5/bigip/resource.py', 'f5sdk_plugins/fixtures.py'
62+
'f5/bigip/resource.py', 'f5sdk_plugins/fixtures.py',
63+
'f5/bigip/__init__.py'
6364
]
6465
output_file = "pytest.{0}.jenkins.txt".format(product)
6566

f5-sdk-dist/deb_dist/stdeb.cfg

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ Depends:
33
python-six (>=1.9.0),
44
python-six (<2.0.0),
55
python-f5-icontrol-rest (>=1.3.0),
6-
python-f5-icontrol-rest (<2.0.0),
6+
python-f5-icontrol-rest (<2.0.0),
7+
python-eventlet (>=0.21.0),

f5/bigip/__init__.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,18 @@
2323
except ImportError:
2424
import urllib.parse as urlparse
2525

26+
try:
27+
import eventlet
28+
29+
# Workaround for bug 401 addressed here
30+
# https://github.com/eventlet/eventlet/issues/401#issuecomment-325015989
31+
#
32+
# This seems to happen on containers often.
33+
eventlet.hubs.get_hub()
34+
HAS_EVENTLET = True
35+
except ImportError:
36+
HAS_EVENTLET = False
37+
2638
from f5.bigip.cm import Cm
2739
from f5.bigip.resource import PathElement
2840
from f5.bigip.shared import Shared
@@ -35,6 +47,7 @@
3547
from f5.bigip.tm.sys import Sys
3648
from f5.bigip.tm import Tm
3749
from f5.bigip.tm.transaction import Transactions
50+
from f5.sdk_exception import TimeoutError
3851

3952

4053
class BaseManagement(PathElement):
@@ -73,6 +86,7 @@ def _get_icr_session(self, *args, **kwargs):
7386
params['auth_provider'] = kwargs['auth_provider']
7487
else:
7588
params['token'] = kwargs['token']
89+
7690
result = iControlRESTSession(**params)
7791
return result
7892

@@ -99,7 +113,15 @@ def post_configuration_setup(self):
99113
def _get_tmos_version(self):
100114
connect = self._meta_data['bigip']._meta_data['icr_session']
101115
base_uri = self._meta_data['uri'] + 'tm/sys/'
102-
response = connect.get(base_uri)
116+
if HAS_EVENTLET:
117+
eventlet.monkey_patch()
118+
try:
119+
with eventlet.Timeout(self.args['timeout']):
120+
response = connect.get(base_uri)
121+
except eventlet.Timeout:
122+
raise TimeoutError("Timed out waiting for response")
123+
else:
124+
response = connect.get(base_uri)
103125
ver = response.json()
104126
version = urlparse.parse_qs(
105127
urlparse.urlparse(ver['selfLink']).query)['ver'][0]

f5/bigip/test/functional/test__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import pytest
1616

1717
from f5.bigip import ManagementRoot
18+
from f5.sdk_exception import TimeoutError
1819

1920

2021
def test_invalid_args(opt_bigip, opt_username, opt_password, opt_port):
@@ -34,3 +35,10 @@ def test_tmos_version(mgmt_root):
3435
mgmt_root._meta_data['bigip']._meta_data['tmos_version']
3536
assert mgmt_root.tmos_version is not None
3637
assert mgmt_root._meta_data['bigip']._meta_data['tmos_version'] != ''
38+
39+
40+
def test_hard_timeout():
41+
# The IP and port here are set to these values deliberately. They should never resolve.
42+
with pytest.raises(TimeoutError) as ex:
43+
ManagementRoot('10.255.255.1', 'foo', 'bar', port=81, timeout=1)
44+
assert str(ex.value) == 'Timed out waiting for response'

f5/sdk_exception.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,3 +248,7 @@ def __init__(self, required_one_of):
248248
errors.append(error)
249249
msg = message.format(' or '.join(errors))
250250
super(RequiredOneOf, self).__init__(msg)
251+
252+
253+
class TimeoutError(F5SDKError):
254+
"""Raised when hard-timeout (timeout keyword to ManagementRoot) is met"""

setup.cfg

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
[bdist_rpm]
22
release = 1
3-
requires = f5-icontrol-rest >= 1.3.0
4-
f5-icontrol-rest < 2
5-
python-six >= 1.9
6-
python-six < 2
3+
requires = six>=1.9.0
4+
six<2.0.0
5+
f5-icontrol-rest>=1.3.0
6+
f5-icontrol-rest<2.0.0
7+
eventlet>=0.21.0

setup_requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ six>=1.9.0
33
six<2.0.0
44
f5-icontrol-rest>=1.3.0
55
f5-icontrol-rest<2.0.0
6+
eventlet>=0.21.0

0 commit comments

Comments
 (0)