fix: propagate 405 when suspending a node without suspend support

Suspending a single VPCS/IOU node returned a fake 204: the controller
route swallowed the compute 405 that the node types honestly raise, so
callers saw success while the node stayed started. Surface the 405
instead. The best-effort swallow on suspend_all is kept (and now covered
by a test) since mixed projects legitimately contain always-running node
types.
This commit is contained in:
YueGuobin 2026-08-25 21:29:53 +08:00
parent 97e7a79117
commit c9bc635996
No known key found for this signature in database
2 changed files with 40 additions and 7 deletions

View File

@ -372,14 +372,13 @@ async def suspend_node(node: Node = Depends(dep_node)) -> None:
"""
Suspend a node.
Node types without suspend support return a 405 error instead of a
silent no-op, so the caller cannot mistake it for a suspended node.
Required privilege: Node.PowerMgmt
"""
try:
await node.suspend()
except HTTPException as e:
if not e.status_code == status.HTTP_405_METHOD_NOT_ALLOWED:
raise
@router.post(

View File

@ -18,7 +18,7 @@
import pytest
from fastapi import FastAPI, status
from fastapi import FastAPI, HTTPException, status
from httpx import AsyncClient
from unittest.mock import MagicMock
@ -300,6 +300,40 @@ class TestNodeRoutes:
response = await client.post(app.url_path_for("suspend_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_suspend_node_unsupported(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
) -> None:
# node types without suspend support (e.g. VPCS, IOU) must surface the
# compute 405 instead of reporting a fake success
compute.post = AsyncioMagicMock(
side_effect=HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported")
)
response = await client.post(app.url_path_for("suspend_node", project_id=project.id, node_id=node.id))
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
async def test_suspend_all_nodes_tolerates_unsupported(
self,
app: FastAPI,
client: AsyncClient,
project: Project,
compute: Compute,
node: Node
) -> None:
# suspending all nodes of a mixed project stays best-effort: nodes
# without suspend support are skipped without failing the request
compute.post = AsyncioMagicMock(
side_effect=HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Suspend is not supported")
)
response = await client.post(app.url_path_for("suspend_all_nodes", project_id=project.id))
assert response.status_code == status.HTTP_204_NO_CONTENT
async def test_reload_node(
self,