diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py index 063cf8d156..73b5cd11aa 100755 --- a/tests/unit/test_web.py +++ b/tests/unit/test_web.py @@ -373,6 +373,13 @@ class TestWeb(BaseTestWeb): 'voting': True }], data) + def test_web_nodes_list(self): + # can we fetch the nodes list + data = self.get_url('api/tenant/tenant-one/nodes').json() + self.assertGreater(len(data), 0) + self.assertEqual("test-provider", data[0]["provider"]) + self.assertEqual("label1", data[0]["type"]) + def test_web_labels_list(self): # can we fetch the labels list data = self.get_url('api/tenant/tenant-one/labels').json() diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py index ff3154330d..a3a50cb1e8 100755 --- a/zuul/web/__init__.py +++ b/zuul/web/__init__.py @@ -361,6 +361,21 @@ class ZuulWebAPI(object): resp.headers['Access-Control-Allow-Origin'] = '*' return ret + @cherrypy.expose + @cherrypy.tools.save_params() + @cherrypy.tools.json_out(content_type='application/json; charset=utf-8') + def nodes(self, tenant): + ret = [] + for node in self.zk.nodeIterator(): + node_data = {} + for key in ("id", "type", "connection_type", "external_id", + "provider", "state", "state_time", "comment"): + node_data[key] = node.get(key) + ret.append(node_data) + resp = cherrypy.response + resp.headers['Access-Control-Allow-Origin'] = '*' + return ret + @cherrypy.expose @cherrypy.tools.save_params() def key(self, tenant, project): @@ -619,6 +634,8 @@ class ZuulWeb(object): controller=api, action='pipelines') route_map.connect('api', '/api/tenant/{tenant}/labels', controller=api, action='labels') + route_map.connect('api', '/api/tenant/{tenant}/nodes', + controller=api, action='nodes') route_map.connect('api', '/api/tenant/{tenant}/key/{project:.*}.pub', controller=api, action='key') route_map.connect('api', '/api/tenant/{tenant}/' diff --git a/zuul/zk.py b/zuul/zk.py index 0ccc1f6210..d35a5df1ac 100644 --- a/zuul/zk.py +++ b/zuul/zk.py @@ -384,6 +384,7 @@ class ZooKeeper(object): return count # Copy of nodepool/zk.py begins here + NODE_ROOT = "/nodepool/nodes" LAUNCHER_ROOT = "/nodepool/launchers" def _bytesToDict(self, data): @@ -392,6 +393,9 @@ class ZooKeeper(object): def _launcherPath(self, launcher): return "%s/%s" % (self.LAUNCHER_ROOT, launcher) + def _nodePath(self, node): + return "%s/%s" % (self.NODE_ROOT, node) + def getRegisteredLaunchers(self): ''' Get a list of all launchers that have registered with ZooKeeper. @@ -415,6 +419,46 @@ class ZooKeeper(object): objs.append(Launcher.fromDict(self._bytesToDict(data))) return objs + def getNodes(self): + ''' + Get the current list of all nodes. + + :returns: A list of nodes. + ''' + try: + return self.client.get_children(self.NODE_ROOT) + except kze.NoNodeError: + return [] + + def getNode(self, node): + ''' + Get the data for a specific node. + + :param str node: The node ID. + + :returns: The node data, or None if the node was not found. + ''' + path = self._nodePath(node) + try: + data, stat = self.client.get(path) + except kze.NoNodeError: + return None + if not data: + return None + + d = self._bytesToDict(data) + d['id'] = node + return d + + def nodeIterator(self): + ''' + Utility generator method for iterating through all nodes. + ''' + for node_id in self.getNodes(): + node = self.getNode(node_id) + if node: + yield node + class Launcher(): '''