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/schemas/appliance.py b/gns3server/schemas/appliance.py index 5ce51903..e8cba49b 100644 --- a/gns3server/schemas/appliance.py +++ b/gns3server/schemas/appliance.py @@ -16,24 +16,39 @@ # 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", + "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,802 +56,18 @@ 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"] - }, - "image": { - "description": "Path to the IOS image", - "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" - }, - "exec_area": { - "description": "Exec area value", - "type": "integer", - }, - "disk0": { - "description": "Disk0 size in MB", - "type": "integer" - }, - "disk1": { - "description": "Disk1 size in MB", - "type": "integer" - }, - "mac_addr": { - "description": "Base MAC address", - "type": "string", - "anyOf": [ - {"pattern": "^([0-9a-fA-F]{4}\\.){2}[0-9a-fA-F]{4}$"}, - {"pattern": "^$"} - ] - }, - "system_id": { - "description": "System ID", - "type": "string", - "minLength": 1, - }, - "startup_config": { - "description": "IOS startup configuration file", - "type": "string" - }, - "private_config": { - "description": "IOS private configuration file", - "type": "string" - }, - "idlepc": { - "description": "Idle-PC value", - "type": "string", - "pattern": "^(0x[0-9a-fA-F]+)?$" - }, - "idlemax": { - "description": "Idlemax value", - "type": "integer", - }, - "idlesleep": { - "description": "Idlesleep value", - "type": "integer", - }, - "iomem": { - "description": "I/O memory percentage", - "type": "integer", - "minimum": 0, - "maximum": 100 - }, - "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"] - }, - "auto_delete_disks": { - "description": "Automatically delete nvram and disk files", - "type": "boolean" - }, - "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"] - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean" - } -} - -DYNAMIPS_APPLIANCE_PROPERTIES.update(BASE_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", - }, - "serial_adapters": { - "description": "Number of serial adapters", - "type": "integer" - }, - "ram": { - "description": "RAM in MB", - "type": "integer" - }, - "nvram": { - "description": "NVRAM in KB", - "type": "integer" - }, - "use_default_iou_values": { - "description": "Use default IOU values", - "type": "boolean" - }, - "startup_config": { - "description": "Startup-config of IOU", - "type": "string" - }, - "private_config": { - "description": "Private-config of IOU", - "type": "string" - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"] - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean" - }, -} - -IOU_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -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 - }, - "start_command": { - "description": "Docker CMD entry", - "type": "string", - "minLength": 1 - }, - "environment": { - "description": "Docker environment variables", - "type": "string", - "minLength": 1 - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "vnc", "http", "https", "none"] - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean" - }, - "console_http_port": { - "description": "Internal port in the container for the HTTP server", - "type": "integer", - "minimum": 1, - "maximum": 65535 - }, - "console_http_path": { - "description": "Path of the web interface", - "type": "string", - "minLength": 1 - }, - "console_resolution": { - "description": "Console resolution for VNC", - "type": "string", - "pattern": "^[0-9]+x[0-9]+$" - }, - "extra_hosts": { - "description": "Docker extra hosts (added to /etc/hosts)", - "type": "string", - "minLength": 1 - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -DOCKER_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -QEMU_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["qemu"] - }, - "usage": { - "description": "How to use the Qemu VM", - "type": "string", - "minLength": 1 - }, - "qemu_path": { - "description": "Path to QEMU", - "type": ["string", "null"], - "minLength": 1, - }, - "platform": { - "description": "Platform to emulate", - "enum": QEMU_PLATFORMS - }, - "linked_clone": { - "description": "Whether the VM is a linked clone or not", - "type": "boolean" - }, - "ram": { - "description": "Amount of RAM in MB", - "type": "integer" - }, - "cpus": { - "description": "Number of vCPUs", - "type": "integer", - "minimum": 1, - "maximum": 255 - }, - "adapters": { - "description": "Number of adapters", - "type": "integer", - "minimum": 0, - "maximum": 275 - }, - "adapter_type": { - "description": "QEMU adapter type", - "type": "string", - "minLength": 1 - }, - "mac_address": { - "description": "QEMU MAC address", - "type": "string", - "minLength": 1, - "pattern": "^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$" - }, - "first_port_name": { - "description": "Optional name of the first networking port example: eth0", - "type": "string", - "minLength": 1 - }, - "port_name_format": { - "description": "Optional formatting of the networking port example: eth{0}", - "type": "string", - "minLength": 1 - }, - "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" - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "vnc", "spice", "spice+agent", "none"] - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean" - }, - "boot_priority": { - "description": "QEMU boot priority", - "enum": ["c", "d", "n", "cn", "cd", "dn", "dc", "nc", "nd"] - }, - "hda_disk_image": { - "description": "QEMU hda disk image path", - "type": "string", - "minLength": 1 - }, - "hda_disk_interface": { - "description": "QEMU hda interface", - "type": "string", - "minLength": 1 - }, - "hdb_disk_image": { - "description": "QEMU hdb disk image path", - "type": "string", - "minLength": 1 - }, - "hdb_disk_interface": { - "description": "QEMU hdb interface", - "type": "string", - "minLength": 1 - }, - "hdc_disk_image": { - "description": "QEMU hdc disk image path", - "type": "string", - "minLength": 1 - }, - "hdc_disk_interface": { - "description": "QEMU hdc interface", - "type": "string", - "minLength": 1 - }, - "hdd_disk_image": { - "description": "QEMU hdd disk image path", - "type": "string", - "minLength": 1 - }, - "hdd_disk_interface": { - "description": "QEMU hdd interface", - "type": "string", - "minLength": 1 - }, - "cdrom_image": { - "description": "QEMU cdrom image path", - "type": "string", - "minLength": 1 - }, - "initrd": { - "description": "QEMU initrd path", - "type": "string", - "minLength": 1 - }, - "kernel_image": { - "description": "QEMU kernel image path", - "type": "string", - "minLength": 1 - }, - "bios_image": { - "description": "QEMU bios image path", - "type": "string", - "minLength": 1 - }, - "kernel_command_line": { - "description": "QEMU kernel command line", - "type": "string", - "minLength": 1 - }, - "legacy_networking": { - "description": "Use QEMU legagy networking commands (-net syntax)", - "type": "boolean" - }, - "on_close": { - "description": "Action to execute on the VM is closed", - "enum": ["power_off", "shutdown_signal", "save_vm_state"], - }, - "cpu_throttling": { - "description": "Percentage of CPU allowed for QEMU", - "minimum": 0, - "maximum": 800, - "type": "integer" - }, - "process_priority": { - "description": "Process priority for QEMU", - "enum": ["realtime", "very high", "high", "normal", "low", "very low"] - }, - "options": { - "description": "Additional QEMU options", - "type": "string", - "minLength": 1 - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -QEMU_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -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" - }, - "first_port_name": { - "description": "Optional name of the first networking port example: eth0", - "type": "string", - "minLength": 1 - }, - "port_name_format": { - "description": "Optional formatting of the networking port example: eth{0}", - "type": "string", - "minLength": 1 - }, - "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" - }, - "adapters": { - "description": "Number of adapters", - "type": "integer", - "minimum": 0, - "maximum": 10, # maximum adapters support by VMware VMs - }, - "adapter_type": { - "description": "VMware adapter type", - "type": "string", - "minLength": 1, - }, - "use_any_adapter": { - "description": "Allow GNS3 to use any VMware adapter", - "type": "boolean", - }, - "headless": { - "description": "Headless mode", - "type": "boolean" - }, - "on_close": { - "description": "Action to execute on the VM is closed", - "enum": ["power_off", "shutdown_signal", "save_vm_state"], - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"] - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean" - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -VMWARE_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -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" - }, - "linked_clone": { - "description": "Whether the VM is a linked clone or not", - "type": "boolean" - }, - "adapters": { - "description": "Number of adapters", - "type": "integer", - "minimum": 0, - "maximum": 36, # maximum given by the ICH9 chipset in VirtualBox - }, - "use_any_adapter": { - "description": "Allow GNS3 to use any VirtualBox adapter", - "type": "boolean", - }, - "adapter_type": { - "description": "VirtualBox adapter type", - "type": "string", - "minLength": 1, - }, - "first_port_name": { - "description": "Optional name of the first networking port example: eth0", - "type": "string", - "minLength": 1 - }, - "port_name_format": { - "description": "Optional formatting of the networking port example: eth{0}", - "type": "string", - "minLength": 1 - }, - "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" - }, - "headless": { - "description": "Headless mode", - "type": "boolean" - }, - "on_close": { - "description": "Action to execute on the VM is closed", - "enum": ["power_off", "shutdown_signal", "save_vm_state"], - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"] - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean" - }, - "custom_adapters": CUSTOM_ADAPTERS_ARRAY_SCHEMA -} - -VIRTUALBOX_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -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"] - }, -} - -TRACENG_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -VPCS_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["vpcs"] - }, - "base_script_file": { - "description": "Script file", - "type": "string", - "minLength": 1, - }, - "console_type": { - "description": "Console type", - "enum": ["telnet", "none"] - }, - "console_auto_start": { - "description": "Automatically start the console when the node has started", - "type": "boolean" - }, -} - -VPCS_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -ETHERNET_SWITCH_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["ethernet_switch"] - }, - "ports_mapping": { - "type": "array", - "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"] - }, -} - -ETHERNET_SWITCH_APPLIANCE_PROPERTIES.update(BASE_APPLIANCE_PROPERTIES) - -ETHERNET_HUB_APPLIANCE_PROPERTIES = { - "appliance_type": { - "enum": ["ethernet_hub"] - }, - "ports_mapping": { - "type": "array", - "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(BASE_APPLIANCE_PROPERTIES) - -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(BASE_APPLIANCE_PROPERTIES) - 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, - "additionalProperties": False, - "required": ["platform", "image", "ram"] - }, - "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", "linked_clone"] - }, - "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/Dynamips"}, - {"$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_id", "appliance_type", "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) @@ -844,9 +75,10 @@ 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") +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 41dec3f8..72943e61 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__) @@ -58,10 +58,12 @@ async def parse_request(request, input_schema, raw): try: jsonschema.validate(request.json, input_schema) 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))) + 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 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 05835fd3..d856cce6 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,729 @@ 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 + + +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 + + +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 + + +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 + + +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 +950,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