From 8436ed38b7af1a698516f7bcc840510c1e9bb9f9 Mon Sep 17 00:00:00 2001 From: Tristan Cacqueray Date: Sat, 17 Mar 2018 15:49:43 +0000 Subject: [PATCH] web: add /{tenant}/nodes route This change adds a /nodes route to return the nodes status. Change-Id: I81b495d29659f9a130c75f4c3f32cfd0f47ef15f --- tests/unit/test_web.py | 7 +++++++ zuul/web/__init__.py | 17 ++++++++++++++++ zuul/zk.py | 44 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py index d538fce13e..d11b4306b3 100755 --- a/tests/unit/test_web.py +++ b/tests/unit/test_web.py @@ -372,6 +372,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 95594ad0b7..2d0ec526a0 100755 --- a/zuul/web/__init__.py +++ b/zuul/web/__init__.py @@ -356,6 +356,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): @@ -594,6 +609,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 51fb48baba..fc6f44aa37 100644 --- a/zuul/zk.py +++ b/zuul/zk.py @@ -294,6 +294,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): @@ -302,6 +303,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. @@ -325,6 +329,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(): '''