From 46b6e7c5eef1bea202442aad952f6a39ba0b941d Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Wed, 14 Dec 2016 16:53:20 +0100 Subject: [PATCH] Fix hot link issues in Docker Fix #817 --- gns3server/compute/docker/docker_vm.py | 85 ++++++++------------------ tests/compute/docker/test_docker_vm.py | 41 +++++-------- 2 files changed, 42 insertions(+), 84 deletions(-) diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index c8004b21..e5e4c8eb 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -348,7 +348,7 @@ class DockerVM(BaseNode): yield from self._clean_servers() yield from self.manager.query("POST", "containers/{}/start".format(self._cid)) - namespace = yield from self._get_namespace() + self._namespace = yield from self._get_namespace() yield from self._start_ubridge() @@ -356,7 +356,7 @@ class DockerVM(BaseNode): nio = self._ethernet_adapters[adapter_number].get_nio(0) with (yield from self.manager.ubridge_lock): try: - yield from self._add_ubridge_connection(nio, adapter_number, namespace) + yield from self._add_ubridge_connection(nio, adapter_number) except UbridgeNamespaceError: yield from self.stop() @@ -626,13 +626,12 @@ class DockerVM(BaseNode): return @asyncio.coroutine - def _add_ubridge_connection(self, nio, adapter_number, namespace): + def _add_ubridge_connection(self, nio, adapter_number): """ Creates a connection in uBridge. :param nio: NIO instance or None if it's a dummy interface (if an interface is missing in ubridge you can't see it via ifconfig in the container) :param adapter_number: adapter number - :param namespace: Container namespace (pid) """ try: @@ -652,45 +651,34 @@ class DockerVM(BaseNode): yield from self._ubridge_send('bridge create bridge{}'.format(adapter_number)) yield from self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(adapter_number=adapter_number, hostif=adapter.host_ifc)) - log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, namespace) + log.debug("Move container %s adapter %s to namespace %s", self.name, adapter.host_ifc, self._namespace) try: yield from self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format(ifc=adapter.host_ifc, - ns=namespace, + ns=self._namespace, adapter=adapter_number)) except UbridgeError as e: raise UbridgeNamespaceError(e) - if isinstance(nio, NIOUDP): - yield from self._ubridge_send('bridge add_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(adapter=adapter_number, - lport=nio.lport, - rhost=nio.rhost, - rport=nio.rport)) - - if nio and nio.capturing: - yield from self._ubridge_send('bridge start_capture bridge{adapter} "{pcap_file}"'.format(adapter=adapter_number, - pcap_file=nio.pcap_output_file)) - if nio: - yield from self._ubridge_send('bridge start bridge{adapter}'.format(adapter=adapter_number)) - - def _delete_ubridge_connection(self, adapter_number): - """Deletes a connection in uBridge. - - :param adapter_number: adapter number - """ - if not self.ubridge: - return - - try: - yield from self._ubridge_send("bridge delete bridge{name}".format(name=adapter_number)) - except UbridgeError as e: - log.debug(str(e)) + yield from self._connect_nio(adapter_number, nio) @asyncio.coroutine def _get_namespace(self): result = yield from self.manager.query("GET", "containers/{}/json".format(self._cid)) return int(result['State']['Pid']) + @asyncio.coroutine + def _connect_nio(self, adapter_number, nio): + yield from self._ubridge_send('bridge add_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(adapter=adapter_number, + lport=nio.lport, + rhost=nio.rhost, + rport=nio.rport)) + + if nio.capturing: + yield from self._ubridge_send('bridge start_capture bridge{adapter} "{pcap_file}"'.format(adapter=adapter_number, + pcap_file=nio.pcap_output_file)) + yield from self._ubridge_send('bridge start bridge{adapter}'.format(adapter=adapter_number)) + @asyncio.coroutine def adapter_add_nio_binding(self, adapter_number, nio): """Adds an adapter NIO binding. @@ -705,28 +693,7 @@ class DockerVM(BaseNode): adapter_number=adapter_number)) if self.status == "started" and self.ubridge: - # the container is running, let's add the UDP tunnel to connect to another node - yield from self._ubridge_send('bridge create bridge{}'.format(adapter_number)) - yield from self._ubridge_send('bridge add_nio_linux_raw bridge{adapter} {ifc}'.format(ifc=adapter.host_ifc, adapter=adapter_number)) - - yield from self._ubridge_send('bridge add_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(adapter=adapter_number, - lport=nio.lport, - rhost=nio.rhost, - rport=nio.rport)) - - yield from self._ubridge_send('bridge start bridge{adapter}'.format(adapter=adapter_number)) - - if self.status == "started" and self.ubridge: - # the container is running, let's add the UDP tunnel to connect to another node - yield from self._ubridge_hypervisor.send('bridge create bridge{}'.format(adapter_number)) - yield from self._ubridge_hypervisor.send('bridge add_nio_linux_raw bridge{adapter} {ifc}'.format(ifc=adapter.host_ifc, adapter=adapter_number)) - - yield from self._ubridge_hypervisor.send('bridge add_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(adapter=adapter_number, - lport=nio.lport, - rhost=nio.rhost, - rport=nio.rport)) - - yield from self._ubridge_hypervisor.send('bridge start bridge{adapter}'.format(adapter=adapter_number)) + yield from self._connect_nio(adapter_number, nio) adapter.add_nio(0, nio) log.info("Docker container '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name, @@ -749,13 +716,15 @@ class DockerVM(BaseNode): raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name, adapter_number=adapter_number)) + if self.ubridge: + nio = adapter.get_nio(0) + yield from self._ubridge_send("bridge stop bridge{name}".format(name=adapter_number)) + yield from self._ubridge_send('bridge remove_nio_udp bridge{adapter} {lport} {rhost} {rport}'.format(adapter=adapter_number, + lport=nio.lport, + rhost=nio.rhost, + rport=nio.rport)) + adapter.remove_nio(0) - if self.status == "started" and self.ubridge: - # the container is running, just delete the UDP tunnel so we can reconnect it later if needed - yield from self._ubridge_send("bridge delete bridge{name}".format(name=adapter_number)) - else: - # the container is not running, let's completely delete the connection - yield from self._delete_ubridge_connection(adapter_number) log.info("Docker VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name, id=self.id, diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index 6ee74761..d55b411e 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -419,7 +419,7 @@ def test_start(loop, vm, manager, free_console_port): loop.run_until_complete(asyncio.async(vm.start())) mock_query.assert_called_with("POST", "containers/e90e34656842/start") - vm._add_ubridge_connection.assert_called_once_with(nio, 0, 42) + vm._add_ubridge_connection.assert_called_once_with(nio, 0) assert vm._start_ubridge.called assert vm._start_console.called assert vm._start_aux.called @@ -445,7 +445,7 @@ def test_start_namespace_failed(loop, vm, manager, free_console_port): loop.run_until_complete(asyncio.async(vm.start())) mock_query.assert_any_call("POST", "containers/e90e34656842/start") - mock_add_ubridge_connection.assert_called_once_with(nio, 0, 42) + mock_add_ubridge_connection.assert_called_once_with(nio, 0) assert mock_start_ubridge.called assert vm.status == "stopped" @@ -691,8 +691,9 @@ def test_add_ubridge_connection(loop, vm): nio = vm.manager.create_nio(nio) nio.startPacketCapture("/tmp/capture.pcap") vm._ubridge_hypervisor = MagicMock() + vm._namespace = 42 - loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 0, 42))) + loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 0))) calls = [ call.send('bridge create bridge0'), @@ -710,8 +711,9 @@ def test_add_ubridge_connection_none_nio(loop, vm): nio = None vm._ubridge_hypervisor = MagicMock() + vm._namespace = 42 - loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 0, 42))) + loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 0))) calls = [ call.send('bridge create bridge0'), @@ -731,7 +733,7 @@ def test_add_ubridge_connection_invalid_adapter_number(loop, vm): "rhost": "127.0.0.1"} nio = vm.manager.create_nio(nio) with pytest.raises(DockerError): - loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 12, 42))) + loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 12))) def test_add_ubridge_connection_no_free_interface(loop, vm): @@ -747,25 +749,7 @@ def test_add_ubridge_connection_no_free_interface(loop, vm): interfaces = ["tap-gns3-e{}".format(index) for index in range(4096)] with patch("psutil.net_if_addrs", return_value=interfaces): - loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 0, 42))) - - -def test_delete_ubridge_connection(loop, vm): - - vm._ubridge_hypervisor = MagicMock() - nio = {"type": "nio_udp", - "lport": 4242, - "rport": 4343, - "rhost": "127.0.0.1"} - nio = vm.manager.create_nio(nio) - - loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 0, 42))) - loop.run_until_complete(asyncio.async(vm._delete_ubridge_connection(0))) - - calls = [ - call.send("bridge delete bridge0"), - ] - vm._ubridge_hypervisor.assert_has_calls(calls, any_order=True) + loop.run_until_complete(asyncio.async(vm._add_ubridge_connection(nio, 0))) def test_adapter_add_nio_binding(vm, loop): @@ -789,16 +773,21 @@ def test_adapter_add_nio_binding_invalid_adapter(vm, loop): def test_adapter_remove_nio_binding(vm, loop): + vm.ubridge = MagicMock() + vm.ubridge.is_running.return_value = True + nio = {"type": "nio_udp", "lport": 4242, "rport": 4343, "rhost": "127.0.0.1"} nio = vm.manager.create_nio(nio) loop.run_until_complete(asyncio.async(vm.adapter_add_nio_binding(0, nio))) - with asyncio_patch("gns3server.compute.docker.DockerVM._delete_ubridge_connection") as delete_ubridge_mock: + + with asyncio_patch("gns3server.compute.docker.DockerVM._ubridge_send") as delete_ubridge_mock: loop.run_until_complete(asyncio.async(vm.adapter_remove_nio_binding(0))) assert vm._ethernet_adapters[0].get_nio(0) is None - delete_ubridge_mock.assert_called_with(0) + delete_ubridge_mock.assert_any_call('bridge stop bridge0') + delete_ubridge_mock.assert_any_call('bridge remove_nio_udp bridge0 4242 127.0.0.1 4343') def test_adapter_remove_nio_binding_invalid_adapter(vm, loop):