From 7ce5e19a6e002edd5329bd5c4371b6b20982c3c5 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 23 Oct 2021 16:23:19 +1030 Subject: [PATCH] Fix issue when updating a template --- gns3server/schemas/__init__.py | 25 +++++---- .../schemas/controller/templates/__init__.py | 10 ++-- .../controller/templates/cloud_templates.py | 5 ++ .../controller/templates/docker_templates.py | 5 ++ .../templates/dynamips_templates.py | 42 +++++++++++++++ .../templates/ethernet_hub_templates.py | 5 ++ .../templates/ethernet_switch_templates.py | 5 ++ .../controller/templates/iou_templates.py | 5 ++ .../controller/templates/qemu_templates.py | 5 ++ .../templates/virtualbox_templates.py | 5 ++ .../controller/templates/vmware_templates.py | 5 ++ .../controller/templates/vpcs_templates.py | 5 ++ gns3server/services/templates.py | 54 ++++++++++++++----- 13 files changed, 151 insertions(+), 25 deletions(-) diff --git a/gns3server/schemas/__init__.py b/gns3server/schemas/__init__.py index 5a3e99a3..ae9b7316 100644 --- a/gns3server/schemas/__init__.py +++ b/gns3server/schemas/__init__.py @@ -37,24 +37,31 @@ from .controller.iou_license import IOULicense from .controller.capabilities import Capabilities # Controller template schemas -from .controller.templates.vpcs_templates import VPCSTemplate -from .controller.templates.cloud_templates import CloudTemplate -from .controller.templates.iou_templates import IOUTemplate -from .controller.templates.docker_templates import DockerTemplate -from .controller.templates.ethernet_hub_templates import EthernetHubTemplate -from .controller.templates.ethernet_switch_templates import EthernetSwitchTemplate -from .controller.templates.virtualbox_templates import VirtualBoxTemplate -from .controller.templates.vmware_templates import VMwareTemplate -from .controller.templates.qemu_templates import QemuTemplate +from .controller.templates.vpcs_templates import VPCSTemplate, VPCSTemplateUpdate +from .controller.templates.cloud_templates import CloudTemplate, CloudTemplateUpdate +from .controller.templates.iou_templates import IOUTemplate, IOUTemplateUpdate +from .controller.templates.docker_templates import DockerTemplate, DockerTemplateUpdate +from .controller.templates.ethernet_hub_templates import EthernetHubTemplate, EthernetHubTemplateUpdate +from .controller.templates.ethernet_switch_templates import EthernetSwitchTemplate, EthernetSwitchTemplateUpdate +from .controller.templates.virtualbox_templates import VirtualBoxTemplate, VirtualBoxTemplateUpdate +from .controller.templates.vmware_templates import VMwareTemplate, VMwareTemplateUpdate +from .controller.templates.qemu_templates import QemuTemplate, QemuTemplateUpdate from .controller.templates.dynamips_templates import ( DynamipsTemplate, C1700DynamipsTemplate, + C1700DynamipsTemplateUpdate, C2600DynamipsTemplate, + C2600DynamipsTemplateUpdate, C2691DynamipsTemplate, + C2691DynamipsTemplateUpdate, C3600DynamipsTemplate, + C3600DynamipsTemplateUpdate, C3725DynamipsTemplate, + C3725DynamipsTemplateUpdate, C3745DynamipsTemplate, + C3745DynamipsTemplateUpdate, C7200DynamipsTemplate, + C7200DynamipsTemplateUpdate ) # Compute schemas diff --git a/gns3server/schemas/controller/templates/__init__.py b/gns3server/schemas/controller/templates/__init__.py index 14db9982..225a90c6 100644 --- a/gns3server/schemas/controller/templates/__init__.py +++ b/gns3server/schemas/controller/templates/__init__.py @@ -50,9 +50,6 @@ class TemplateBase(BaseModel): compute_id: Optional[str] = None usage: Optional[str] = "" - class Config: - extra = "allow" - class TemplateCreate(TemplateBase): """ @@ -63,10 +60,14 @@ class TemplateCreate(TemplateBase): template_type: NodeType compute_id: str + class Config: + extra = "allow" + class TemplateUpdate(TemplateBase): - pass + class Config: + extra = "allow" class Template(DateTimeModelMixin, TemplateBase): @@ -80,6 +81,7 @@ class Template(DateTimeModelMixin, TemplateBase): compute_id: Union[str, None] class Config: + extra = "allow" orm_mode = True diff --git a/gns3server/schemas/controller/templates/cloud_templates.py b/gns3server/schemas/controller/templates/cloud_templates.py index 37fd1942..448a521d 100644 --- a/gns3server/schemas/controller/templates/cloud_templates.py +++ b/gns3server/schemas/controller/templates/cloud_templates.py @@ -37,3 +37,8 @@ class CloudTemplate(TemplateBase): remote_console_port: Optional[int] = Field(23, gt=0, le=65535, description="Remote console TCP port") 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") + + +class CloudTemplateUpdate(CloudTemplate): + + pass diff --git a/gns3server/schemas/controller/templates/docker_templates.py b/gns3server/schemas/controller/templates/docker_templates.py index ceb05040..277caf9e 100644 --- a/gns3server/schemas/controller/templates/docker_templates.py +++ b/gns3server/schemas/controller/templates/docker_templates.py @@ -51,3 +51,8 @@ class DockerTemplate(TemplateBase): 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(default_factory=list, description="Custom adapters") + + +class DockerTemplateUpdate(DockerTemplate): + + image: Optional[str] = Field(None, description="Docker image name") \ No newline at end of file diff --git a/gns3server/schemas/controller/templates/dynamips_templates.py b/gns3server/schemas/controller/templates/dynamips_templates.py index 9598505e..36b43461 100644 --- a/gns3server/schemas/controller/templates/dynamips_templates.py +++ b/gns3server/schemas/controller/templates/dynamips_templates.py @@ -77,6 +77,12 @@ class C7200DynamipsTemplate(DynamipsTemplate): sparsemem: Optional[bool] = Field(True, description="Sparse memory feature") +class C7200DynamipsTemplateUpdate(C7200DynamipsTemplate): + + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") + image: Optional[str] = Field(None, description="Path to the IOS image") + + class C3725DynamipsTemplate(DynamipsTemplate): ram: Optional[int] = Field(128, description="Amount of RAM in MB") @@ -85,6 +91,12 @@ class C3725DynamipsTemplate(DynamipsTemplate): sparsemem: Optional[bool] = Field(True, description="Sparse memory feature") +class C3725DynamipsTemplateUpdate(C3725DynamipsTemplate): + + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") + image: Optional[str] = Field(None, description="Path to the IOS image") + + class C3745DynamipsTemplate(DynamipsTemplate): ram: Optional[int] = Field(256, description="Amount of RAM in MB") @@ -93,6 +105,12 @@ class C3745DynamipsTemplate(DynamipsTemplate): sparsemem: Optional[bool] = Field(True, description="Sparse memory feature") +class C3745DynamipsTemplateUpdate(C3745DynamipsTemplate): + + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") + image: Optional[str] = Field(None, description="Path to the IOS image") + + class C3600ChassisType(str, Enum): chassis_3620 = "3620" @@ -109,6 +127,12 @@ class C3600DynamipsTemplate(DynamipsTemplate): sparsemem: Optional[bool] = Field(True, description="Sparse memory feature") +class C3600DynamipsTemplateUpdate(C3600DynamipsTemplate): + + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") + image: Optional[str] = Field(None, description="Path to the IOS image") + + class C2691DynamipsTemplate(DynamipsTemplate): ram: Optional[int] = Field(192, description="Amount of RAM in MB") @@ -117,6 +141,12 @@ class C2691DynamipsTemplate(DynamipsTemplate): sparsemem: Optional[bool] = Field(True, description="Sparse memory feature") +class C2691DynamipsTemplateUpdate(C2691DynamipsTemplate): + + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") + image: Optional[str] = Field(None, description="Path to the IOS image") + + class C2600ChassisType(str, Enum): chassis_2610 = "2610" @@ -139,6 +169,12 @@ class C2600DynamipsTemplate(DynamipsTemplate): sparsemem: Optional[bool] = Field(True, description="Sparse memory feature") +class C2600DynamipsTemplateUpdate(C2600DynamipsTemplate): + + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") + image: Optional[str] = Field(None, description="Path to the IOS image") + + class C1700ChassisType(str, Enum): chassis_1720 = "1720" @@ -155,3 +191,9 @@ class C1700DynamipsTemplate(DynamipsTemplate): 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") + + +class C1700DynamipsTemplateUpdate(C1700DynamipsTemplate): + + platform: Optional[DynamipsPlatform] = Field(None, description="Cisco router platform") + image: Optional[str] = Field(None, description="Path to the IOS image") diff --git a/gns3server/schemas/controller/templates/ethernet_hub_templates.py b/gns3server/schemas/controller/templates/ethernet_hub_templates.py index 7b61cd01..b3fd3101 100644 --- a/gns3server/schemas/controller/templates/ethernet_hub_templates.py +++ b/gns3server/schemas/controller/templates/ethernet_hub_templates.py @@ -39,3 +39,8 @@ class EthernetHubTemplate(TemplateBase): default_name_format: Optional[str] = "Hub{0}" symbol: Optional[str] = ":/symbols/hub.svg" ports_mapping: Optional[List[EthernetHubPort]] = Field(DEFAULT_PORTS, description="Ports") + + +class EthernetHubTemplateUpdate(EthernetHubTemplate): + + pass diff --git a/gns3server/schemas/controller/templates/ethernet_switch_templates.py b/gns3server/schemas/controller/templates/ethernet_switch_templates.py index 2e42aaa2..80869869 100644 --- a/gns3server/schemas/controller/templates/ethernet_switch_templates.py +++ b/gns3server/schemas/controller/templates/ethernet_switch_templates.py @@ -50,3 +50,8 @@ class EthernetSwitchTemplate(TemplateBase): 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") + + +class EthernetSwitchTemplateUpdate(EthernetSwitchTemplate): + + pass diff --git a/gns3server/schemas/controller/templates/iou_templates.py b/gns3server/schemas/controller/templates/iou_templates.py index f7bba8d4..6cb75d80 100644 --- a/gns3server/schemas/controller/templates/iou_templates.py +++ b/gns3server/schemas/controller/templates/iou_templates.py @@ -40,3 +40,8 @@ class IOUTemplate(TemplateBase): console_auto_start: Optional[bool] = Field( False, description="Automatically start the console when the node has started" ) + + +class IOUTemplateUpdate(IOUTemplate): + + path: Optional[str] = Field(None, description="Path of IOU executable") diff --git a/gns3server/schemas/controller/templates/qemu_templates.py b/gns3server/schemas/controller/templates/qemu_templates.py index 68645595..57afbfb1 100644 --- a/gns3server/schemas/controller/templates/qemu_templates.py +++ b/gns3server/schemas/controller/templates/qemu_templates.py @@ -85,3 +85,8 @@ class QemuTemplate(TemplateBase): 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(default_factory=list, description="Custom adapters") + + +class QemuTemplateUpdate(QemuTemplate): + + pass diff --git a/gns3server/schemas/controller/templates/virtualbox_templates.py b/gns3server/schemas/controller/templates/virtualbox_templates.py index 82cd63f4..a35846b5 100644 --- a/gns3server/schemas/controller/templates/virtualbox_templates.py +++ b/gns3server/schemas/controller/templates/virtualbox_templates.py @@ -58,3 +58,8 @@ class VirtualBoxTemplate(TemplateBase): False, description="Automatically start the console when the node has started" ) custom_adapters: Optional[List[CustomAdapter]] = Field(default_factory=list, description="Custom adapters") + + +class VirtualBoxTemplateUpdate(VirtualBoxTemplate): + + vmname: Optional[str] = Field(None, description="VirtualBox VM name (in VirtualBox itself)") diff --git a/gns3server/schemas/controller/templates/vmware_templates.py b/gns3server/schemas/controller/templates/vmware_templates.py index a8d604d8..e3876f77 100644 --- a/gns3server/schemas/controller/templates/vmware_templates.py +++ b/gns3server/schemas/controller/templates/vmware_templates.py @@ -54,3 +54,8 @@ class VMwareTemplate(TemplateBase): False, description="Automatically start the console when the node has started" ) custom_adapters: Optional[List[CustomAdapter]] = Field(default_factory=list, description="Custom adapters") + + +class VMwareTemplateUpdate(VMwareTemplate): + + vmx_path: Optional[str] = Field(None, description="Path to the vmx file") diff --git a/gns3server/schemas/controller/templates/vpcs_templates.py b/gns3server/schemas/controller/templates/vpcs_templates.py index f75009ce..046c9c3c 100644 --- a/gns3server/schemas/controller/templates/vpcs_templates.py +++ b/gns3server/schemas/controller/templates/vpcs_templates.py @@ -32,3 +32,8 @@ class VPCSTemplate(TemplateBase): console_auto_start: Optional[bool] = Field( False, description="Automatically start the console when the node has started" ) + + +class VPCSTemplateUpdate(VPCSTemplate): + + pass diff --git a/gns3server/services/templates.py b/gns3server/services/templates.py index 569f70e8..8270a703 100644 --- a/gns3server/services/templates.py +++ b/gns3server/services/templates.py @@ -32,7 +32,7 @@ from gns3server.controller.controller_error import ( ControllerForbiddenError, ) -TEMPLATE_TYPE_TO_SHEMA = { +TEMPLATE_TYPE_TO_SCHEMA = { "cloud": schemas.CloudTemplate, "ethernet_hub": schemas.EthernetHubTemplate, "ethernet_switch": schemas.EthernetSwitchTemplate, @@ -45,7 +45,19 @@ TEMPLATE_TYPE_TO_SHEMA = { "qemu": schemas.QemuTemplate, } -DYNAMIPS_PLATFORM_TO_SHEMA = { +TEMPLATE_TYPE_TO_UPDATE_SCHEMA = { + "cloud": schemas.CloudTemplateUpdate, + "ethernet_hub": schemas.EthernetHubTemplateUpdate, + "ethernet_switch": schemas.EthernetSwitchTemplateUpdate, + "docker": schemas.DockerTemplateUpdate, + "vpcs": schemas.VPCSTemplateUpdate, + "virtualbox": schemas.VirtualBoxTemplateUpdate, + "vmware": schemas.VMwareTemplateUpdate, + "iou": schemas.IOUTemplateUpdate, + "qemu": schemas.QemuTemplateUpdate, +} + +DYNAMIPS_PLATFORM_TO_SCHEMA = { "c7200": schemas.C7200DynamipsTemplate, "c3745": schemas.C3745DynamipsTemplate, "c3725": schemas.C3725DynamipsTemplate, @@ -55,6 +67,16 @@ DYNAMIPS_PLATFORM_TO_SHEMA = { "c1700": schemas.C1700DynamipsTemplate, } +DYNAMIPS_PLATFORM_TO_UPDATE_SCHEMA = { + "c7200": schemas.C7200DynamipsTemplateUpdate, + "c3745": schemas.C3745DynamipsTemplateUpdate, + "c3725": schemas.C3725DynamipsTemplateUpdate, + "c3600": schemas.C3600DynamipsTemplateUpdate, + "c2691": schemas.C2691DynamipsTemplateUpdate, + "c2600": schemas.C2600DynamipsTemplateUpdate, + "c1700": schemas.C1700DynamipsTemplateUpdate, +} + # built-in templates have their compute_id set to None to tell clients to select a compute BUILTIN_TEMPLATES = [ { @@ -205,20 +227,18 @@ class TemplatesService: try: # get the default template settings - template_settings = jsonable_encoder(template_create, exclude_unset=True) - template_schema = TEMPLATE_TYPE_TO_SHEMA[template_create.template_type] - template_settings_with_defaults = template_schema.parse_obj(template_settings) - settings = template_settings_with_defaults.dict() + create_settings = jsonable_encoder(template_create, exclude_unset=True) + template_schema = TEMPLATE_TYPE_TO_SCHEMA[template_create.template_type] + template_settings = template_schema.parse_obj(create_settings).dict() if template_create.template_type == "dynamips": # special case for Dynamips to cover all platform types that contain specific settings - dynamips_template_schema = DYNAMIPS_PLATFORM_TO_SHEMA[settings["platform"]] - dynamips_template_settings_with_defaults = dynamips_template_schema.parse_obj(template_settings) - settings = dynamips_template_settings_with_defaults.dict() + dynamips_template_schema = DYNAMIPS_PLATFORM_TO_SCHEMA[template_settings["platform"]] + template_settings = dynamips_template_schema.parse_obj(create_settings).dict() except pydantic.ValidationError as e: raise ControllerBadRequestError(f"JSON schema error received while creating new template: {e}") - images_to_add_to_template = await self._find_images(template_create.template_type, settings) - db_template = await self._templates_repo.create_template(template_create.template_type, settings) + images_to_add_to_template = await self._find_images(template_create.template_type, template_settings) + db_template = await self._templates_repo.create_template(template_create.template_type, template_settings) for image in images_to_add_to_template: await self._templates_repo.add_image_to_template(db_template.template_id, image) template = db_template.asjson() @@ -245,12 +265,22 @@ class TemplatesService: if self.get_builtin_template(template_id): raise ControllerForbiddenError(f"Template '{template_id}' cannot be updated because it is built-in") - template_settings = jsonable_encoder(template_update, exclude_unset=True) db_template = await self._templates_repo.get_template(template_id) if not db_template: raise ControllerNotFoundError(f"Template '{template_id}' not found") + try: + # validate the update settings + update_settings = jsonable_encoder(template_update, exclude_unset=True) + if db_template.template_type == "dynamips": + template_schema = DYNAMIPS_PLATFORM_TO_UPDATE_SCHEMA[db_template.platform] + else: + template_schema = TEMPLATE_TYPE_TO_UPDATE_SCHEMA[db_template.template_type] + template_settings = template_schema.parse_obj(update_settings).dict(exclude_unset=True) + except pydantic.ValidationError as e: + raise ControllerBadRequestError(f"JSON schema error received while updating template: {e}") + images_to_add_to_template = await self._find_images(db_template.template_type, template_settings) if db_template.template_type == "dynamips" and "image" in template_settings: await self._remove_image(db_template.template_id, db_template.image)