From 672a617102bba30424e065c005624de9cde72fa1 Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 19 May 2016 16:21:35 +0200 Subject: [PATCH] Compute IDLE PC via controller Fix #1234 --- gns3server/compute/dynamips/nodes/router.py | 8 +++- gns3server/controller/compute.py | 24 +++++------ gns3server/controller/node.py | 19 ++++++++- .../handlers/api/controller/node_handler.py | 40 +++++++++++++++++++ gns3server/schemas/node.py | 5 +++ tests/controller/test_node.py | 21 ++++++++++ tests/handlers/api/controller/test_node.py | 27 +++++++++++++ 7 files changed, 128 insertions(+), 16 deletions(-) diff --git a/gns3server/compute/dynamips/nodes/router.py b/gns3server/compute/dynamips/nodes/router.py index ace73b78..38dd4c03 100644 --- a/gns3server/compute/dynamips/nodes/router.py +++ b/gns3server/compute/dynamips/nodes/router.py @@ -628,9 +628,11 @@ class Router(BaseNode): """ is_running = yield from self.is_running() + was_auto_started = False if not is_running: - # router is not running - raise DynamipsError('Router "{name}" is not running'.format(name=self._name)) + yield from self.start() + was_auto_started = True + yield from asyncio.sleep(20) # leave time to the router to boot log.info('Router "{name}" [{id}] has started calculating Idle-PC values'.format(name=self._name, id=self._id)) begin = time.time() @@ -638,6 +640,8 @@ class Router(BaseNode): log.info('Router "{name}" [{id}] has finished calculating Idle-PC values after {time:.4f} seconds'.format(name=self._name, id=self._id, time=time.time() - begin)) + if was_auto_started: + yield from self.stop() return idlepcs @asyncio.coroutine diff --git a/gns3server/controller/compute.py b/gns3server/controller/compute.py index db8eb89e..8fb6de37 100644 --- a/gns3server/controller/compute.py +++ b/gns3server/controller/compute.py @@ -157,12 +157,12 @@ class Compute: return response.content @asyncio.coroutine - def http_query(self, method, path, data=None): + def http_query(self, method, path, data=None, **kwargs): if not self._connected: yield from self._connect() if not self._connected: raise aiohttp.web.HTTPConflict(text="The server {} is not a GNS3 server".format(self._id)) - response = yield from self._run_http_query(method, path, data=data) + response = yield from self._run_http_query(method, path, data=data, **kwargs) return response @asyncio.coroutine @@ -202,8 +202,8 @@ class Compute: return "{}://{}:{}/v2/compute{}".format(self._protocol, self._host, self._port, path) @asyncio.coroutine - def _run_http_query(self, method, path, data=None): - with aiohttp.Timeout(10): + def _run_http_query(self, method, path, data=None, timeout=10): + with aiohttp.Timeout(timeout): url = self._getUrl(path) headers = {'content-type': 'application/json'} if data == {}: @@ -243,19 +243,19 @@ class Compute: return response @asyncio.coroutine - def get(self, path): - return (yield from self.http_query("GET", path)) + def get(self, path, **kwargs): + return (yield from self.http_query("GET", path, **kwargs)) @asyncio.coroutine - def post(self, path, data={}): - response = yield from self.http_query("POST", path, data) + def post(self, path, data={}, **kwargs): + response = yield from self.http_query("POST", path, data, **kwargs) return response @asyncio.coroutine - def put(self, path, data={}): - response = yield from self.http_query("PUT", path, data) + def put(self, path, data={}, **kwargs): + response = yield from self.http_query("PUT", path, data, **kwargs) return response @asyncio.coroutine - def delete(self, path): - return (yield from self.http_query("DELETE", path)) + def delete(self, path, **kwargs): + return (yield from self.http_query("DELETE", path, **kwargs)) diff --git a/gns3server/controller/node.py b/gns3server/controller/node.py index ceafa210..b2b6bab5 100644 --- a/gns3server/controller/node.py +++ b/gns3server/controller/node.py @@ -147,7 +147,7 @@ class Node: self._console_type = value elif key == "name": self._name = value - elif key in ["node_id", "project_id"]: + elif key in ["node_id", "project_id", "console_host"]: pass else: self._properties[key] = value @@ -166,7 +166,7 @@ class Node: # None properties are not be send. Because it can mean the emulator doesn't support it for key in list(data.keys()): - if data[key] is None: + if data[key] is None or key in ["console_host"]: del data[key] return data @@ -236,6 +236,20 @@ class Node: else: return (yield from self._compute.delete("/projects/{}/{}/nodes/{}{}".format(self._project.id, self._node_type, self._id, path))) + @asyncio.coroutine + def dynamips_auto_idlepc(self): + """ + Compute the idle PC for a dynamips node + """ + return (yield from self._compute.get("/projects/{}/{}/nodes/{}/auto_idlepc".format(self._project.id, self._node_type, self._id), timeout=240)).json + + @asyncio.coroutine + def dynamips_idlepc_proposals(self): + """ + Compute a list of potential idle PC + """ + return (yield from self._compute.get("/projects/{}/{}/nodes/{}/idlepc_proposals".format(self._project.id, self._node_type, self._id), timeout=240)).json + def __repr__(self): return "".format(self._node_type, self._name) @@ -248,6 +262,7 @@ class Node: "node_directory": self._node_directory, "name": self._name, "console": self._console, + "console_host": self._compute.host, "console_type": self._console_type, "command_line": self._command_line, "properties": self._properties, diff --git a/gns3server/handlers/api/controller/node_handler.py b/gns3server/handlers/api/controller/node_handler.py index c5ab6a8b..ca27478e 100644 --- a/gns3server/handlers/api/controller/node_handler.py +++ b/gns3server/handlers/api/controller/node_handler.py @@ -262,3 +262,43 @@ class NodeHandler: project = Controller.instance().get_project(request.match_info["project_id"]) yield from project.delete_node(request.match_info["node_id"]) response.set_status(204) + + @Route.get( + r"/projects/{project_id}/nodes/{node_id}/dynamips/auto_idlepc", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 204: "Instance reloaded", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Compute the IDLE PC for a Dynamips node") + def auto_idlepc(request, response): + + project = Controller.instance().get_project(request.match_info["project_id"]) + node = project.get_node(request.match_info["node_id"]) + idle = yield from node.dynamips_auto_idlepc() + response.json(idle) + response.set_status(200) + + @Route.get( + r"/projects/{project_id}/nodes/{node_id}/dynamips/idlepc_proposals", + parameters={ + "project_id": "Project UUID", + "node_id": "Node UUID" + }, + status_codes={ + 204: "Instance reloaded", + 400: "Invalid request", + 404: "Instance doesn't exist" + }, + description="Compute a list of potential idle PC for a node") + def idlepc_proposals(request, response): + + project = Controller.instance().get_project(request.match_info["project_id"]) + node = project.get_node(request.match_info["node_id"]) + idle = yield from node.dynamips_idlepc_proposals() + response.json(idle) + response.set_status(200) diff --git a/gns3server/schemas/node.py b/gns3server/schemas/node.py index 0a7f1262..c2edce9e 100644 --- a/gns3server/schemas/node.py +++ b/gns3server/schemas/node.py @@ -109,6 +109,11 @@ NODE_OBJECT_SCHEMA = { "maximum": 65535, "type": ["integer", "null"] }, + "console_host": { + "description": "Console host", + "type": "string", + "minLength": 1, + }, "console_type": { "description": "Console type", "enum": ["serial", "vnc", "telnet", None] diff --git a/tests/controller/test_node.py b/tests/controller/test_node.py index 0990d282..6217a1d1 100644 --- a/tests/controller/test_node.py +++ b/tests/controller/test_node.py @@ -55,6 +55,7 @@ def test_json(node, compute): "name": "demo", "console": node.console, "console_type": node.console_type, + "console_host": compute.host, "command_line": None, "node_directory": None, "properties": node.properties, @@ -172,3 +173,23 @@ def test_post(node, compute, async_run): def test_delete(node, compute, async_run): async_run(node.delete("/test")) compute.delete.assert_called_with("/projects/{}/vpcs/nodes/{}/test".format(node.project.id, node.id)) + + +def test_dynamips_idle_pc(node, async_run, compute): + node._node_type = "dynamips" + response = MagicMock() + response.json = {"idlepc": "0x60606f54"} + compute.get = AsyncioMagicMock(return_value=response) + + async_run(node.dynamips_auto_idlepc()) + compute.get.assert_called_with("/projects/{}/dynamips/nodes/{}/auto_idlepc".format(node.project.id, node.id)) + + +def test_dynamips_idlepc_proposals(node, async_run, compute): + node._node_type = "dynamips" + response = MagicMock() + response.json = ["0x60606f54", "0x30ff6f37"] + compute.get = AsyncioMagicMock(return_value=response) + + async_run(node.dynamips_idlepc_proposals()) + compute.get.assert_called_with("/projects/{}/dynamips/nodes/{}/idlepc_proposals".format(node.project.id, node.id)) diff --git a/tests/handlers/api/controller/test_node.py b/tests/handlers/api/controller/test_node.py index 1f0e7b87..b9cd6b1b 100644 --- a/tests/handlers/api/controller/test_node.py +++ b/tests/handlers/api/controller/test_node.py @@ -38,6 +38,7 @@ from gns3server.controller.node import Node def compute(http_controller, async_run): compute = MagicMock() compute.id = "example.com" + compute.host = "example.org" Controller.instance()._computes = {"example.com": compute} return compute @@ -107,6 +108,7 @@ def test_update_node(http_controller, tmpdir, project, compute, node): assert response.json["name"] == "test" assert "name" not in response.json["properties"] + def test_start_all_nodes(http_controller, tmpdir, project, compute): response = MagicMock() compute.post = AsyncioMagicMock() @@ -114,6 +116,7 @@ def test_start_all_nodes(http_controller, tmpdir, project, compute): response = http_controller.post("/projects/{}/nodes/start".format(project.id), example=True) assert response.status == 204 + def test_stop_all_nodes(http_controller, tmpdir, project, compute): response = MagicMock() compute.post = AsyncioMagicMock() @@ -121,6 +124,7 @@ def test_stop_all_nodes(http_controller, tmpdir, project, compute): response = http_controller.post("/projects/{}/nodes/stop".format(project.id), example=True) assert response.status == 204 + def test_suspend_all_nodes(http_controller, tmpdir, project, compute): response = MagicMock() compute.post = AsyncioMagicMock() @@ -128,6 +132,7 @@ def test_suspend_all_nodes(http_controller, tmpdir, project, compute): response = http_controller.post("/projects/{}/nodes/suspend".format(project.id), example=True) assert response.status == 204 + def test_reload_all_nodes(http_controller, tmpdir, project, compute): response = MagicMock() compute.post = AsyncioMagicMock() @@ -135,6 +140,7 @@ def test_reload_all_nodes(http_controller, tmpdir, project, compute): response = http_controller.post("/projects/{}/nodes/reload".format(project.id), example=True) assert response.status == 204 + def test_start_node(http_controller, tmpdir, project, compute, node): response = MagicMock() compute.post = AsyncioMagicMock() @@ -142,6 +148,7 @@ def test_start_node(http_controller, tmpdir, project, compute, node): response = http_controller.post("/projects/{}/nodes/{}/start".format(project.id, node.id), example=True) assert response.status == 204 + def test_stop_node(http_controller, tmpdir, project, compute, node): response = MagicMock() compute.post = AsyncioMagicMock() @@ -172,3 +179,23 @@ def test_delete_node(http_controller, tmpdir, project, compute, node): response = http_controller.delete("/projects/{}/nodes/{}".format(project.id, node.id), example=True) assert response.status == 204 + + +def test_dynamips_idle_pc(http_controller, tmpdir, project, compute, node): + response = MagicMock() + response.json = {"idlepc": "0x60606f54"} + compute.get = AsyncioMagicMock(return_value=response) + + response = http_controller.get("/projects/{}/nodes/{}/dynamips/auto_idlepc".format(project.id, node.id), example=True) + assert response.status == 200 + assert response.json["idlepc"] == "0x60606f54" + + +def test_dynamips_idlepc_proposals(http_controller, tmpdir, project, compute, node): + response = MagicMock() + response.json = ["0x60606f54", "0x33805a22"] + compute.get = AsyncioMagicMock(return_value=response) + + response = http_controller.get("/projects/{}/nodes/{}/dynamips/idlepc_proposals".format(project.id, node.id), example=True) + assert response.status == 200 + assert response.json == ["0x60606f54", "0x33805a22"]