From: David Johansen Date: Tue, 29 Oct 2013 02:29:17 +0000 (-0700) Subject: Added ability to query slaves for executors. X-Git-Tag: v0.2.23~64^2~2 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=b1efd11f8d96ce5e25960681059931578a9575b0;p=tools%2Fpython-jenkinsapi.git Added ability to query slaves for executors. - can pull list of executor objects for each slave - added unit tests for new code. --- diff --git a/jenkinsapi/__init__.py b/jenkinsapi/__init__.py index f6810da..9829b5a 100644 --- a/jenkinsapi/__init__.py +++ b/jenkinsapi/__init__.py @@ -52,13 +52,13 @@ from jenkinsapi import ( utils, # Files - api, artifact, build, config, constants, custom_exceptions, fingerprint, + api, artifact, build, config, constants, custom_exceptions, fingerprint, executors, executor, jenkins, jenkinsbase, job, node, result_set, result, view ) __all__ = [ "command_line", "utils", - "api", "artifact", "build", "config", "constants", "custom_exceptions", "fingerprint", - "jenkins", "jenkinsbase", "job", "node", "result_set", "result", "view" + "api", "artifact", "build", "config", "constants", "custom_exceptions", "executors", "executor", + "fingerprint", "jenkins", "jenkinsbase", "job", "node", "result_set", "result", "view" ] __docformat__ = "epytext" diff --git a/jenkinsapi/executor.py b/jenkinsapi/executor.py new file mode 100644 index 0000000..531713c --- /dev/null +++ b/jenkinsapi/executor.py @@ -0,0 +1,68 @@ +""" +Module for jenkinsapi Executer class +""" + +from jenkinsapi.jenkinsbase import JenkinsBase +import logging + +log = logging.getLogger(__name__) + + +class Executor(JenkinsBase): + """ + Class to hold information on nodes that are attached as slaves to the + master jenkins instance + """ + + def __init__(self, baseurl, nodename, jenkins_obj, number): + """ + Init a node object by providing all relevant pointers to it + :param baseurl: basic url for querying information on a node + :param nodename: hostname of the node + :param jenkins_obj: ref to the jenkins obj + :return: Node obj + """ + self.nodename = nodename + self.number = number + self.jenkins = jenkins_obj + self.baseurl = baseurl + JenkinsBase.__init__(self, baseurl) + + def __str__(self): + return '%s %s' % (self.nodename, self.number) + + def get_jenkins_obj(self): + return self.jenkins + + def get_progress(self): + """Returns percentage""" + self.poll() + return self._data['progress'] + + def get_number(self): + """ + Get Executor number. + """ + self.poll() + return self._data[''] + + def is_idle(self): + """ + Returns Boolean: whether Executor is idle or not. + """ + self.poll() + return self._data['idle'] + + def likely_stuck(self): + """ + Returns Boolean: whether Executor is likely stuck or not. + """ + self.poll() + return self._data['likelyStuck'] + + def get_current_executable(self): + """ + Returns the current Queue.Task this executor is running. + """ + self.poll() + return self._data['currentExecutable'] diff --git a/jenkinsapi/executors.py b/jenkinsapi/executors.py new file mode 100644 index 0000000..5199214 --- /dev/null +++ b/jenkinsapi/executors.py @@ -0,0 +1,44 @@ +""" +This module implements the Executors class, which is intended to be a +container-like interface for all of the executors defined on a single +Jenkins node. +""" +import logging +from jenkinsapi.executor import Executor +from jenkinsapi.custom_exceptions import JenkinsAPIException +from jenkinsapi.jenkinsbase import JenkinsBase + +log = logging.getLogger(__name__) + + +class Executors(JenkinsBase): + """ + This class provides a container-like API which gives + access to all executors on a Jenkins node. + + Returns a list of Executor Objects. + """ + def __init__(self, baseurl, nodename, jenkins): + self.nodename = nodename + self.jenkins = jenkins + JenkinsBase.__init__(self, baseurl) + self.count = self._data['numExecutors'] + + def __str__(self): + return 'Executors @ %s' % self.baseurl + + def get_jenkins_obj(self): + return self.jenkins + + def __iter__(self): + for index in range(self.count): + executor_url = '%s/executors/%s' % (self.baseurl, index) + try: + yield Executor( + executor_url, + self.nodename, + self.jenkins, + index + ) + except JenkinsAPIException as exe: + log.error("Error querying Executors: %s", (exe,)) diff --git a/jenkinsapi/jenkins.py b/jenkinsapi/jenkins.py index 13c4e20..0a7216d 100644 --- a/jenkinsapi/jenkins.py +++ b/jenkinsapi/jenkins.py @@ -9,6 +9,7 @@ import logging import urlparse from jenkinsapi import config +from jenkinsapi.executors import Executors from jenkinsapi.job import Job from jenkinsapi.jobs import Jobs from jenkinsapi.node import Node @@ -307,3 +308,7 @@ class Jenkins(JenkinsBase): def has_plugin(self, plugin_name): return plugin_name in self.get_plugins() + + def get_executors(self, nodename): + url = '%s/computer/%s' % (self.baseurl, nodename) + return Executors(url, nodename, self) diff --git a/jenkinsapi_tests/unittests/test_executors.py b/jenkinsapi_tests/unittests/test_executors.py new file mode 100644 index 0000000..453721f --- /dev/null +++ b/jenkinsapi_tests/unittests/test_executors.py @@ -0,0 +1,323 @@ +import mock +import types +import unittest + +from jenkinsapi.jenkins import Jenkins +from jenkinsapi.executors import Executors +from jenkinsapi.executor import Executor + + +class TestExecutors(unittest.TestCase): + + DATAM = { + 'assignedLabels': [{}], + 'description': None, + 'jobs': [], + 'mode': 'NORMAL', + 'nodeDescription': 'the master Jenkins node', + 'nodeName': '', + 'numExecutors': 2, + 'overallLoad': {}, + 'primaryView': {'name': 'All', 'url': 'http://localhost:8080/'}, + 'quietingDown': False, + 'slaveAgentPort': 0, + 'unlabeledLoad': {}, + 'useCrumbs': False, + 'useSecurity': False, + 'views': [ + {'name': 'All', 'url': 'http://localhost:8080/'}, + {'name': 'BigMoney', 'url': 'http://localhost:8080/view/BigMoney/'} + ] + } + + DATA0 = { + "actions": [ + ], + "displayName": "host0.host.com", + "executors": [ + {}, + {}, + {}, + {}, + {}, + {}, + {}, + {} + ], + "icon": "computer.png", + "idle": False, + "jnlpAgent": True, + "launchSupported": False, + "loadStatistics": { + + }, + "manualLaunchAllowed": True, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 8462417920, + "availableSwapSpace": 0, + "totalPhysicalMemory": 75858042880, + "totalSwapSpace": 0 + }, + "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 2 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "path": "/tmp", + "size": 430744551424 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "path": "/data/jenkins", + "size": 1214028627968 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": 1 + } + }, + "numExecutors": 8, + "offline": False, + "offlineCause": None, + "offlineCauseReason": "", + "oneOffExecutors": [ + {}, + {} + ], + "temporarilyOffline": False + } + + DATA1 = { + "actions": [ + ], + "displayName": "host1.host.com", + "executors": [ + {}, + {} + ], + "icon": "computer.png", + "idle": False, + "jnlpAgent": True, + "launchSupported": False, + "loadStatistics": { + }, + "manualLaunchAllowed": True, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 8462417920, + "availableSwapSpace": 0, + "totalPhysicalMemory": 75858042880, + "totalSwapSpace": 0 + }, + "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 2 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "path": "/tmp", + "size": 430744551424 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "path": "/data/jenkins", + "size": 1214028627968 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": 1 + } + }, + "numExecutors": 2, + "offline": False, + "offlineCause": None, + "offlineCauseReason": "", + "oneOffExecutors": [ + {}, + {} + ], + "temporarilyOffline": False + } + + DATA2 = { + "actions": [ + ], + "displayName": "host2.host.com", + "executors": [ + {}, + {}, + {}, + {} + ], + "icon": "computer.png", + "idle": False, + "jnlpAgent": True, + "launchSupported": False, + "loadStatistics": { + }, + "manualLaunchAllowed": True, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 8462417920, + "availableSwapSpace": 0, + "totalPhysicalMemory": 75858042880, + "totalSwapSpace": 0 + }, + "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 2 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "path": "/tmp", + "size": 430744551424 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "path": "/data/jenkins", + "size": 1214028627968 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": 1 + } + }, + "numExecutors": 4, + "offline": False, + "offlineCause": None, + "offlineCauseReason": "", + "oneOffExecutors": [ + {}, + {} + ], + "temporarilyOffline": False + } + + DATA3 = { + "actions": [ + ], + "displayName": "host3.host.com", + "executors": [ + {} + ], + "icon": "computer.png", + "idle": False, + "jnlpAgent": True, + "launchSupported": False, + "loadStatistics": {}, + "manualLaunchAllowed": True, + "monitorData": { + "hudson.node_monitors.SwapSpaceMonitor": { + "availablePhysicalMemory": 8462417920, + "availableSwapSpace": 0, + "totalPhysicalMemory": 75858042880, + "totalSwapSpace": 0 + }, + "hudson.node_monitors.ArchitectureMonitor": "Linux (amd64)", + "hudson.node_monitors.ResponseTimeMonitor": { + "average": 2 + }, + "hudson.node_monitors.TemporarySpaceMonitor": { + "path": "/tmp", + "size": 430744551424 + }, + "hudson.node_monitors.DiskSpaceMonitor": { + "path": "/data/jenkins", + "size": 1214028627968 + }, + "hudson.node_monitors.ClockMonitor": { + "diff": 1 + } + }, + "numExecutors": 1, + "offline": False, + "offlineCause": None, + "offlineCauseReason": "", + "oneOffExecutors": [ + {}, + {} + ], + "temporarilyOffline": False + } + + EXEC0 = { + "currentExecutable": { + "number": 4168, + "url": "http://localhost:8080/job/testjob/4168/" + }, + "currentWorkUnit": {}, + "idle": False, + "likelyStuck": False, + "number": 0, + "progress": 48 + } + + EXEC1 = { + "currentExecutable": None, + "currentWorkUnit": None, + "idle": True, + "likelyStuck": False, + "number": 0, + "progress": -1 + } + + @mock.patch.object(Jenkins, '_poll') + def setUp(self, _poll_jenkins): + _poll_jenkins.return_value = self.DATAM + self.J = Jenkins('http://localhost:8080') + + def testRepr(self): + # Can we produce a repr string for this object + assert repr(self.J) + + def testCheckURL(self): + self.assertEquals(self.J.baseurl, 'http://localhost:8080') + + @mock.patch.object(Executors, '_poll') + @mock.patch.object(Executor, '_poll') + def testGetExecutors(self, _poll_executor, _poll_executors): + _poll_executor.return_value = self.EXEC0 + _poll_executors.return_value = self.DATA3 + exec_info = self.J.get_executors(self.DATA3['displayName']) + + self.assertIsInstance(exec_info, object) + self.assertIsInstance(repr(exec_info), str) + + for e in exec_info: + self.assertEquals(e.get_progress(), 48, 'Should return 48 %') + + @mock.patch.object(Executors, '_poll') + @mock.patch.object(Executor, '_poll') + def testis_idle(self, _poll_executor, _poll_executors): + _poll_executor.return_value = self.EXEC1 + _poll_executors.return_value = self.DATA3 + exec_info = self.J.get_executors('host3.host.com') + + self.assertIsInstance(exec_info, object) + for e in exec_info: + self.assertEquals(e.get_progress(), -1, 'Should return 48 %') + self.assertEquals(e.is_idle(), True, 'Should return True') + self.assertEquals( + repr(e), + '' + ) + + @mock.patch.object(Executor, '_poll') + def test_likely_stuck(self, _poll_executor): + _poll_executor.return_value = self.EXEC0 + baseurl = 'http://localhost:8080/computer/host0.host.com/executors/0' + nodename = 'host0.host.com' + single_executer = Executor(baseurl, nodename, self.J, '0') + self.assertEquals(single_executer.likely_stuck(), False) + + @mock.patch.object(Executor, '_poll') + def test_get_current_executable(self, _poll_executor): + _poll_executor.return_value = self.EXEC0 + baseurl = 'http://localhost:8080/computer/host0.host.com/executors/0' + nodename = 'host0.host.com' + single_executer = Executor(baseurl, nodename, self.J, '0') + self.assertEquals( + single_executer.get_current_executable()['number'], + 4168 + ) + self.assertEquals( + single_executer.get_current_executable()['url'], + 'http://localhost:8080/job/testjob/4168/' + ) + + +if __name__ == '__main__': + unittest.main()