mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-12 13:35:31 +03:00
fix(controller): bind and release link members on their live ports
Link.delete cleared the port link through the port object stored at add_node time; a node update since then rebuilt the port list, so that object was stale and the live port kept its link — the next link on the port was refused with "Port is already used". Masked until now by the older rebuild bug (c2d68a65d): links never survived a rebuild, so the guard never fired and the stale clear never mattered.
This commit is contained in:
parent
1e999d916d
commit
928757542e
@ -438,9 +438,7 @@ class Link:
|
||||
|
||||
if len(self._nodes) == 2 and not batch:
|
||||
await self.create()
|
||||
for n in self._nodes:
|
||||
n["node"].add_link(self)
|
||||
n["port"].link = self
|
||||
self._bind_members()
|
||||
self._created = True
|
||||
self._project.emit_notification("link.created", self.asdict())
|
||||
|
||||
@ -471,14 +469,32 @@ class Link:
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def _bind_members(self):
|
||||
"""
|
||||
Wire the back-references on every member: the node's link set and the
|
||||
port's link. The port is resolved live, through the node's current
|
||||
port list — the object stored at add_node time goes stale whenever a
|
||||
node update rebuilds that list (``parse_node_response``), and a
|
||||
back-reference set on the stale object is invisible to the "Port is
|
||||
already used" guard in add_node, which reads the live one.
|
||||
"""
|
||||
|
||||
for n in self._nodes:
|
||||
n["node"].add_link(self)
|
||||
port = n["node"].get_port(n["adapter_number"], n["port_number"]) or n["port"]
|
||||
port.link = self
|
||||
|
||||
async def delete(self):
|
||||
"""
|
||||
Delete the link
|
||||
"""
|
||||
for n in self._nodes:
|
||||
# It could be different of self if we rollback an already existing link
|
||||
if n["port"].link == self:
|
||||
n["port"].link = None
|
||||
# The live port, for the same staleness reason as _bind_members;
|
||||
# the link could be different from self if we rollback an
|
||||
# already existing link
|
||||
port = n["node"].get_port(n["adapter_number"], n["port_number"]) or n["port"]
|
||||
if port.link == self:
|
||||
port.link = None
|
||||
n["node"].remove_link(self)
|
||||
|
||||
async def reset(self):
|
||||
|
||||
@ -987,9 +987,7 @@ class Project:
|
||||
# an image is missing. Keep the link on the controller (so the
|
||||
# topology is preserved) but defer the NIO creation until the
|
||||
# missing image is resolved.
|
||||
for n in link._nodes:
|
||||
n["node"].add_link(link)
|
||||
n["port"].link = link
|
||||
link._bind_members()
|
||||
link._deferred = True
|
||||
log.info(
|
||||
"Project '%s' [%s]: deferring link %s until missing image(s) are resolved",
|
||||
@ -1990,9 +1988,7 @@ class Project:
|
||||
# Finalise every link: wire node/port back-references, mark created,
|
||||
# notify clients, and apply project-level marker definitions.
|
||||
for link, _entries in valid:
|
||||
for n in link._nodes:
|
||||
n["node"].add_link(link)
|
||||
n["port"].link = link
|
||||
link._bind_members()
|
||||
link._created = True
|
||||
self.emit_notification("link.created", link.asdict())
|
||||
log.info("Project '%s' [%s]: created %d links", self._name, self._id, len(valid))
|
||||
|
||||
@ -141,6 +141,37 @@ async def test_add_node_already_connected_after_node_update(project, compute):
|
||||
await link2.add_node(node1, 0, 0)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_link_delete_after_node_update_releases_port(project, compute):
|
||||
"""
|
||||
Link.delete drops the port's link reference through the port object it
|
||||
stored at add_node time; a node update since then rebuilt the port list,
|
||||
so that stored object is stale. The delete must release the *live* port,
|
||||
or the next link on it is refused with "Port is already used".
|
||||
"""
|
||||
|
||||
project.dump = AsyncioMagicMock()
|
||||
|
||||
node1 = Node(project, compute, "node1", node_type="qemu")
|
||||
node2 = Node(project, compute, "node2", node_type="qemu")
|
||||
|
||||
link = Link(project)
|
||||
link.create = AsyncioMagicMock()
|
||||
link.node_updated = AsyncioMagicMock()
|
||||
link._project.emit_notification = MagicMock()
|
||||
await link.add_node(node1, 0, 0)
|
||||
await link.add_node(node2, 0, 0)
|
||||
|
||||
# a node update (e.g. the status change of a start) rebuilds the ports
|
||||
await node1.parse_node_response({"status": "started"})
|
||||
|
||||
await link.delete()
|
||||
|
||||
link2 = Link(project)
|
||||
link2.create = AsyncioMagicMock()
|
||||
await link2.add_node(node1, 0, 0) # must not raise "Port is already used"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_node_cloud(project, compute):
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user