diff --git a/jenkins/__init__.py b/jenkins/__init__.py index 829f726..3151ff2 100644 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -81,7 +81,7 @@ STOP_BUILD = 'job/%(name)s/%(number)s/stop' BUILD_WITH_PARAMS_JOB = 'job/%(name)s/buildWithParameters' BUILD_INFO = 'job/%(name)s/%(number)d/api/json?depth=%(depth)s' BUILD_CONSOLE_OUTPUT = 'job/%(name)s/%(number)d/consoleText' - +NODE_LIST = 'computer/api/json' CREATE_NODE = 'computer/doCreateItem?%s' DELETE_NODE = 'computer/%(name)s/doDelete' NODE_INFO = 'computer/%(name)s/api/json?depth=%(depth)s' @@ -648,6 +648,24 @@ class Jenkins(object): ''' self.jenkins_open(Request(self.server + STOP_BUILD % self._get_encoded_params(locals()))) + def get_nodes(self): + '''Get a list of nodes connected to the Master + + Each node is a dict with keys 'name' and 'offline' + + :returns: List of nodes, ``[ { str: str, str: bool} ]`` + ''' + try: + nodes_data = json.loads(self.jenkins_open(Request(self.server + NODE_LIST))) + return [{'name': c["displayName"], 'offline': c["offline"]} + for c in nodes_data["computer"]] + except (HTTPError, BadStatusLine): + raise BadHTTPException("Error communicating with server[%s]" + % self.server) + except ValueError: + raise JenkinsException("Could not parse JSON info for server[%s]" + % self.server) + def get_node_info(self, name, depth=0): '''Get node information dictionary diff --git a/tests/test_jenkins.py b/tests/test_jenkins.py index b59f798..2c6207e 100644 --- a/tests/test_jenkins.py +++ b/tests/test_jenkins.py @@ -1144,6 +1144,63 @@ class JenkinsTest(unittest.TestCase): jenkins_mock.call_args[0][0].get_full_url(), u'http://example.com/queue/cancelItem?id=52') + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_get_nodes(self, jenkins_mock): + jenkins_mock.return_value = json.dumps({ + "computer": [{ + "displayName": "master", + "offline": False + }], + "busyExecutors": 2}) + j = jenkins.Jenkins('http://example.com/', 'test', 'test') + self.assertEqual(j.get_nodes(), + [{'name': 'master', 'offline': False}]) + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_get_nodes__invalid_json(self, jenkins_mock): + jenkins_mock.side_effect = [ + 'Invalid JSON', + ] + j = jenkins.Jenkins('http://example.com/', 'test', 'test') + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + j.get_nodes() + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + 'http://example.com/computer/api/json') + self.assertEqual( + str(context_manager.exception), + 'Could not parse JSON info for server[http://example.com/]') + + @patch('jenkins.urlopen') + def test_get_nodes__BadStatusLine(self, urlopen_mock): + urlopen_mock.side_effect = jenkins.BadStatusLine('not a valid status line') + j = jenkins.Jenkins('http://example.com/', 'test', 'test') + with self.assertRaises(jenkins.BadHTTPException) as context_manager: + j.get_nodes() + self.assertEqual( + str(context_manager.exception), + 'Error communicating with server[http://example.com/]') + + @patch.object(jenkins.Jenkins, 'jenkins_open') + def test_get_nodes__HTTPError(self, jenkins_mock): + jenkins_mock.side_effect = jenkins.HTTPError( + 'http://example.com/job/TestJob', + code=401, + msg="basic auth failed", + hdrs=[], + fp=None) + j = jenkins.Jenkins('http://example.com/', 'test', 'test') + + with self.assertRaises(jenkins.JenkinsException) as context_manager: + j.get_nodes() + self.assertEqual( + jenkins_mock.call_args[0][0].get_full_url(), + 'http://example.com/computer/api/json') + self.assertEqual( + str(context_manager.exception), + 'Error communicating with server[http://example.com/]') + @patch.object(jenkins.Jenkins, 'jenkins_open') def test_get_node_info(self, jenkins_mock): node_info = {