From 627c7e9cfe7d9d325c6ba7550c5d2150f637ed45 Mon Sep 17 00:00:00 2001 From: grossmj Date: Fri, 16 Nov 2018 23:02:10 +0700 Subject: [PATCH 1/3] Use schema to set appliance default values and better schema validation error messages. --- .../api/controller/appliance_handler.py | 3 +- gns3server/schemas/appliance.py | 714 ++++++++++++++---- gns3server/web/route.py | 59 +- 3 files changed, 608 insertions(+), 168 deletions(-) diff --git a/gns3server/handlers/api/controller/appliance_handler.py b/gns3server/handlers/api/controller/appliance_handler.py index 4c3115f7..3ee979aa 100644 --- a/gns3server/handlers/api/controller/appliance_handler.py +++ b/gns3server/handlers/api/controller/appliance_handler.py @@ -58,7 +58,8 @@ class ApplianceHandler: 400: "Invalid request" }, input=APPLIANCE_CREATE_SCHEMA, - output=APPLIANCE_OBJECT_SCHEMA) + output=APPLIANCE_OBJECT_SCHEMA, + set_input_schema_defaults=True) def create(request, response): controller = Controller.instance() diff --git a/gns3server/schemas/appliance.py b/gns3server/schemas/appliance.py index 5ce51903..25e69bdc 100644 --- a/gns3server/schemas/appliance.py +++ b/gns3server/schemas/appliance.py @@ -30,10 +30,25 @@ BASE_APPLIANCE_PROPERTIES = { "maxLength": 36, "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, + "name": { + "description": "Appliance name", + "type": "string", + "minLength": 1, + }, "compute_id": { "description": "Compute identifier", "type": "string" }, + "default_name_format": { + "description": "Default name format", + "type": "string", + "minLength": 1 + }, + "symbol": { + "description": "Symbol of the appliance", + "type": "string", + "minLength": 1 + }, "category": { "description": "Appliance category", "anyOf": [ @@ -41,28 +56,12 @@ BASE_APPLIANCE_PROPERTIES = { {"enum": ["router", "switch", "guest", "firewall"]} ] }, - "name": { - "description": "Appliance name", - "type": "string", - "minLength": 1, - }, - "default_name_format": { - "description": "Default name format", - "type": "string", - "minLength": 1, - }, - "symbol": { - "description": "Symbol of the appliance", - "type": "string", - "minLength": 1 - }, "builtin": { "description": "Appliance is builtin", "type": "boolean" }, } -#TODO: improve schema for Dynamips (match platform specific options, e.g. NPE allowd only for c7200) DYNAMIPS_APPLIANCE_PROPERTIES = { "appliance_type": { "enum": ["dynamips"] @@ -72,42 +71,15 @@ DYNAMIPS_APPLIANCE_PROPERTIES = { "type": "string", "minLength": 1 }, - "chassis": { - "description": "Chassis type", - "enum": ["1720","1721", "1750", "1751", "1760", "2610", "2620", "2610XM", "2620XM", "2650XM", "2621", "2611XM", - "2621XM", "2651XM", "3620", "3640", "3660", ""] - }, - "platform": { - "description": "Platform type", - "enum": ["c1700", "c2600", "c2691", "c3725", "c3745", "c3600", "c7200"] - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer" - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer" - }, "mmap": { "description": "MMAP feature", - "type": "boolean" - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean" + "type": "boolean", + "default": True }, "exec_area": { "description": "Exec area value", "type": "integer", - }, - "disk0": { - "description": "Disk0 size in MB", - "type": "integer" - }, - "disk1": { - "description": "Disk1 size in MB", - "type": "integer" + "default": 64 }, "mac_addr": { "description": "Base MAC address", @@ -115,58 +87,55 @@ DYNAMIPS_APPLIANCE_PROPERTIES = { "anyOf": [ {"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"}, {"pattern": "^$"} - ] + ], + "default": "" }, "system_id": { "description": "System ID", "type": "string", "minLength": 1, + "default": "FTX0945W0MY" }, "startup_config": { "description": "IOS startup configuration file", - "type": "string" + "type": "string", + "default": "ios_base_startup-config.txt" }, "private_config": { "description": "IOS private configuration file", - "type": "string" + "type": "string", + "default": "" }, "idlepc": { "description": "Idle-PC value", "type": "string", - "pattern": "^(0x[0-9a-fA-F]+)?$" + "pattern": "^(0x[0-9a-fA-F]+)?$", + "default": "" }, "idlemax": { "description": "Idlemax value", "type": "integer", + "default": 500 }, "idlesleep": { "description": "Idlesleep value", "type": "integer", + "default": 30 }, - "iomem": { - "description": "I/O memory percentage", + "disk0": { + "description": "Disk0 size in MB", "type": "integer", - "minimum": 0, - "maximum": 100 + "default": 0 }, - "npe": { - "description": "NPE model", - "enum": ["npe-100", - "npe-150", - "npe-175", - "npe-200", - "npe-225", - "npe-300", - "npe-400", - "npe-g2"] - }, - "midplane": { - "description": "Midplane model", - "enum": ["std", "vxr"] + "disk1": { + "description": "Disk1 size in MB", + "type": "integer", + "default": 0 }, "auto_delete_disks": { "description": "Automatically delete nvram and disk files", - "type": "boolean" + "type": "boolean", + "default": False }, "wic0": DYNAMIPS_WICS, "wic1": DYNAMIPS_WICS, @@ -180,15 +149,280 @@ DYNAMIPS_APPLIANCE_PROPERTIES = { "slot6": DYNAMIPS_ADAPTERS, "console_type": { "description": "Console type", - "enum": ["telnet", "none"] + "enum": ["telnet", "none"], + "default": "telnet" }, "console_auto_start": { "description": "Automatically start the console when the node has started", - "type": "boolean" + "type": "boolean", + "default": False } } -DYNAMIPS_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +DYNAMIPS_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +DYNAMIPS_APPLIANCE_PROPERTIES["category"]["default"] = "router" +DYNAMIPS_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "R{0}" +DYNAMIPS_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/router.svg" + +C7200_DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c7200"] + }, + # "chassis": { + # "description": "Chassis type", + # "type": ["string", "null"], + # "maxLength": 0, + # "default": None + # }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 512 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 512 + }, + "npe": { + "description": "NPE model", + "enum": ["npe-100", "npe-150", "npe-175", "npe-200", "npe-225", "npe-300", "npe-400", "npe-g2"], + "default": "npe-400" + }, + "midplane": { + "description": "Midplane model", + "enum": ["std", "vxr"], + "default": "vxr" + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C7200_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C3745_DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c3745"] + }, + # "chassis": { + # "description": "Chassis type", + # "type": ["string", "null"], + # "maxLength": 0, + # "default": None + # }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 256 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 256 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C3745_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C3725_DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c3725"] + }, + # "chassis": { + # "description": "Chassis type", + # "type": ["string", "null"], + # "maxLength": 0, + # "default": None + # }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 128 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 256 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C3725_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C3600_DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c3600"] + }, + "chassis": { + "description": "Chassis type", + "enum": ["3620", "3640", "3660"], + "default": "3660" + }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 192 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 128 + }, + + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C3600_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C2691_DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c2691"] + }, + # "chassis": { + # "description": "Chassis type", + # "type": ["string", "null"], + # "maxLength": 0, + # "default": None + # }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 192 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 256 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C2691_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C2600_DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c2600"] + }, + "chassis": { + "description": "Chassis type", + "enum": ["2610", "2620", "2610XM", "2620XM", "2650XM", "2621", "2611XM", "2621XM", "2651XM"], + "default": "2651XM" + }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 160 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 128 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C2600_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C1700_DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c1700"] + }, + "chassis": { + "description": "Chassis type", + "enum": ["1720", "1721", "1750", "1751", "1760"], + "default": "1760" + }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 160 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 128 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": False + } +} + +C1700_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) IOU_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -202,42 +436,54 @@ IOU_APPLIANCE_PROPERTIES = { "ethernet_adapters": { "description": "Number of ethernet adapters", "type": "integer", + "default": 2 }, "serial_adapters": { "description": "Number of serial adapters", - "type": "integer" + "type": "integer", + "default": 2 }, "ram": { "description": "RAM in MB", - "type": "integer" + "type": "integer", + "default": 256 }, "nvram": { "description": "NVRAM in KB", - "type": "integer" + "type": "integer", + "default": 128 }, "use_default_iou_values": { "description": "Use default IOU values", - "type": "boolean" + "type": "boolean", + "default": True }, "startup_config": { "description": "Startup-config of IOU", - "type": "string" + "type": "string", + "default": "iou_l2_base_startup-config.txt" }, "private_config": { "description": "Private-config of IOU", - "type": "string" + "type": "string", + "default": "" }, "console_type": { "description": "Console type", - "enum": ["telnet", "none"] + "enum": ["telnet", "none"], + "default": "telnet" }, "console_auto_start": { "description": "Automatically start the console when the node has started", - "type": "boolean" + "type": "boolean", + "default": False }, } -IOU_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +IOU_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +IOU_APPLIANCE_PROPERTIES["category"]["default"] = "router" +IOU_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "IOU{0}" +IOU_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/multilayer_switch.svg" DOCKER_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -252,51 +498,63 @@ DOCKER_APPLIANCE_PROPERTIES = { "description": "Number of adapters", "type": "integer", "minimum": 0, - "maximum": 99 + "maximum": 99, + "default": 1 }, "start_command": { "description": "Docker CMD entry", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "environment": { "description": "Docker environment variables", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "console_type": { "description": "Console type", - "enum": ["telnet", "vnc", "http", "https", "none"] + "enum": ["telnet", "vnc", "http", "https", "none"], + "default": "telnet" }, "console_auto_start": { "description": "Automatically start the console when the node has started", - "type": "boolean" + "type": "boolean", + "default": False, }, "console_http_port": { "description": "Internal port in the container for the HTTP server", "type": "integer", "minimum": 1, - "maximum": 65535 + "maximum": 65535, + "default": 80 }, "console_http_path": { "description": "Path of the web interface", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "/" }, "console_resolution": { "description": "Console resolution for VNC", "type": "string", - "pattern": "^[0-9]+x[0-9]+$" + "pattern": "^[0-9]+x[0-9]+$", + "default": "1024x768" }, "extra_hosts": { "description": "Docker extra hosts (added to /etc/hosts)", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "", }, "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA } -DOCKER_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +DOCKER_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +DOCKER_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +DOCKER_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +DOCKER_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/docker_guest.svg" QEMU_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -305,166 +563,213 @@ QEMU_APPLIANCE_PROPERTIES = { "usage": { "description": "How to use the Qemu VM", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "qemu_path": { "description": "Path to QEMU", - "type": ["string", "null"], + "type": "string", "minLength": 1, + "default": "" }, "platform": { "description": "Platform to emulate", - "enum": QEMU_PLATFORMS + "enum": QEMU_PLATFORMS, + "default": "i386" }, "linked_clone": { "description": "Whether the VM is a linked clone or not", - "type": "boolean" + "type": "boolean", + "default": True }, "ram": { "description": "Amount of RAM in MB", - "type": "integer" + "type": "integer", + "default": 256 }, "cpus": { "description": "Number of vCPUs", "type": "integer", "minimum": 1, - "maximum": 255 + "maximum": 255, + "default": 1 }, "adapters": { "description": "Number of adapters", "type": "integer", "minimum": 0, - "maximum": 275 + "maximum": 275, + "default": 1 }, "adapter_type": { "description": "QEMU adapter type", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "e1000" }, "mac_address": { "description": "QEMU MAC address", "type": "string", "minLength": 1, - "pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$" + "anyOf": [ + {"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"}, + {"pattern": "^$"} + ], + "default": "", }, "first_port_name": { "description": "Optional name of the first networking port example: eth0", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "port_name_format": { "description": "Optional formatting of the networking port example: eth{0}", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "Ethernet{0}" }, "port_segment_size": { "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", - "type": "integer" + "type": "integer", + "default": 0 }, "console_type": { "description": "Console type", - "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] + "enum": ["telnet", "vnc", "spice", "spice+agent", "none"], + "default": "telnet" }, "console_auto_start": { "description": "Automatically start the console when the node has started", - "type": "boolean" + "type": "boolean", + "default": False }, "boot_priority": { "description": "QEMU boot priority", - "enum": ["c", "d", "n", "cn", "cd", "dn", "dc", "nc", "nd"] + "enum": ["c", "d", "n", "cn", "cd", "dn", "dc", "nc", "nd"], + "default": "c" }, "hda_disk_image": { "description": "QEMU hda disk image path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "hda_disk_interface": { "description": "QEMU hda interface", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "ide" }, "hdb_disk_image": { "description": "QEMU hdb disk image path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "hdb_disk_interface": { "description": "QEMU hdb interface", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "ide" }, "hdc_disk_image": { "description": "QEMU hdc disk image path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "hdc_disk_interface": { "description": "QEMU hdc interface", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "ide" }, "hdd_disk_image": { "description": "QEMU hdd disk image path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "hdd_disk_interface": { "description": "QEMU hdd interface", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "ide" }, "cdrom_image": { "description": "QEMU cdrom image path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "initrd": { "description": "QEMU initrd path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "kernel_image": { "description": "QEMU kernel image path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "bios_image": { "description": "QEMU bios image path", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "kernel_command_line": { "description": "QEMU kernel command line", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "legacy_networking": { "description": "Use QEMU legagy networking commands (-net syntax)", - "type": "boolean" + "type": "boolean", + "default": False }, "on_close": { "description": "Action to execute on the VM is closed", "enum": ["power_off", "shutdown_signal", "save_vm_state"], + "default": "power_off" }, "cpu_throttling": { "description": "Percentage of CPU allowed for QEMU", "minimum": 0, "maximum": 800, - "type": "integer" + "type": "integer", + "default": 0 }, "process_priority": { "description": "Process priority for QEMU", - "enum": ["realtime", "very high", "high", "normal", "low", "very low"] + "enum": ["realtime", "very high", "high", "normal", "low", "very low"], + "default": "normal" }, "options": { "description": "Additional QEMU options", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA } -QEMU_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +QEMU_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +QEMU_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +QEMU_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +QEMU_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/qemu_guest.svg" + +VMWARE_VM_SETTINGS = { + "vmx_path": "", + + + "console_type": "none", + "console_auto_start": False, +} VMWARE_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -477,57 +782,71 @@ VMWARE_APPLIANCE_PROPERTIES = { }, "linked_clone": { "description": "Whether the VM is a linked clone or not", - "type": "boolean" + "type": "boolean", + "default": False }, "first_port_name": { "description": "Optional name of the first networking port example: eth0", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "port_name_format": { "description": "Optional formatting of the networking port example: eth{0}", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "Ethernet{0}" }, "port_segment_size": { "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", - "type": "integer" + "type": "integer", + "default": 0 }, "adapters": { "description": "Number of adapters", "type": "integer", "minimum": 0, - "maximum": 10, # maximum adapters support by VMware VMs + "maximum": 10, # maximum adapters support by VMware VMs, + "default": 1 }, "adapter_type": { "description": "VMware adapter type", "type": "string", "minLength": 1, + "default": "e1000" }, "use_any_adapter": { "description": "Allow GNS3 to use any VMware adapter", "type": "boolean", + "default": False }, "headless": { "description": "Headless mode", - "type": "boolean" + "type": "boolean", + "default": False }, "on_close": { "description": "Action to execute on the VM is closed", "enum": ["power_off", "shutdown_signal", "save_vm_state"], + "default": "power_off" }, "console_type": { "description": "Console type", - "enum": ["telnet", "none"] + "enum": ["telnet", "none"], + "default": "none" }, "console_auto_start": { "description": "Automatically start the console when the node has started", - "type": "boolean" + "type": "boolean", + "default": False }, "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA } -VMWARE_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +VMWARE_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +VMWARE_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +VMWARE_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +VMWARE_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vmware_guest.svg" VIRTUALBOX_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -542,61 +861,76 @@ VIRTUALBOX_APPLIANCE_PROPERTIES = { "description": "Amount of RAM", "minimum": 0, "maximum": 65535, - "type": "integer" + "type": "integer", + "default": 256 }, "linked_clone": { "description": "Whether the VM is a linked clone or not", - "type": "boolean" + "type": "boolean", + "default": False }, "adapters": { "description": "Number of adapters", "type": "integer", "minimum": 0, "maximum": 36, # maximum given by the ICH9 chipset in VirtualBox + "default": 1 }, "use_any_adapter": { "description": "Allow GNS3 to use any VirtualBox adapter", "type": "boolean", + "default": False }, "adapter_type": { "description": "VirtualBox adapter type", "type": "string", "minLength": 1, + "default": "Intel PRO/1000 MT Desktop (82540EM)" }, "first_port_name": { "description": "Optional name of the first networking port example: eth0", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "" }, "port_name_format": { "description": "Optional formatting of the networking port example: eth{0}", "type": "string", - "minLength": 1 + "minLength": 1, + "default": "Ethernet{0}" }, "port_segment_size": { "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", - "type": "integer" + "type": "integer", + "default": 0 }, "headless": { "description": "Headless mode", - "type": "boolean" + "type": "boolean", + "default": False }, "on_close": { "description": "Action to execute on the VM is closed", "enum": ["power_off", "shutdown_signal", "save_vm_state"], + "default": "power_off" }, "console_type": { "description": "Console type", - "enum": ["telnet", "none"] + "enum": ["telnet", "none"], + "default": "none" }, "console_auto_start": { "description": "Automatically start the console when the node has started", - "type": "boolean" + "type": "boolean", + "default": False }, "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA } -VIRTUALBOX_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +VIRTUALBOX_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +VIRTUALBOX_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +VIRTUALBOX_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +VIRTUALBOX_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vbox_guest.svg" TRACENG_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -614,11 +948,15 @@ TRACENG_APPLIANCE_PROPERTIES = { }, "console_type": { "description": "Console type", - "enum": ["none"] + "enum": ["none"], + "default": "none" }, } -TRACENG_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +TRACENG_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +TRACENG_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +TRACENG_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "TraceNG{0}" +TRACENG_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/traceng.svg" VPCS_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -628,18 +966,24 @@ VPCS_APPLIANCE_PROPERTIES = { "description": "Script file", "type": "string", "minLength": 1, + "default": "vpcs_base_config.txt" }, "console_type": { "description": "Console type", - "enum": ["telnet", "none"] + "enum": ["telnet", "none"], + "default": "telnet" }, "console_auto_start": { "description": "Automatically start the console when the node has started", - "type": "boolean" + "type": "boolean", + "default": False }, } -VPCS_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +VPCS_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +VPCS_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +VPCS_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "PC{0}" +VPCS_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vpcs_guest.svg" ETHERNET_SWITCH_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -684,11 +1028,15 @@ ETHERNET_SWITCH_APPLIANCE_PROPERTIES = { }, "console_type": { "description": "Console type", - "enum": ["telnet", "none"] + "enum": ["telnet", "none"], + "default": "telnet" }, } -ETHERNET_SWITCH_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +ETHERNET_SWITCH_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +ETHERNET_SWITCH_APPLIANCE_PROPERTIES["category"]["default"] = "switch" +ETHERNET_SWITCH_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Switch{0}" +ETHERNET_SWITCH_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/ethernet_switch.svg" ETHERNET_HUB_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -721,7 +1069,10 @@ ETHERNET_HUB_APPLIANCE_PROPERTIES = { } } -ETHERNET_HUB_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +ETHERNET_HUB_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +ETHERNET_HUB_APPLIANCE_PROPERTIES["category"]["default"] = "switch" +ETHERNET_HUB_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Hub{0}" +ETHERNET_HUB_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/hub.svg" CLOUD_APPLIANCE_PROPERTIES = { "appliance_type": { @@ -755,18 +1106,57 @@ CLOUD_APPLIANCE_PROPERTIES = { }, } -CLOUD_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) +CLOUD_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +CLOUD_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +CLOUD_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Cloud{0}" +CLOUD_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/cloud.svg" APPLIANCE_OBJECT_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "A template object", "type": "object", "definitions": { - "Dynamips": { - "description": "Dynamips appliance", - "properties": DYNAMIPS_APPLIANCE_PROPERTIES, + "c7200": { + "description": "c7200 appliance", + "properties": C7200_DYNAMIPS_APPLIANCE_PROPERTIES, "additionalProperties": False, - "required": ["platform", "image", "ram"] + "required": ["platform", "image"] + }, + "c3745": { + "description": "c3745 appliance", + "properties": C3745_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False, + "required": ["platform", "image"] + }, + "c3725": { + "description": "c3725 appliance", + "properties": C3725_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False, + "required": ["platform", "image"] + }, + "c3600": { + "description": "c3600 appliance", + "properties": C3600_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False, + "required": ["platform", "image", "chassis"] + }, + "c2691": { + "description": "c2691 appliance", + "properties": C2691_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False, + "required": ["platform", "image"] + }, + "c2600": { + "description": "c2600 appliance", + "properties": C2600_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False, + "required": ["platform", "image", "chassis"] + }, + "c1700": { + "description": "c1700 appliance", + "properties": C1700_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False, + "required": ["platform", "image", "chassis"] }, "IOU": { "description": "IOU appliance", @@ -789,7 +1179,7 @@ APPLIANCE_OBJECT_SCHEMA = { "description": "VMware appliance", "properties": VMWARE_APPLIANCE_PROPERTIES, "additionalProperties": False, - "required": ["vmx_path", "linked_clone"] + "required": ["vmx_path"] }, "VirtualBox": { "description": "VirtualBox appliance", @@ -824,7 +1214,13 @@ APPLIANCE_OBJECT_SCHEMA = { }, }, "oneOf": [ - {"$ref": "#/definitions/Dynamips"}, + {"$ref": "#/definitions/c7200"}, + {"$ref": "#/definitions/c3745"}, + {"$ref": "#/definitions/c3725"}, + {"$ref": "#/definitions/c3600"}, + {"$ref": "#/definitions/c2691"}, + {"$ref": "#/definitions/c2600"}, + {"$ref": "#/definitions/c1700"}, {"$ref": "#/definitions/IOU"}, {"$ref": "#/definitions/Docker"}, {"$ref": "#/definitions/Qemu"}, @@ -836,7 +1232,7 @@ APPLIANCE_OBJECT_SCHEMA = { {"$ref": "#/definitions/EthernetHub"}, {"$ref": "#/definitions/Cloud"}, ], - "required": ["name", "appliance_id", "appliance_type", "category", "compute_id", "default_name_format", "symbol"] + "required": ["name", "appliance_type", "appliance_id", "category", "compute_id", "default_name_format", "symbol"] } APPLIANCE_CREATE_SCHEMA = copy.deepcopy(APPLIANCE_OBJECT_SCHEMA) @@ -844,7 +1240,7 @@ APPLIANCE_CREATE_SCHEMA = copy.deepcopy(APPLIANCE_OBJECT_SCHEMA) # create schema # these properties are not required to create an appliance APPLIANCE_CREATE_SCHEMA["required"].remove("appliance_id") -APPLIANCE_CREATE_SCHEMA["required"].remove("compute_id") +APPLIANCE_CREATE_SCHEMA["required"].remove("category") APPLIANCE_CREATE_SCHEMA["required"].remove("default_name_format") APPLIANCE_CREATE_SCHEMA["required"].remove("symbol") diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 41dec3f8..22e66f52 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -23,7 +23,7 @@ import aiohttp import logging import traceback import jsonschema - +import jsonschema.exceptions log = logging.getLogger(__name__) @@ -36,7 +36,30 @@ from ..crash_report import CrashReport from ..config import Config -async def parse_request(request, input_schema, raw): +# 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) + + +async def parse_request(request, input_schema, raw, set_input_schema_defaults=False): """Parse body of request and raise HTTP errors in case of problems""" request.json = {} @@ -55,16 +78,35 @@ async def parse_request(request, input_schema, raw): request.json[k] = v[0] if input_schema: + + if set_input_schema_defaults: + validator = ValidatorWithDefaults(input_schema) + else: + validator = jsonschema.Draft4Validator(input_schema) try: - jsonschema.validate(request.json, input_schema) + validator.validate(request.json) except jsonschema.ValidationError as e: - log.error("Invalid input query. JSON schema error: {}".format(e.message)) - raise aiohttp.web.HTTPBadRequest(text="Invalid JSON: {} in schema: {}".format( - e.message, - json.dumps(e.schema))) + best_match = jsonschema.exceptions.best_match(validator.iter_errors(request.json)) + message = "JSON schema error with API request '{}': {} (best matched error: {})".format(request.path_qs, e.message, best_match.message) + log.error(message) + log.debug("Input schema: {}".format(json.dumps(input_schema))) + raise aiohttp.web.HTTPBadRequest(text=message) return request + # if set_input_schema_defaults: + # validator = ValidatorWithDefaults(input_schema) + # else: + # validator = jsonschema.Draft4Validator(input_schema) + # error = jsonschema.exceptions.best_match(validator.iter_errors(request.json)) + # if error: + # message = "JSON schema error with API request '{}' while validating JSON data '{}': {}".format(request.path_qs, request.json, error.message) + # log.error(message) + # log.debug("Input schema: {}".format(json.dumps(input_schema))) + # raise aiohttp.web.HTTPBadRequest(text=message) + # + # return request + class Route(object): @@ -132,6 +174,7 @@ class Route(object): input_schema = kw.get("input", {}) api_version = kw.get("api_version", 2) raw = kw.get("raw", False) + set_input_schema_defaults = kw.get("set_input_schema_defaults", False) def register(func): # Add the type of server to the route @@ -180,7 +223,7 @@ class Route(object): return response # API call - request = await parse_request(request, input_schema, raw) + request = await parse_request(request, input_schema, raw, set_input_schema_defaults) record_file = server_config.get("record") if record_file: try: From 71fcf855b40f777bfc2b7297567c4dfc166f7774 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 17 Nov 2018 15:37:20 +0700 Subject: [PATCH 2/3] Add tests for all appliance types. --- gns3server/schemas/appliance.py | 181 +++-- gns3server/web/route.py | 6 +- .../handlers/api/controller/test_appliance.py | 746 +++++++++++++++++- 3 files changed, 851 insertions(+), 82 deletions(-) diff --git a/gns3server/schemas/appliance.py b/gns3server/schemas/appliance.py index 25e69bdc..764245e8 100644 --- a/gns3server/schemas/appliance.py +++ b/gns3server/schemas/appliance.py @@ -169,12 +169,6 @@ C7200_DYNAMIPS_APPLIANCE_PROPERTIES = { "description": "Platform type", "enum": ["c7200"] }, - # "chassis": { - # "description": "Chassis type", - # "type": ["string", "null"], - # "maxLength": 0, - # "default": None - # }, "ram": { "description": "Amount of RAM in MB", "type": "integer", @@ -209,12 +203,6 @@ C3745_DYNAMIPS_APPLIANCE_PROPERTIES = { "description": "Platform type", "enum": ["c3745"] }, - # "chassis": { - # "description": "Chassis type", - # "type": ["string", "null"], - # "maxLength": 0, - # "default": None - # }, "ram": { "description": "Amount of RAM in MB", "type": "integer", @@ -246,12 +234,6 @@ C3725_DYNAMIPS_APPLIANCE_PROPERTIES = { "description": "Platform type", "enum": ["c3725"] }, - # "chassis": { - # "description": "Chassis type", - # "type": ["string", "null"], - # "maxLength": 0, - # "default": None - # }, "ram": { "description": "Amount of RAM in MB", "type": "integer", @@ -320,12 +302,6 @@ C2691_DYNAMIPS_APPLIANCE_PROPERTIES = { "description": "Platform type", "enum": ["c2691"] }, - # "chassis": { - # "description": "Chassis type", - # "type": ["string", "null"], - # "maxLength": 0, - # "default": None - # }, "ram": { "description": "Amount of RAM in MB", "type": "integer", @@ -461,7 +437,7 @@ IOU_APPLIANCE_PROPERTIES = { "startup_config": { "description": "Startup-config of IOU", "type": "string", - "default": "iou_l2_base_startup-config.txt" + "default": "iou_l3_base_startup-config.txt" }, "private_config": { "description": "Private-config of IOU", @@ -504,13 +480,11 @@ DOCKER_APPLIANCE_PROPERTIES = { "start_command": { "description": "Docker CMD entry", "type": "string", - "minLength": 1, "default": "" }, "environment": { "description": "Docker environment variables", "type": "string", - "minLength": 1, "default": "" }, "console_type": { @@ -545,7 +519,6 @@ DOCKER_APPLIANCE_PROPERTIES = { "extra_hosts": { "description": "Docker extra hosts (added to /etc/hosts)", "type": "string", - "minLength": 1, "default": "", }, "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA @@ -563,13 +536,11 @@ QEMU_APPLIANCE_PROPERTIES = { "usage": { "description": "How to use the Qemu VM", "type": "string", - "minLength": 1, "default": "" }, "qemu_path": { "description": "Path to QEMU", "type": "string", - "minLength": 1, "default": "" }, "platform": { @@ -604,13 +575,14 @@ QEMU_APPLIANCE_PROPERTIES = { "adapter_type": { "description": "QEMU adapter type", "type": "string", - "minLength": 1, + "enum": ["e1000", "i82550", "i82551", "i82557a", "i82557b", "i82557c", "i82558a","i82558b", "i82559a", + "i82559b", "i82559c", "i82559er", "i82562", "i82801", "ne2k_pci", "pcnet", "rtl8139", "virtio", + "virtio-net-pci", "vmxnet3"], "default": "e1000" }, "mac_address": { "description": "QEMU MAC address", "type": "string", - "minLength": 1, "anyOf": [ {"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"}, {"pattern": "^$"} @@ -620,13 +592,11 @@ QEMU_APPLIANCE_PROPERTIES = { "first_port_name": { "description": "Optional name of the first networking port example: eth0", "type": "string", - "minLength": 1, "default": "" }, "port_name_format": { "description": "Optional formatting of the networking port example: eth{0}", "type": "string", - "minLength": 1, "default": "Ethernet{0}" }, "port_segment_size": { @@ -652,79 +622,66 @@ QEMU_APPLIANCE_PROPERTIES = { "hda_disk_image": { "description": "QEMU hda disk image path", "type": "string", - "minLength": 1, "default": "" }, "hda_disk_interface": { "description": "QEMU hda interface", - "type": "string", - "minLength": 1, + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], "default": "ide" }, "hdb_disk_image": { "description": "QEMU hdb disk image path", "type": "string", - "minLength": 1, "default": "" }, "hdb_disk_interface": { "description": "QEMU hdb interface", - "type": "string", - "minLength": 1, + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], "default": "ide" }, "hdc_disk_image": { "description": "QEMU hdc disk image path", "type": "string", - "minLength": 1, "default": "" }, "hdc_disk_interface": { "description": "QEMU hdc interface", - "type": "string", - "minLength": 1, + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], "default": "ide" }, "hdd_disk_image": { "description": "QEMU hdd disk image path", "type": "string", - "minLength": 1, "default": "" }, "hdd_disk_interface": { "description": "QEMU hdd interface", - "type": "string", - "minLength": 1, + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], "default": "ide" }, "cdrom_image": { "description": "QEMU cdrom image path", "type": "string", - "minLength": 1, "default": "" }, "initrd": { "description": "QEMU initrd path", "type": "string", - "minLength": 1, "default": "" }, "kernel_image": { "description": "QEMU kernel image path", "type": "string", - "minLength": 1, "default": "" }, "bios_image": { "description": "QEMU bios image path", "type": "string", - "minLength": 1, "default": "" }, "kernel_command_line": { "description": "QEMU kernel command line", "type": "string", - "minLength": 1, "default": "" }, "legacy_networking": { @@ -752,7 +709,6 @@ QEMU_APPLIANCE_PROPERTIES = { "options": { "description": "Additional QEMU options", "type": "string", - "minLength": 1, "default": "" }, "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA @@ -788,13 +744,11 @@ VMWARE_APPLIANCE_PROPERTIES = { "first_port_name": { "description": "Optional name of the first networking port example: eth0", "type": "string", - "minLength": 1, "default": "" }, "port_name_format": { "description": "Optional formatting of the networking port example: eth{0}", "type": "string", - "minLength": 1, "default": "Ethernet{0}" }, "port_segment_size": { @@ -811,8 +765,7 @@ VMWARE_APPLIANCE_PROPERTIES = { }, "adapter_type": { "description": "VMware adapter type", - "type": "string", - "minLength": 1, + "enum": ["default", "e1000", "e1000e", "flexible", "vlance", "vmxnet", "vmxnet2", "vmxnet3"], "default": "e1000" }, "use_any_adapter": { @@ -883,20 +836,22 @@ VIRTUALBOX_APPLIANCE_PROPERTIES = { }, "adapter_type": { "description": "VirtualBox adapter type", - "type": "string", - "minLength": 1, + "enum": ["PCnet-PCI II (Am79C970A)", + "PCNet-FAST III (Am79C973)", + "Intel PRO/1000 MT Desktop (82540EM)", + "Intel PRO/1000 T Server (82543GC)", + "Intel PRO/1000 MT Server (82545EM)", + "Paravirtualized Network (virtio-net)"], "default": "Intel PRO/1000 MT Desktop (82540EM)" }, "first_port_name": { "description": "Optional name of the first networking port example: eth0", "type": "string", - "minLength": 1, "default": "" }, "port_name_format": { "description": "Optional formatting of the networking port example: eth{0}", "type": "string", - "minLength": 1, "default": "Ethernet{0}" }, "port_segment_size": { @@ -991,6 +946,55 @@ ETHERNET_SWITCH_APPLIANCE_PROPERTIES = { }, "ports_mapping": { "type": "array", + "default": [{"ethertype": "", + "name": "Ethernet0", + "vlan": 1, + "type": "access", + "port_number": 0 + }, + {"ethertype": "", + "name": "Ethernet1", + "vlan": 1, + "type": "access", + "port_number": 1 + }, + {"ethertype": "", + "name": "Ethernet2", + "vlan": 1, + "type": "access", + "port_number": 2 + }, + {"ethertype": "", + "name": "Ethernet3", + "vlan": 1, + "type": "access", + "port_number": 3 + }, + {"ethertype": "", + "name": "Ethernet4", + "vlan": 1, + "type": "access", + "port_number": 4 + }, + {"ethertype": "", + "name": "Ethernet5", + "vlan": 1, + "type": "access", + "port_number": 5 + }, + {"ethertype": "", + "name": "Ethernet6", + "vlan": 1, + "type": "access", + "port_number": 6 + }, + {"ethertype": "", + "name": "Ethernet7", + "vlan": 1, + "type": "access", + "port_number": 7 + } + ], "items": [ {"type": "object", "oneOf": [ @@ -1044,27 +1048,46 @@ ETHERNET_HUB_APPLIANCE_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 - }, - ]}, + "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} + ], + } ] } } diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 22e66f52..764286c3 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -86,8 +86,10 @@ async def parse_request(request, input_schema, raw, set_input_schema_defaults=Fa try: validator.validate(request.json) except jsonschema.ValidationError as e: - best_match = jsonschema.exceptions.best_match(validator.iter_errors(request.json)) - message = "JSON schema error with API request '{}': {} (best matched error: {})".format(request.path_qs, e.message, best_match.message) + message = "JSON schema error with API request '{}': {}".format(request.path_qs, e.message) + if "is not valid under any of the given schemas" not in message: + best_match = jsonschema.exceptions.best_match(validator.iter_errors(request.json)) + message += " (best matched error: {})".format(best_match.message) log.error(message) log.debug("Input schema: {}".format(json.dumps(input_schema))) raise aiohttp.web.HTTPBadRequest(text=message) diff --git a/tests/handlers/api/controller/test_appliance.py b/tests/handlers/api/controller/test_appliance.py index 05835fd3..41ead4bc 100644 --- a/tests/handlers/api/controller/test_appliance.py +++ b/tests/handlers/api/controller/test_appliance.py @@ -113,6 +113,24 @@ def test_appliance_create_with_id(http_controller, controller): assert len(controller.appliances) == 1 +def test_appliance_create_wrong_type(http_controller, controller): + + params = {"appliance_id": str(uuid.uuid4()), + "base_script_file": "vpcs_base_config.txt", + "category": "guest", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "PC{0}", + "name": "VPCS_TEST", + "compute_id": "local", + "symbol": ":/symbols/vpcs_guest.svg", + "appliance_type": "invalid_appliance_type"} + + response = http_controller.post("/appliances", params) + assert response.status == 400 + assert len(controller.appliances) == 0 + + def test_appliance_get(http_controller, controller): appliance_id = str(uuid.uuid4()) @@ -192,6 +210,733 @@ def test_appliance_delete(http_controller, controller): assert len(controller.appliances) == 0 +def test_c7200_dynamips_appliance_create(http_controller): + + params = {"name": "Cisco c7200 appliance", + "platform": "c7200", + "compute_id": "local", + "image": "c7200-adventerprisek9-mz.124-24.T5.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "dynamips", + "auto_delete_disks": False, + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "R{0}", + "disk0": 0, + "disk1": 0, + "exec_area": 64, + "idlemax": 500, + "idlepc": "", + "idlesleep": 30, + "image": "c7200-adventerprisek9-mz.124-24.T5.image", + "mac_addr": "", + "midplane": "vxr", + "mmap": True, + "name": "Cisco c7200 appliance", + "npe": "npe-400", + "nvram": 512, + "platform": "c7200", + "private_config": "", + "ram": 512, + "sparsemem": True, + "startup_config": "ios_base_startup-config.txt", + "symbol": ":/symbols/router.svg", + "system_id": "FTX0945W0MY"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_c3745_dynamips_appliance_create(http_controller): + + params = {"name": "Cisco c3745 appliance", + "platform": "c3745", + "compute_id": "local", + "image": "c3745-adventerprisek9-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "dynamips", + "auto_delete_disks": False, + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "R{0}", + "disk0": 0, + "disk1": 0, + "exec_area": 64, + "idlemax": 500, + "idlepc": "", + "idlesleep": 30, + "image": "c3745-adventerprisek9-mz.124-25d.image", + "mac_addr": "", + "mmap": True, + "name": "Cisco c3745 appliance", + "iomem": 5, + "nvram": 256, + "platform": "c3745", + "private_config": "", + "ram": 256, + "sparsemem": True, + "startup_config": "ios_base_startup-config.txt", + "symbol": ":/symbols/router.svg", + "system_id": "FTX0945W0MY"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_c3725_dynamips_appliance_create(http_controller): + + params = {"name": "Cisco c3725 appliance", + "platform": "c3725", + "compute_id": "local", + "image": "c3725-adventerprisek9-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "dynamips", + "auto_delete_disks": False, + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "R{0}", + "disk0": 0, + "disk1": 0, + "exec_area": 64, + "idlemax": 500, + "idlepc": "", + "idlesleep": 30, + "image": "c3725-adventerprisek9-mz.124-25d.image", + "mac_addr": "", + "mmap": True, + "name": "Cisco c3725 appliance", + "iomem": 5, + "nvram": 256, + "platform": "c3725", + "private_config": "", + "ram": 128, + "sparsemem": True, + "startup_config": "ios_base_startup-config.txt", + "symbol": ":/symbols/router.svg", + "system_id": "FTX0945W0MY"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_c3600_dynamips_appliance_create(http_controller): + + params = {"name": "Cisco c3600 appliance", + "platform": "c3600", + "chassis": "3660", + "compute_id": "local", + "image": "c3660-a3jk9s-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "dynamips", + "auto_delete_disks": False, + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "R{0}", + "disk0": 0, + "disk1": 0, + "exec_area": 64, + "idlemax": 500, + "idlepc": "", + "idlesleep": 30, + "image": "c3660-a3jk9s-mz.124-25d.image", + "mac_addr": "", + "mmap": True, + "name": "Cisco c3600 appliance", + "iomem": 5, + "nvram": 128, + "platform": "c3600", + "chassis": "3660", + "private_config": "", + "ram": 192, + "sparsemem": True, + "startup_config": "ios_base_startup-config.txt", + "symbol": ":/symbols/router.svg", + "system_id": "FTX0945W0MY"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_c3600_dynamips_appliance_create_wrong_chassis(http_controller): + + params = {"name": "Cisco c3600 appliance", + "platform": "c3600", + "chassis": "3650", + "compute_id": "local", + "image": "c3660-a3jk9s-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params) + assert response.status == 400 + assert "is not valid under any of the given schemas" in response.json["message"] + + +def test_c2691_dynamips_appliance_create(http_controller): + + params = {"name": "Cisco c2691 appliance", + "platform": "c2691", + "compute_id": "local", + "image": "c2691-adventerprisek9-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "dynamips", + "auto_delete_disks": False, + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "R{0}", + "disk0": 0, + "disk1": 0, + "exec_area": 64, + "idlemax": 500, + "idlepc": "", + "idlesleep": 30, + "image": "c2691-adventerprisek9-mz.124-25d.image", + "mac_addr": "", + "mmap": True, + "name": "Cisco c2691 appliance", + "iomem": 5, + "nvram": 256, + "platform": "c2691", + "private_config": "", + "ram": 192, + "sparsemem": True, + "startup_config": "ios_base_startup-config.txt", + "symbol": ":/symbols/router.svg", + "system_id": "FTX0945W0MY"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_c2600_dynamips_appliance_create(http_controller): + + params = {"name": "Cisco c2600 appliance", + "platform": "c2600", + "chassis": "2651XM", + "compute_id": "local", + "image": "c2600-adventerprisek9-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "dynamips", + "auto_delete_disks": False, + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "R{0}", + "disk0": 0, + "disk1": 0, + "exec_area": 64, + "idlemax": 500, + "idlepc": "", + "idlesleep": 30, + "image": "c2600-adventerprisek9-mz.124-25d.image", + "mac_addr": "", + "mmap": True, + "name": "Cisco c2600 appliance", + "iomem": 15, + "nvram": 128, + "platform": "c2600", + "chassis": "2651XM", + "private_config": "", + "ram": 160, + "sparsemem": True, + "startup_config": "ios_base_startup-config.txt", + "symbol": ":/symbols/router.svg", + "system_id": "FTX0945W0MY"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_c2600_dynamips_appliance_create_wrong_chassis(http_controller): + + params = {"name": "Cisco c2600 appliance", + "platform": "c2600", + "chassis": "2660XM", + "compute_id": "local", + "image": "c2600-adventerprisek9-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params) + assert response.status == 400 + assert "is not valid under any of the given schemas" in response.json["message"] + + +def test_c1700_dynamips_appliance_create(http_controller): + + params = {"name": "Cisco c1700 appliance", + "platform": "c1700", + "chassis": "1760", + "compute_id": "local", + "image": "c1700-adventerprisek9-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "dynamips", + "auto_delete_disks": False, + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "R{0}", + "disk0": 0, + "disk1": 0, + "exec_area": 64, + "idlemax": 500, + "idlepc": "", + "idlesleep": 30, + "image": "c1700-adventerprisek9-mz.124-25d.image", + "mac_addr": "", + "mmap": True, + "name": "Cisco c1700 appliance", + "iomem": 15, + "nvram": 128, + "platform": "c1700", + "chassis": "1760", + "private_config": "", + "ram": 160, + "sparsemem": False, + "startup_config": "ios_base_startup-config.txt", + "symbol": ":/symbols/router.svg", + "system_id": "FTX0945W0MY"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_c1700_dynamips_appliance_create_wrong_chassis(http_controller): + + params = {"name": "Cisco c1700 appliance", + "platform": "c1700", + "chassis": "1770", + "compute_id": "local", + "image": "c1700-adventerprisek9-mz.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params) + assert response.status == 400 + assert "is not valid under any of the given schemas" in response.json["message"] + + +def test_dynamips_appliance_create_wrong_platform(http_controller): + + params = {"name": "Cisco c3900 appliance", + "platform": "c3900", + "compute_id": "local", + "image": "c3900-test.124-25d.image", + "appliance_type": "dynamips"} + + response = http_controller.post("/appliances", params) + assert response.status == 400 + assert "is not valid under any of the given schemas" in response.json["message"] + + +def test_iou_appliance_create(http_controller): + + params = {"name": "IOU appliance", + "compute_id": "local", + "path": "/path/to/i86bi_linux-ipbase-ms-12.4.bin", + "appliance_type": "iou"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "iou", + "builtin": False, + "category": "router", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "IOU{0}", + "ethernet_adapters": 2, + "name": "IOU appliance", + "nvram": 128, + "path": "/path/to/i86bi_linux-ipbase-ms-12.4.bin", + "private_config": "", + "ram": 256, + "serial_adapters": 2, + "startup_config": "iou_l3_base_startup-config.txt", + "symbol": ":/symbols/multilayer_switch.svg", + "use_default_iou_values": True} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_docker_appliance_create(http_controller): + + params = {"name": "Docker appliance", + "compute_id": "local", + "image": "gns3/endhost:latest", + "appliance_type": "docker"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"adapters": 1, + "appliance_type": "docker", + "builtin": False, + "category": "guest", + "compute_id": "local", + "console_auto_start": False, + "console_http_path": "/", + "console_http_port": 80, + "console_resolution": "1024x768", + "console_type": "telnet", + "default_name_format": "{name}-{0}", + "environment": "", + "extra_hosts": "", + "image": "gns3/endhost:latest", + "name": "Docker appliance", + "start_command": "", + "symbol": ":/symbols/docker_guest.svg"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_qemu_appliance_create(http_controller): + + params = {"name": "Qemu appliance", + "compute_id": "local", + "platform": "i386", + "hda_disk_image": "IOSvL2-15.2.4.0.55E.qcow2", + "ram": 512, + "appliance_type": "qemu"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"adapter_type": "e1000", + "adapters": 1, + "appliance_type": "qemu", + "bios_image": "", + "boot_priority": "c", + "builtin": False, + "category": "guest", + "cdrom_image": "", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "cpu_throttling": 0, + "cpus": 1, + "default_name_format": "{name}-{0}", + "first_port_name": "", + "hda_disk_image": "IOSvL2-15.2.4.0.55E.qcow2", + "hda_disk_interface": "ide", + "hdb_disk_image": "", + "hdb_disk_interface": "ide", + "hdc_disk_image": "", + "hdc_disk_interface": "ide", + "hdd_disk_image": "", + "hdd_disk_interface": "ide", + "initrd": "", + "kernel_command_line": "", + "kernel_image": "", + "legacy_networking": False, + "linked_clone": True, + "mac_address": "", + "name": "Qemu appliance", + "on_close": "power_off", + "options": "", + "platform": "i386", + "port_name_format": "Ethernet{0}", + "port_segment_size": 0, + "process_priority": "normal", + "qemu_path": "", + "ram": 512, + "symbol": ":/symbols/qemu_guest.svg", + "usage": ""} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_vmware_appliance_create(http_controller): + + params = {"name": "VMware appliance", + "compute_id": "local", + "appliance_type": "vmware", + "vmx_path": "/path/to/vm.vmx"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"adapter_type": "e1000", + "adapters": 1, + "appliance_type": "vmware", + "builtin": False, + "category": "guest", + "compute_id": "local", + "console_auto_start": False, + "console_type": "none", + "default_name_format": "{name}-{0}", + "first_port_name": "", + "headless": False, + "linked_clone": False, + "name": "VMware appliance", + "on_close": "power_off", + "port_name_format": "Ethernet{0}", + "port_segment_size": 0, + "symbol": ":/symbols/vmware_guest.svg", + "use_any_adapter": False, + "vmx_path": "/path/to/vm.vmx"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_virtualbox_appliance_create(http_controller): + + params = {"name": "VirtualBox appliance", + "compute_id": "local", + "appliance_type": "virtualbox", + "vmname": "My VirtualBox VM"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"adapter_type": "Intel PRO/1000 MT Desktop (82540EM)", + "adapters": 1, + "appliance_type": "virtualbox", + "builtin": False, + "category": "guest", + "compute_id": "local", + "console_auto_start": False, + "console_type": "none", + "default_name_format": "{name}-{0}", + "first_port_name": "", + "headless": False, + "linked_clone": False, + "name": "VirtualBox appliance", + "on_close": "power_off", + "port_name_format": "Ethernet{0}", + "port_segment_size": 0, + "ram": 256, + "symbol": ":/symbols/vbox_guest.svg", + "use_any_adapter": False, + "vmname": "My VirtualBox VM"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + +def test_vpcs_appliance_create(http_controller): + + params = {"name": "VPCS appliance", + "compute_id": "local", + "appliance_type": "vpcs"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "vpcs", + "base_script_file": "vpcs_base_config.txt", + "builtin": False, + "category": "guest", + "compute_id": "local", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "PC{0}", + "name": "VPCS appliance", + "symbol": ":/symbols/vpcs_guest.svg"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + +def test_ethernet_switch_appliance_create(http_controller): + + params = {"name": "Ethernet switch appliance", + "compute_id": "local", + "appliance_type": "ethernet_switch"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "ethernet_switch", + "builtin": False, + "category": "switch", + "compute_id": "local", + "console_type": "telnet", + "default_name_format": "Switch{0}", + "name": "Ethernet switch appliance", + "ports_mapping": [{"ethertype": "", + "name": "Ethernet0", + "port_number": 0, + "type": "access", + "vlan": 1 + }, + {"ethertype": "", + "name": "Ethernet1", + "port_number": 1, + "type": "access", + "vlan": 1 + }, + {"ethertype": "", + "name": "Ethernet2", + "port_number": 2, + "type": "access", + "vlan": 1 + }, + {"ethertype": "", + "name": "Ethernet3", + "port_number": 3, + "type": "access", + "vlan": 1 + }, + {"ethertype": "", + "name": "Ethernet4", + "port_number": 4, + "type": "access", + "vlan": 1 + }, + {"ethertype": "", + "name": "Ethernet5", + "port_number": 5, + "type": "access", + "vlan": 1 + }, + {"ethertype": "", + "name": "Ethernet6", + "port_number": 6, + "type": "access", + "vlan": 1 + }, + {"ethertype": "", + "name": "Ethernet7", + "port_number": 7, + "type": "access", + "vlan": 1 + }], + "symbol": ":/symbols/ethernet_switch.svg"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_cloud_appliance_create(http_controller): + + params = {"name": "Cloud appliance", + "compute_id": "local", + "appliance_type": "cloud"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"appliance_type": "cloud", + "builtin": False, + "category": "guest", + "compute_id": "local", + "default_name_format": "Cloud{0}", + "name": "Cloud appliance", + "symbol": ":/symbols/cloud.svg"} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + +def test_ethernet_hub_appliance_create(http_controller): + + params = {"name": "Ethernet hub appliance", + "compute_id": "local", + "appliance_type": "ethernet_hub"} + + response = http_controller.post("/appliances", params, example=True) + assert response.status == 201 + assert response.json["appliance_id"] is not None + + expected_response = {"ports_mapping": [{"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" + }], + "compute_id": "local", + "name": "Ethernet hub appliance", + "symbol": ":/symbols/hub.svg", + "default_name_format": "Hub{0}", + "appliance_type": "ethernet_hub", + "category": "switch", + "builtin": False} + + for item, value in expected_response.items(): + assert response.json.get(item) == value + + def test_create_node_from_appliance(http_controller, controller, project, compute): id = str(uuid.uuid4()) @@ -209,6 +954,5 @@ def test_create_node_from_appliance(http_controller, controller, project, comput "y": 12 }) mock.assert_called_with(id, x=42, y=12, compute_id=None) - print(response.body) assert response.route == "/projects/{project_id}/appliances/{appliance_id}" assert response.status == 201 From 499ab9844a0d596f029009b68a4c0929b7261be7 Mon Sep 17 00:00:00 2001 From: grossmj Date: Sat, 17 Nov 2018 18:12:46 +0700 Subject: [PATCH 3/3] Reorganize how appliance creation is validated against JSON schemas. This allows for clearer error messages when validation fails. --- gns3server/controller/__init__.py | 15 +- gns3server/controller/appliance.py | 94 +- .../api/controller/appliance_handler.py | 3 +- gns3server/schemas/appliance.py | 1205 +---------------- gns3server/schemas/cloud_appliance.py | 61 + gns3server/schemas/docker_appliance.py | 95 ++ gns3server/schemas/dynamips_appliance.py | 399 ++++++ gns3server/schemas/ethernet_hub_appliance.py | 80 ++ .../schemas/ethernet_switch_appliance.py | 127 ++ gns3server/schemas/iou_appliance.py | 87 ++ gns3server/schemas/qemu_appliance.py | 217 +++ gns3server/schemas/traceng_appliance.py | 51 + gns3server/schemas/virtualbox_appliance.py | 113 ++ gns3server/schemas/vmware_appliance.py | 101 ++ gns3server/schemas/vpcs_appliance.py | 52 + gns3server/web/route.py | 55 +- tests/controller/test_appliance.py | 67 +- tests/controller/test_project.py | 23 +- .../handlers/api/controller/test_appliance.py | 4 - 19 files changed, 1524 insertions(+), 1325 deletions(-) create mode 100644 gns3server/schemas/cloud_appliance.py create mode 100644 gns3server/schemas/docker_appliance.py create mode 100644 gns3server/schemas/dynamips_appliance.py create mode 100644 gns3server/schemas/ethernet_hub_appliance.py create mode 100644 gns3server/schemas/ethernet_switch_appliance.py create mode 100644 gns3server/schemas/iou_appliance.py create mode 100644 gns3server/schemas/qemu_appliance.py create mode 100644 gns3server/schemas/traceng_appliance.py create mode 100644 gns3server/schemas/virtualbox_appliance.py create mode 100644 gns3server/schemas/vmware_appliance.py create mode 100644 gns3server/schemas/vpcs_appliance.py diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index da316b52..0d3235d9 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -23,6 +23,7 @@ import socket import shutil import asyncio import aiohttp +import jsonschema from ..config import Config from .project import Project @@ -144,10 +145,9 @@ class Controller: appliance_id = settings.setdefault("appliance_id", str(uuid.uuid4())) try: appliance = Appliance(appliance_id, settings) - appliance.__json__() # Check if loaded without error - except KeyError as e: - # appliance settings is not complete - raise aiohttp.web.HTTPConflict(text="Cannot create new appliance: key '{}' is missing for appliance ID '{}'".format(e, appliance_id)) + except jsonschema.ValidationError as e: + message = "JSON schema error adding appliance with JSON data '{}': {}".format(settings, e.message) + raise aiohttp.web.HTTPBadRequest(text=message) self._appliances[appliance.id] = appliance self.save() self.notification.controller_emit("appliance.created", appliance.__json__()) @@ -327,11 +327,10 @@ class Controller: for appliance_settings in controller_settings["appliances"]: try: appliance = Appliance(appliance_settings["appliance_id"], appliance_settings) - appliance.__json__() # Check if loaded without error self._appliances[appliance.id] = appliance - except KeyError as e: - # appliance data is not complete (missing name or type) - log.warning("Cannot load appliance template {} ('{}'): missing key {}".format(appliance_settings["appliance_id"], appliance_settings.get("name", "unknown"), e)) + except jsonschema.ValidationError as e: + message = "Cannot load appliance with JSON data '{}': {}".format(appliance_settings, e.message) + log.warning(message) continue # load GNS3 VM settings diff --git a/gns3server/controller/appliance.py b/gns3server/controller/appliance.py index ecc304ce..65075938 100644 --- a/gns3server/controller/appliance.py +++ b/gns3server/controller/appliance.py @@ -17,6 +17,56 @@ import copy import uuid +import json +import jsonschema + +from gns3server.schemas.cloud_appliance import CLOUD_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.ethernet_switch_appliance import ETHERNET_SWITCH_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.ethernet_hub_appliance import ETHERNET_HUB_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.docker_appliance import DOCKER_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.vpcs_appliance import VPCS_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.traceng_appliance import TRACENG_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.virtualbox_appliance import VIRTUALBOX_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.vmware_appliance import VMWARE_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.iou_appliance import IOU_APPLIANCE_OBJECT_SCHEMA +from gns3server.schemas.qemu_appliance import QEMU_APPLIANCE_OBJECT_SCHEMA + +from gns3server.schemas.dynamips_appliance import ( + DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + C7200_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + C3745_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + C3725_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + C3600_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + C2691_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + C2600_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + C1700_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA +) + +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", @@ -25,6 +75,30 @@ ID_TO_CATEGORY = { 0: "router" } +APPLIANCE_TYPE_TO_SHEMA = { + "cloud": CLOUD_APPLIANCE_OBJECT_SCHEMA, + "ethernet_hub": ETHERNET_HUB_APPLIANCE_OBJECT_SCHEMA, + "ethernet_switch": ETHERNET_SWITCH_APPLIANCE_OBJECT_SCHEMA, + "docker": DOCKER_APPLIANCE_OBJECT_SCHEMA, + "dynamips": DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + "vpcs": VPCS_APPLIANCE_OBJECT_SCHEMA, + "traceng": TRACENG_APPLIANCE_OBJECT_SCHEMA, + "virtualbox": VIRTUALBOX_APPLIANCE_OBJECT_SCHEMA, + "vmware": VMWARE_APPLIANCE_OBJECT_SCHEMA, + "iou": IOU_APPLIANCE_OBJECT_SCHEMA, + "qemu": QEMU_APPLIANCE_OBJECT_SCHEMA +} + +DYNAMIPS_PLATFORM_TO_SHEMA = { + "c7200": C7200_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + "c3745": C3745_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + "c3725": C3725_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + "c3600": C3600_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + "c2691": C2691_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + "c2600": C2600_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA, + "c1700": C1700_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA +} + class Appliance: @@ -66,6 +140,13 @@ class Appliance: self._builtin = builtin + if builtin is False: + self.validate_and_apply_defaults(APPLIANCE_TYPE_TO_SHEMA[self.appliance_type]) + + if self.appliance_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"]]) + @property def id(self): return self._id @@ -102,6 +183,17 @@ class Appliance: controller.notification.controller_emit("appliance.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): """ Appliance settings. @@ -109,8 +201,6 @@ class Appliance: settings = self._settings settings.update({"appliance_id": self._id, - "default_name_format": settings.get("default_name_format", "{name}-{0}"), - "symbol": settings.get("symbol", ":/symbols/computer.svg"), "builtin": self.builtin}) if not self.builtin: diff --git a/gns3server/handlers/api/controller/appliance_handler.py b/gns3server/handlers/api/controller/appliance_handler.py index 3ee979aa..4c3115f7 100644 --- a/gns3server/handlers/api/controller/appliance_handler.py +++ b/gns3server/handlers/api/controller/appliance_handler.py @@ -58,8 +58,7 @@ class ApplianceHandler: 400: "Invalid request" }, input=APPLIANCE_CREATE_SCHEMA, - output=APPLIANCE_OBJECT_SCHEMA, - set_input_schema_defaults=True) + output=APPLIANCE_OBJECT_SCHEMA) def create(request, response): controller = Controller.instance() diff --git a/gns3server/schemas/appliance.py b/gns3server/schemas/appliance.py index 764245e8..e8cba49b 100644 --- a/gns3server/schemas/appliance.py +++ b/gns3server/schemas/appliance.py @@ -16,20 +16,20 @@ # along with this program. If not, see . import copy -from .dynamips_vm import DYNAMIPS_ADAPTERS, DYNAMIPS_WICS -from .qemu import QEMU_PLATFORMS -from .port import PORT_OBJECT_SCHEMA -from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA - BASE_APPLIANCE_PROPERTIES = { "appliance_id": { "description": "Appliance UUID from which the node has been created. Read only", - "type": ["null", "string"], + "type": "string", "minLength": 36, "maxLength": 36, "pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$" }, + "appliance_type": { + "description": "Type of node", + "enum": ["cloud", "ethernet_hub", "ethernet_switch", "docker", "dynamips", "vpcs", "traceng", + "virtualbox", "vmware", "iou", "qemu"] + }, "name": { "description": "Appliance name", "type": "string", @@ -62,1200 +62,12 @@ BASE_APPLIANCE_PROPERTIES = { }, } -DYNAMIPS_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["dynamips"] - }, - "image": { - "description": "Path to the IOS image", - "type": "string", - "minLength": 1 - }, - "mmap": { - "description": "MMAP feature", - "type": "boolean", - "default": True - }, - "exec_area": { - "description": "Exec area value", - "type": "integer", - "default": 64 - }, - "mac_addr": { - "description": "Base MAC address", - "type": "string", - "anyOf": [ - {"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"}, - {"pattern": "^$"} - ], - "default": "" - }, - "system_id": { - "description": "System ID", - "type": "string", - "minLength": 1, - "default": "FTX0945W0MY" - }, - "startup_config": { - "description": "IOS startup configuration file", - "type": "string", - "default": "ios_base_startup-config.txt" - }, - "private_config": { - "description": "IOS private configuration file", - "type": "string", - "default": "" - }, - "idlepc": { - "description": "Idle-PC value", - "type": "string", - "pattern": "^(0x[0-9a-fA-F]+)?$", - "default": "" - }, - "idlemax": { - "description": "Idlemax value", - "type": "integer", - "default": 500 - }, - "idlesleep": { - "description": "Idlesleep value", - "type": "integer", - "default": 30 - }, - "disk0": { - "description": "Disk0 size in MB", - "type": "integer", - "default": 0 - }, - "disk1": { - "description": "Disk1 size in MB", - "type": "integer", - "default": 0 - }, - "auto_delete_disks": { - "description": "Automatically delete nvram and disk files", - "type": "boolean", - "default": False - }, - "wic0": DYNAMIPS_WICS, - "wic1": DYNAMIPS_WICS, - "wic2": DYNAMIPS_WICS, - "slot0": DYNAMIPS_ADAPTERS, - "slot1": DYNAMIPS_ADAPTERS, - "slot2": DYNAMIPS_ADAPTERS, - "slot3": DYNAMIPS_ADAPTERS, - "slot4": DYNAMIPS_ADAPTERS, - "slot5": DYNAMIPS_ADAPTERS, - "slot6": DYNAMIPS_ADAPTERS, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"], - "default": "telnet" - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean", - "default": False - } -} - -DYNAMIPS_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -DYNAMIPS_APPLIANCE_PROPERTIES["category"]["default"] = "router" -DYNAMIPS_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "R{0}" -DYNAMIPS_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/router.svg" - -C7200_DYNAMIPS_APPLIANCE_PROPERTIES = { - "platform": { - "description": "Platform type", - "enum": ["c7200"] - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 512 - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer", - "default": 512 - }, - "npe": { - "description": "NPE model", - "enum": ["npe-100", "npe-150", "npe-175", "npe-200", "npe-225", "npe-300", "npe-400", "npe-g2"], - "default": "npe-400" - }, - "midplane": { - "description": "Midplane model", - "enum": ["std", "vxr"], - "default": "vxr" - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean", - "default": True - } -} - -C7200_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) - -C3745_DYNAMIPS_APPLIANCE_PROPERTIES = { - "platform": { - "description": "Platform type", - "enum": ["c3745"] - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 256 - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer", - "default": 256 - }, - "iomem": { - "description": "I/O memory percentage", - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 5 - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean", - "default": True - } -} - -C3745_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) - -C3725_DYNAMIPS_APPLIANCE_PROPERTIES = { - "platform": { - "description": "Platform type", - "enum": ["c3725"] - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 128 - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer", - "default": 256 - }, - "iomem": { - "description": "I/O memory percentage", - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 5 - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean", - "default": True - } -} - -C3725_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) - -C3600_DYNAMIPS_APPLIANCE_PROPERTIES = { - "platform": { - "description": "Platform type", - "enum": ["c3600"] - }, - "chassis": { - "description": "Chassis type", - "enum": ["3620", "3640", "3660"], - "default": "3660" - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 192 - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer", - "default": 128 - }, - - "iomem": { - "description": "I/O memory percentage", - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 5 - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean", - "default": True - } -} - -C3600_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) - -C2691_DYNAMIPS_APPLIANCE_PROPERTIES = { - "platform": { - "description": "Platform type", - "enum": ["c2691"] - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 192 - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer", - "default": 256 - }, - "iomem": { - "description": "I/O memory percentage", - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 5 - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean", - "default": True - } -} - -C2691_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) - -C2600_DYNAMIPS_APPLIANCE_PROPERTIES = { - "platform": { - "description": "Platform type", - "enum": ["c2600"] - }, - "chassis": { - "description": "Chassis type", - "enum": ["2610", "2620", "2610XM", "2620XM", "2650XM", "2621", "2611XM", "2621XM", "2651XM"], - "default": "2651XM" - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 160 - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer", - "default": 128 - }, - "iomem": { - "description": "I/O memory percentage", - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 15 - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean", - "default": True - } -} - -C2600_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) - -C1700_DYNAMIPS_APPLIANCE_PROPERTIES = { - "platform": { - "description": "Platform type", - "enum": ["c1700"] - }, - "chassis": { - "description": "Chassis type", - "enum": ["1720", "1721", "1750", "1751", "1760"], - "default": "1760" - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 160 - }, - "nvram": { - "description": "Amount of NVRAM in KB", - "type": "integer", - "default": 128 - }, - "iomem": { - "description": "I/O memory percentage", - "type": "integer", - "minimum": 0, - "maximum": 100, - "default": 15 - }, - "sparsemem": { - "description": "Sparse memory feature", - "type": "boolean", - "default": False - } -} - -C1700_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) - -IOU_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["iou"] - }, - "path": { - "description": "Path of IOU executable", - "type": "string", - "minLength": 1 - }, - "ethernet_adapters": { - "description": "Number of ethernet adapters", - "type": "integer", - "default": 2 - }, - "serial_adapters": { - "description": "Number of serial adapters", - "type": "integer", - "default": 2 - }, - "ram": { - "description": "RAM in MB", - "type": "integer", - "default": 256 - }, - "nvram": { - "description": "NVRAM in KB", - "type": "integer", - "default": 128 - }, - "use_default_iou_values": { - "description": "Use default IOU values", - "type": "boolean", - "default": True - }, - "startup_config": { - "description": "Startup-config of IOU", - "type": "string", - "default": "iou_l3_base_startup-config.txt" - }, - "private_config": { - "description": "Private-config of IOU", - "type": "string", - "default": "" - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"], - "default": "telnet" - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean", - "default": False - }, -} - -IOU_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -IOU_APPLIANCE_PROPERTIES["category"]["default"] = "router" -IOU_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "IOU{0}" -IOU_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/multilayer_switch.svg" - -DOCKER_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["docker"] - }, - "image": { - "description": "Docker image name", - "type": "string", - "minLength": 1 - }, - "adapters": { - "description": "Number of adapters", - "type": "integer", - "minimum": 0, - "maximum": 99, - "default": 1 - }, - "start_command": { - "description": "Docker CMD entry", - "type": "string", - "default": "" - }, - "environment": { - "description": "Docker environment variables", - "type": "string", - "default": "" - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "vnc", "http", "https", "none"], - "default": "telnet" - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean", - "default": False, - }, - "console_http_port": { - "description": "Internal port in the container for the HTTP server", - "type": "integer", - "minimum": 1, - "maximum": 65535, - "default": 80 - }, - "console_http_path": { - "description": "Path of the web interface", - "type": "string", - "minLength": 1, - "default": "/" - }, - "console_resolution": { - "description": "Console resolution for VNC", - "type": "string", - "pattern": "^[0-9]+x[0-9]+$", - "default": "1024x768" - }, - "extra_hosts": { - "description": "Docker extra hosts (added to /etc/hosts)", - "type": "string", - "default": "", - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -DOCKER_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -DOCKER_APPLIANCE_PROPERTIES["category"]["default"] = "guest" -DOCKER_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" -DOCKER_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/docker_guest.svg" - -QEMU_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["qemu"] - }, - "usage": { - "description": "How to use the Qemu VM", - "type": "string", - "default": "" - }, - "qemu_path": { - "description": "Path to QEMU", - "type": "string", - "default": "" - }, - "platform": { - "description": "Platform to emulate", - "enum": QEMU_PLATFORMS, - "default": "i386" - }, - "linked_clone": { - "description": "Whether the VM is a linked clone or not", - "type": "boolean", - "default": True - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer", - "default": 256 - }, - "cpus": { - "description": "Number of vCPUs", - "type": "integer", - "minimum": 1, - "maximum": 255, - "default": 1 - }, - "adapters": { - "description": "Number of adapters", - "type": "integer", - "minimum": 0, - "maximum": 275, - "default": 1 - }, - "adapter_type": { - "description": "QEMU adapter type", - "type": "string", - "enum": ["e1000", "i82550", "i82551", "i82557a", "i82557b", "i82557c", "i82558a","i82558b", "i82559a", - "i82559b", "i82559c", "i82559er", "i82562", "i82801", "ne2k_pci", "pcnet", "rtl8139", "virtio", - "virtio-net-pci", "vmxnet3"], - "default": "e1000" - }, - "mac_address": { - "description": "QEMU MAC address", - "type": "string", - "anyOf": [ - {"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"}, - {"pattern": "^$"} - ], - "default": "", - }, - "first_port_name": { - "description": "Optional name of the first networking port example: eth0", - "type": "string", - "default": "" - }, - "port_name_format": { - "description": "Optional formatting of the networking port example: eth{0}", - "type": "string", - "default": "Ethernet{0}" - }, - "port_segment_size": { - "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", - "type": "integer", - "default": 0 - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "vnc", "spice", "spice+agent", "none"], - "default": "telnet" - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean", - "default": False - }, - "boot_priority": { - "description": "QEMU boot priority", - "enum": ["c", "d", "n", "cn", "cd", "dn", "dc", "nc", "nd"], - "default": "c" - }, - "hda_disk_image": { - "description": "QEMU hda disk image path", - "type": "string", - "default": "" - }, - "hda_disk_interface": { - "description": "QEMU hda interface", - "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], - "default": "ide" - }, - "hdb_disk_image": { - "description": "QEMU hdb disk image path", - "type": "string", - "default": "" - }, - "hdb_disk_interface": { - "description": "QEMU hdb interface", - "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], - "default": "ide" - }, - "hdc_disk_image": { - "description": "QEMU hdc disk image path", - "type": "string", - "default": "" - }, - "hdc_disk_interface": { - "description": "QEMU hdc interface", - "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], - "default": "ide" - }, - "hdd_disk_image": { - "description": "QEMU hdd disk image path", - "type": "string", - "default": "" - }, - "hdd_disk_interface": { - "description": "QEMU hdd interface", - "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], - "default": "ide" - }, - "cdrom_image": { - "description": "QEMU cdrom image path", - "type": "string", - "default": "" - }, - "initrd": { - "description": "QEMU initrd path", - "type": "string", - "default": "" - }, - "kernel_image": { - "description": "QEMU kernel image path", - "type": "string", - "default": "" - }, - "bios_image": { - "description": "QEMU bios image path", - "type": "string", - "default": "" - }, - "kernel_command_line": { - "description": "QEMU kernel command line", - "type": "string", - "default": "" - }, - "legacy_networking": { - "description": "Use QEMU legagy networking commands (-net syntax)", - "type": "boolean", - "default": False - }, - "on_close": { - "description": "Action to execute on the VM is closed", - "enum": ["power_off", "shutdown_signal", "save_vm_state"], - "default": "power_off" - }, - "cpu_throttling": { - "description": "Percentage of CPU allowed for QEMU", - "minimum": 0, - "maximum": 800, - "type": "integer", - "default": 0 - }, - "process_priority": { - "description": "Process priority for QEMU", - "enum": ["realtime", "very high", "high", "normal", "low", "very low"], - "default": "normal" - }, - "options": { - "description": "Additional QEMU options", - "type": "string", - "default": "" - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -QEMU_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -QEMU_APPLIANCE_PROPERTIES["category"]["default"] = "guest" -QEMU_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" -QEMU_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/qemu_guest.svg" - -VMWARE_VM_SETTINGS = { - "vmx_path": "", - - - "console_type": "none", - "console_auto_start": False, -} - -VMWARE_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["vmware"] - }, - "vmx_path": { - "description": "Path to the vmx file", - "type": "string", - "minLength": 1, - }, - "linked_clone": { - "description": "Whether the VM is a linked clone or not", - "type": "boolean", - "default": False - }, - "first_port_name": { - "description": "Optional name of the first networking port example: eth0", - "type": "string", - "default": "" - }, - "port_name_format": { - "description": "Optional formatting of the networking port example: eth{0}", - "type": "string", - "default": "Ethernet{0}" - }, - "port_segment_size": { - "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", - "type": "integer", - "default": 0 - }, - "adapters": { - "description": "Number of adapters", - "type": "integer", - "minimum": 0, - "maximum": 10, # maximum adapters support by VMware VMs, - "default": 1 - }, - "adapter_type": { - "description": "VMware adapter type", - "enum": ["default", "e1000", "e1000e", "flexible", "vlance", "vmxnet", "vmxnet2", "vmxnet3"], - "default": "e1000" - }, - "use_any_adapter": { - "description": "Allow GNS3 to use any VMware adapter", - "type": "boolean", - "default": False - }, - "headless": { - "description": "Headless mode", - "type": "boolean", - "default": False - }, - "on_close": { - "description": "Action to execute on the VM is closed", - "enum": ["power_off", "shutdown_signal", "save_vm_state"], - "default": "power_off" - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"], - "default": "none" - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean", - "default": False - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -VMWARE_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -VMWARE_APPLIANCE_PROPERTIES["category"]["default"] = "guest" -VMWARE_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" -VMWARE_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vmware_guest.svg" - -VIRTUALBOX_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["virtualbox"] - }, - "vmname": { - "description": "VirtualBox VM name (in VirtualBox itself)", - "type": "string", - "minLength": 1, - }, - "ram": { - "description": "Amount of RAM", - "minimum": 0, - "maximum": 65535, - "type": "integer", - "default": 256 - }, - "linked_clone": { - "description": "Whether the VM is a linked clone or not", - "type": "boolean", - "default": False - }, - "adapters": { - "description": "Number of adapters", - "type": "integer", - "minimum": 0, - "maximum": 36, # maximum given by the ICH9 chipset in VirtualBox - "default": 1 - }, - "use_any_adapter": { - "description": "Allow GNS3 to use any VirtualBox adapter", - "type": "boolean", - "default": False - }, - "adapter_type": { - "description": "VirtualBox adapter type", - "enum": ["PCnet-PCI II (Am79C970A)", - "PCNet-FAST III (Am79C973)", - "Intel PRO/1000 MT Desktop (82540EM)", - "Intel PRO/1000 T Server (82543GC)", - "Intel PRO/1000 MT Server (82545EM)", - "Paravirtualized Network (virtio-net)"], - "default": "Intel PRO/1000 MT Desktop (82540EM)" - }, - "first_port_name": { - "description": "Optional name of the first networking port example: eth0", - "type": "string", - "default": "" - }, - "port_name_format": { - "description": "Optional formatting of the networking port example: eth{0}", - "type": "string", - "default": "Ethernet{0}" - }, - "port_segment_size": { - "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", - "type": "integer", - "default": 0 - }, - "headless": { - "description": "Headless mode", - "type": "boolean", - "default": False - }, - "on_close": { - "description": "Action to execute on the VM is closed", - "enum": ["power_off", "shutdown_signal", "save_vm_state"], - "default": "power_off" - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"], - "default": "none" - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean", - "default": False - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -VIRTUALBOX_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -VIRTUALBOX_APPLIANCE_PROPERTIES["category"]["default"] = "guest" -VIRTUALBOX_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" -VIRTUALBOX_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vbox_guest.svg" - -TRACENG_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["traceng"] - }, - "ip_address": { - "description": "Source IP address for tracing", - "type": ["string"], - "minLength": 1 - }, - "default_destination": { - "description": "Default destination IP address or hostname for tracing", - "type": ["string"], - "minLength": 1 - }, - "console_type": { - "description": "Console type", - "enum": ["none"], - "default": "none" - }, -} - -TRACENG_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -TRACENG_APPLIANCE_PROPERTIES["category"]["default"] = "guest" -TRACENG_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "TraceNG{0}" -TRACENG_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/traceng.svg" - -VPCS_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["vpcs"] - }, - "base_script_file": { - "description": "Script file", - "type": "string", - "minLength": 1, - "default": "vpcs_base_config.txt" - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"], - "default": "telnet" - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean", - "default": False - }, -} - -VPCS_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -VPCS_APPLIANCE_PROPERTIES["category"]["default"] = "guest" -VPCS_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "PC{0}" -VPCS_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vpcs_guest.svg" - -ETHERNET_SWITCH_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["ethernet_switch"] - }, - "ports_mapping": { - "type": "array", - "default": [{"ethertype": "", - "name": "Ethernet0", - "vlan": 1, - "type": "access", - "port_number": 0 - }, - {"ethertype": "", - "name": "Ethernet1", - "vlan": 1, - "type": "access", - "port_number": 1 - }, - {"ethertype": "", - "name": "Ethernet2", - "vlan": 1, - "type": "access", - "port_number": 2 - }, - {"ethertype": "", - "name": "Ethernet3", - "vlan": 1, - "type": "access", - "port_number": 3 - }, - {"ethertype": "", - "name": "Ethernet4", - "vlan": 1, - "type": "access", - "port_number": 4 - }, - {"ethertype": "", - "name": "Ethernet5", - "vlan": 1, - "type": "access", - "port_number": 5 - }, - {"ethertype": "", - "name": "Ethernet6", - "vlan": 1, - "type": "access", - "port_number": 6 - }, - {"ethertype": "", - "name": "Ethernet7", - "vlan": 1, - "type": "access", - "port_number": 7 - } - ], - "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 - }, - "type": { - "description": "Port type", - "enum": ["access", "dot1q", "qinq"], - }, - "vlan": {"description": "VLAN number", - "type": "integer", - "minimum": 1 - }, - "ethertype": { - "description": "QinQ Ethertype", - "enum": ["", "0x8100", "0x88A8", "0x9100", "0x9200"], - }, - }, - "required": ["name", "port_number", "type"], - "additionalProperties": False - }, - ]}, - ] - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"], - "default": "telnet" - }, -} - -ETHERNET_SWITCH_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -ETHERNET_SWITCH_APPLIANCE_PROPERTIES["category"]["default"] = "switch" -ETHERNET_SWITCH_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Switch{0}" -ETHERNET_SWITCH_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/ethernet_switch.svg" - -ETHERNET_HUB_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["ethernet_hub"] - }, - "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_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -ETHERNET_HUB_APPLIANCE_PROPERTIES["category"]["default"] = "switch" -ETHERNET_HUB_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Hub{0}" -ETHERNET_HUB_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/hub.svg" - -CLOUD_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["cloud"] - }, - "ports_mapping": { - "type": "array", - "items": [ - PORT_OBJECT_SCHEMA - ] - }, - "remote_console_host": { - "description": "Remote console host or IP", - "type": ["string"], - "minLength": 1 - }, - "remote_console_port": { - "description": "Console TCP port", - "minimum": 1, - "maximum": 65535, - "type": "integer" - }, - "remote_console_type": { - "description": "Console type", - "enum": ["telnet", "vnc", "spice", "http", "https", "none"] - }, - "remote_console_http_path": { - "description": "Path of the remote web interface", - "type": "string", - "minLength": 1 - }, -} - -CLOUD_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) -CLOUD_APPLIANCE_PROPERTIES["category"]["default"] = "guest" -CLOUD_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Cloud{0}" -CLOUD_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/cloud.svg" - APPLIANCE_OBJECT_SCHEMA = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "A template object", "type": "object", - "definitions": { - "c7200": { - "description": "c7200 appliance", - "properties": C7200_DYNAMIPS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["platform", "image"] - }, - "c3745": { - "description": "c3745 appliance", - "properties": C3745_DYNAMIPS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["platform", "image"] - }, - "c3725": { - "description": "c3725 appliance", - "properties": C3725_DYNAMIPS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["platform", "image"] - }, - "c3600": { - "description": "c3600 appliance", - "properties": C3600_DYNAMIPS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["platform", "image", "chassis"] - }, - "c2691": { - "description": "c2691 appliance", - "properties": C2691_DYNAMIPS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["platform", "image"] - }, - "c2600": { - "description": "c2600 appliance", - "properties": C2600_DYNAMIPS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["platform", "image", "chassis"] - }, - "c1700": { - "description": "c1700 appliance", - "properties": C1700_DYNAMIPS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["platform", "image", "chassis"] - }, - "IOU": { - "description": "IOU appliance", - "properties": IOU_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["path"] - }, - "Docker": { - "description": "Docker appliance", - "properties": DOCKER_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["image"] - }, - "Qemu": { - "description": "Qemu appliance", - "properties": QEMU_APPLIANCE_PROPERTIES, - "additionalProperties": False, - }, - "VMware": { - "description": "VMware appliance", - "properties": VMWARE_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["vmx_path"] - }, - "VirtualBox": { - "description": "VirtualBox appliance", - "properties": VIRTUALBOX_APPLIANCE_PROPERTIES, - "additionalProperties": False, - "required": ["vmname"] - }, - "TraceNG": { - "description": "TraceNG appliance", - "properties": TRACENG_APPLIANCE_PROPERTIES, - "additionalProperties": False, - }, - "VPCS": { - "description": "VPCS appliance", - "properties": VPCS_APPLIANCE_PROPERTIES, - "additionalProperties": False, - }, - "EthernetSwitch": { - "description": "Ethernet switch appliance", - "properties": ETHERNET_SWITCH_APPLIANCE_PROPERTIES, - "additionalProperties": False, - }, - "EthernetHub": { - "description": "Ethernet hub appliance", - "properties": ETHERNET_HUB_APPLIANCE_PROPERTIES, - "additionalProperties": False, - }, - "Cloud": { - "description": "Cloud appliance", - "properties": CLOUD_APPLIANCE_PROPERTIES, - "additionalProperties": False, - }, - }, - "oneOf": [ - {"$ref": "#/definitions/c7200"}, - {"$ref": "#/definitions/c3745"}, - {"$ref": "#/definitions/c3725"}, - {"$ref": "#/definitions/c3600"}, - {"$ref": "#/definitions/c2691"}, - {"$ref": "#/definitions/c2600"}, - {"$ref": "#/definitions/c1700"}, - {"$ref": "#/definitions/IOU"}, - {"$ref": "#/definitions/Docker"}, - {"$ref": "#/definitions/Qemu"}, - {"$ref": "#/definitions/VMware"}, - {"$ref": "#/definitions/VirtualBox"}, - {"$ref": "#/definitions/TraceNG"}, - {"$ref": "#/definitions/VPCS"}, - {"$ref": "#/definitions/EthernetSwitch"}, - {"$ref": "#/definitions/EthernetHub"}, - {"$ref": "#/definitions/Cloud"}, - ], - "required": ["name", "appliance_type", "appliance_id", "category", "compute_id", "default_name_format", "symbol"] + "properties": BASE_APPLIANCE_PROPERTIES, + "required": ["name", "appliance_type", "appliance_id", "category", "compute_id", "default_name_format", "symbol", "builtin"] } APPLIANCE_CREATE_SCHEMA = copy.deepcopy(APPLIANCE_OBJECT_SCHEMA) @@ -1266,6 +78,7 @@ APPLIANCE_CREATE_SCHEMA["required"].remove("appliance_id") APPLIANCE_CREATE_SCHEMA["required"].remove("category") APPLIANCE_CREATE_SCHEMA["required"].remove("default_name_format") APPLIANCE_CREATE_SCHEMA["required"].remove("symbol") +APPLIANCE_CREATE_SCHEMA["required"].remove("builtin") # update schema APPLIANCE_UPDATE_SCHEMA = copy.deepcopy(APPLIANCE_OBJECT_SCHEMA) diff --git a/gns3server/schemas/cloud_appliance.py b/gns3server/schemas/cloud_appliance.py new file mode 100644 index 00000000..64136c10 --- /dev/null +++ b/gns3server/schemas/cloud_appliance.py @@ -0,0 +1,61 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES +from .port import PORT_OBJECT_SCHEMA + + +CLOUD_APPLIANCE_PROPERTIES = { + "ports_mapping": { + "type": "array", + "items": [PORT_OBJECT_SCHEMA] + }, + "remote_console_host": { + "description": "Remote console host or IP", + "type": ["string"], + "minLength": 1 + }, + "remote_console_port": { + "description": "Console TCP port", + "minimum": 1, + "maximum": 65535, + "type": "integer" + }, + "remote_console_type": { + "description": "Console type", + "enum": ["telnet", "vnc", "spice", "http", "https", "none"] + }, + "remote_console_http_path": { + "description": "Path of the remote web interface", + "type": "string", + "minLength": 1 + }, +} + +CLOUD_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +CLOUD_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +CLOUD_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Cloud{0}" +CLOUD_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/cloud.svg" + +CLOUD_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A cloud template object", + "type": "object", + "properties": CLOUD_APPLIANCE_PROPERTIES, + "additionalProperties": False +} diff --git a/gns3server/schemas/docker_appliance.py b/gns3server/schemas/docker_appliance.py new file mode 100644 index 00000000..b5b6e241 --- /dev/null +++ b/gns3server/schemas/docker_appliance.py @@ -0,0 +1,95 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES +from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA + + +DOCKER_APPLIANCE_PROPERTIES = { + "image": { + "description": "Docker image name", + "type": "string", + "minLength": 1 + }, + "adapters": { + "description": "Number of adapters", + "type": "integer", + "minimum": 0, + "maximum": 99, + "default": 1 + }, + "start_command": { + "description": "Docker CMD entry", + "type": "string", + "default": "" + }, + "environment": { + "description": "Docker environment variables", + "type": "string", + "default": "" + }, + "console_type": { + "description": "Console type", + "enum": ["telnet", "vnc", "http", "https", "none"], + "default": "telnet" + }, + "console_auto_start": { + "description": "Automatically start the console when the node has started", + "type": "boolean", + "default": False, + }, + "console_http_port": { + "description": "Internal port in the container for the HTTP server", + "type": "integer", + "minimum": 1, + "maximum": 65535, + "default": 80 + }, + "console_http_path": { + "description": "Path of the web interface", + "type": "string", + "minLength": 1, + "default": "/" + }, + "console_resolution": { + "description": "Console resolution for VNC", + "type": "string", + "pattern": "^[0-9]+x[0-9]+$", + "default": "1024x768" + }, + "extra_hosts": { + "description": "Docker extra hosts (added to /etc/hosts)", + "type": "string", + "default": "", + }, + "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA +} + +DOCKER_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +DOCKER_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +DOCKER_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +DOCKER_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/docker_guest.svg" + +DOCKER_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A Docker template object", + "type": "object", + "properties": DOCKER_APPLIANCE_PROPERTIES, + "required": ["image"], + "additionalProperties": False +} diff --git a/gns3server/schemas/dynamips_appliance.py b/gns3server/schemas/dynamips_appliance.py new file mode 100644 index 00000000..5aa2a23b --- /dev/null +++ b/gns3server/schemas/dynamips_appliance.py @@ -0,0 +1,399 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES +from .dynamips_vm import DYNAMIPS_ADAPTERS, DYNAMIPS_WICS + + +DYNAMIPS_APPLIANCE_PROPERTIES = { + "platform": { + "description": "Platform type", + "enum": ["c7200", "c3745", "c3725", "c3600", "c2691", "c2600", "c1700"] + }, + "image": { + "description": "Path to the IOS image", + "type": "string", + "minLength": 1 + }, + "mmap": { + "description": "MMAP feature", + "type": "boolean", + "default": True + }, + "exec_area": { + "description": "Exec area value", + "type": "integer", + "default": 64 + }, + "mac_addr": { + "description": "Base MAC address", + "type": "string", + "anyOf": [ + {"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"}, + {"pattern": "^$"} + ], + "default": "" + }, + "system_id": { + "description": "System ID", + "type": "string", + "minLength": 1, + "default": "FTX0945W0MY" + }, + "startup_config": { + "description": "IOS startup configuration file", + "type": "string", + "default": "ios_base_startup-config.txt" + }, + "private_config": { + "description": "IOS private configuration file", + "type": "string", + "default": "" + }, + "idlepc": { + "description": "Idle-PC value", + "type": "string", + "pattern": "^(0x[0-9a-fA-F]+)?$", + "default": "" + }, + "idlemax": { + "description": "Idlemax value", + "type": "integer", + "default": 500 + }, + "idlesleep": { + "description": "Idlesleep value", + "type": "integer", + "default": 30 + }, + "disk0": { + "description": "Disk0 size in MB", + "type": "integer", + "default": 0 + }, + "disk1": { + "description": "Disk1 size in MB", + "type": "integer", + "default": 0 + }, + "auto_delete_disks": { + "description": "Automatically delete nvram and disk files", + "type": "boolean", + "default": False + }, + "wic0": DYNAMIPS_WICS, + "wic1": DYNAMIPS_WICS, + "wic2": DYNAMIPS_WICS, + "slot0": DYNAMIPS_ADAPTERS, + "slot1": DYNAMIPS_ADAPTERS, + "slot2": DYNAMIPS_ADAPTERS, + "slot3": DYNAMIPS_ADAPTERS, + "slot4": DYNAMIPS_ADAPTERS, + "slot5": DYNAMIPS_ADAPTERS, + "slot6": DYNAMIPS_ADAPTERS, + "console_type": { + "description": "Console type", + "enum": ["telnet", "none"], + "default": "telnet" + }, + "console_auto_start": { + "description": "Automatically start the console when the node has started", + "type": "boolean", + "default": False + } +} + +DYNAMIPS_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +DYNAMIPS_APPLIANCE_PROPERTIES["category"]["default"] = "router" +DYNAMIPS_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "R{0}" +DYNAMIPS_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/router.svg" + +DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A Dynamips template object", + "type": "object", + "properties": DYNAMIPS_APPLIANCE_PROPERTIES, + "required": ["platform", "image"], +} + +C7200_DYNAMIPS_APPLIANCE_PROPERTIES = { + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 512 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 512 + }, + "npe": { + "description": "NPE model", + "enum": ["npe-100", "npe-150", "npe-175", "npe-200", "npe-225", "npe-300", "npe-400", "npe-g2"], + "default": "npe-400" + }, + "midplane": { + "description": "Midplane model", + "enum": ["std", "vxr"], + "default": "vxr" + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C7200_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C7200_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A c7200 Dynamips template object", + "type": "object", + "properties": C7200_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False +} + +C3745_DYNAMIPS_APPLIANCE_PROPERTIES = { + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 256 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 256 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C3745_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C3745_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A c3745 Dynamips template object", + "type": "object", + "properties": C3745_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False +} + +C3725_DYNAMIPS_APPLIANCE_PROPERTIES = { + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 128 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 256 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C3725_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C3725_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A c3725 Dynamips template object", + "type": "object", + "properties": C3725_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False +} + +C3600_DYNAMIPS_APPLIANCE_PROPERTIES = { + "chassis": { + "description": "Chassis type", + "enum": ["3620", "3640", "3660"], + "default": "3660" + }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 192 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 128 + }, + + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C3600_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C3600_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A c3600 Dynamips template object", + "type": "object", + "properties": C3600_DYNAMIPS_APPLIANCE_PROPERTIES, + "required": ["chassis"], + "additionalProperties": False +} + +C2691_DYNAMIPS_APPLIANCE_PROPERTIES = { + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 192 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 256 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 5 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C2691_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C2691_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A c2691 Dynamips template object", + "type": "object", + "properties": C2691_DYNAMIPS_APPLIANCE_PROPERTIES, + "additionalProperties": False +} + +C2600_DYNAMIPS_APPLIANCE_PROPERTIES = { + "chassis": { + "description": "Chassis type", + "enum": ["2610", "2620", "2610XM", "2620XM", "2650XM", "2621", "2611XM", "2621XM", "2651XM"], + "default": "2651XM" + }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 160 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 128 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": True + } +} + +C2600_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C2600_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A c2600 Dynamips template object", + "type": "object", + "properties": C2600_DYNAMIPS_APPLIANCE_PROPERTIES, + "required": ["chassis"], + "additionalProperties": False +} + +C1700_DYNAMIPS_APPLIANCE_PROPERTIES = { + "chassis": { + "description": "Chassis type", + "enum": ["1720", "1721", "1750", "1751", "1760"], + "default": "1760" + }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 160 + }, + "nvram": { + "description": "Amount of NVRAM in KB", + "type": "integer", + "default": 128 + }, + "iomem": { + "description": "I/O memory percentage", + "type": "integer", + "minimum": 0, + "maximum": 100, + "default": 15 + }, + "sparsemem": { + "description": "Sparse memory feature", + "type": "boolean", + "default": False + } +} + +C1700_DYNAMIPS_APPLIANCE_PROPERTIES.update(DYNAMIPS_APPLIANCE_PROPERTIES) + +C1700_DYNAMIPS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A c1700 Dynamips template object", + "type": "object", + "properties": C1700_DYNAMIPS_APPLIANCE_PROPERTIES, + "required": ["chassis"], + "additionalProperties": False +} diff --git a/gns3server/schemas/ethernet_hub_appliance.py b/gns3server/schemas/ethernet_hub_appliance.py new file mode 100644 index 00000000..9b0a11b7 --- /dev/null +++ b/gns3server/schemas/ethernet_hub_appliance.py @@ -0,0 +1,80 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES + + +ETHERNET_HUB_APPLIANCE_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_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +ETHERNET_HUB_APPLIANCE_PROPERTIES["category"]["default"] = "switch" +ETHERNET_HUB_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Hub{0}" +ETHERNET_HUB_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/hub.svg" + +ETHERNET_HUB_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "An Ethernet hub template object", + "type": "object", + "properties": ETHERNET_HUB_APPLIANCE_PROPERTIES, + "additionalProperties": False +} diff --git a/gns3server/schemas/ethernet_switch_appliance.py b/gns3server/schemas/ethernet_switch_appliance.py new file mode 100644 index 00000000..a4a8f695 --- /dev/null +++ b/gns3server/schemas/ethernet_switch_appliance.py @@ -0,0 +1,127 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES + + +ETHERNET_SWITCH_APPLIANCE_PROPERTIES = { + "ports_mapping": { + "type": "array", + "default": [{"ethertype": "", + "name": "Ethernet0", + "vlan": 1, + "type": "access", + "port_number": 0 + }, + {"ethertype": "", + "name": "Ethernet1", + "vlan": 1, + "type": "access", + "port_number": 1 + }, + {"ethertype": "", + "name": "Ethernet2", + "vlan": 1, + "type": "access", + "port_number": 2 + }, + {"ethertype": "", + "name": "Ethernet3", + "vlan": 1, + "type": "access", + "port_number": 3 + }, + {"ethertype": "", + "name": "Ethernet4", + "vlan": 1, + "type": "access", + "port_number": 4 + }, + {"ethertype": "", + "name": "Ethernet5", + "vlan": 1, + "type": "access", + "port_number": 5 + }, + {"ethertype": "", + "name": "Ethernet6", + "vlan": 1, + "type": "access", + "port_number": 6 + }, + {"ethertype": "", + "name": "Ethernet7", + "vlan": 1, + "type": "access", + "port_number": 7 + } + ], + "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 + }, + "type": { + "description": "Port type", + "enum": ["access", "dot1q", "qinq"], + }, + "vlan": {"description": "VLAN number", + "type": "integer", + "minimum": 1 + }, + "ethertype": { + "description": "QinQ Ethertype", + "enum": ["", "0x8100", "0x88A8", "0x9100", "0x9200"], + }, + }, + "required": ["name", "port_number", "type"], + "additionalProperties": False + }, + ]}, + ] + }, + "console_type": { + "description": "Console type", + "enum": ["telnet", "none"], + "default": "telnet" + }, +} + +ETHERNET_SWITCH_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +ETHERNET_SWITCH_APPLIANCE_PROPERTIES["category"]["default"] = "switch" +ETHERNET_SWITCH_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "Switch{0}" +ETHERNET_SWITCH_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/ethernet_switch.svg" + +ETHERNET_SWITCH_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "An Ethernet switch template object", + "type": "object", + "properties": ETHERNET_SWITCH_APPLIANCE_PROPERTIES, + "additionalProperties": False +} diff --git a/gns3server/schemas/iou_appliance.py b/gns3server/schemas/iou_appliance.py new file mode 100644 index 00000000..430fb811 --- /dev/null +++ b/gns3server/schemas/iou_appliance.py @@ -0,0 +1,87 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES + + +IOU_APPLIANCE_PROPERTIES = { + "path": { + "description": "Path of IOU executable", + "type": "string", + "minLength": 1 + }, + "ethernet_adapters": { + "description": "Number of ethernet adapters", + "type": "integer", + "default": 2 + }, + "serial_adapters": { + "description": "Number of serial adapters", + "type": "integer", + "default": 2 + }, + "ram": { + "description": "RAM in MB", + "type": "integer", + "default": 256 + }, + "nvram": { + "description": "NVRAM in KB", + "type": "integer", + "default": 128 + }, + "use_default_iou_values": { + "description": "Use default IOU values", + "type": "boolean", + "default": True + }, + "startup_config": { + "description": "Startup-config of IOU", + "type": "string", + "default": "iou_l3_base_startup-config.txt" + }, + "private_config": { + "description": "Private-config of IOU", + "type": "string", + "default": "" + }, + "console_type": { + "description": "Console type", + "enum": ["telnet", "none"], + "default": "telnet" + }, + "console_auto_start": { + "description": "Automatically start the console when the node has started", + "type": "boolean", + "default": False + }, +} + +IOU_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +IOU_APPLIANCE_PROPERTIES["category"]["default"] = "router" +IOU_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "IOU{0}" +IOU_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/multilayer_switch.svg" + +IOU_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A IOU template object", + "type": "object", + "properties": IOU_APPLIANCE_PROPERTIES, + "required": ["path"], + "additionalProperties": False +} diff --git a/gns3server/schemas/qemu_appliance.py b/gns3server/schemas/qemu_appliance.py new file mode 100644 index 00000000..dd7caace --- /dev/null +++ b/gns3server/schemas/qemu_appliance.py @@ -0,0 +1,217 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES +from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA +from .qemu import QEMU_PLATFORMS + + +QEMU_APPLIANCE_PROPERTIES = { + "usage": { + "description": "How to use the Qemu VM", + "type": "string", + "default": "" + }, + "qemu_path": { + "description": "Path to QEMU", + "type": "string", + "default": "" + }, + "platform": { + "description": "Platform to emulate", + "enum": QEMU_PLATFORMS, + "default": "i386" + }, + "linked_clone": { + "description": "Whether the VM is a linked clone or not", + "type": "boolean", + "default": True + }, + "ram": { + "description": "Amount of RAM in MB", + "type": "integer", + "default": 256 + }, + "cpus": { + "description": "Number of vCPUs", + "type": "integer", + "minimum": 1, + "maximum": 255, + "default": 1 + }, + "adapters": { + "description": "Number of adapters", + "type": "integer", + "minimum": 0, + "maximum": 275, + "default": 1 + }, + "adapter_type": { + "description": "QEMU adapter type", + "type": "string", + "enum": ["e1000", "i82550", "i82551", "i82557a", "i82557b", "i82557c", "i82558a","i82558b", "i82559a", + "i82559b", "i82559c", "i82559er", "i82562", "i82801", "ne2k_pci", "pcnet", "rtl8139", "virtio", + "virtio-net-pci", "vmxnet3"], + "default": "e1000" + }, + "mac_address": { + "description": "QEMU MAC address", + "type": "string", + "anyOf": [ + {"pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$"}, + {"pattern": "^$"} + ], + "default": "", + }, + "first_port_name": { + "description": "Optional name of the first networking port example: eth0", + "type": "string", + "default": "" + }, + "port_name_format": { + "description": "Optional formatting of the networking port example: eth{0}", + "type": "string", + "default": "Ethernet{0}" + }, + "port_segment_size": { + "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", + "type": "integer", + "default": 0 + }, + "console_type": { + "description": "Console type", + "enum": ["telnet", "vnc", "spice", "spice+agent", "none"], + "default": "telnet" + }, + "console_auto_start": { + "description": "Automatically start the console when the node has started", + "type": "boolean", + "default": False + }, + "boot_priority": { + "description": "QEMU boot priority", + "enum": ["c", "d", "n", "cn", "cd", "dn", "dc", "nc", "nd"], + "default": "c" + }, + "hda_disk_image": { + "description": "QEMU hda disk image path", + "type": "string", + "default": "" + }, + "hda_disk_interface": { + "description": "QEMU hda interface", + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], + "default": "ide" + }, + "hdb_disk_image": { + "description": "QEMU hdb disk image path", + "type": "string", + "default": "" + }, + "hdb_disk_interface": { + "description": "QEMU hdb interface", + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], + "default": "ide" + }, + "hdc_disk_image": { + "description": "QEMU hdc disk image path", + "type": "string", + "default": "" + }, + "hdc_disk_interface": { + "description": "QEMU hdc interface", + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], + "default": "ide" + }, + "hdd_disk_image": { + "description": "QEMU hdd disk image path", + "type": "string", + "default": "" + }, + "hdd_disk_interface": { + "description": "QEMU hdd interface", + "enum": ["ide", "sata", "scsi", "sd", "mtd", "floppy", "pflash", "virtio", "none"], + "default": "ide" + }, + "cdrom_image": { + "description": "QEMU cdrom image path", + "type": "string", + "default": "" + }, + "initrd": { + "description": "QEMU initrd path", + "type": "string", + "default": "" + }, + "kernel_image": { + "description": "QEMU kernel image path", + "type": "string", + "default": "" + }, + "bios_image": { + "description": "QEMU bios image path", + "type": "string", + "default": "" + }, + "kernel_command_line": { + "description": "QEMU kernel command line", + "type": "string", + "default": "" + }, + "legacy_networking": { + "description": "Use QEMU legagy networking commands (-net syntax)", + "type": "boolean", + "default": False + }, + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], + "default": "power_off" + }, + "cpu_throttling": { + "description": "Percentage of CPU allowed for QEMU", + "minimum": 0, + "maximum": 800, + "type": "integer", + "default": 0 + }, + "process_priority": { + "description": "Process priority for QEMU", + "enum": ["realtime", "very high", "high", "normal", "low", "very low"], + "default": "normal" + }, + "options": { + "description": "Additional QEMU options", + "type": "string", + "default": "" + }, + "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA +} + +QEMU_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +QEMU_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +QEMU_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +QEMU_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/qemu_guest.svg" + +QEMU_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A Qemu template object", + "type": "object", + "properties": QEMU_APPLIANCE_PROPERTIES, + "additionalProperties": False +} diff --git a/gns3server/schemas/traceng_appliance.py b/gns3server/schemas/traceng_appliance.py new file mode 100644 index 00000000..a45abffc --- /dev/null +++ b/gns3server/schemas/traceng_appliance.py @@ -0,0 +1,51 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES + + +TRACENG_APPLIANCE_PROPERTIES = { + "ip_address": { + "description": "Source IP address for tracing", + "type": ["string"], + "minLength": 1 + }, + "default_destination": { + "description": "Default destination IP address or hostname for tracing", + "type": ["string"], + "minLength": 1 + }, + "console_type": { + "description": "Console type", + "enum": ["none"], + "default": "none" + }, +} + +TRACENG_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +TRACENG_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +TRACENG_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "TraceNG{0}" +TRACENG_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/traceng.svg" + +TRACENG_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A TraceNG template object", + "type": "object", + "properties": TRACENG_APPLIANCE_PROPERTIES, + "additionalProperties": False +} diff --git a/gns3server/schemas/virtualbox_appliance.py b/gns3server/schemas/virtualbox_appliance.py new file mode 100644 index 00000000..6e510f09 --- /dev/null +++ b/gns3server/schemas/virtualbox_appliance.py @@ -0,0 +1,113 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES +from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA + + +VIRTUALBOX_APPLIANCE_PROPERTIES = { + "vmname": { + "description": "VirtualBox VM name (in VirtualBox itself)", + "type": "string", + "minLength": 1, + }, + "ram": { + "description": "Amount of RAM", + "minimum": 0, + "maximum": 65535, + "type": "integer", + "default": 256 + }, + "linked_clone": { + "description": "Whether the VM is a linked clone or not", + "type": "boolean", + "default": False + }, + "adapters": { + "description": "Number of adapters", + "type": "integer", + "minimum": 0, + "maximum": 36, # maximum given by the ICH9 chipset in VirtualBox + "default": 1 + }, + "use_any_adapter": { + "description": "Allow GNS3 to use any VirtualBox adapter", + "type": "boolean", + "default": False + }, + "adapter_type": { + "description": "VirtualBox adapter type", + "enum": ["PCnet-PCI II (Am79C970A)", + "PCNet-FAST III (Am79C973)", + "Intel PRO/1000 MT Desktop (82540EM)", + "Intel PRO/1000 T Server (82543GC)", + "Intel PRO/1000 MT Server (82545EM)", + "Paravirtualized Network (virtio-net)"], + "default": "Intel PRO/1000 MT Desktop (82540EM)" + }, + "first_port_name": { + "description": "Optional name of the first networking port example: eth0", + "type": "string", + "default": "" + }, + "port_name_format": { + "description": "Optional formatting of the networking port example: eth{0}", + "type": "string", + "default": "Ethernet{0}" + }, + "port_segment_size": { + "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", + "type": "integer", + "default": 0 + }, + "headless": { + "description": "Headless mode", + "type": "boolean", + "default": False + }, + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], + "default": "power_off" + }, + "console_type": { + "description": "Console type", + "enum": ["telnet", "none"], + "default": "none" + }, + "console_auto_start": { + "description": "Automatically start the console when the node has started", + "type": "boolean", + "default": False + }, + "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA +} + +VIRTUALBOX_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +VIRTUALBOX_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +VIRTUALBOX_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +VIRTUALBOX_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vbox_guest.svg" + +VIRTUALBOX_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A VirtualBox template object", + "type": "object", + "properties": VIRTUALBOX_APPLIANCE_PROPERTIES, + "required": ["vmname"], + "additionalProperties": False +} diff --git a/gns3server/schemas/vmware_appliance.py b/gns3server/schemas/vmware_appliance.py new file mode 100644 index 00000000..4ad16fce --- /dev/null +++ b/gns3server/schemas/vmware_appliance.py @@ -0,0 +1,101 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES +from .custom_adapters import CUSTOM_ADAPTERS_ARRAY_SCHEMA + + +VMWARE_APPLIANCE_PROPERTIES = { + "vmx_path": { + "description": "Path to the vmx file", + "type": "string", + "minLength": 1, + }, + "linked_clone": { + "description": "Whether the VM is a linked clone or not", + "type": "boolean", + "default": False + }, + "first_port_name": { + "description": "Optional name of the first networking port example: eth0", + "type": "string", + "default": "" + }, + "port_name_format": { + "description": "Optional formatting of the networking port example: eth{0}", + "type": "string", + "default": "Ethernet{0}" + }, + "port_segment_size": { + "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", + "type": "integer", + "default": 0 + }, + "adapters": { + "description": "Number of adapters", + "type": "integer", + "minimum": 0, + "maximum": 10, # maximum adapters support by VMware VMs, + "default": 1 + }, + "adapter_type": { + "description": "VMware adapter type", + "enum": ["default", "e1000", "e1000e", "flexible", "vlance", "vmxnet", "vmxnet2", "vmxnet3"], + "default": "e1000" + }, + "use_any_adapter": { + "description": "Allow GNS3 to use any VMware adapter", + "type": "boolean", + "default": False + }, + "headless": { + "description": "Headless mode", + "type": "boolean", + "default": False + }, + "on_close": { + "description": "Action to execute on the VM is closed", + "enum": ["power_off", "shutdown_signal", "save_vm_state"], + "default": "power_off" + }, + "console_type": { + "description": "Console type", + "enum": ["telnet", "none"], + "default": "none" + }, + "console_auto_start": { + "description": "Automatically start the console when the node has started", + "type": "boolean", + "default": False + }, + "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA +} + +VMWARE_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +VMWARE_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +VMWARE_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "{name}-{0}" +VMWARE_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vmware_guest.svg" + +VMWARE_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A VMware template object", + "type": "object", + "properties": VMWARE_APPLIANCE_PROPERTIES, + "required": ["vmx_path"], + "additionalProperties": False +} diff --git a/gns3server/schemas/vpcs_appliance.py b/gns3server/schemas/vpcs_appliance.py new file mode 100644 index 00000000..f8205316 --- /dev/null +++ b/gns3server/schemas/vpcs_appliance.py @@ -0,0 +1,52 @@ +# -*- 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 .appliance import BASE_APPLIANCE_PROPERTIES + + +VPCS_APPLIANCE_PROPERTIES = { + "base_script_file": { + "description": "Script file", + "type": "string", + "minLength": 1, + "default": "vpcs_base_config.txt" + }, + "console_type": { + "description": "Console type", + "enum": ["telnet", "none"], + "default": "telnet" + }, + "console_auto_start": { + "description": "Automatically start the console when the node has started", + "type": "boolean", + "default": False + }, +} + +VPCS_APPLIANCE_PROPERTIES.update(copy.deepcopy(BASE_APPLIANCE_PROPERTIES)) +VPCS_APPLIANCE_PROPERTIES["category"]["default"] = "guest" +VPCS_APPLIANCE_PROPERTIES["default_name_format"]["default"] = "PC{0}" +VPCS_APPLIANCE_PROPERTIES["symbol"]["default"] = ":/symbols/vpcs_guest.svg" + +VPCS_APPLIANCE_OBJECT_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "A VPCS template object", + "type": "object", + "properties": VPCS_APPLIANCE_PROPERTIES, + "additionalProperties": False +} diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 764286c3..72943e61 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -36,30 +36,7 @@ from ..crash_report import CrashReport from ..config import Config -# 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) - - -async def parse_request(request, input_schema, raw, set_input_schema_defaults=False): +async def parse_request(request, input_schema, raw): """Parse body of request and raise HTTP errors in case of problems""" request.json = {} @@ -78,37 +55,18 @@ async def parse_request(request, input_schema, raw, set_input_schema_defaults=Fa request.json[k] = v[0] if input_schema: - - if set_input_schema_defaults: - validator = ValidatorWithDefaults(input_schema) - else: - validator = jsonschema.Draft4Validator(input_schema) try: - validator.validate(request.json) + jsonschema.validate(request.json, input_schema) except jsonschema.ValidationError as e: - message = "JSON schema error with API request '{}': {}".format(request.path_qs, e.message) - if "is not valid under any of the given schemas" not in message: - best_match = jsonschema.exceptions.best_match(validator.iter_errors(request.json)) - message += " (best matched error: {})".format(best_match.message) + message = "JSON schema error with API request '{}' and JSON data '{}': {}".format(request.path_qs, + request.json, + e.message) log.error(message) log.debug("Input schema: {}".format(json.dumps(input_schema))) raise aiohttp.web.HTTPBadRequest(text=message) return request - # if set_input_schema_defaults: - # validator = ValidatorWithDefaults(input_schema) - # else: - # validator = jsonschema.Draft4Validator(input_schema) - # error = jsonschema.exceptions.best_match(validator.iter_errors(request.json)) - # if error: - # message = "JSON schema error with API request '{}' while validating JSON data '{}': {}".format(request.path_qs, request.json, error.message) - # log.error(message) - # log.debug("Input schema: {}".format(json.dumps(input_schema))) - # raise aiohttp.web.HTTPBadRequest(text=message) - # - # return request - class Route(object): @@ -176,7 +134,6 @@ class Route(object): input_schema = kw.get("input", {}) api_version = kw.get("api_version", 2) raw = kw.get("raw", False) - set_input_schema_defaults = kw.get("set_input_schema_defaults", False) def register(func): # Add the type of server to the route @@ -225,7 +182,7 @@ class Route(object): return response # API call - request = await parse_request(request, input_schema, raw, set_input_schema_defaults) + request = await parse_request(request, input_schema, raw) record_file = server_config.get("record") if record_file: try: diff --git a/tests/controller/test_appliance.py b/tests/controller/test_appliance.py index a30bd370..a5f458d6 100644 --- a/tests/controller/test_appliance.py +++ b/tests/controller/test_appliance.py @@ -15,6 +15,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import pytest +import jsonschema + from gns3server.controller.appliance import Appliance @@ -26,42 +29,25 @@ def test_appliance_json(): "category": 0, "symbol": "qemu.svg", "server": "local", - "platform": None + "platform": "i386" }) - assert a.__json__() == { - "appliance_id": a.id, - "appliance_type": "qemu", - "builtin": False, - "name": "Test", - "default_name_format": "{name}-{0}", - "category": "router", - "symbol": "qemu.svg", - "compute_id": "local", - "platform": None - } + settings = a.__json__() + assert settings["appliance_id"] == a.id + assert settings["appliance_type"] == "qemu" + assert settings["builtin"] == False def test_appliance_json_with_not_known_category(): - a = Appliance(None, { - "node_type": "qemu", - "name": "Test", - "default_name_format": "{name}-{0}", - "category": 'Not known', - "symbol": "qemu.svg", - "server": "local", - "platform": None - }) - assert a.__json__() == { - "appliance_id": a.id, - "appliance_type": "qemu", - "builtin": False, - "name": "Test", - "default_name_format": "{name}-{0}", - "category": "Not known", - "symbol": "qemu.svg", - "compute_id": "local", - "platform": None - } + with pytest.raises(jsonschema.ValidationError): + a = Appliance(None, { + "node_type": "qemu", + "name": "Test", + "default_name_format": "{name}-{0}", + "category": 'Not known', + "symbol": "qemu.svg", + "server": "local", + "platform": "i386" + }) def test_appliance_json_with_platform(): @@ -71,20 +57,15 @@ def test_appliance_json_with_platform(): "default_name_format": "{name}-{0}", "category": 0, "symbol": "dynamips.svg", + "image": "IOS_image.bin", "server": "local", "platform": "c3725" }) - assert a.__json__() == { - "appliance_id": a.id, - "appliance_type": "dynamips", - "builtin": False, - "name": "Test", - "default_name_format": "{name}-{0}", - "category": "router", - "symbol": "dynamips.svg", - "compute_id": "local", - "platform": "c3725" - } + settings = a.__json__() + assert settings["appliance_id"] == a.id + assert settings["appliance_type"] == "dynamips" + assert settings["builtin"] == False + assert settings["platform"] == "c3725" def test_appliance_fix_linked_base(): diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py index 78920ce0..49678f4f 100644 --- a/tests/controller/test_project.py +++ b/tests/controller/test_project.py @@ -213,14 +213,10 @@ def test_add_node_from_appliance(async_run, controller): project = Project(controller=controller, name="Test") controller._notification = MagicMock() appliance = Appliance(str(uuid.uuid4()), { - "server": "local", + "compute_id": "local", "name": "Test", - "default_name_format": "{name}-{0}", "appliance_type": "vpcs", "builtin": False, - "properties": { - "a": 1 - } }) controller._appliances[appliance.id] = appliance controller._computes["local"] = compute @@ -230,30 +226,15 @@ def test_add_node_from_appliance(async_run, controller): compute.post = AsyncioMagicMock(return_value=response) node = async_run(project.add_node_from_appliance(appliance.id, x=23, y=12)) - compute.post.assert_any_call('/projects', data={ "name": project._name, "project_id": project._id, "path": project._path }) - compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id), - data={'node_id': node.id, - 'name': 'Test-1', - 'a': 1, - }, - timeout=1200) + assert compute in project._project_created_on_compute controller.notification.project_emit.assert_any_call("node.created", node.__json__()) - # Make sure we can call twice the node creation - node = async_run(project.add_node_from_appliance(appliance.id, x=13, y=12)) - compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id), - data={'node_id': node.id, - 'name': 'Test-2', - 'a': 1 - }, - timeout=1200) - def test_delete_node(async_run, controller): """ diff --git a/tests/handlers/api/controller/test_appliance.py b/tests/handlers/api/controller/test_appliance.py index 41ead4bc..d856cce6 100644 --- a/tests/handlers/api/controller/test_appliance.py +++ b/tests/handlers/api/controller/test_appliance.py @@ -400,7 +400,6 @@ def test_c3600_dynamips_appliance_create_wrong_chassis(http_controller): response = http_controller.post("/appliances", params) assert response.status == 400 - assert "is not valid under any of the given schemas" in response.json["message"] def test_c2691_dynamips_appliance_create(http_controller): @@ -504,7 +503,6 @@ def test_c2600_dynamips_appliance_create_wrong_chassis(http_controller): response = http_controller.post("/appliances", params) assert response.status == 400 - assert "is not valid under any of the given schemas" in response.json["message"] def test_c1700_dynamips_appliance_create(http_controller): @@ -564,7 +562,6 @@ def test_c1700_dynamips_appliance_create_wrong_chassis(http_controller): response = http_controller.post("/appliances", params) assert response.status == 400 - assert "is not valid under any of the given schemas" in response.json["message"] def test_dynamips_appliance_create_wrong_platform(http_controller): @@ -577,7 +574,6 @@ def test_dynamips_appliance_create_wrong_platform(http_controller): response = http_controller.post("/appliances", params) assert response.status == 400 - assert "is not valid under any of the given schemas" in response.json["message"] def test_iou_appliance_create(http_controller):