diff --git a/gns3server/controller/schemas/__init__.py b/gns3server/controller/schemas/__init__.py
new file mode 100644
index 00000000..dcb72c6c
--- /dev/null
+++ b/gns3server/controller/schemas/__init__.py
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+from .vpcs_templates import VPCSTemplate
+from .cloud_templates import CloudTemplate
+from .iou_templates import IOUTemplate
+from .docker_templates import DockerTemplate
+from .ethernet_hub_templates import EthernetHubTemplate
+from .ethernet_switch_templates import EthernetSwitchTemplate
+from .virtualbox_templates import VirtualBoxTemplate
+from .vmware_templates import VMwareTemplate
+from .qemu_templates import QemuTemplate
+from .dynamips_templates import (
+ DynamipsTemplate,
+ C1700DynamipsTemplate,
+ C2600DynamipsTemplate,
+ C2691DynamipsTemplate,
+ C3600DynamipsTemplate,
+ C3725DynamipsTemplate,
+ C3745DynamipsTemplate,
+ C7200DynamipsTemplate
+)
diff --git a/gns3server/endpoints/schemas/cloud_templates.py b/gns3server/controller/schemas/cloud_templates.py
similarity index 54%
rename from gns3server/endpoints/schemas/cloud_templates.py
rename to gns3server/controller/schemas/cloud_templates.py
index 3ce0e05b..04558942 100644
--- a/gns3server/endpoints/schemas/cloud_templates.py
+++ b/gns3server/controller/schemas/cloud_templates.py
@@ -16,30 +16,14 @@
# along with this program. If not, see .
-from .templates import Category, TemplateBase
-from .cloud_nodes import EthernetPort, TAPPort, UDPPort
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.cloud_nodes import EthernetPort, TAPPort, UDPPort, CloudConsoleType
from pydantic import Field
from typing import Optional, Union, List
-from enum import Enum
-
-from .nodes import NodeType
-class RemoteConsoleType(str, Enum):
- """
- Supported remote console types for cloud nodes.
- """
-
- none = "none"
- telnet = "telnet"
- vnc = "vnc"
- spice = "spice"
- http = "http"
- https = "https"
-
-
-class CloudTemplateBase(TemplateBase):
+class CloudTemplate(TemplateBase):
category: Optional[Category] = "guest"
default_name_format: Optional[str] = "Cloud{0}"
@@ -47,28 +31,5 @@ class CloudTemplateBase(TemplateBase):
ports_mapping: List[Union[EthernetPort, TAPPort, UDPPort]] = []
remote_console_host: Optional[str] = Field("127.0.0.1", description="Remote console host or IP")
remote_console_port: Optional[int] = Field(23, gt=0, le=65535, description="Remote console TCP port")
- remote_console_type: Optional[RemoteConsoleType] = Field("none", description="Remote console type")
- remote_console_path: Optional[str] = Field("/", description="Path of the remote web interface")
-
-
-class CloudTemplateCreate(CloudTemplateBase):
-
- name: str
- template_type: NodeType
- compute_id: str
-
-
-class CloudTemplateUpdate(CloudTemplateBase):
-
- pass
-
-
-class CloudTemplate(CloudTemplateBase):
-
- template_id: str
- name: str
- category: Category
- symbol: str
- builtin: bool
- template_type: NodeType
- compute_id: Union[str, None]
+ remote_console_type: Optional[CloudConsoleType] = Field("none", description="Remote console type")
+ remote_console_http_path: Optional[str] = Field("/", description="Path of the remote web interface")
diff --git a/gns3server/controller/schemas/docker_templates.py b/gns3server/controller/schemas/docker_templates.py
new file mode 100644
index 00000000..3d243217
--- /dev/null
+++ b/gns3server/controller/schemas/docker_templates.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.nodes import CustomAdapter
+from gns3server.endpoints.schemas.docker_nodes import ConsoleType, AuxType
+
+from pydantic import Field
+from typing import Optional, List
+
+
+class DockerTemplate(TemplateBase):
+
+ category: Optional[Category] = "guest"
+ default_name_format: Optional[str] = "{name}-{0}"
+ symbol: Optional[str] = ":/symbols/docker_guest.svg"
+ image: str = Field(..., description="Docker image name")
+ adapters: Optional[int] = Field(1, ge=0, le=100, description="Number of adapters")
+ start_command: Optional[str] = Field("", description="Docker CMD entry")
+ environment: Optional[str] = Field("", description="Docker environment variables")
+ console_type: Optional[ConsoleType] = Field("telnet", description="Console type")
+ aux_type: Optional[AuxType] = Field("none", description="Auxiliary console type")
+ console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started")
+ console_http_port: Optional[int] = Field(80, gt=0, le=65535, description="Internal port in the container for the HTTP server")
+ console_http_path: Optional[str] = Field("/", description="Path of the web interface",)
+ console_resolution: Optional[str] = Field("1024x768", regex="^[0-9]+x[0-9]+$", description="Console resolution for VNC")
+ extra_hosts: Optional[str] = Field("", description="Docker extra hosts (added to /etc/hosts)")
+ extra_volumes: Optional[List] = Field([], description="Additional directories to make persistent")
+ memory: Optional[int] = Field(0, description="Maximum amount of memory the container can use in MB")
+ cpus: Optional[int] = Field(0, description="Maximum amount of CPU resources the container can use")
+ custom_adapters: Optional[List[CustomAdapter]] = Field([], description="Custom adapters")
diff --git a/gns3server/controller/schemas/dynamips_templates.py b/gns3server/controller/schemas/dynamips_templates.py
new file mode 100644
index 00000000..b1ca419d
--- /dev/null
+++ b/gns3server/controller/schemas/dynamips_templates.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.dynamips_nodes import (
+ DynamipsConsoleType,
+ DynamipsPlatform,
+ DynamipsAdapters,
+ DynamipsWics,
+ DynamipsNPE,
+ DynamipsMidplane
+)
+
+from pydantic import Field
+from pathlib import Path
+from typing import Optional
+from enum import Enum
+
+
+class DynamipsTemplate(TemplateBase):
+
+ category: Optional[Category] = "router"
+ default_name_format: Optional[str] = "R{0}"
+ symbol: Optional[str] = ":/symbols/router.svg"
+ platform: DynamipsPlatform = Field(..., description="Cisco router platform")
+ image: Path = Field(..., description="Path to the IOS image")
+ exec_area: Optional[int] = Field(64, description="Exec area value")
+ mmap: Optional[bool] = Field(True, description="MMAP feature")
+ mac_addr: Optional[str] = Field("", description="Base MAC address", regex="^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$|^$")
+ system_id: Optional[str] = Field("FTX0945W0MY", description="System ID")
+ startup_config: Optional[str] = Field("ios_base_startup-config.txt", description="IOS startup configuration file")
+ private_config: Optional[str] = Field("", description="IOS private configuration file")
+ idlepc: Optional[str] = Field("", description="Idle-PC value", regex="^(0x[0-9a-fA-F]+)?$|^$")
+ idlemax: Optional[int] = Field(500, description="Idlemax value")
+ idlesleep: Optional[int] = Field(30, description="Idlesleep value")
+ disk0: Optional[int] = Field(0, description="Disk0 size in MB")
+ disk1: Optional[int] = Field(0, description="Disk1 size in MB")
+ auto_delete_disks: Optional[bool] = Field(False, description="Automatically delete nvram and disk files")
+ console_type: Optional[DynamipsConsoleType] = Field("telnet", description="Console type")
+ console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started")
+ aux_type: Optional[DynamipsConsoleType] = Field("none", description="Auxiliary console type")
+ slot0: Optional[DynamipsAdapters] = Field(None, description="Network module slot 0")
+ slot1: Optional[DynamipsAdapters] = Field(None, description="Network module slot 1")
+ slot2: Optional[DynamipsAdapters] = Field(None, description="Network module slot 2")
+ slot3: Optional[DynamipsAdapters] = Field(None, description="Network module slot 3")
+ slot4: Optional[DynamipsAdapters] = Field(None, description="Network module slot 4")
+ slot5: Optional[DynamipsAdapters] = Field(None, description="Network module slot 5")
+ slot6: Optional[DynamipsAdapters] = Field(None, description="Network module slot 6")
+ wic0: Optional[DynamipsWics] = Field(None, description="Network module WIC slot 0")
+ wic1: Optional[DynamipsWics] = Field(None, description="Network module WIC slot 1")
+ wic2: Optional[DynamipsWics] = Field(None, description="Network module WIC slot 2")
+
+
+class C7200DynamipsTemplate(DynamipsTemplate):
+
+ ram: Optional[int] = Field(512, description="Amount of RAM in MB")
+ nvram: Optional[int] = Field(512, description="Amount of NVRAM in KB")
+ npe: Optional[DynamipsNPE] = Field("npe-400", description="NPE model")
+ midplane: Optional[DynamipsMidplane] = Field("vxr", description="Midplane model")
+ sparsemem: Optional[bool] = Field(True, description="Sparse memory feature")
+
+
+class C3725DynamipsTemplate(DynamipsTemplate):
+
+ ram: Optional[int] = Field(128, description="Amount of RAM in MB")
+ nvram: Optional[int] = Field(256, description="Amount of NVRAM in KB")
+ iomem: Optional[int] = Field(5, ge=0, le=100, description="I/O memory percentage")
+ sparsemem: Optional[bool] = Field(True, description="Sparse memory feature")
+
+
+class C3745DynamipsTemplate(DynamipsTemplate):
+
+ ram: Optional[int] = Field(256, description="Amount of RAM in MB")
+ nvram: Optional[int] = Field(256, description="Amount of NVRAM in KB")
+ iomem: Optional[int] = Field(5, ge=0, le=100, description="I/O memory percentage")
+ sparsemem: Optional[bool] = Field(True, description="Sparse memory feature")
+
+
+class C3600ChassisType(str, Enum):
+
+ chassis_3620 = "3620"
+ chassis_3640 = "3640"
+ chassis_3660 = "3660"
+
+
+class C3600DynamipsTemplate(DynamipsTemplate):
+
+ chassis: Optional[C3600ChassisType] = Field("c3660", description="Chassis type")
+ ram: Optional[int] = Field(192, description="Amount of RAM in MB")
+ nvram: Optional[int] = Field(128, description="Amount of NVRAM in KB")
+ iomem: Optional[int] = Field(5, ge=0, le=100, description="I/O memory percentage")
+ sparsemem: Optional[bool] = Field(True, description="Sparse memory feature")
+
+
+class C2691DynamipsTemplate(DynamipsTemplate):
+
+ ram: Optional[int] = Field(192, description="Amount of RAM in MB")
+ nvram: Optional[int] = Field(256, description="Amount of NVRAM in KB")
+ iomem: Optional[int] = Field(5, ge=0, le=100, description="I/O memory percentage")
+ sparsemem: Optional[bool] = Field(True, description="Sparse memory feature")
+
+
+class C2600ChassisType(str, Enum):
+
+ chassis_2610 = "2610"
+ chassis_2620 = "2620"
+ chassis_2610xm = "2610XM"
+ chassis_2620xm = "2620XM"
+ chassis_2650xm = "2650XM"
+ chassis_2621 = "2621"
+ chassis_2611xm = "2611XM"
+ chassis_2621xm = "2621XM"
+ chassis_2651xm = "2651XM"
+
+
+class C2600DynamipsTemplate(DynamipsTemplate):
+
+ chassis: Optional[C2600ChassisType] = Field("2651XM", description="Chassis type")
+ ram: Optional[int] = Field(160, description="Amount of RAM in MB")
+ nvram: Optional[int] = Field(128, description="Amount of NVRAM in KB")
+ iomem: Optional[int] = Field(15, ge=0, le=100, description="I/O memory percentage")
+ sparsemem: Optional[bool] = Field(True, description="Sparse memory feature")
+
+
+class C1700ChassisType(str, Enum):
+
+ chassis_1720 = "1720"
+ chassis_1721 = "1721"
+ chassis_1750 = "1750"
+ chassis_1751 = "1751"
+ chassis_1760 = "1760"
+
+
+class C1700DynamipsTemplate(DynamipsTemplate):
+
+ chassis: Optional[C1700ChassisType] = Field("1760", description="Chassis type")
+ ram: Optional[int] = Field(160, description="Amount of RAM in MB")
+ nvram: Optional[int] = Field(128, description="Amount of NVRAM in KB")
+ iomem: Optional[int] = Field(15, ge=0, le=100, description="I/O memory percentage")
+ sparsemem: Optional[bool] = Field(False, description="Sparse memory feature")
diff --git a/gns3server/controller/schemas/ethernet_hub_templates.py b/gns3server/controller/schemas/ethernet_hub_templates.py
new file mode 100644
index 00000000..2676a120
--- /dev/null
+++ b/gns3server/controller/schemas/ethernet_hub_templates.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.ethernet_hub_nodes import EthernetHubPort
+
+from pydantic import Field
+from typing import Optional, List
+
+
+DEFAULT_PORTS = [
+ dict(port_number=0, name="Ethernet0"),
+ dict(port_number=1, name="Ethernet1"),
+ dict(port_number=2, name="Ethernet2"),
+ dict(port_number=3, name="Ethernet3"),
+ dict(port_number=4, name="Ethernet4"),
+ dict(port_number=5, name="Ethernet5"),
+ dict(port_number=6, name="Ethernet6"),
+ dict(port_number=7, name="Ethernet7")
+]
+
+
+class EthernetHubTemplate(TemplateBase):
+
+ category: Optional[Category] = "switch"
+ default_name_format: Optional[str] = "Hub{0}"
+ symbol: Optional[str] = ":/symbols/hub.svg"
+ ports_mapping: Optional[List[EthernetHubPort]] = Field(DEFAULT_PORTS, description="Ports")
diff --git a/gns3server/controller/schemas/ethernet_switch_templates.py b/gns3server/controller/schemas/ethernet_switch_templates.py
new file mode 100644
index 00000000..c82977ea
--- /dev/null
+++ b/gns3server/controller/schemas/ethernet_switch_templates.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.ethernet_switch_nodes import EthernetSwitchPort
+
+from pydantic import Field
+from typing import Optional, List
+from enum import Enum
+
+DEFAULT_PORTS = [
+ dict(port_number=0, name="Ethernet0", vlan=1, type="access", ethertype=""),
+ dict(port_number=1, name="Ethernet1", vlan=1, type="access", ethertype=""),
+ dict(port_number=2, name="Ethernet2", vlan=1, type="access", ethertype=""),
+ dict(port_number=3, name="Ethernet3", vlan=1, type="access", ethertype=""),
+ dict(port_number=4, name="Ethernet4", vlan=1, type="access", ethertype=""),
+ dict(port_number=5, name="Ethernet5", vlan=1, type="access", ethertype=""),
+ dict(port_number=6, name="Ethernet6", vlan=1, type="access", ethertype=""),
+ dict(port_number=7, name="Ethernet7", vlan=1, type="access", ethertype="")
+]
+
+
+class ConsoleType(str, Enum):
+ """
+ Supported console types for Ethernet switch nodes.
+ """
+
+ none = "none"
+ telnet = "telnet"
+
+
+class EthernetSwitchTemplate(TemplateBase):
+
+ category: Optional[Category] = "switch"
+ default_name_format: Optional[str] = "Switch{0}"
+ symbol: Optional[str] = ":/symbols/ethernet_switch.svg"
+ ports_mapping: Optional[List[EthernetSwitchPort]] = Field(DEFAULT_PORTS, description="Ports")
+ console_type: Optional[ConsoleType] = Field("none", description="Console type")
diff --git a/gns3server/endpoints/schemas/iou_templates.py b/gns3server/controller/schemas/iou_templates.py
similarity index 74%
rename from gns3server/endpoints/schemas/iou_templates.py
rename to gns3server/controller/schemas/iou_templates.py
index eaf90182..cc7ca9ac 100644
--- a/gns3server/endpoints/schemas/iou_templates.py
+++ b/gns3server/controller/schemas/iou_templates.py
@@ -16,31 +16,19 @@
# along with this program. If not, see .
-from .templates import Category, TemplateBase
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.iou_nodes import ConsoleType
from pydantic import Field
from pathlib import Path
-from typing import Optional, Union
-from enum import Enum
-
-from .nodes import NodeType
+from typing import Optional
-class ConsoleType(str, Enum):
- """
- Supported console types for IOU nodes
- """
-
- none = "none"
- telnet = "telnet"
-
-
-class IOUTemplateBase(TemplateBase):
+class IOUTemplate(TemplateBase):
category: Optional[Category] = "router"
default_name_format: Optional[str] = "IOU{0}"
symbol: Optional[str] = ":/symbols/multilayer_switch.svg"
-
path: Path = Field(..., description="Path of IOU executable")
ethernet_adapters: Optional[int] = Field(2, description="Number of ethernet adapters")
serial_adapters: Optional[int] = Field(2, description="Number of serial adapters")
@@ -53,25 +41,3 @@ class IOUTemplateBase(TemplateBase):
console_type: Optional[ConsoleType] = Field("telnet", description="Console type")
console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started")
-
-class IOUTemplateCreate(IOUTemplateBase):
-
- name: str
- template_type: NodeType
- compute_id: str
-
-
-class IOUTemplateUpdate(IOUTemplateBase):
-
- pass
-
-
-class IOUTemplate(IOUTemplateBase):
-
- template_id: str
- name: str
- category: Category
- symbol: str
- builtin: bool
- template_type: NodeType
- compute_id: Union[str, None]
diff --git a/gns3server/controller/schemas/qemu_templates.py b/gns3server/controller/schemas/qemu_templates.py
new file mode 100644
index 00000000..a4f7a28f
--- /dev/null
+++ b/gns3server/controller/schemas/qemu_templates.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.qemu_nodes import (
+ QemuConsoleType,
+ QemuPlatform,
+ QemuAdapterType,
+ QemuOnCloseAction,
+ QemuBootPriority,
+ QemuDiskInterfaceType,
+ QemuProcessPriority,
+ CustomAdapter
+)
+
+from pathlib import Path
+from pydantic import Field
+from typing import Optional, List
+
+
+class QemuTemplate(TemplateBase):
+
+ category: Optional[Category] = "guest"
+ default_name_format: Optional[str] = "{name}-{0}"
+ symbol: Optional[str] = ":/symbols/qemu_guest.svg"
+ qemu_path: Optional[Path] = Field("", description="Qemu executable path")
+ platform: Optional[QemuPlatform] = Field("i386", description="Platform to emulate")
+ linked_clone: Optional[bool] = Field(True, description="Whether the VM is a linked clone or not")
+ ram: Optional[int] = Field(256, description="Amount of RAM in MB")
+ cpus: Optional[int] = Field(1, ge=1, le=255, description="Number of vCPUs")
+ maxcpus: Optional[int] = Field(1, ge=1, le=255, description="Maximum number of hotpluggable vCPUs")
+ adapters: Optional[int] = Field(1, ge=0, le=275, description="Number of adapters")
+ adapter_type: Optional[QemuAdapterType] = Field("e1000", description="QEMU adapter type")
+ mac_address: Optional[str] = Field("", description="QEMU MAC address", regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$|^$")
+ first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0")
+ port_name_format: Optional[str] = Field("Ethernet{0}", description="Optional formatting of the networking port example: eth{0}")
+ port_segment_size: Optional[int] = Field(0, description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2")
+ console_type: Optional[QemuConsoleType] = Field("telnet", description="Console type")
+ console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started")
+ aux_type: Optional[QemuConsoleType] = Field("none", description="Auxiliary console type")
+ boot_priority: Optional[QemuBootPriority] = Field("c", description="QEMU boot priority")
+ hda_disk_image: Optional[Path] = Field("", description="QEMU hda disk image path")
+ hda_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hda interface")
+ hdb_disk_image: Optional[Path] = Field("", description="QEMU hdb disk image path")
+ hdb_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hdb interface")
+ hdc_disk_image: Optional[Path] = Field("", description="QEMU hdc disk image path")
+ hdc_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hdc interface")
+ hdd_disk_image: Optional[Path] = Field("", description="QEMU hdd disk image path")
+ hdd_disk_interface: Optional[QemuDiskInterfaceType] = Field("none", description="QEMU hdd interface")
+ cdrom_image: Optional[Path] = Field("", description="QEMU cdrom image path")
+ initrd: Optional[Path] = Field("", description="QEMU initrd path")
+ kernel_image: Optional[Path] = Field("", description="QEMU kernel image path")
+ bios_image: Optional[Path] = Field("", description="QEMU bios image path")
+ kernel_command_line: Optional[str] = Field("", description="QEMU kernel command line")
+ legacy_networking: Optional[bool] = Field(False, description="Use QEMU legagy networking commands (-net syntax)")
+ replicate_network_connection_state: Optional[bool] = Field(True, description="Replicate the network connection state for links in Qemu")
+ create_config_disk: Optional[bool] = Field(False, description="Automatically create a config disk on HDD disk interface (secondary slave)")
+ on_close: Optional[QemuOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed")
+ cpu_throttling: Optional[int] = Field(0, ge=0, le=800, description="Percentage of CPU allowed for QEMU")
+ process_priority: Optional[QemuProcessPriority] = Field("normal", description="Process priority for QEMU")
+ options: Optional[str] = Field("", description="Additional QEMU options")
+ custom_adapters: Optional[List[CustomAdapter]] = Field([], description="Custom adapters")
diff --git a/gns3server/controller/schemas/topology.py b/gns3server/controller/schemas/topology.py
new file mode 100644
index 00000000..162b2eea
--- /dev/null
+++ b/gns3server/controller/schemas/topology.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2015 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 .
+
+#
+# This file contains the validation for checking a .gns3 file
+#
+
+from gns3server.endpoints.schemas.computes import Compute
+from gns3server.endpoints.schemas.drawings import Drawing
+from gns3server.endpoints.schemas.links import Link
+from gns3server.endpoints.schemas.nodes import Node
+
+from gns3server.endpoints.schemas.projects import (
+ Supplier,
+ Variable
+)
+
+from pydantic import BaseModel, Field
+from typing import Optional, List
+from enum import Enum
+from uuid import UUID
+
+
+class TopologyType(str, Enum):
+
+ topology = "topology"
+
+
+class TopologyContent(BaseModel):
+
+ computes: List[Compute] = Field(..., description="List of computes")
+ drawings: List[Drawing] = Field(..., description="List of drawings")
+ links: List[Link] = Field(..., description="List of links")
+ nodes: List[Node] = Field(..., description="List of nodes")
+
+
+class Topology(BaseModel):
+
+ project_id: UUID = Field(..., description="Project UUID")
+ type: TopologyType = Field(..., description="Type of file. It's always topology")
+ revision: int = Field(..., description="Version of the .gns3 specification")
+ version: str = Field(..., description="Version of the GNS3 software which have update the file for the last time")
+ name: str = Field(..., description="Name of the project")
+ topology: TopologyContent = Field(..., description="Topology content")
+ auto_start: Optional[bool] = Field(None, description="Start the topology when opened")
+ auto_close: Optional[bool] = Field(None, description="Close the topology when no client is connected")
+ scene_height: Optional[int] = Field(None, description="Height of the drawing area")
+ scene_width: Optional[int] = Field(None, description="Width of the drawing area")
+ zoom: Optional[int] = Field(None, description="Zoom of the drawing area")
+ show_layers: Optional[bool] = Field(None, description="Show layers on the drawing area")
+ snap_to_grid: Optional[bool] = Field(None, description="Snap to grid on the drawing area")
+ show_grid: Optional[bool] = Field(None, description="Show the grid on the drawing area")
+ grid_size: Optional[int] = Field(None, description="Grid size for the drawing area for nodes")
+ drawing_grid_size: Optional[int] = Field(None, description="Grid size for the drawing area for drawings")
+ show_interface_labels: Optional[bool] = Field(None, description="Show interface labels on the drawing area")
+ supplier: Optional[Supplier] = Field(None, description="Supplier of the project")
+ variables: Optional[List[Variable]] = Field(None, description="Variables required to run the project")
+
+
+def main():
+
+ import json
+ import sys
+
+ with open(sys.argv[1]) as f:
+ data = json.load(f)
+ Topology.parse_obj(data)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/gns3server/controller/schemas/virtualbox_templates.py b/gns3server/controller/schemas/virtualbox_templates.py
new file mode 100644
index 00000000..3dc911a5
--- /dev/null
+++ b/gns3server/controller/schemas/virtualbox_templates.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.virtualbox_nodes import (
+ VirtualBoxConsoleType,
+ VirtualBoxAdapterType,
+ VirtualBoxOnCloseAction,
+ CustomAdapter
+)
+
+from pydantic import Field
+from typing import Optional, List
+
+
+class VirtualBoxTemplate(TemplateBase):
+
+ category: Optional[Category] = "guest"
+ default_name_format: Optional[str] = "{name}-{0}"
+ symbol: Optional[str] = ":/symbols/vbox_guest.svg"
+ vmname: str = Field(..., description="VirtualBox VM name (in VirtualBox itself)")
+ ram: Optional[int] = Field(256, gt=0, description="Amount of RAM in MB")
+ linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not")
+ adapters: Optional[int] = Field(1, ge=0, le=36, description="Number of adapters") # 36 is the maximum given by the ICH9 chipset in VirtualBox
+ use_any_adapter: Optional[bool] = Field(False, description="Allow GNS3 to use any VirtualBox adapter")
+ adapter_type: Optional[VirtualBoxAdapterType] = Field("Intel PRO/1000 MT Desktop (82540EM)", description="VirtualBox adapter type")
+ first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0")
+ port_name_format: Optional[str] = Field("Ethernet{0}", description="Optional formatting of the networking port example: eth{0}")
+ port_segment_size: Optional[int] = Field(0, description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2")
+ headless: Optional[bool] = Field(False, description="Headless mode")
+ on_close: Optional[VirtualBoxOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed")
+ console_type: Optional[VirtualBoxConsoleType] = Field("none", description="Console type")
+ console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started")
+ custom_adapters: Optional[List[CustomAdapter]] = Field([], description="Custom adapters")
diff --git a/gns3server/controller/schemas/vmware_templates.py b/gns3server/controller/schemas/vmware_templates.py
new file mode 100644
index 00000000..867be053
--- /dev/null
+++ b/gns3server/controller/schemas/vmware_templates.py
@@ -0,0 +1,49 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2020 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 .
+
+
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.vmware_nodes import (
+ VMwareConsoleType,
+ VMwareAdapterType,
+ VMwareOnCloseAction,
+ CustomAdapter
+)
+
+from pathlib import Path
+from pydantic import Field
+from typing import Optional, List
+
+
+class VMwareTemplate(TemplateBase):
+
+ category: Optional[Category] = "guest"
+ default_name_format: Optional[str] = "{name}-{0}"
+ symbol: Optional[str] = ":/symbols/vmware_guest.svg"
+ vmx_path: Path = Field(..., description="Path to the vmx file")
+ linked_clone: Optional[bool] = Field(False, description="Whether the VM is a linked clone or not")
+ first_port_name: Optional[str] = Field("", description="Optional name of the first networking port example: eth0")
+ port_name_format: Optional[str] = Field("Ethernet{0}", description="Optional formatting of the networking port example: eth{0}")
+ port_segment_size: Optional[int] = Field(0, description="Optional port segment size. A port segment is a block of port. For example Ethernet0/0 Ethernet0/1 is the module 0 with a port segment size of 2")
+ adapters: Optional[int] = Field(1, ge=0, le=10, description="Number of adapters") # 10 is the maximum adapters support by VMware VMs
+ adapter_type: Optional[VMwareAdapterType] = Field("e1000", description="VMware adapter type")
+ use_any_adapter: Optional[bool] = Field(False, description="Allow GNS3 to use any VMware adapter")
+ headless: Optional[bool] = Field(False, description="Headless mode")
+ on_close: Optional[VMwareOnCloseAction] = Field("power_off", description="Action to execute on the VM is closed")
+ console_type: Optional[VMwareConsoleType] = Field("none", description="Console type")
+ console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started")
+ custom_adapters: Optional[List[CustomAdapter]] = Field([], description="Custom adapters")
diff --git a/gns3server/endpoints/schemas/vpcs_templates.py b/gns3server/controller/schemas/vpcs_templates.py
similarity index 64%
rename from gns3server/endpoints/schemas/vpcs_templates.py
rename to gns3server/controller/schemas/vpcs_templates.py
index 6ee5f6a4..1adcc69c 100644
--- a/gns3server/endpoints/schemas/vpcs_templates.py
+++ b/gns3server/controller/schemas/vpcs_templates.py
@@ -16,53 +16,18 @@
# along with this program. If not, see .
-from .templates import Category, TemplateBase
+from gns3server.endpoints.schemas.templates import Category, TemplateBase
+from gns3server.endpoints.schemas.vpcs_nodes import ConsoleType
from pydantic import Field
-from typing import Optional, Union
-from enum import Enum
-
-from .nodes import NodeType
+from typing import Optional
-class ConsoleType(str, Enum):
- """
- Supported console types for VPCS nodes
- """
-
- none = "none"
- telnet = "telnet"
-
-
-class VPCSTemplateBase(TemplateBase):
+class VPCSTemplate(TemplateBase):
category: Optional[Category] = "guest"
default_name_format: Optional[str] = "PC{0}"
symbol: Optional[str] = ":/symbols/vpcs_guest.svg"
-
base_script_file: Optional[str] = Field("vpcs_base_config.txt", description="Script file")
console_type: Optional[ConsoleType] = Field("telnet", description="Console type")
console_auto_start: Optional[bool] = Field(False, description="Automatically start the console when the node has started")
-
-
-class VPCSTemplateCreate(VPCSTemplateBase):
-
- name: str
- template_type: NodeType
- compute_id: str
-
-
-class VPCSTemplateUpdate(VPCSTemplateBase):
-
- pass
-
-
-class VPCSTemplate(VPCSTemplateBase):
-
- template_id: str
- name: str
- category: Category
- symbol: str
- builtin: bool
- template_type: NodeType
- compute_id: Union[str, None]
diff --git a/gns3server/controller/template.py b/gns3server/controller/template.py
index fd800c21..1774ddac 100644
--- a/gns3server/controller/template.py
+++ b/gns3server/controller/template.py
@@ -1,6 +1,6 @@
#!/usr/bin/env python
#
-# Copyright (C) 2016 GNS3 Technologies Inc.
+# Copyright (C) 2020 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
@@ -17,57 +17,14 @@
import copy
import uuid
-import json
-import jsonschema
-from gns3server.schemas.cloud_template import CLOUD_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.ethernet_switch_template import ETHERNET_SWITCH_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.ethernet_hub_template import ETHERNET_HUB_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.docker_template import DOCKER_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.vpcs_template import VPCS_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.traceng_template import TRACENG_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.virtualbox_template import VIRTUALBOX_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.vmware_template import VMWARE_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.iou_template import IOU_TEMPLATE_OBJECT_SCHEMA
-from gns3server.schemas.qemu_template import QEMU_TEMPLATE_OBJECT_SCHEMA
-
-from gns3server.schemas.dynamips_template import (
- DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- C7200_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- C3745_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- C3725_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- C3600_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- C2691_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- C2600_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- C1700_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA
-)
+from pydantic import ValidationError
+from fastapi.encoders import jsonable_encoder
+from gns3server.controller import schemas
import logging
log = logging.getLogger(__name__)
-
-# Add default values for missing entries in a request, largely taken from jsonschema documentation example
-# https://python-jsonschema.readthedocs.io/en/latest/faq/#why-doesn-t-my-schema-s-default-property-set-the-default-on-my-instance
-def extend_with_default(validator_class):
-
- validate_properties = validator_class.VALIDATORS["properties"]
- def set_defaults(validator, properties, instance, schema):
- if jsonschema.Draft4Validator(schema).is_valid(instance):
- # only add default for the matching sub-schema (e.g. when using 'oneOf')
- for property, subschema in properties.items():
- if "default" in subschema:
- instance.setdefault(property, subschema["default"])
-
- for error in validate_properties(validator, properties, instance, schema,):
- yield error
-
- return jsonschema.validators.extend(
- validator_class, {"properties" : set_defaults},
- )
-
-
-ValidatorWithDefaults = extend_with_default(jsonschema.Draft4Validator)
-
ID_TO_CATEGORY = {
3: "firewall",
2: "guest",
@@ -76,27 +33,26 @@ ID_TO_CATEGORY = {
}
TEMPLATE_TYPE_TO_SHEMA = {
- "cloud": CLOUD_TEMPLATE_OBJECT_SCHEMA,
- "ethernet_hub": ETHERNET_HUB_TEMPLATE_OBJECT_SCHEMA,
- "ethernet_switch": ETHERNET_SWITCH_TEMPLATE_OBJECT_SCHEMA,
- "docker": DOCKER_TEMPLATE_OBJECT_SCHEMA,
- "dynamips": DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- "vpcs": VPCS_TEMPLATE_OBJECT_SCHEMA,
- "traceng": TRACENG_TEMPLATE_OBJECT_SCHEMA,
- "virtualbox": VIRTUALBOX_TEMPLATE_OBJECT_SCHEMA,
- "vmware": VMWARE_TEMPLATE_OBJECT_SCHEMA,
- "iou": IOU_TEMPLATE_OBJECT_SCHEMA,
- "qemu": QEMU_TEMPLATE_OBJECT_SCHEMA
+ "cloud": schemas.CloudTemplate,
+ "ethernet_hub": schemas.EthernetHubTemplate,
+ "ethernet_switch": schemas.EthernetSwitchTemplate,
+ "docker": schemas.DockerTemplate,
+ "dynamips": schemas.DynamipsTemplate,
+ "vpcs": schemas.VPCSTemplate,
+ "virtualbox": schemas.VirtualBoxTemplate,
+ "vmware": schemas.VMwareTemplate,
+ "iou": schemas.IOUTemplate,
+ "qemu": schemas.QemuTemplate
}
DYNAMIPS_PLATFORM_TO_SHEMA = {
- "c7200": C7200_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- "c3745": C3745_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- "c3725": C3725_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- "c3600": C3600_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- "c2691": C2691_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- "c2600": C2600_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA,
- "c1700": C1700_DYNAMIPS_TEMPLATE_OBJECT_SCHEMA
+ "c7200": schemas.C7200DynamipsTemplate,
+ "c3745": schemas.C3745DynamipsTemplate,
+ "c3725": schemas.C3725DynamipsTemplate,
+ "c3600": schemas.C3600DynamipsTemplate,
+ "c2691": schemas.C2691DynamipsTemplate,
+ "c2600": schemas.C2600DynamipsTemplate,
+ "c1700": schemas.C1700DynamipsTemplate
}
@@ -141,11 +97,18 @@ class Template:
self._builtin = builtin
if builtin is False:
- self.validate_and_apply_defaults(TEMPLATE_TYPE_TO_SHEMA[self.template_type])
-
- if self.template_type == "dynamips":
- # special case for Dynamips to cover all platform types that contain specific settings
- self.validate_and_apply_defaults(DYNAMIPS_PLATFORM_TO_SHEMA[self._settings["platform"]])
+ try:
+ template_schema = TEMPLATE_TYPE_TO_SHEMA[self.template_type]
+ template_settings_with_defaults = template_schema .parse_obj(self.__json__())
+ self._settings = jsonable_encoder(template_settings_with_defaults.dict())
+ if self.template_type == "dynamips":
+ # special case for Dynamips to cover all platform types that contain specific settings
+ dynamips_template_schema = DYNAMIPS_PLATFORM_TO_SHEMA[self._settings["platform"]]
+ dynamips_template_settings_with_defaults = dynamips_template_schema.parse_obj(self.__json__())
+ self._settings = jsonable_encoder(dynamips_template_settings_with_defaults.dict())
+ except ValidationError as e:
+ print(e) #TODO: handle errors
+ raise
log.debug('Template "{name}" [{id}] loaded'.format(name=self.name, id=self._id))
@@ -179,7 +142,6 @@ class Template:
def update(self, **kwargs):
-
from gns3server.controller import Controller
controller = Controller.instance()
Controller.instance().check_can_write_config()
@@ -187,17 +149,6 @@ class Template:
controller.notification.controller_emit("template.updated", self.__json__())
controller.save()
- def validate_and_apply_defaults(self, schema):
-
- validator = ValidatorWithDefaults(schema)
- try:
- validator.validate(self.__json__())
- except jsonschema.ValidationError as e:
- message = "JSON schema error {}".format(e.message)
- log.error(message)
- log.debug("Input schema: {}".format(json.dumps(schema)))
- raise
-
def __json__(self):
"""
Template settings.
diff --git a/gns3server/controller/template_manager.py b/gns3server/controller/template_manager.py
index ddd857bf..663d63e1 100644
--- a/gns3server/controller/template_manager.py
+++ b/gns3server/controller/template_manager.py
@@ -17,7 +17,7 @@
import copy
import uuid
-import jsonschema
+import pydantic
from .controller_error import ControllerError, ControllerNotFoundError
from .template import Template
@@ -53,8 +53,8 @@ class TemplateManager:
try:
template = Template(template_settings.get("template_id"), template_settings)
self._templates[template.id] = template
- except jsonschema.ValidationError as e:
- message = "Cannot load template with JSON data '{}': {}".format(template_settings, e.message)
+ except pydantic.ValidationError as e:
+ message = "Cannot load template with JSON data '{}': {}".format(template_settings, e)
log.warning(message)
continue
@@ -90,8 +90,8 @@ class TemplateManager:
template_id = settings.setdefault("template_id", str(uuid.uuid4()))
try:
template = Template(template_id, settings)
- except jsonschema.ValidationError as e:
- message = "JSON schema error adding template with JSON data '{}': {}".format(settings, e.message)
+ except pydantic.ValidationError as e:
+ message = "JSON schema error adding template with JSON data '{}': {}".format(settings, e)
raise ControllerError(message)
from . import Controller
diff --git a/gns3server/controller/topology.py b/gns3server/controller/topology.py
index 91eca0b7..bdf65c11 100644
--- a/gns3server/controller/topology.py
+++ b/gns3server/controller/topology.py
@@ -18,21 +18,22 @@
import os
import html
import json
-import copy
import uuid
import glob
import shutil
import zipfile
-import jsonschema
+import pydantic
+from typing import Optional
from ..version import __version__
-from ..schemas.topology import TOPOLOGY_SCHEMA
-from ..schemas import dynamips_vm
from ..utils.qt import qt_font_to_style
from ..compute.dynamips import PLATFORMS_DEFAULT_RAM
from .controller_error import ControllerError
+from gns3server.controller.schemas.topology import Topology
+from gns3server.endpoints.schemas.dynamips_nodes import DynamipsCreate, NodeStatus
+
import logging
log = logging.getLogger(__name__)
@@ -40,29 +41,21 @@ log = logging.getLogger(__name__)
GNS3_FILE_FORMAT_REVISION = 9
+class DynamipsNodeValidation(DynamipsCreate):
+ name: Optional[str] = None
+
+
def _check_topology_schema(topo):
try:
- jsonschema.validate(topo, TOPOLOGY_SCHEMA)
+ Topology.parse_obj(topo)
# Check the nodes property against compute schemas
for node in topo["topology"].get("nodes", []):
- schema = None
if node["node_type"] == "dynamips":
- schema = copy.deepcopy(dynamips_vm.VM_CREATE_SCHEMA)
+ DynamipsNodeValidation.parse_obj(node.get("properties", {}))
- if schema:
- # Properties send to compute but in an other place in topology
- delete_properties = ["name", "node_id"]
- for prop in delete_properties:
- del schema["properties"][prop]
- schema["required"] = [p for p in schema["required"] if p not in delete_properties]
-
- jsonschema.validate(node.get("properties", {}), schema)
-
- except jsonschema.ValidationError as e:
- error = "Invalid data in topology file: {} in schema: {}".format(
- e.message,
- json.dumps(e.schema))
+ except pydantic.ValidationError as e:
+ error = "Invalid data in topology file: {}".format(e)
log.critical(error)
raise ControllerError(error)
diff --git a/gns3server/endpoints/controller/templates.py b/gns3server/endpoints/controller/templates.py
index b4edd434..60f5e584 100644
--- a/gns3server/endpoints/controller/templates.py
+++ b/gns3server/endpoints/controller/templates.py
@@ -27,13 +27,14 @@ log = logging.getLogger(__name__)
from fastapi import APIRouter, Request, Response, HTTPException, status
from fastapi.encoders import jsonable_encoder
-from typing import Union, List
+from typing import List
from uuid import UUID
from gns3server.endpoints.schemas.common import ErrorMessage
from gns3server.endpoints import schemas
from gns3server.controller import Controller
+
router = APIRouter()
responses = {
@@ -41,11 +42,6 @@ responses = {
}
-#template_create_models = Union[schemas.VPCSTemplateCreate, schemas.CloudTemplateCreate, schemas.IOUTemplateCreate]
-#template_update_models = Union[schemas.VPCSTemplateUpdate, schemas.CloudTemplateUpdate, schemas.IOUTemplateUpdate]
-#template_response_models = Union[schemas.VPCSTemplate, schemas.CloudTemplate, schemas.IOUTemplate]
-
-
@router.post("/templates",
status_code=status.HTTP_201_CREATED,
response_model=schemas.Template)
diff --git a/gns3server/endpoints/schemas/__init__.py b/gns3server/endpoints/schemas/__init__.py
index bf8a68b0..f39cf031 100644
--- a/gns3server/endpoints/schemas/__init__.py
+++ b/gns3server/endpoints/schemas/__init__.py
@@ -40,6 +40,3 @@ from .qemu_nodes import QemuCreate, QemuUpdate, Qemu, QemuDiskResize, QemuImageC
from .virtualbox_nodes import VirtualBoxCreate, VirtualBoxUpdate, VirtualBox
from .vmware_nodes import VMwareCreate, VMwareUpdate, VMware
from .vpcs_nodes import VPCSCreate, VPCSUpdate, VPCS
-from .vpcs_templates import VPCSTemplateCreate, VPCSTemplateUpdate, VPCSTemplate
-from .cloud_templates import CloudTemplateCreate, CloudTemplateUpdate, CloudTemplate
-from .iou_templates import IOUTemplateCreate, IOUTemplateUpdate, IOUTemplate
diff --git a/gns3server/endpoints/schemas/computes.py b/gns3server/endpoints/schemas/computes.py
index f903e970..92fa5305 100644
--- a/gns3server/endpoints/schemas/computes.py
+++ b/gns3server/endpoints/schemas/computes.py
@@ -103,12 +103,12 @@ class Compute(ComputeBase):
compute_id: Union[str, UUID]
name: str
- connected: bool = Field(..., description="Whether the controller is connected to the compute or not")
- cpu_usage_percent: float = Field(..., description="CPU usage of the compute", ge=0, le=100)
- memory_usage_percent: float = Field(..., description="Memory usage of the compute", ge=0, le=100)
- disk_usage_percent: float = Field(..., description="Disk usage of the compute", ge=0, le=100)
+ connected: Optional[bool] = Field(None, description="Whether the controller is connected to the compute or not")
+ cpu_usage_percent: Optional[float] = Field(None, description="CPU usage of the compute", ge=0, le=100)
+ memory_usage_percent: Optional[float] = Field(None, description="Memory usage of the compute", ge=0, le=100)
+ disk_usage_percent: Optional[float] = Field(None, description="Disk usage of the compute", ge=0, le=100)
last_error: Optional[str] = Field(None, description="Last error found on the compute")
- capabilities: Capabilities
+ capabilities: Optional[Capabilities] = None
class AutoIdlePC(BaseModel):
diff --git a/gns3server/endpoints/schemas/dynamips_nodes.py b/gns3server/endpoints/schemas/dynamips_nodes.py
index 3f9e244e..24f05a27 100644
--- a/gns3server/endpoints/schemas/dynamips_nodes.py
+++ b/gns3server/endpoints/schemas/dynamips_nodes.py
@@ -25,6 +25,20 @@ from uuid import UUID
from .nodes import NodeStatus
+class DynamipsPlatform(str, Enum):
+ """
+ Supported Dynamips Platforms.
+ """
+
+ c7200 = "c7200"
+ c3725 = "c3725"
+ c3745 = "c3745"
+ c3600 = "c3600"
+ c2691 = "c2691"
+ c2600 = "c2600"
+ c1700 = "c1700"
+
+
class DynamipsAdapters(str, Enum):
"""
Supported Dynamips Network Modules.
@@ -56,6 +70,7 @@ class DynamipsAdapters(str, Enum):
gt96100_fe = "GT96100-FE"
leopard_2fe = "Leopard-2FE"
+
class DynamipsWics(str, Enum):
"""
Supported Dynamips WICs.
@@ -108,7 +123,7 @@ class DynamipsBase(BaseModel):
node_id: Optional[UUID] = None
name: Optional[str] = None
dynamips_id: Optional[int] = Field(None, description="Dynamips internal ID")
- platform: Optional[str] = Field(None, description="Cisco router platform", regex="^c[0-9]{4}$")
+ platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform")
ram: Optional[int] = Field(None, description="Amount of RAM in MB")
nvram: Optional[int] = Field(None, description="Amount of NVRAM in KB")
image: Optional[Path] = Field(None, description="Path to the IOS image")
@@ -133,16 +148,16 @@ class DynamipsBase(BaseModel):
aux_type: Optional[DynamipsConsoleType] = Field(None, description="Auxiliary console type")
mac_addr: Optional[str] = Field(None, description="Base MAC address", regex="^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$")
system_id: Optional[str] = Field(None, description="System ID")
- slot0: Optional[str] = Field(None, description="Network module slot 0")
- slot1: Optional[str] = Field(None, description="Network module slot 1")
- slot2: Optional[str] = Field(None, description="Network module slot 2")
- slot3: Optional[str] = Field(None, description="Network module slot 3")
- slot4: Optional[str] = Field(None, description="Network module slot 4")
- slot5: Optional[str] = Field(None, description="Network module slot 5")
- slot6: Optional[str] = Field(None, description="Network module slot 6")
- wic0: Optional[str] = Field(None, description="Network module WIC slot 0")
- wic1: Optional[str] = Field(None, description="Network module WIC slot 1")
- wic2: Optional[str] = Field(None, description="Network module WIC slot 2")
+ slot0: Optional[DynamipsAdapters] = Field(None, description="Network module slot 0")
+ slot1: Optional[DynamipsAdapters] = Field(None, description="Network module slot 1")
+ slot2: Optional[DynamipsAdapters] = Field(None, description="Network module slot 2")
+ slot3: Optional[DynamipsAdapters] = Field(None, description="Network module slot 3")
+ slot4: Optional[DynamipsAdapters] = Field(None, description="Network module slot 4")
+ slot5: Optional[DynamipsAdapters] = Field(None, description="Network module slot 5")
+ slot6: Optional[DynamipsAdapters] = Field(None, description="Network module slot 6")
+ wic0: Optional[DynamipsWics] = Field(None, description="Network module WIC slot 0")
+ wic1: Optional[DynamipsWics] = Field(None, description="Network module WIC slot 1")
+ wic2: Optional[DynamipsWics] = Field(None, description="Network module WIC slot 2")
npe: Optional[DynamipsNPE] = Field(None, description="NPE model")
midplane: Optional[DynamipsMidplane] = Field(None, description="Midplane model")
sensors: Optional[List] = Field(None, description="Temperature sensors")
diff --git a/gns3server/endpoints/schemas/projects.py b/gns3server/endpoints/schemas/projects.py
index 9fc6b771..ec0c14b0 100644
--- a/gns3server/endpoints/schemas/projects.py
+++ b/gns3server/endpoints/schemas/projects.py
@@ -35,7 +35,7 @@ class ProjectStatus(str, Enum):
class Supplier(BaseModel):
logo: str = Field(..., description="Path to the project supplier logo")
- url: HttpUrl = Field(..., description="URL to the project supplier site")
+ url: Optional[HttpUrl] = Field(None, description="URL to the project supplier site")
class Variable(BaseModel):
diff --git a/gns3server/endpoints/schemas/qemu_nodes.py b/gns3server/endpoints/schemas/qemu_nodes.py
index 6fad2b5e..1d42ec07 100644
--- a/gns3server/endpoints/schemas/qemu_nodes.py
+++ b/gns3server/endpoints/schemas/qemu_nodes.py
@@ -104,6 +104,54 @@ class QemuProcessPriority(str, Enum):
very_low = "very low"
+class QemuAdapterType(str, Enum):
+ """
+ Supported Qemu VM adapter types.
+ """
+
+ e1000 = "e1000"
+ e1000_82544gc = "e1000-82544gc"
+ e1000_82545em = "e1000-82545em"
+ e1000e = "e1000e"
+ i82550 = "i82550"
+ i82551 = "i82551"
+ i82557a = "i82557a"
+ i82557b = "i82557b"
+ i82557c = "i82557c"
+ i82558a = "i82558a"
+ i82558b = "i82558b"
+ i82559a = "i82559a"
+ i82559b = "i82559b"
+ i82559c = "i82559c"
+ i82559er = "i82559er"
+ i82562 = "i82562"
+ i82801 = "i82801"
+ ne2k_pci = "ne2k_pci"
+ pcnet = "pcnet"
+ rocker = "rocker"
+ rtl8139 = "rtl8139"
+ virtio = "virtio"
+ virtio_net_pci = "virtio-net-pci"
+ vmxnet3 = "vmxnet3"
+
+
+class QemuDiskInterfaceType(str, Enum):
+ """
+ Supported Qemu VM disk interface types.
+ """
+
+ ide = "ide"
+ sate = "sata"
+ nvme = "nvme"
+ scsi = "scsi"
+ sd = "sd"
+ mtd = "mtd"
+ floppy = "floppy"
+ pflash = "pflash"
+ virtio = "virtio"
+ none = "none"
+
+
class QemuBase(BaseModel):
"""
Common Qemu node properties.
@@ -121,16 +169,16 @@ class QemuBase(BaseModel):
aux_type: Optional[QemuConsoleType] = Field(None, description="Auxiliary console type")
hda_disk_image: Optional[Path] = Field(None, description="QEMU hda disk image path")
hda_disk_image_md5sum: Optional[str] = Field(None, description="QEMU hda disk image checksum")
- hda_disk_image_interface: Optional[str] = Field(None, description="QEMU hda interface")
+ hda_disk_interface: Optional[QemuDiskInterfaceType] = Field(None, description="QEMU hda interface")
hdb_disk_image: Optional[Path] = Field(None, description="QEMU hdb disk image path")
hdb_disk_image_md5sum: Optional[str] = Field(None, description="QEMU hdb disk image checksum")
- hdb_disk_image_interface: Optional[str] = Field(None, description="QEMU hdb interface")
+ hdb_disk_interface: Optional[QemuDiskInterfaceType] = Field(None, description="QEMU hdb interface")
hdc_disk_image: Optional[Path] = Field(None, description="QEMU hdc disk image path")
hdc_disk_image_md5sum: Optional[str] = Field(None, description="QEMU hdc disk image checksum")
- hdc_disk_image_interface: Optional[str] = Field(None, description="QEMU hdc interface")
+ hdc_disk_interface: Optional[QemuDiskInterfaceType] = Field(None, description="QEMU hdc interface")
hdd_disk_image: Optional[Path] = Field(None, description="QEMU hdd disk image path")
hdd_disk_image_md5sum: Optional[str] = Field(None, description="QEMU hdd disk image checksum")
- hdd_disk_image_interface: Optional[str] = Field(None, description="QEMU hdd interface")
+ hdd_disk_interface: Optional[QemuDiskInterfaceType] = Field(None, description="QEMU hdd interface")
cdrom_image: Optional[Path] = Field(None, description="QEMU cdrom image path")
cdrom_image_md5sum: Optional[str] = Field(None, description="QEMU cdrom image checksum")
bios_image: Optional[Path] = Field(None, description="QEMU bios image path")
@@ -140,12 +188,12 @@ class QemuBase(BaseModel):
kernel_image: Optional[Path] = Field(None, description="QEMU kernel image path")
kernel_image_md5sum: Optional[str] = Field(None, description="QEMU kernel image checksum")
kernel_command_line: Optional[str] = Field(None, description="QEMU kernel command line")
- boot_priotiry: Optional[QemuBootPriority] = Field(None, description="QEMU boot priority")
+ boot_priority: Optional[QemuBootPriority] = Field(None, description="QEMU boot priority")
ram: Optional[int] = Field(None, description="Amount of RAM in MB")
cpus: Optional[int] = Field(None, ge=1, le=255, description="Number of vCPUs")
maxcpus: Optional[int] = Field(None, ge=1, le=255, description="Maximum number of hotpluggable vCPUs")
adapters: Optional[int] = Field(None, ge=0, le=275, description="Number of adapters")
- adapter_type: Optional[str] = Field(None, description="QEMU adapter type")
+ adapter_type: Optional[QemuAdapterType] = Field(None, description="QEMU adapter type")
mac_address: Optional[str] = Field(None, description="QEMU MAC address", regex="^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$")
legacy_networking: Optional[bool] = Field(None, description="Use QEMU legagy networking commands (-net syntax)")
replicate_network_connection_state: Optional[bool] = Field(None, description="Replicate the network connection state for links in Qemu")
diff --git a/gns3server/endpoints/schemas/templates.py b/gns3server/endpoints/schemas/templates.py
index 04e23697..af3dcec4 100644
--- a/gns3server/endpoints/schemas/templates.py
+++ b/gns3server/endpoints/schemas/templates.py
@@ -45,8 +45,8 @@ class TemplateBase(BaseModel):
symbol: Optional[str] = None
builtin: Optional[bool] = None
template_type: Optional[NodeType] = None
- usage: Optional[str] = None
compute_id: Optional[str] = None
+ usage: Optional[str] = ""
class Config:
extra = "allow"
diff --git a/gns3server/endpoints/schemas/virtualbox_nodes.py b/gns3server/endpoints/schemas/virtualbox_nodes.py
index 804f3825..56f26565 100644
--- a/gns3server/endpoints/schemas/virtualbox_nodes.py
+++ b/gns3server/endpoints/schemas/virtualbox_nodes.py
@@ -42,6 +42,16 @@ class VirtualBoxOnCloseAction(str, Enum):
save_vm_state = "save_vm_state"
+class VirtualBoxAdapterType(str, Enum):
+
+ pcnet_pci_ii = "PCnet-PCI II (Am79C970A)",
+ pcnet_fast_iii = "PCNet-FAST III (Am79C973)",
+ intel_pro_1000_mt_desktop = "Intel PRO/1000 MT Desktop (82540EM)",
+ intel_pro_1000_t_server = "Intel PRO/1000 T Server (82543GC)",
+ intel_pro_1000_mt_server = "Intel PRO/1000 MT Server (82545EM)",
+ paravirtualized_network = "Paravirtualized Network (virtio-net)"
+
+
class VirtualBoxBase(BaseModel):
"""
Common VirtualBox node properties.
@@ -54,14 +64,14 @@ class VirtualBoxBase(BaseModel):
usage: Optional[str] = Field(None, description="How to use the node")
# 36 adapters is the maximum given by the ICH9 chipset in VirtualBox
adapters: Optional[int] = Field(None, ge=0, le=36, description="Number of adapters")
- adapter_type: Optional[str] = Field(None, description="VirtualBox adapter type")
+ adapter_type: Optional[VirtualBoxAdapterType] = Field(None, description="VirtualBox adapter type")
use_any_adapter: Optional[bool] = Field(None, description="Allow GNS3 to use any VirtualBox adapter")
console: Optional[int] = Field(None, gt=0, le=65535, description="Console TCP port")
console_type: Optional[VirtualBoxConsoleType] = Field(None, description="Console type")
ram: Optional[int] = Field(None, ge=0, le=65535, description="Amount of RAM in MB")
headless: Optional[bool] = Field(None, description="Headless mode")
on_close: Optional[VirtualBoxOnCloseAction] = Field(None, description="Action to execute on the VM is closed")
- custom_adapters: Optional[List[CustomAdapter]] = Field(None, description="Custom adpaters")
+ custom_adapters: Optional[List[CustomAdapter]] = Field(None, description="Custom adapters")
class VirtualBoxCreate(VirtualBoxBase):
diff --git a/gns3server/endpoints/schemas/vmware_nodes.py b/gns3server/endpoints/schemas/vmware_nodes.py
index ca5aef75..fadb6701 100644
--- a/gns3server/endpoints/schemas/vmware_nodes.py
+++ b/gns3server/endpoints/schemas/vmware_nodes.py
@@ -43,6 +43,21 @@ class VMwareOnCloseAction(str, Enum):
save_vm_state = "save_vm_state"
+class VMwareAdapterType(str, Enum):
+ """
+ Supported VMware VM adapter types.
+ """
+
+ default = "default"
+ e1000 = "e1000"
+ e1000e = "e1000e"
+ flexible = "flexible"
+ vlance = "vlance"
+ vmxnet = "vmxnet"
+ vmxnet2 = "vmxnet2"
+ vmxnet3 = "vmxnet3"
+
+
class VMwareBase(BaseModel):
"""
Common VMware node properties.
@@ -59,7 +74,7 @@ class VMwareBase(BaseModel):
on_close: Optional[VMwareOnCloseAction] = Field(None, description="Action to execute on the VM is closed")
# 10 adapters is the maximum supported by VMware VMs.
adapters: Optional[int] = Field(None, ge=0, le=10, description="Number of adapters")
- adapter_type: Optional[str] = Field(None, description="VMware adapter type")
+ adapter_type: Optional[VMwareAdapterType] = Field(None, description="VMware adapter type")
use_any_adapter: Optional[bool] = Field(None, description="Allow GNS3 to use any VMware adapter")
custom_adapters: Optional[List[CustomAdapter]] = Field(None, description="Custom adpaters")
diff --git a/gns3server/endpoints/schemas/vpcs_nodes.py b/gns3server/endpoints/schemas/vpcs_nodes.py
index 4698413e..a09d40b3 100644
--- a/gns3server/endpoints/schemas/vpcs_nodes.py
+++ b/gns3server/endpoints/schemas/vpcs_nodes.py
@@ -23,7 +23,7 @@ from uuid import UUID
from .nodes import NodeStatus, CustomAdapter
-class VPCSConsoleType(str, Enum):
+class ConsoleType(str, Enum):
"""
Supported console types.
"""
@@ -41,7 +41,7 @@ class VPCSBase(BaseModel):
node_id: Optional[UUID]
usage: Optional[str] = Field(None, description="How to use the node")
console: Optional[int] = Field(None, gt=0, le=65535, description="Console TCP port")
- console_type: Optional[VPCSConsoleType] = Field(None, description="Console type")
+ console_type: Optional[ConsoleType] = Field(None, description="Console type")
startup_script: Optional[str] = Field(None, description="Content of the VPCS startup script")
diff --git a/gns3server/schemas/ethernet_hub_template.py b/gns3server/schemas/ethernet_hub_template.py
deleted file mode 100644
index f7a5edcd..00000000
--- a/gns3server/schemas/ethernet_hub_template.py
+++ /dev/null
@@ -1,80 +0,0 @@
-# -*- coding: utf-8 -*-
-#
-# Copyright (C) 2016 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 .
-
-import copy
-from .template import BASE_TEMPLATE_PROPERTIES
-
-
-ETHERNET_HUB_TEMPLATE_PROPERTIES = {
- "ports_mapping": {
- "type": "array",
- "default": [{"port_number": 0,
- "name": "Ethernet0"
- },
- {"port_number": 1,
- "name": "Ethernet1"
- },
- {"port_number": 2,
- "name": "Ethernet2"
- },
- {"port_number": 3,
- "name": "Ethernet3"
- },
- {"port_number": 4,
- "name": "Ethernet4"
- },
- {"port_number": 5,
- "name": "Ethernet5"
- },
- {"port_number": 6,
- "name": "Ethernet6"
- },
- {"port_number": 7,
- "name": "Ethernet7"
- }
- ],
- "items": [
- {"type": "object",
- "oneOf": [{"description": "Ethernet port",
- "properties": {"name": {"description": "Port name",
- "type": "string",
- "minLength": 1},
- "port_number": {
- "description": "Port number",
- "type": "integer",
- "minimum": 0}
- },
- "required": ["name", "port_number"],
- "additionalProperties": False}
- ],
- }
- ]
- }
-}
-
-ETHERNET_HUB_TEMPLATE_PROPERTIES.update(copy.deepcopy(BASE_TEMPLATE_PROPERTIES))
-ETHERNET_HUB_TEMPLATE_PROPERTIES["category"]["default"] = "switch"
-ETHERNET_HUB_TEMPLATE_PROPERTIES["default_name_format"]["default"] = "Hub{0}"
-ETHERNET_HUB_TEMPLATE_PROPERTIES["symbol"]["default"] = ":/symbols/hub.svg"
-
-ETHERNET_HUB_TEMPLATE_OBJECT_SCHEMA = {
- "$schema": "http://json-schema.org/draft-04/schema#",
- "description": "An Ethernet hub template object",
- "type": "object",
- "properties": ETHERNET_HUB_TEMPLATE_PROPERTIES,
- "additionalProperties": False
-}
diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py
index f49fee94..93dbe27c 100644
--- a/tests/controller/test_project.py
+++ b/tests/controller/test_project.py
@@ -655,7 +655,8 @@ async def test_clean_pictures_and_keep_supplier_logo(project):
"""
project.supplier = {
- 'logo': 'logo.png'
+ 'logo': 'logo.png',
+ 'url': 'http://acme.com'
}
drawing = await project.add_drawing()
diff --git a/tests/controller/test_template.py b/tests/controller/test_template.py
index 26592503..7c5039c0 100644
--- a/tests/controller/test_template.py
+++ b/tests/controller/test_template.py
@@ -16,7 +16,7 @@
# along with this program. If not, see .
import pytest
-import jsonschema
+import pydantic
from gns3server.controller.template import Template
@@ -39,7 +39,7 @@ def test_template_json():
def test_template_json_with_not_known_category():
- with pytest.raises(jsonschema.ValidationError):
+ with pytest.raises(pydantic.ValidationError):
Template(None, {
"node_type": "qemu",
"name": "Test",
diff --git a/tests/endpoints/controller/test_templates.py b/tests/endpoints/controller/test_templates.py
index 3c9abf67..35f09583 100644
--- a/tests/endpoints/controller/test_templates.py
+++ b/tests/endpoints/controller/test_templates.py
@@ -908,7 +908,12 @@ async def test_cloud_template_create(controller_api):
"compute_id": "local",
"default_name_format": "Cloud{0}",
"name": "Cloud template",
- "symbol": ":/symbols/cloud.svg"}
+ "ports_mapping": [],
+ "symbol": ":/symbols/cloud.svg",
+ "remote_console_host": "127.0.0.1",
+ "remote_console_port": 23,
+ "remote_console_type": "none",
+ "remote_console_http_path": "/"}
for item, value in expected_response.items():
assert response.json.get(item) == value