diff options
author | Salim Fadhley <sal@stodge.org> | 2014-08-02 01:15:34 +0100 |
---|---|---|
committer | Salim Fadhley <sal@stodge.org> | 2014-08-02 01:15:34 +0100 |
commit | a4c3fab827673da3c70e834ffd4d362f24190de1 (patch) | |
tree | cf14737ee07f7c19590b564b0669e31bd73813a9 | |
parent | 53ca463e6dad71352608b19224cecad06df5858e (diff) | |
download | python-jenkinsapi-a4c3fab827673da3c70e834ffd4d362f24190de1.tar.gz python-jenkinsapi-a4c3fab827673da3c70e834ffd4d362f24190de1.tar.bz2 python-jenkinsapi-a4c3fab827673da3c70e834ffd4d362f24190de1.zip |
reducing the number of errors from the refactor of invocation/queues
-rw-r--r-- | jenkinsapi/custom_exceptions.py | 7 | ||||
-rw-r--r-- | jenkinsapi/jenkinsbase.py | 7 | ||||
-rw-r--r-- | jenkinsapi/job.py | 98 | ||||
-rw-r--r-- | jenkinsapi/queue.py | 70 | ||||
-rw-r--r-- | jenkinsapi/utils/requester.py | 12 | ||||
-rw-r--r-- | jenkinsapi_tests/systests/job_configs.py | 2 | ||||
-rw-r--r-- | jenkinsapi_tests/systests/test_invocation.py | 72 | ||||
-rw-r--r-- | jenkinsapi_tests/systests/test_queue.py | 1 |
8 files changed, 155 insertions, 114 deletions
diff --git a/jenkinsapi/custom_exceptions.py b/jenkinsapi/custom_exceptions.py index ce536fa..f3bf286 100644 --- a/jenkinsapi/custom_exceptions.py +++ b/jenkinsapi/custom_exceptions.py @@ -66,6 +66,13 @@ class NoBuildData(NotFound): pass +class NotBuiltYet(NotFound): + """ + A job has no build data. + """ + pass + + class ArtifactBroken(JenkinsAPIException): """ An artifact is broken, wrong diff --git a/jenkinsapi/jenkinsbase.py b/jenkinsapi/jenkinsbase.py index 6287dfb..d0974dc 100644 --- a/jenkinsapi/jenkinsbase.py +++ b/jenkinsapi/jenkinsbase.py @@ -3,6 +3,7 @@ Module for JenkinsBase class """ import ast +import pprint import logging from jenkinsapi import config from jenkinsapi.custom_exceptions import JenkinsAPIException @@ -54,6 +55,7 @@ class JenkinsBase(object): self._data = self._poll() if 'jobs' in self._data: self._data['jobs'] = self.resolve_job_folders(self._data['jobs']) + return self def _poll(self): url = self.python_api_url(self.baseurl) @@ -71,6 +73,11 @@ class JenkinsBase(object): logging.exception('Inappropriate content found at %s', url) raise JenkinsAPIException('Cannot parse %s' % response.content) + def pprint(self): + """Print out all the data in this object for debugging. + """ + pprint.pprint(self._data) + def resolve_job_folders(self, jobs): for job in list(jobs): if 'color' not in job.keys(): diff --git a/jenkinsapi/job.py b/jenkinsapi/job.py index b0ceb48..9f79529 100644 --- a/jenkinsapi/job.py +++ b/jenkinsapi/job.py @@ -15,7 +15,7 @@ import xml.etree.ElementTree as ET from collections import defaultdict from time import sleep from jenkinsapi.build import Build -from jenkinsapi.invocation import Invocation +from jenkinsapi.queue import QueueItem from jenkinsapi.jenkinsbase import JenkinsBase from jenkinsapi.queue import QueueItem from jenkinsapi.mutable_jenkins_thing import MutableJenkinsThing @@ -45,6 +45,7 @@ class Job(JenkinsBase, MutableJenkinsThing): Represents a jenkins job A job can hold N builds which are the actual execution environments """ + def __init__(self, url, name, jenkins_obj): self.name = name self.jenkins = jenkins_obj @@ -107,7 +108,8 @@ class Job(JenkinsBase, MutableJenkinsThing): and updates it with the missing builds if needed.''' if not data.get("builds"): return data - # do not call _buildid_for_type here: it would poll and do an infinite loop + # do not call _buildid_for_type here: it would poll and do an infinite + # loop oldest_loaded_build_number = data["builds"][-1]["number"] if not data['firstBuild']: first_build_number = oldest_loaded_build_number @@ -117,7 +119,8 @@ class Job(JenkinsBase, MutableJenkinsThing): if all_builds_loaded: return data api_url = self.python_api_url(self.baseurl) - response = self.get_data(api_url, params={'tree': 'allBuilds[number,url]'}) + response = self.get_data( + api_url, params={'tree': 'allBuilds[number,url]'}) data['builds'] = response['allBuilds'] return data @@ -170,74 +173,39 @@ class Job(JenkinsBase, MutableJenkinsThing): assert isinstance(block, bool) assert isinstance(skip_if_running, bool) - # Create a new invocation instance - invocation = Invocation(self) # Either copy the params dict or make a new one. build_params = build_params and dict( build_params.items()) or {} # Via POSTed JSON params = {} # Via Get string - with invocation: - if len(self.get_params_list()) == 0: - if self.is_queued(): - raise WillNotBuild('%s is already queued' % repr(self)) - - elif self.is_running(): - if skip_if_running: - log.warn( - "Will not request new build because %s is already running", self.name) - else: - log.warn( - "Will re-schedule %s even though it is already running", self.name) - elif self.has_queued_build(build_params): - msg = 'A build with these parameters is already queued.' - raise WillNotBuild(msg) - - log.info("Attempting to start %s on %s", self.name, repr( - self.get_jenkins_obj())) - - url = self.get_build_triggerurl() - # If job has file parameters - it must be triggered - # using "/build", not by "/buildWithParameters" - # "/buildWithParameters" will ignore non-file parameters - if files: - url = "%s/build" % self.baseurl - - if cause: - build_params['cause'] = cause - - if securitytoken: - params['token'] = securitytoken - - build_params['json'] = self.mk_json_from_build_parameters(build_params, files) - data = build_params - - response = self.jenkins.requester.post_and_confirm_status( - url, - data=data, - params=params, - files=files, - valid=[200, 201] - ) - response = response - if invoke_pre_check_delay > 0: - log.info( - "Waiting for %is to allow Jenkins to catch up", invoke_pre_check_delay) - sleep(invoke_pre_check_delay) - if block: - total_wait = 0 - - while self.is_queued(): - log.info( - "Waited %is for %s to begin...", total_wait, self.name) - sleep(invoke_block_delay) - total_wait += invoke_block_delay - if self.is_running(): - running_build = self.get_last_build() - running_build.block_until_complete( - delay=invoke_pre_check_delay) - return invocation + url = self.get_build_triggerurl() + # If job has file parameters - it must be triggered + # using "/build", not by "/buildWithParameters" + # "/buildWithParameters" will ignore non-file parameters + if files: + url = "%s/build" % self.baseurl + + if cause: + build_params['cause'] = cause + + if securitytoken: + params['token'] = securitytoken + + build_params['json'] = self.mk_json_from_build_parameters( + build_params, files) + data = build_params + + response = self.jenkins.requester.post_url( + url, + data=data, + params=params, + files=files, + ) + + queue_url = response.headers['location'] + qi = QueueItem(queue_url, self.jenkins) + return qi def _buildid_for_type(self, buildtype): """Gets a buildid for a given type of build""" diff --git a/jenkinsapi/queue.py b/jenkinsapi/queue.py index d0efc5b..967ccf0 100644 --- a/jenkinsapi/queue.py +++ b/jenkinsapi/queue.py @@ -3,8 +3,9 @@ Queue module for jenkinsapi """ from jenkinsapi.jenkinsbase import JenkinsBase -from jenkinsapi.custom_exceptions import UnknownQueueItem +from jenkinsapi.custom_exceptions import UnknownQueueItem, NotBuiltYet import logging +import time log = logging.getLogger(__name__) @@ -31,7 +32,9 @@ class Queue(JenkinsBase): def iteritems(self): for item in self._data['items']: - yield item['id'], QueueItem(self.jenkins, **item) + id = item['id'] + item_baseurl = "%s/item/%i" % (self.baseurl, id) + yield item['id'], QueueItem(baseurl=item_baseurl, jenkins_obj=self.jenkins) def iterkeys(self): for item in self._data['items']: @@ -74,15 +77,21 @@ class Queue(JenkinsBase): self.get_jenkins_obj().requester.post_url(deleteurl) -class QueueItem(object): - """ - Flexible class to handle queue items. - If the Jenkins API changes this support those changes +class QueueItem(JenkinsBase): + """An individual item in the queue """ - def __init__(self, jenkins, **kwargs): - self.jenkins = jenkins - self.__dict__.update(kwargs) + def __init__(self, baseurl, jenkins_obj): + self.jenkins = jenkins_obj + JenkinsBase.__init__(self, baseurl) + + @property + def id(self): + return self._data['id'] + + + def get_jenkins_obj(self): + return self.jenkins def get_job(self): """ @@ -105,4 +114,45 @@ class QueueItem(object): self.__class__.__name__, str(self)) def __str__(self): - return "%s #%i" % (self.task['name'], self.id) + return "%s Queue #%i" % (self._data['task']['name'], self._data['id']) + + def get_build(self): + build_number = self.get_build_number() + job_name = self.get_job_name() + return self.jenkins[job_name][build_number] + + + def block_until_complete(self, delay=15): + build = self.block_until_building(delay) + return build.block_until_complete(delay=delay) + + + def block_until_building(self, delay=5): + while True: + try: + return self.poll().get_build() + except NotBuiltYet: + time.sleep(delay) + continue + + + def is_running(self): + """Return True if this queued item is running. + """ + try: + return self.get_build().is_running() + except NotBuiltYet: + return False + + def get_build_number(self): + try: + return self._data['executable']['number'] + except KeyError: + raise NotBuiltYet() + + def get_job_name(self): + try: + return self._data['task']['name'] + except KeyError: + raise NotBuiltYet() +
\ No newline at end of file diff --git a/jenkinsapi/utils/requester.py b/jenkinsapi/utils/requester.py index e818f7a..50e077b 100644 --- a/jenkinsapi/utils/requester.py +++ b/jenkinsapi/utils/requester.py @@ -46,8 +46,8 @@ class Requester(object): self.password = password self.ssl_verify = ssl_verify - def get_request_dict(self, params=None, data=None, files=None, headers=None): - requestKwargs = {} + def get_request_dict(self, params=None, data=None, files=None, headers=None, **kwargs): + requestKwargs = kwargs if self.username: requestKwargs['auth'] = (self.username, self.password) @@ -90,12 +90,12 @@ class Requester(object): ) return url - def get_url(self, url, params=None, headers=None): - requestKwargs = self.get_request_dict(params=params, headers=headers) + def get_url(self, url, params=None, headers=None, allow_redirects=True): + requestKwargs = self.get_request_dict(params=params, headers=headers, allow_redirects=allow_redirects) return requests.get(self._update_url_scheme(url), **requestKwargs) - def post_url(self, url, params=None, data=None, files=None, headers=None): - requestKwargs = self.get_request_dict(params=params, data=data, files=files, headers=headers) + def post_url(self, url, params=None, data=None, files=None, headers=None, allow_redirects=True): + requestKwargs = self.get_request_dict(params=params, data=data, files=files, headers=headers, allow_redirects=allow_redirects) return requests.post(self._update_url_scheme(url), **requestKwargs) def post_xml_and_confirm_status(self, url, params=None, data=None, valid=None): diff --git a/jenkinsapi_tests/systests/job_configs.py b/jenkinsapi_tests/systests/job_configs.py index 2f127a4..c578121 100644 --- a/jenkinsapi_tests/systests/job_configs.py +++ b/jenkinsapi_tests/systests/job_configs.py @@ -61,7 +61,7 @@ SHORTISH_JOB = """ <concurrentBuild>false</concurrentBuild> <builders> <hudson.tasks.Shell> - <command>ping -c 10 localhost</command> + <command>ping -c 5 localhost</command> </hudson.tasks.Shell> </builders> <publishers/> diff --git a/jenkinsapi_tests/systests/test_invocation.py b/jenkinsapi_tests/systests/test_invocation.py index 9c0c230..aaf74c4 100644 --- a/jenkinsapi_tests/systests/test_invocation.py +++ b/jenkinsapi_tests/systests/test_invocation.py @@ -7,75 +7,83 @@ try: except ImportError: import unittest import time +import logging from jenkinsapi.build import Build -from jenkinsapi.invocation import Invocation +from jenkinsapi.queue import QueueItem from jenkinsapi_tests.systests.base import BaseSystemTest from jenkinsapi_tests.test_utils.random_strings import random_string from jenkinsapi_tests.systests.job_configs import LONG_RUNNING_JOB from jenkinsapi_tests.systests.job_configs import SHORTISH_JOB, EMPTY_JOB +log = logging.getLogger(__name__) + + class TestInvocation(BaseSystemTest): def test_invocation_object(self): job_name = 'create_%s' % random_string() - job = self.jenkins.create_job(job_name, LONG_RUNNING_JOB) - ii = job.invoke(invoke_pre_check_delay=7) - self.assertIsInstance(ii, Invocation) + job = self.jenkins.create_job(job_name, SHORTISH_JOB) + qq = job.invoke(invoke_pre_check_delay=7) + self.assertIsInstance(qq, QueueItem) # Let Jenkins catchup - time.sleep(3) - self.assertTrue(ii.is_queued_or_running()) - self.assertEquals(ii.get_build_number(), 1) + qq.block_until_building() + self.assertEquals(qq.get_build_number(), 1) def test_get_block_until_build_running(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, LONG_RUNNING_JOB) - ii = job.invoke(invoke_pre_check_delay=7) + qq = job.invoke(invoke_pre_check_delay=7) time.sleep(3) - bn = ii.get_build_number() + bn = qq.block_until_building(delay=3).get_number() self.assertIsInstance(bn, int) - ii.block(until='not_queued') - self.assertTrue(ii.is_running()) - b = ii.get_build() + + b = qq.get_build() self.assertIsInstance(b, Build) - ii.stop() - self.assertFalse(ii.is_running()) - self.assertIsInstance(ii.get_build().get_console(), str) - self.assertIn('Started by user', ii.get_build().get_console()) - + self.assertTrue(b.is_running()) + + b.stop() + time.sleep(1) + self.assertFalse(b.poll().is_running()) + console = b.get_console() + self.assertIsInstance(console, str) + self.assertIn('Started by user', console) + def test_get_block_until_build_complete(self): job_name = 'create_%s' % random_string() job = self.jenkins.create_job(job_name, SHORTISH_JOB) - ii = job.invoke() - ii.block(until='completed') - self.assertFalse(ii.is_running()) - + qq = job.invoke() + qq.block_until_complete() + self.assertFalse(qq.get_build().is_running()) + def test_multiple_invocations_and_get_last_build(self): job_name = 'create_%s' % random_string() - + job = self.jenkins.create_job(job_name, SHORTISH_JOB) - + for _ in range(3): ii = job.invoke() - ii.block(until='completed') - + ii.block_until_complete(delay=2) + build_number = job.get_last_good_buildnumber() self.assertEquals(build_number, 3) - + build = job.get_build(build_number) self.assertIsInstance(build, Build) - + def test_multiple_invocations_and_get_build_number(self): job_name = 'create_%s' % random_string() - + job = self.jenkins.create_job(job_name, EMPTY_JOB) - + for invocation in range(3): - ii = job.invoke() - ii.block(until='completed') - build_number = ii.get_build_number() + qq = job.invoke() + qq.block_until_complete(delay=1) + build_number = qq.get_build_number() self.assertEquals(build_number, invocation + 1) if __name__ == '__main__': +# logging.basicConfig() +# logging.getLogger("").setLevel(logging.INFO) unittest.main() diff --git a/jenkinsapi_tests/systests/test_queue.py b/jenkinsapi_tests/systests/test_queue.py index de95684..e53cf38 100644 --- a/jenkinsapi_tests/systests/test_queue.py +++ b/jenkinsapi_tests/systests/test_queue.py @@ -38,6 +38,7 @@ class TestQueue(BaseSystemTest): self.assertTrue(j.is_queued_or_running()) queue = self.jenkins.get_queue() + reprString = repr(queue) self.assertIn(queue.baseurl, reprString) |