diff --git a/gns3server/controller/link.py b/gns3server/controller/link.py index d9f44ded..034ece2d 100644 --- a/gns3server/controller/link.py +++ b/gns3server/controller/link.py @@ -21,9 +21,10 @@ import asyncio class Link: - def __init__(self): + def __init__(self, project): self._id = str(uuid.uuid4()) self._vms = [] + self._project = project @asyncio.coroutine def addVM(self, vm, adapter_number, port_number): diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py index 71f74e72..840731b5 100644 --- a/gns3server/controller/project.py +++ b/gns3server/controller/project.py @@ -98,7 +98,7 @@ class Project: """ Create a link. By default the link is empty """ - link = Link() + link = Link(self) self._links[link.id] = link return link diff --git a/gns3server/controller/udp_link.py b/gns3server/controller/udp_link.py new file mode 100644 index 00000000..a96b061b --- /dev/null +++ b/gns3server/controller/udp_link.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import asyncio + + +from .link import Link + + +class UDPLink(Link): + pass + + @asyncio.coroutine + def create(self): + """ + Create the link on the VMs + """ + vm1 = self._vms[0]["vm"] + adapter_number1 = self._vms[0]["adapter_number"] + port_number1 = self._vms[0]["port_number"] + vm2 = self._vms[1]["vm"] + adapter_number2 = self._vms[1]["adapter_number"] + port_number2 = self._vms[1]["port_number"] + + # Reserve a TCP port on both side + response = yield from vm1.post("/ports/udp".format(self._project.id)) + vm1_port = response.json["udp_port"] + response = yield from vm2.post("/ports/udp".format(self._project.id)) + vm2_port = response.json["udp_port"] + + # Create the tunnel on both side + data = { + "lport": vm1_port, + "rhost": vm2.hypervisor.host, + "rport": vm2_port, + "type": "nio_udp" + } + yield from vm1.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number1, port_number=port_number1), data=data) + + data = { + "lport": vm2_port, + "rhost": vm1.hypervisor.host, + "rport": vm1_port, + "type": "nio_udp" + } + yield from vm2.post("/adapters/{adapter_number}/ports/{port_number}/nio".format(adapter_number=adapter_number2, port_number=port_number2), data=data) + diff --git a/gns3server/controller/vm.py b/gns3server/controller/vm.py index d869e935..4b6f664b 100644 --- a/gns3server/controller/vm.py +++ b/gns3server/controller/vm.py @@ -72,6 +72,10 @@ class VM: def project(self): return self._project + @property + def hypervisor(self): + return self._hypervisor + @asyncio.coroutine def create(self): data = copy.copy(self._properties) @@ -81,6 +85,13 @@ class VM: data["console_type"] = self._console_type yield from self._hypervisor.post("/projects/{}/{}/vms".format(self._project.id, self._vm_type), data=data) + @asyncio.coroutine + def post(self, path, data={}): + """ + HTTP post on the VM + """ + return (yield from self._hypervisor.post("/projects/{}/{}/vms/{}{}".format(self._project.id, self._vm_type, self._id, path), data=data)) + def __json__(self): return { "hypervisor_id": self._hypervisor.id, diff --git a/tests/controller/test_link.py b/tests/controller/test_link.py index b97f7946..795c8fda 100644 --- a/tests/controller/test_link.py +++ b/tests/controller/test_link.py @@ -36,7 +36,7 @@ def hypervisor(): def test_addVM(async_run, project, hypervisor): vm1 = VM(project, hypervisor) - link = Link() + link = Link(project) async_run(link.addVM(vm1, 0, 4)) assert link._vms == [ { @@ -51,7 +51,7 @@ def test_json(async_run, project, hypervisor): vm1 = VM(project, hypervisor) vm2 = VM(project, hypervisor) - link = Link() + link = Link(project) async_run(link.addVM(vm1, 0, 4)) async_run(link.addVM(vm2, 1, 3)) assert link.__json__() == { diff --git a/tests/controller/test_udp_link.py b/tests/controller/test_udp_link.py new file mode 100644 index 00000000..11b1d43d --- /dev/null +++ b/tests/controller/test_udp_link.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python +# +# Copyright (C) 2016 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import pytest +import asyncio +from unittest.mock import MagicMock + +from gns3server.controller.project import Project +from gns3server.controller.hypervisor import Hypervisor +from gns3server.controller.udp_link import UDPLink +from gns3server.controller.vm import VM + + +@pytest.fixture +def project(): + return Project() + + +def test_create(async_run, project): + hypervisor1 = MagicMock() + hypervisor2 = MagicMock() + + vm1 = VM(project, hypervisor1, vm_type="vpcs") + vm2 = VM(project, hypervisor2, vm_type="vpcs") + + link = UDPLink(project) + async_run(link.addVM(vm1, 0, 4)) + async_run(link.addVM(vm2, 3, 1)) + + @asyncio.coroutine + def hypervisor1_callback(path, data={}): + """ + Fake server + """ + if "/ports/udp" in path: + response = MagicMock() + response.json = {"udp_port": 1024} + return response + + @asyncio.coroutine + def hypervisor2_callback(path, data={}): + """ + Fake server + """ + if "/ports/udp" in path: + response = MagicMock() + response.json = {"udp_port": 2048} + return response + + hypervisor1.post.side_effect = hypervisor1_callback + hypervisor1.host = "example.com" + hypervisor2.post.side_effect = hypervisor2_callback + hypervisor2.host = "example.org" + async_run(link.create()) + + hypervisor1.post.assert_any_call("/projects/{}/vpcs/vms/{}/adapters/0/ports/4/nio".format(project.id, vm1.id), data={ + "lport": 1024, + "rhost": hypervisor2.host, + "rport": 2048, + "type": "nio_udp" + }) + hypervisor2.post.assert_any_call("/projects/{}/vpcs/vms/{}/adapters/3/ports/1/nio".format(project.id, vm2.id), data={ + "lport": 2048, + "rhost": hypervisor1.host, + "rport": 1024, + "type": "nio_udp" + }) diff --git a/tests/controller/test_vm.py b/tests/controller/test_vm.py index 6c7f2cfd..2ba61da3 100644 --- a/tests/controller/test_vm.py +++ b/tests/controller/test_vm.py @@ -73,3 +73,8 @@ def test_create(vm, hypervisor, project, async_run): "name": "demo" } hypervisor.post.assert_called_with("/projects/{}/vpcs/vms".format(vm.project.id), data=data) + + +def test_post(vm, hypervisor, async_run): + async_run(vm.post("/test", {"a": "b"})) + hypervisor.post.assert_called_with("/projects/{}/vpcs/vms/{}/test".format(vm.project.id, vm.id), data={"a": "b"})