mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-10-12 13:35:31 +03:00
Merge branch '3.1' into feat/mcp-text-drawing-tool
This commit is contained in:
commit
cff42e32e8
@ -16,7 +16,6 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import contextlib
|
|
||||||
import copy
|
import copy
|
||||||
import html
|
import html
|
||||||
import logging
|
import logging
|
||||||
@ -139,7 +138,6 @@ class Node:
|
|||||||
self._node_directory = None
|
self._node_directory = None
|
||||||
self._status = "stopped"
|
self._status = "stopped"
|
||||||
self._status_changed = asyncio.Event()
|
self._status_changed = asyncio.Event()
|
||||||
self._transition = None
|
|
||||||
self._template_id = template_id
|
self._template_id = template_id
|
||||||
self._x = 0
|
self._x = 0
|
||||||
self._y = 0
|
self._y = 0
|
||||||
@ -907,19 +905,6 @@ class Node:
|
|||||||
async def destroy(self):
|
async def destroy(self):
|
||||||
await self.delete()
|
await self.delete()
|
||||||
|
|
||||||
@contextlib.asynccontextmanager
|
|
||||||
async def _transitioning(self, transition):
|
|
||||||
"""
|
|
||||||
Report a transitional status while a lifecycle call is in flight
|
|
||||||
"""
|
|
||||||
|
|
||||||
self._transition = transition
|
|
||||||
self.project.emit_notification("node.updated", self.asdict())
|
|
||||||
try:
|
|
||||||
yield
|
|
||||||
finally:
|
|
||||||
self._transition = None
|
|
||||||
|
|
||||||
async def wait_for_status(self, status):
|
async def wait_for_status(self, status):
|
||||||
"""
|
"""
|
||||||
Wait until the controller side status of the node matches
|
Wait until the controller side status of the node matches
|
||||||
@ -954,19 +939,18 @@ class Node:
|
|||||||
f"Cannot start node '{self._name}': {len(failed_links)} deferred link(s) "
|
f"Cannot start node '{self._name}': {len(failed_links)} deferred link(s) "
|
||||||
"could not be restored. Please try again."
|
"could not be restored. Please try again."
|
||||||
)
|
)
|
||||||
async with self._transitioning("starting"):
|
try:
|
||||||
try:
|
# For IOU: we need to send the licence everytime we start a node
|
||||||
# For IOU: we need to send the licence everytime we start a node
|
if self.node_type == "iou":
|
||||||
if self.node_type == "iou":
|
license_check = self._project.controller.iou_license.get("license_check", True)
|
||||||
license_check = self._project.controller.iou_license.get("license_check", True)
|
iourc_content = self._project.controller.iou_license.get("iourc_content", None)
|
||||||
iourc_content = self._project.controller.iou_license.get("iourc_content", None)
|
await self.post(
|
||||||
await self.post(
|
"/start", timeout=240, data={"license_check": license_check, "iourc_content": iourc_content}
|
||||||
"/start", timeout=240, data={"license_check": license_check, "iourc_content": iourc_content}
|
)
|
||||||
)
|
else:
|
||||||
else:
|
await self.post("/start", data=data, timeout=240)
|
||||||
await self.post("/start", data=data, timeout=240)
|
except asyncio.TimeoutError:
|
||||||
except asyncio.TimeoutError:
|
raise ControllerTimeoutError(f"Timeout when starting {self._name}")
|
||||||
raise ControllerTimeoutError(f"Timeout when starting {self._name}")
|
|
||||||
self._confirm_status("started")
|
self._confirm_status("started")
|
||||||
# A started node may have brought kernel anchors into existence that
|
# A started node may have brought kernel anchors into existence that
|
||||||
# a link needs to finish wiring (the Ethernet-switch fast path joins
|
# a link needs to finish wiring (the Ethernet-switch fast path joins
|
||||||
@ -983,16 +967,15 @@ class Node:
|
|||||||
if self.missing_image:
|
if self.missing_image:
|
||||||
return
|
return
|
||||||
stopped = False
|
stopped = False
|
||||||
async with self._transitioning("stopping"):
|
try:
|
||||||
try:
|
await self.post("/stop", timeout=240, dont_connect=True)
|
||||||
await self.post("/stop", timeout=240, dont_connect=True)
|
stopped = True
|
||||||
stopped = True
|
except (ComputeError, ControllerError):
|
||||||
except (ComputeError, ControllerError):
|
# We don't care if a node is down at this step
|
||||||
# We don't care if a node is down at this step
|
if strict:
|
||||||
if strict:
|
raise
|
||||||
raise
|
except asyncio.TimeoutError:
|
||||||
except asyncio.TimeoutError:
|
raise ControllerTimeoutError(f"Timeout when stopping {self._name}")
|
||||||
raise ControllerTimeoutError(f"Timeout when stopping {self._name}")
|
|
||||||
if stopped:
|
if stopped:
|
||||||
self._confirm_status("stopped")
|
self._confirm_status("stopped")
|
||||||
|
|
||||||
@ -1388,7 +1371,7 @@ class Node:
|
|||||||
additional_data = {
|
additional_data = {
|
||||||
"project_id": self._project.id,
|
"project_id": self._project.id,
|
||||||
"command_line": self._command_line,
|
"command_line": self._command_line,
|
||||||
"status": self._transition or self._status,
|
"status": self._status,
|
||||||
"console_host": str(self._compute.console_host),
|
"console_host": str(self._compute.console_host),
|
||||||
"node_directory": self._node_directory,
|
"node_directory": self._node_directory,
|
||||||
"ports": [port.asdict() for port in self.ports],
|
"ports": [port.asdict() for port in self.ports],
|
||||||
|
|||||||
@ -486,7 +486,7 @@ class TestNodeRoutes:
|
|||||||
response = await client.get(app.url_path_for("get_node", project_id=project.id, node_id=node.id))
|
response = await client.get(app.url_path_for("get_node", project_id=project.id, node_id=node.id))
|
||||||
assert response.json()["status"] == "started"
|
assert response.json()["status"] == "started"
|
||||||
|
|
||||||
async def test_start_node_reports_starting_while_in_flight(
|
async def test_start_node_keeps_real_status_while_in_flight(
|
||||||
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
|
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
@ -498,10 +498,11 @@ class TestNodeRoutes:
|
|||||||
compute.post = post
|
compute.post = post
|
||||||
response = await client.post(app.url_path_for("start_node", project_id=project.id, node_id=node.id), json={})
|
response = await client.post(app.url_path_for("start_node", project_id=project.id, node_id=node.id), json={})
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert seen == ["starting"]
|
# no transitional status is reported while the call is in flight
|
||||||
|
assert seen == ["stopped"]
|
||||||
assert response.json()["status"] == "started"
|
assert response.json()["status"] == "started"
|
||||||
|
|
||||||
async def test_stop_node_reports_stopping_while_in_flight(
|
async def test_stop_node_keeps_real_status_while_in_flight(
|
||||||
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
|
self, app: FastAPI, client: AsyncClient, project: Project, compute: Compute, node: Node
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
@ -514,7 +515,8 @@ class TestNodeRoutes:
|
|||||||
node._status = "started"
|
node._status = "started"
|
||||||
response = await client.post(app.url_path_for("stop_node", project_id=project.id, node_id=node.id))
|
response = await client.post(app.url_path_for("stop_node", project_id=project.id, node_id=node.id))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert seen == ["stopping"]
|
# no transitional status is reported while the call is in flight
|
||||||
|
assert seen == ["started"]
|
||||||
assert response.json()["status"] == "stopped"
|
assert response.json()["status"] == "stopped"
|
||||||
|
|
||||||
async def test_start_node_wait_timeout(
|
async def test_start_node_wait_timeout(
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from fastapi import FastAPI, status
|
from fastapi import FastAPI, status
|
||||||
from httpx import AsyncClient
|
from httpx import AsyncClient
|
||||||
|
from starlette.routing import Mount
|
||||||
|
|
||||||
from gns3server.version import __version__
|
from gns3server.version import __version__
|
||||||
|
|
||||||
@ -26,6 +27,14 @@ pytestmark = pytest.mark.asyncio
|
|||||||
class TestVersionRoutes:
|
class TestVersionRoutes:
|
||||||
async def test_version_output(self, app: FastAPI, client: AsyncClient) -> None:
|
async def test_version_output(self, app: FastAPI, client: AsyncClient) -> None:
|
||||||
|
|
||||||
|
# controller_host is recorded on the mounted compute sub-application
|
||||||
|
# when the controller queries the compute capabilities. Set it
|
||||||
|
# directly so this test does not depend on another test having made
|
||||||
|
# that call first (it failed when run on its own).
|
||||||
|
for route in app.routes:
|
||||||
|
if isinstance(route, Mount) and route.name == "compute":
|
||||||
|
route.app.state.controller_host = "127.0.0.1"
|
||||||
|
|
||||||
response = await client.get(app.url_path_for("get_version"))
|
response = await client.get(app.url_path_for("get_version"))
|
||||||
assert response.status_code == status.HTTP_200_OK
|
assert response.status_code == status.HTTP_200_OK
|
||||||
assert response.json() == {"controller_host": "127.0.0.1", "local": False, "version": __version__}
|
assert response.json() == {"controller_host": "127.0.0.1", "local": False, "version": __version__}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user