Skip to content

Commit

Permalink
Adds hard timeout for management initialization (#1333)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
caphrim007 authored Nov 30, 2017
1 parent 868360a commit 037d244
Show file tree
Hide file tree
Showing 7 changed files with 45 additions and 7 deletions.
3 changes: 2 additions & 1 deletion devtools/bin/create-test-list.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ def determine_files_to_test(product, commit):
results = []
build_all = [
'setup.py', 'f5/bigip/contexts.py', 'f5/bigip/mixins.py',
'f5/bigip/resource.py', 'f5sdk_plugins/fixtures.py'
'f5/bigip/resource.py', 'f5sdk_plugins/fixtures.py',
'f5/bigip/__init__.py'
]
output_file = "pytest.{0}.jenkins.txt".format(product)

Expand Down
3 changes: 2 additions & 1 deletion f5-sdk-dist/deb_dist/stdeb.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ Depends:
python-six (>=1.9.0),
python-six (<2.0.0),
python-f5-icontrol-rest (>=1.3.0),
python-f5-icontrol-rest (<2.0.0),
python-f5-icontrol-rest (<2.0.0),
python-eventlet (>=0.21.0),
24 changes: 23 additions & 1 deletion f5/bigip/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@
except ImportError:
import urllib.parse as urlparse

try:
import eventlet

# Workaround for bug 401 addressed here
# https://github.com/eventlet/eventlet/issues/401#issuecomment-325015989
#
# This seems to happen on containers often.
eventlet.hubs.get_hub()
HAS_EVENTLET = True
except ImportError:
HAS_EVENTLET = False

from f5.bigip.cm import Cm
from f5.bigip.resource import PathElement
from f5.bigip.shared import Shared
Expand All @@ -35,6 +47,7 @@
from f5.bigip.tm.sys import Sys
from f5.bigip.tm import Tm
from f5.bigip.tm.transaction import Transactions
from f5.sdk_exception import TimeoutError


class BaseManagement(PathElement):
Expand Down Expand Up @@ -73,6 +86,7 @@ def _get_icr_session(self, *args, **kwargs):
params['auth_provider'] = kwargs['auth_provider']
else:
params['token'] = kwargs['token']

result = iControlRESTSession(**params)
return result

Expand All @@ -99,7 +113,15 @@ def post_configuration_setup(self):
def _get_tmos_version(self):
connect = self._meta_data['bigip']._meta_data['icr_session']
base_uri = self._meta_data['uri'] + 'tm/sys/'
response = connect.get(base_uri)
if HAS_EVENTLET:
eventlet.monkey_patch()
try:
with eventlet.Timeout(self.args['timeout']):
response = connect.get(base_uri)
except eventlet.Timeout:
raise TimeoutError("Timed out waiting for response")
else:
response = connect.get(base_uri)
ver = response.json()
version = urlparse.parse_qs(
urlparse.urlparse(ver['selfLink']).query)['ver'][0]
Expand Down
8 changes: 8 additions & 0 deletions f5/bigip/test/functional/test__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import pytest

from f5.bigip import ManagementRoot
from f5.sdk_exception import TimeoutError


def test_invalid_args(opt_bigip, opt_username, opt_password, opt_port):
Expand All @@ -34,3 +35,10 @@ def test_tmos_version(mgmt_root):
mgmt_root._meta_data['bigip']._meta_data['tmos_version']
assert mgmt_root.tmos_version is not None
assert mgmt_root._meta_data['bigip']._meta_data['tmos_version'] != ''


def test_hard_timeout():
# The IP and port here are set to these values deliberately. They should never resolve.
with pytest.raises(TimeoutError) as ex:
ManagementRoot('10.255.255.1', 'foo', 'bar', port=81, timeout=1)
assert str(ex.value) == 'Timed out waiting for response'
4 changes: 4 additions & 0 deletions f5/sdk_exception.py
Original file line number Diff line number Diff line change
Expand Up @@ -248,3 +248,7 @@ def __init__(self, required_one_of):
errors.append(error)
msg = message.format(' or '.join(errors))
super(RequiredOneOf, self).__init__(msg)


class TimeoutError(F5SDKError):
"""Raised when hard-timeout (timeout keyword to ManagementRoot) is met"""
9 changes: 5 additions & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[bdist_rpm]
release = 1
requires = f5-icontrol-rest >= 1.3.0
f5-icontrol-rest < 2
python-six >= 1.9
python-six < 2
requires = six>=1.9.0
six<2.0.0
f5-icontrol-rest>=1.3.0
f5-icontrol-rest<2.0.0
eventlet>=0.21.0
1 change: 1 addition & 0 deletions setup_requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ six>=1.9.0
six<2.0.0
f5-icontrol-rest>=1.3.0
f5-icontrol-rest<2.0.0
eventlet>=0.21.0

0 comments on commit 037d244

Please sign in to comment.