Allow to wait for jenkins to enter normal operation
During Jenkins deployment via e.g. puppet jenkins might be started but not yet serving requests. Allow to wait for jenkins to enter normal operation mode and fail if it doesn't show up after N seconds. Allow for 0 seconds to just test and return success or failure without any waiting. This will allow us to avoid open coding "is jenkins ready?" for different provisioners like puppet, chef, salt, ansible, ... when this gets jused by jjb. Change-Id: I7fd8aed43f571528f27dac681cc1e1f77a0e0ad7 Co-Authored-By: Darragh Bailey <dbailey@hp.com>
This commit is contained in:
parent
1b08573a94
commit
0f6739103b
@ -235,3 +235,34 @@ where *prom_name* is the name of the promotion that will get added to the job.
|
||||
print server.get_promotion_config('prom_name', 'prom_job')
|
||||
|
||||
server.delete_promotion('prom_name', 'prom_job')
|
||||
|
||||
|
||||
Example 10: Waiting for Jenkins to be ready
|
||||
-------------------------------------------
|
||||
|
||||
It is possible to ask the API to wait for Jenkins to be ready with a given
|
||||
timeout. This can be used to aid launching of Jenkins and then waiting for the
|
||||
REST API to be responsive before continuing with subsequent configuration.
|
||||
|
||||
::
|
||||
# timeout here is the socket connection timeout, for each connection
|
||||
# attempt it will wait at most 5 seconds before assuming there is
|
||||
# nothing listening. Useful where firewalls may black hole connections.
|
||||
server = jenkins.Jenkins('http://localhost:8080', timeout=5)
|
||||
|
||||
# wait for at least 30 seconds for Jenkins to be ready
|
||||
if server.wait_for_normal_op(30):
|
||||
# actions once running
|
||||
...
|
||||
else:
|
||||
print("Jenkins failed to be ready in sufficient time")
|
||||
exit 2
|
||||
|
||||
Note that the timeout arg to `jenkins.Jenkins()` is the socket connection
|
||||
timeout. If you set this to be more than the timeout value passed to
|
||||
`wait_for_normal_op()`, then in cases where the underlying connection is not
|
||||
rejected (firewall black-hole, or slow connection) then `wait_for_normal_op()`
|
||||
may wait at least the connection timeout, or a multiple of it where multiple
|
||||
connection attempts are made. A connection timeout of 5 seconds and a wait
|
||||
timeout of 8 will result in potentially waiting 10 seconds if both connections
|
||||
attempts do not get responses.
|
||||
|
@ -252,6 +252,7 @@ def auth_headers(username, password):
|
||||
|
||||
|
||||
class Jenkins(object):
|
||||
_timeout_warning_issued = False
|
||||
|
||||
def __init__(self, url, username=None, password=None,
|
||||
timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
|
||||
@ -1642,3 +1643,61 @@ class Jenkins(object):
|
||||
info = self.get_info()
|
||||
if not info['quietingDown']:
|
||||
raise JenkinsException('quiet down failed')
|
||||
|
||||
def wait_for_normal_op(self, timeout):
|
||||
'''Wait for jenkins to enter normal operation mode.
|
||||
|
||||
:param timeout: number of seconds to wait, ``int``
|
||||
Note this is not the same as the connection timeout set via
|
||||
__init__ as that controls the socket timeout. Instead this is
|
||||
how long to wait until the status returned.
|
||||
:returns: ``True`` if Jenkins became ready in time, ``False``
|
||||
otherwise.
|
||||
|
||||
Setting timeout to be less than the configured connection timeout
|
||||
may result in this waiting for at least the connection timeout
|
||||
length of time before returning. It is recommended that the timeout
|
||||
here should be at least as long as any set connection timeout.
|
||||
'''
|
||||
if timeout < 0:
|
||||
raise ValueError("Timeout must be >= 0 not %d" % timeout)
|
||||
|
||||
if (not self._timeout_warning_issued and
|
||||
self.timeout != socket._GLOBAL_DEFAULT_TIMEOUT and
|
||||
timeout < self.timeout):
|
||||
warnings.warn("Requested timeout to wait for jenkins to resume "
|
||||
"normal operations is less than configured "
|
||||
"connection timeout. Unexpected behaviour may "
|
||||
"occur.")
|
||||
self._timeout_warning_issued = True
|
||||
|
||||
start_time = time.time()
|
||||
|
||||
def is_ready():
|
||||
# only call get_version until it returns without exception
|
||||
while True:
|
||||
if self.get_version():
|
||||
while True:
|
||||
# json API will only return valid info once Jenkins
|
||||
# is ready, so just check any known field exists
|
||||
# when not in normal mode, most requests will
|
||||
# be ignored or fail
|
||||
yield 'mode' in self.get_info()
|
||||
else:
|
||||
yield False
|
||||
|
||||
while True:
|
||||
try:
|
||||
if next(is_ready()):
|
||||
return True
|
||||
except (KeyError, JenkinsException):
|
||||
# key missing from JSON, empty response or errors in
|
||||
# get_info due to incomplete HTTP responses
|
||||
pass
|
||||
# check time passed as the communication will also
|
||||
# take time
|
||||
if time.time() > start_time + timeout:
|
||||
break
|
||||
time.sleep(1)
|
||||
|
||||
return False
|
||||
|
@ -222,3 +222,27 @@ class JenkinsOpenTest(JenkinsTestBase):
|
||||
jenkins_mock.call_args[0][0].get_full_url(),
|
||||
self.make_url('job/TestJob'))
|
||||
self._check_requests(jenkins_mock.call_args_list)
|
||||
|
||||
@patch.object(jenkins.Jenkins, 'jenkins_open',
|
||||
return_value=json.dumps({'mode': 'NORMAL'}))
|
||||
@patch.object(jenkins.Jenkins, 'get_version',
|
||||
return_value="Version42")
|
||||
def test_wait_for_normal_op(self, version_mock, jenkins_mock):
|
||||
j = jenkins.Jenkins('http://example.com', 'test', 'test')
|
||||
self.assertTrue(j.wait_for_normal_op(0))
|
||||
|
||||
@patch.object(jenkins.Jenkins, 'jenkins_open',
|
||||
side_effect=jenkins.EmptyResponseException())
|
||||
@patch.object(jenkins.Jenkins, 'get_version',
|
||||
side_effect=jenkins.EmptyResponseException())
|
||||
def test_wait_for_normal_op__empty_response(self, version_mock, jenkins_mock):
|
||||
j = jenkins.Jenkins('http://example.com', 'test', 'test')
|
||||
self.assertFalse(j.wait_for_normal_op(0))
|
||||
|
||||
def test_wait_for_normal_op__negative_timeout(self):
|
||||
j = jenkins.Jenkins('http://example.com', 'test', 'test')
|
||||
with self.assertRaises(ValueError) as context_manager:
|
||||
j.wait_for_normal_op(-1)
|
||||
self.assertEqual(
|
||||
str(context_manager.exception),
|
||||
"Timeout must be >= 0 not -1")
|
||||
|
Loading…
x
Reference in New Issue
Block a user