Merge pull request #2087 from GNS3/enhancement/2076

Checks for valid hostname on server side for Dynamips, IOU, Qemu and Docker nodes
This commit is contained in:
Jeremy Grossmann 2022-07-17 11:59:09 +02:00 committed by GitHub
commit 5d4645b2c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 233 additions and 30 deletions

View File

@ -67,7 +67,7 @@ compute_api.state.controller_host = None
@compute_api.exception_handler(ComputeError) @compute_api.exception_handler(ComputeError)
async def controller_error_handler(request: Request, exc: ComputeError): async def compute_error_handler(request: Request, exc: ComputeError):
log.error(f"Compute error: {exc}") log.error(f"Compute error: {exc}")
return JSONResponse( return JSONResponse(
status_code=409, status_code=409,
@ -76,7 +76,7 @@ async def controller_error_handler(request: Request, exc: ComputeError):
@compute_api.exception_handler(ComputeTimeoutError) @compute_api.exception_handler(ComputeTimeoutError)
async def controller_timeout_error_handler(request: Request, exc: ComputeTimeoutError): async def compute_timeout_error_handler(request: Request, exc: ComputeTimeoutError):
log.error(f"Compute timeout error: {exc}") log.error(f"Compute timeout error: {exc}")
return JSONResponse( return JSONResponse(
status_code=408, status_code=408,
@ -85,7 +85,7 @@ async def controller_timeout_error_handler(request: Request, exc: ComputeTimeout
@compute_api.exception_handler(ComputeUnauthorizedError) @compute_api.exception_handler(ComputeUnauthorizedError)
async def controller_unauthorized_error_handler(request: Request, exc: ComputeUnauthorizedError): async def compute_unauthorized_error_handler(request: Request, exc: ComputeUnauthorizedError):
log.error(f"Compute unauthorized error: {exc}") log.error(f"Compute unauthorized error: {exc}")
return JSONResponse( return JSONResponse(
status_code=401, status_code=401,
@ -94,7 +94,7 @@ async def controller_unauthorized_error_handler(request: Request, exc: ComputeUn
@compute_api.exception_handler(ComputeForbiddenError) @compute_api.exception_handler(ComputeForbiddenError)
async def controller_forbidden_error_handler(request: Request, exc: ComputeForbiddenError): async def compute_forbidden_error_handler(request: Request, exc: ComputeForbiddenError):
log.error(f"Compute forbidden error: {exc}") log.error(f"Compute forbidden error: {exc}")
return JSONResponse( return JSONResponse(
status_code=403, status_code=403,
@ -103,7 +103,7 @@ async def controller_forbidden_error_handler(request: Request, exc: ComputeForbi
@compute_api.exception_handler(ComputeNotFoundError) @compute_api.exception_handler(ComputeNotFoundError)
async def controller_not_found_error_handler(request: Request, exc: ComputeNotFoundError): async def compute_not_found_error_handler(request: Request, exc: ComputeNotFoundError):
log.error(f"Compute not found error: {exc}") log.error(f"Compute not found error: {exc}")
return JSONResponse( return JSONResponse(
status_code=404, status_code=404,
@ -112,7 +112,7 @@ async def controller_not_found_error_handler(request: Request, exc: ComputeNotFo
@compute_api.exception_handler(GNS3VMError) @compute_api.exception_handler(GNS3VMError)
async def controller_error_handler(request: Request, exc: GNS3VMError): async def compute_gns3vm_error_handler(request: Request, exc: GNS3VMError):
log.error(f"Compute GNS3 VM error: {exc}") log.error(f"Compute GNS3 VM error: {exc}")
return JSONResponse( return JSONResponse(
status_code=409, status_code=409,

View File

@ -19,7 +19,6 @@ API routes for Dynamips nodes.
""" """
import os import os
import sys
from fastapi import APIRouter, WebSocket, Depends, Response, status from fastapi import APIRouter, WebSocket, Depends, Response, status
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
@ -29,7 +28,6 @@ from uuid import UUID
from gns3server.compute.dynamips import Dynamips from gns3server.compute.dynamips import Dynamips
from gns3server.compute.dynamips.nodes.router import Router from gns3server.compute.dynamips.nodes.router import Router
from gns3server.compute.dynamips.dynamips_error import DynamipsError
from gns3server import schemas from gns3server import schemas
responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}} responses = {404: {"model": schemas.ErrorMessage, "description": "Could not find project or Dynamips node"}}

View File

@ -20,7 +20,7 @@ API routes for IOU nodes.
import os import os
from fastapi import APIRouter, WebSocket, Depends, Body, Response, status from fastapi import APIRouter, WebSocket, Depends, Body, status
from fastapi.encoders import jsonable_encoder from fastapi.encoders import jsonable_encoder
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from typing import Union from typing import Union

View File

@ -33,6 +33,7 @@ from gns3server.utils.asyncio.raw_command_server import AsyncioRawCommandServer
from gns3server.utils.asyncio import wait_for_file_creation from gns3server.utils.asyncio import wait_for_file_creation
from gns3server.utils.asyncio import monitor_process from gns3server.utils.asyncio import monitor_process
from gns3server.utils.get_resource import get_resource from gns3server.utils.get_resource import get_resource
from gns3server.utils.hostname import is_rfc1123_hostname_valid
from gns3server.compute.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError from gns3server.compute.ubridge.ubridge_error import UbridgeError, UbridgeNamespaceError
from ..base_node import BaseNode from ..base_node import BaseNode
@ -89,6 +90,9 @@ class DockerVM(BaseNode):
cpus=0, cpus=0,
): ):
if not is_rfc1123_hostname_valid(name):
raise DockerError(f"'{name}' is an invalid name to create a Docker node")
super().__init__( super().__init__(
name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type
) )
@ -171,6 +175,18 @@ class DockerVM(BaseNode):
return display return display
display += 1 display += 1
@BaseNode.name.setter
def name(self, new_name):
"""
Sets the name of this Qemu VM.
:param new_name: name
"""
if not is_rfc1123_hostname_valid(new_name):
raise DockerError(f"'{new_name}' is an invalid name to rename Docker container '{self._name}'")
super(DockerVM, DockerVM).name.__set__(self, new_name)
@property @property
def ethernet_adapters(self): def ethernet_adapters(self):
return self._ethernet_adapters return self._ethernet_adapters

View File

@ -37,6 +37,7 @@ from ..dynamips_error import DynamipsError
from gns3server.utils.file_watcher import FileWatcher from gns3server.utils.file_watcher import FileWatcher
from gns3server.utils.asyncio import wait_run_in_executor, monitor_process from gns3server.utils.asyncio import wait_run_in_executor, monitor_process
from gns3server.utils.hostname import is_ios_hostname_valid
from gns3server.utils.images import md5sum from gns3server.utils.images import md5sum
@ -75,6 +76,9 @@ class Router(BaseNode):
ghost_flag=False, ghost_flag=False,
): ):
if not is_ios_hostname_valid(name):
raise DynamipsError(f"{name} is an invalid name to create a Dynamips node")
super().__init__( super().__init__(
name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type name, node_id, project, manager, console=console, console_type=console_type, aux=aux, aux_type=aux_type
) )
@ -1653,6 +1657,9 @@ class Router(BaseNode):
:param new_name: new name string :param new_name: new name string
""" """
if not is_ios_hostname_valid(new_name):
raise DynamipsError(f"{new_name} is an invalid name to rename router '{self._name}'")
await self._hypervisor.send(f'vm rename "{self._name}" "{new_name}"') await self._hypervisor.send(f'vm rename "{self._name}" "{new_name}"')
# change the hostname in the startup-config # change the hostname in the startup-config

View File

@ -42,6 +42,7 @@ from .utils.iou_export import nvram_export
from gns3server.compute.ubridge.ubridge_error import UbridgeError from gns3server.compute.ubridge.ubridge_error import UbridgeError
from gns3server.utils.file_watcher import FileWatcher from gns3server.utils.file_watcher import FileWatcher
from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer from gns3server.utils.asyncio.telnet_server import AsyncioTelnetServer
from gns3server.utils.hostname import is_ios_hostname_valid
from gns3server.utils.asyncio import locking from gns3server.utils.asyncio import locking
import gns3server.utils.asyncio import gns3server.utils.asyncio
import gns3server.utils.images import gns3server.utils.images
@ -70,6 +71,9 @@ class IOUVM(BaseNode):
self, name, node_id, project, manager, application_id=None, path=None, console=None, console_type="telnet" self, name, node_id, project, manager, application_id=None, path=None, console=None, console_type="telnet"
): ):
if not is_ios_hostname_valid(name):
raise IOUError(f"'{name}' is an invalid name to create an IOU node")
super().__init__(name, node_id, project, manager, console=console, console_type=console_type) super().__init__(name, node_id, project, manager, console=console, console_type=console_type)
log.info( log.info(
@ -334,6 +338,8 @@ class IOUVM(BaseNode):
:param new_name: name :param new_name: name
""" """
if not is_ios_hostname_valid(new_name):
raise IOUError(f"'{new_name}' is an invalid name to rename IOU node '{self._name}'")
if self.startup_config_file: if self.startup_config_file:
content = self.startup_config_content content = self.startup_config_content
content = re.sub(r"hostname .+$", "hostname " + new_name, content, flags=re.MULTILINE) content = re.sub(r"hostname .+$", "hostname " + new_name, content, flags=re.MULTILINE)

View File

@ -22,7 +22,6 @@ order to run a QEMU VM.
import sys import sys
import os import os
import re import re
import shlex
import math import math
import shutil import shutil
import struct import struct
@ -47,6 +46,7 @@ from ..base_node import BaseNode
from ...utils.asyncio import monitor_process from ...utils.asyncio import monitor_process
from ...utils.images import md5sum from ...utils.images import md5sum
from ...utils import macaddress_to_int, int_to_macaddress from ...utils import macaddress_to_int, int_to_macaddress
from ...utils.hostname import is_rfc1123_hostname_valid
from gns3server.schemas.compute.qemu_nodes import Qemu, QemuPlatform from gns3server.schemas.compute.qemu_nodes import Qemu, QemuPlatform
@ -86,6 +86,9 @@ class QemuVM(BaseNode):
platform=None, platform=None,
): ):
if not is_rfc1123_hostname_valid(name):
raise QemuError(f"'{name}' is an invalid name to create a Qemu node")
super().__init__( super().__init__(
name, name,
node_id, node_id,
@ -172,6 +175,18 @@ class QemuVM(BaseNode):
log.info(f'QEMU VM "{self._name}" [{self._id}] has been created') log.info(f'QEMU VM "{self._name}" [{self._id}] has been created')
@BaseNode.name.setter
def name(self, new_name):
"""
Sets the name of this Qemu VM.
:param new_name: name
"""
if not is_rfc1123_hostname_valid(new_name):
raise QemuError(f"'{new_name}' is an invalid name to rename Qemu node '{self._name}'")
super(QemuVM, QemuVM).name.__set__(self, new_name)
@property @property
def guest_cid(self): def guest_cid(self):
""" """

View File

@ -23,7 +23,7 @@ import socket
import json import json
import sys import sys
import io import io
from operator import itemgetter from fastapi import HTTPException
from aiohttp import web from aiohttp import web
from ..utils import parse_version from ..utils import parse_version
@ -576,12 +576,13 @@ class Compute:
# If the 409 doesn't come from a GNS3 server # If the 409 doesn't come from a GNS3 server
except ValueError: except ValueError:
raise ControllerError(msg) raise ControllerError(msg)
elif response.status == 500:
raise aiohttp.web.HTTPInternalServerError(text=f"Internal server error {url}")
elif response.status == 503:
raise aiohttp.web.HTTPServiceUnavailable(text=f"Service unavailable {url} {body}")
else: else:
raise NotImplementedError(f"{response.status} status code is not supported for {method} '{url}'\n{body}") raise HTTPException(
status_code=response.status,
detail=f"HTTP error {response.status} received from compute "
f"'{self.name}' for request {method} {path}: {msg}"
)
if body and len(body): if body and len(body):
if raw: if raw:
response.body = body response.body = body

View File

@ -427,6 +427,7 @@ class Node:
# When updating properties used only on controller we don't need to call the compute # When updating properties used only on controller we don't need to call the compute
update_compute = False update_compute = False
old_json = self.asdict() old_json = self.asdict()
old_name = self._name
compute_properties = None compute_properties = None
# Update node properties with additional elements # Update node properties with additional elements
@ -454,7 +455,13 @@ class Node:
self._list_ports() self._list_ports()
if update_compute: if update_compute:
data = self._node_data(properties=compute_properties) data = self._node_data(properties=compute_properties)
try:
response = await self.put(None, data=data) response = await self.put(None, data=data)
except ComputeConflictError:
if old_name != self.name:
# special case when the new name is already updated on controller but refused by the compute
self.name = old_name
raise
await self.parse_node_response(response.json) await self.parse_node_response(response.json)
elif old_json != self.asdict(): elif old_json != self.asdict():
# We send notif only if object has changed # We send notif only if object has changed

View File

@ -0,0 +1,59 @@
#!/usr/bin/env python
#
# Copyright (C) 2022 GNS3 Technologies Inc.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import re
def is_ios_hostname_valid(hostname: str) -> bool:
"""
Check if an IOS hostname is valid
IOS hostname must start with a letter, end with a letter or digit, and
have as interior characters only letters, digits, and hyphens.
They must be 63 characters or fewer (ARPANET rules).
"""
if re.search(r"""^(?!-|[0-9])[a-zA-Z0-9-]{1,63}(?<!-)$""", hostname):
return True
return False
def is_rfc1123_hostname_valid(hostname: str) -> bool:
"""
Check if a hostname is valid according to RFC 1123
Each element of the hostname must be from 1 to 63 characters long
and the entire hostname, including the dots, can be at most 253
characters long. Valid characters for hostnames are ASCII
letters from a to z, the digits from 0 to 9, and the hyphen (-).
A hostname may not start with a hyphen.
"""
if hostname[-1] == ".":
hostname = hostname[:-1] # strip exactly one dot from the right, if present
if len(hostname) > 253:
return False
labels = hostname.split(".")
# the TLD must be not all-numeric
if re.match(r"[0-9]+$", labels[-1]):
return False
allowed = re.compile(r"(?!-)[a-zA-Z0-9-]{1,63}(?<!-)$")
return all(allowed.match(label) for label in labels)

View File

@ -33,7 +33,7 @@ def base_params() -> dict:
"""Return standard parameters""" """Return standard parameters"""
params = { params = {
"name": "PC TEST 1", "name": "DOCKER-TEST-1",
"image": "nginx", "image": "nginx",
"start_command": "nginx-daemon", "start_command": "nginx-daemon",
"adapters": 2, "adapters": 2,
@ -71,10 +71,11 @@ async def test_docker_create(app: FastAPI, compute_client: AsyncClient, compute_
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]): with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}): with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}):
response = await compute_client.post(app.url_path_for("compute:create_docker_node", project_id=compute_project.id), response = await compute_client.post(
json=base_params) app.url_path_for("compute:create_docker_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status.HTTP_201_CREATED assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "DOCKER-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["container_id"] == "8bd8153ea8f5" assert response.json()["container_id"] == "8bd8153ea8f5"
assert response.json()["image"] == "nginx:latest" assert response.json()["image"] == "nginx:latest"
@ -84,6 +85,40 @@ async def test_docker_create(app: FastAPI, compute_client: AsyncClient, compute_
assert response.json()["extra_hosts"] == "test:127.0.0.1" assert response.json()["extra_hosts"] == "test:127.0.0.1"
@pytest.mark.parametrize(
"name, status_code",
(
("valid-name.com", status.HTTP_201_CREATED),
("42name", status.HTTP_201_CREATED),
("424242", status.HTTP_409_CONFLICT),
("name42", status.HTTP_201_CREATED),
("name.424242", status.HTTP_409_CONFLICT),
("-name", status.HTTP_409_CONFLICT),
("name%-test", status.HTTP_409_CONFLICT),
("x" * 63, status.HTTP_201_CREATED),
("x" * 64, status.HTTP_409_CONFLICT),
(("x" * 62 + ".") * 4, status.HTTP_201_CREATED),
("xx" + ("x" * 62 + ".") * 4, status.HTTP_409_CONFLICT),
),
)
async def test_docker_create_with_invalid_name(
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int
) -> None:
base_params["name"] = name
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "nginx"}]):
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value={"Id": "8bd8153ea8f5"}):
response = await compute_client.post(
app.url_path_for("compute:create_docker_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status_code
async def test_docker_start(app: FastAPI, compute_client: AsyncClient, vm: dict) -> None: async def test_docker_start(app: FastAPI, compute_client: AsyncClient, vm: dict) -> None:
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start", return_value=True) as mock: with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start", return_value=True) as mock:

View File

@ -46,7 +46,7 @@ def fake_iou_bin(images_dir) -> str:
def base_params(tmpdir, fake_iou_bin) -> dict: def base_params(tmpdir, fake_iou_bin) -> dict:
"""Return standard parameters""" """Return standard parameters"""
return {"application_id": 42, "name": "PC TEST 1", "path": "iou.bin"} return {"application_id": 42, "name": "IOU-TEST-1", "path": "iou.bin"}
@pytest.fixture @pytest.fixture
@ -68,7 +68,7 @@ async def test_iou_create(app: FastAPI, compute_client: AsyncClient, compute_pro
response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=base_params) response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "IOU-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["serial_adapters"] == 2 assert response.json()["serial_adapters"] == 2
assert response.json()["ethernet_adapters"] == 2 assert response.json()["ethernet_adapters"] == 2
@ -93,7 +93,7 @@ async def test_iou_create_with_params(app: FastAPI,
response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=params) response = await compute_client.post(app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "IOU-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["serial_adapters"] == 4 assert response.json()["serial_adapters"] == 4
assert response.json()["ethernet_adapters"] == 0 assert response.json()["ethernet_adapters"] == 0
@ -106,6 +106,34 @@ async def test_iou_create_with_params(app: FastAPI,
assert f.read() == "hostname test" assert f.read() == "hostname test"
@pytest.mark.parametrize(
"name, status_code",
(
("valid-name", status.HTTP_201_CREATED),
("42name", status.HTTP_409_CONFLICT),
("name42", status.HTTP_201_CREATED),
("-name", status.HTTP_409_CONFLICT),
("name%-test", status.HTTP_409_CONFLICT),
("x" * 63, status.HTTP_201_CREATED),
("x" * 64, status.HTTP_409_CONFLICT),
),
)
async def test_iou_create_with_invalid_name(
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int
) -> None:
base_params["name"] = name
response = await compute_client.post(
app.url_path_for("compute:create_iou_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status_code
async def test_iou_create_startup_config_already_exist( async def test_iou_create_startup_config_already_exist(
app: FastAPI, app: FastAPI,
compute_client: AsyncClient, compute_client: AsyncClient,
@ -133,7 +161,7 @@ async def test_iou_get(app: FastAPI, compute_client: AsyncClient, compute_projec
response = await compute_client.get(app.url_path_for("compute:get_iou_node", project_id=vm["project_id"], node_id=vm["node_id"])) response = await compute_client.get(app.url_path_for("compute:get_iou_node", project_id=vm["project_id"], node_id=vm["node_id"]))
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "IOU-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["serial_adapters"] == 2 assert response.json()["serial_adapters"] == 2
assert response.json()["ethernet_adapters"] == 2 assert response.json()["ethernet_adapters"] == 2

View File

@ -66,7 +66,7 @@ def fake_qemu_img_binary(tmpdir):
def base_params(tmpdir, fake_qemu_bin) -> dict: def base_params(tmpdir, fake_qemu_bin) -> dict:
"""Return standard parameters""" """Return standard parameters"""
return {"name": "PC TEST 1", "qemu_path": fake_qemu_bin} return {"name": "QEMU-TEST-1", "qemu_path": fake_qemu_bin}
@pytest.fixture @pytest.fixture
@ -88,7 +88,7 @@ async def test_qemu_create(app: FastAPI,
response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params) response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["qemu_path"] == fake_qemu_bin assert response.json()["qemu_path"] == fake_qemu_bin
assert response.json()["platform"] == "x86_64" assert response.json()["platform"] == "x86_64"
@ -104,7 +104,7 @@ async def test_qemu_create_platform(app: FastAPI,
base_params["platform"] = "x86_64" base_params["platform"] = "x86_64"
response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params) response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params)
assert response.status_code == status.HTTP_201_CREATED assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["qemu_path"] == fake_qemu_bin assert response.json()["qemu_path"] == fake_qemu_bin
assert response.json()["platform"] == "x86_64" assert response.json()["platform"] == "x86_64"
@ -122,13 +122,44 @@ async def test_qemu_create_with_params(app: FastAPI,
params["hda_disk_image"] = "linux载.img" params["hda_disk_image"] = "linux载.img"
response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=params) response = await compute_client.post(app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=params)
assert response.status_code == status.HTTP_201_CREATED assert response.status_code == status.HTTP_201_CREATED
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["ram"] == 1024 assert response.json()["ram"] == 1024
assert response.json()["hda_disk_image"] == "linux载.img" assert response.json()["hda_disk_image"] == "linux载.img"
assert response.json()["hda_disk_image_md5sum"] == "c4ca4238a0b923820dcc509a6f75849b" assert response.json()["hda_disk_image_md5sum"] == "c4ca4238a0b923820dcc509a6f75849b"
@pytest.mark.parametrize(
"name, status_code",
(
("valid-name.com", status.HTTP_201_CREATED),
("42name", status.HTTP_201_CREATED),
("424242", status.HTTP_409_CONFLICT),
("name42", status.HTTP_201_CREATED),
("name.424242", status.HTTP_409_CONFLICT),
("-name", status.HTTP_409_CONFLICT),
("name%-test", status.HTTP_409_CONFLICT),
("x" * 63, status.HTTP_201_CREATED),
("x" * 64, status.HTTP_409_CONFLICT),
(("x" * 62 + ".") * 4, status.HTTP_201_CREATED),
("xx" + ("x" * 62 + ".") * 4, status.HTTP_409_CONFLICT),
),
)
async def test_qemu_create_with_invalid_name(
app: FastAPI,
compute_client: AsyncClient,
compute_project: Project,
base_params: dict,
name: str,
status_code: int
) -> None:
base_params["name"] = name
response = await compute_client.post(
app.url_path_for("compute:create_qemu_node", project_id=compute_project.id), json=base_params
)
assert response.status_code == status_code
# async def test_qemu_create_with_project_file(app: FastAPI, # async def test_qemu_create_with_project_file(app: FastAPI,
# compute_client: AsyncClient, # compute_client: AsyncClient,
# compute_project: Project, # compute_project: Project,
@ -157,7 +188,7 @@ async def test_qemu_get(app: FastAPI, compute_client: AsyncClient, compute_proje
app.url_path_for("compute:get_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"]) app.url_path_for("compute:get_qemu_node", project_id=qemu_vm["project_id"], node_id=qemu_vm["node_id"])
) )
assert response.status_code == status.HTTP_200_OK assert response.status_code == status.HTTP_200_OK
assert response.json()["name"] == "PC TEST 1" assert response.json()["name"] == "QEMU-TEST-1"
assert response.json()["project_id"] == compute_project.id assert response.json()["project_id"] == compute_project.id
assert response.json()["node_directory"] == os.path.join( assert response.json()["node_directory"] == os.path.join(
compute_project.path, compute_project.path,