1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-11-24 17:28:08 +00:00

Fix hot link issues in Docker

Fix #817
This commit is contained in:
Julien Duponchelle 2016-12-14 16:53:20 +01:00
parent d0f65aebff
commit 46b6e7c5ee
No known key found for this signature in database
GPG Key ID: CE8B29639E07F5E8
2 changed files with 42 additions and 84 deletions

View File

@ -348,7 +348,7 @@ class DockerVM(BaseNode):
yield from self._clean_servers() yield from self._clean_servers()
yield from self.manager.query("POST", "containers/{}/start".format(self._cid)) 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() yield from self._start_ubridge()
@ -356,7 +356,7 @@ class DockerVM(BaseNode):
nio = self._ethernet_adapters[adapter_number].get_nio(0) nio = self._ethernet_adapters[adapter_number].get_nio(0)
with (yield from self.manager.ubridge_lock): with (yield from self.manager.ubridge_lock):
try: try:
yield from self._add_ubridge_connection(nio, adapter_number, namespace) yield from self._add_ubridge_connection(nio, adapter_number)
except UbridgeNamespaceError: except UbridgeNamespaceError:
yield from self.stop() yield from self.stop()
@ -626,13 +626,12 @@ class DockerVM(BaseNode):
return return
@asyncio.coroutine @asyncio.coroutine
def _add_ubridge_connection(self, nio, adapter_number, namespace): def _add_ubridge_connection(self, nio, adapter_number):
""" """
Creates a connection in uBridge. 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 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 adapter_number: adapter number
:param namespace: Container namespace (pid)
""" """
try: 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 create bridge{}'.format(adapter_number))
yield from self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(adapter_number=adapter_number, yield from self._ubridge_send('bridge add_nio_tap bridge{adapter_number} {hostif}'.format(adapter_number=adapter_number,
hostif=adapter.host_ifc)) 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: try:
yield from self._ubridge_send('docker move_to_ns {ifc} {ns} eth{adapter}'.format(ifc=adapter.host_ifc, 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)) adapter=adapter_number))
except UbridgeError as e: except UbridgeError as e:
raise UbridgeNamespaceError(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: if nio:
yield from self._ubridge_send('bridge start bridge{adapter}'.format(adapter=adapter_number)) yield from self._connect_nio(adapter_number, nio)
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))
@asyncio.coroutine @asyncio.coroutine
def _get_namespace(self): def _get_namespace(self):
result = yield from self.manager.query("GET", "containers/{}/json".format(self._cid)) result = yield from self.manager.query("GET", "containers/{}/json".format(self._cid))
return int(result['State']['Pid']) 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 @asyncio.coroutine
def adapter_add_nio_binding(self, adapter_number, nio): def adapter_add_nio_binding(self, adapter_number, nio):
"""Adds an adapter NIO binding. """Adds an adapter NIO binding.
@ -705,28 +693,7 @@ class DockerVM(BaseNode):
adapter_number=adapter_number)) adapter_number=adapter_number))
if self.status == "started" and self.ubridge: 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._connect_nio(adapter_number, nio)
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))
adapter.add_nio(0, nio) adapter.add_nio(0, nio)
log.info("Docker container '{name}' [{id}]: {nio} added to adapter {adapter_number}".format(name=self.name, 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, raise DockerError("Adapter {adapter_number} doesn't exist on Docker VM '{name}'".format(name=self.name,
adapter_number=adapter_number)) 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) 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, log.info("Docker VM '{name}' [{id}]: {nio} removed from adapter {adapter_number}".format(name=self.name,
id=self.id, id=self.id,

View File

@ -419,7 +419,7 @@ def test_start(loop, vm, manager, free_console_port):
loop.run_until_complete(asyncio.async(vm.start())) loop.run_until_complete(asyncio.async(vm.start()))
mock_query.assert_called_with("POST", "containers/e90e34656842/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_ubridge.called
assert vm._start_console.called assert vm._start_console.called
assert vm._start_aux.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())) loop.run_until_complete(asyncio.async(vm.start()))
mock_query.assert_any_call("POST", "containers/e90e34656842/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 mock_start_ubridge.called
assert vm.status == "stopped" assert vm.status == "stopped"
@ -691,8 +691,9 @@ def test_add_ubridge_connection(loop, vm):
nio = vm.manager.create_nio(nio) nio = vm.manager.create_nio(nio)
nio.startPacketCapture("/tmp/capture.pcap") nio.startPacketCapture("/tmp/capture.pcap")
vm._ubridge_hypervisor = MagicMock() 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 = [ calls = [
call.send('bridge create bridge0'), call.send('bridge create bridge0'),
@ -710,8 +711,9 @@ def test_add_ubridge_connection_none_nio(loop, vm):
nio = None nio = None
vm._ubridge_hypervisor = MagicMock() 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 = [ calls = [
call.send('bridge create bridge0'), call.send('bridge create bridge0'),
@ -731,7 +733,7 @@ def test_add_ubridge_connection_invalid_adapter_number(loop, vm):
"rhost": "127.0.0.1"} "rhost": "127.0.0.1"}
nio = vm.manager.create_nio(nio) nio = vm.manager.create_nio(nio)
with pytest.raises(DockerError): 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): 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)] interfaces = ["tap-gns3-e{}".format(index) for index in range(4096)]
with patch("psutil.net_if_addrs", return_value=interfaces): with patch("psutil.net_if_addrs", return_value=interfaces):
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)))
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)
def test_adapter_add_nio_binding(vm, loop): 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): def test_adapter_remove_nio_binding(vm, loop):
vm.ubridge = MagicMock()
vm.ubridge.is_running.return_value = True
nio = {"type": "nio_udp", nio = {"type": "nio_udp",
"lport": 4242, "lport": 4242,
"rport": 4343, "rport": 4343,
"rhost": "127.0.0.1"} "rhost": "127.0.0.1"}
nio = vm.manager.create_nio(nio) nio = vm.manager.create_nio(nio)
loop.run_until_complete(asyncio.async(vm.adapter_add_nio_binding(0, 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))) loop.run_until_complete(asyncio.async(vm.adapter_remove_nio_binding(0)))
assert vm._ethernet_adapters[0].get_nio(0) is None 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): def test_adapter_remove_nio_binding_invalid_adapter(vm, loop):