1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-12-29 18:28:11 +00:00

Reorganize how appliance creation is validated against JSON schemas.

This allows for clearer error messages when validation fails.
This commit is contained in:
grossmj 2018-11-17 18:12:46 +07:00
parent 71fcf855b4
commit 499ab9844a
19 changed files with 1524 additions and 1325 deletions

View File

@ -23,6 +23,7 @@ import socket
import shutil import shutil
import asyncio import asyncio
import aiohttp import aiohttp
import jsonschema
from ..config import Config from ..config import Config
from .project import Project from .project import Project
@ -144,10 +145,9 @@ class Controller:
appliance_id = settings.setdefault("appliance_id", str(uuid.uuid4())) appliance_id = settings.setdefault("appliance_id", str(uuid.uuid4()))
try: try:
appliance = Appliance(appliance_id, settings) appliance = Appliance(appliance_id, settings)
appliance.__json__() # Check if loaded without error except jsonschema.ValidationError as e:
except KeyError as e: message = "JSON schema error adding appliance with JSON data '{}': {}".format(settings, e.message)
# appliance settings is not complete raise aiohttp.web.HTTPBadRequest(text=message)
raise aiohttp.web.HTTPConflict(text="Cannot create new appliance: key '{}' is missing for appliance ID '{}'".format(e, appliance_id))
self._appliances[appliance.id] = appliance self._appliances[appliance.id] = appliance
self.save() self.save()
self.notification.controller_emit("appliance.created", appliance.__json__()) self.notification.controller_emit("appliance.created", appliance.__json__())
@ -327,11 +327,10 @@ class Controller:
for appliance_settings in controller_settings["appliances"]: for appliance_settings in controller_settings["appliances"]:
try: try:
appliance = Appliance(appliance_settings["appliance_id"], appliance_settings) appliance = Appliance(appliance_settings["appliance_id"], appliance_settings)
appliance.__json__() # Check if loaded without error
self._appliances[appliance.id] = appliance self._appliances[appliance.id] = appliance
except KeyError as e: except jsonschema.ValidationError as e:
# appliance data is not complete (missing name or type) message = "Cannot load appliance with JSON data '{}': {}".format(appliance_settings, e.message)
log.warning("Cannot load appliance template {} ('{}'): missing key {}".format(appliance_settings["appliance_id"], appliance_settings.get("name", "unknown"), e)) log.warning(message)
continue continue
# load GNS3 VM settings # load GNS3 VM settings

View File

@ -17,6 +17,56 @@
import copy import copy
import uuid 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 = { ID_TO_CATEGORY = {
3: "firewall", 3: "firewall",
@ -25,6 +75,30 @@ ID_TO_CATEGORY = {
0: "router" 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: class Appliance:
@ -66,6 +140,13 @@ class Appliance:
self._builtin = builtin 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 @property
def id(self): def id(self):
return self._id return self._id
@ -102,6 +183,17 @@ class Appliance:
controller.notification.controller_emit("appliance.updated", self.__json__()) controller.notification.controller_emit("appliance.updated", self.__json__())
controller.save() 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): def __json__(self):
""" """
Appliance settings. Appliance settings.
@ -109,8 +201,6 @@ class Appliance:
settings = self._settings settings = self._settings
settings.update({"appliance_id": self._id, 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}) "builtin": self.builtin})
if not self.builtin: if not self.builtin:

View File

@ -58,8 +58,7 @@ class ApplianceHandler:
400: "Invalid request" 400: "Invalid request"
}, },
input=APPLIANCE_CREATE_SCHEMA, input=APPLIANCE_CREATE_SCHEMA,
output=APPLIANCE_OBJECT_SCHEMA, output=APPLIANCE_OBJECT_SCHEMA)
set_input_schema_defaults=True)
def create(request, response): def create(request, response):
controller = Controller.instance() controller = Controller.instance()

File diff suppressed because it is too large Load Diff

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -36,30 +36,7 @@ from ..crash_report import CrashReport
from ..config import Config from ..config import Config
# Add default values for missing entries in a request, largely taken from jsonschema documentation example async def parse_request(request, input_schema, raw):
# https://python-jsonschema.readthedocs.io/en/latest/faq/#why-doesn-t-my-schema-s-default-property-set-the-default-on-my-instance
def extend_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"]
def set_defaults(validator, properties, instance, schema):
if jsonschema.Draft4Validator(schema).is_valid(instance):
# only add default for the matching sub-schema (e.g. when using 'oneOf')
for property, subschema in properties.items():
if "default" in subschema:
instance.setdefault(property, subschema["default"])
for error in validate_properties(validator, properties, instance, schema,):
yield error
return jsonschema.validators.extend(
validator_class, {"properties" : set_defaults},
)
ValidatorWithDefaults = extend_with_default(jsonschema.Draft4Validator)
async def parse_request(request, input_schema, raw, set_input_schema_defaults=False):
"""Parse body of request and raise HTTP errors in case of problems""" """Parse body of request and raise HTTP errors in case of problems"""
request.json = {} request.json = {}
@ -78,37 +55,18 @@ async def parse_request(request, input_schema, raw, set_input_schema_defaults=Fa
request.json[k] = v[0] request.json[k] = v[0]
if input_schema: if input_schema:
if set_input_schema_defaults:
validator = ValidatorWithDefaults(input_schema)
else:
validator = jsonschema.Draft4Validator(input_schema)
try: try:
validator.validate(request.json) jsonschema.validate(request.json, input_schema)
except jsonschema.ValidationError as e: except jsonschema.ValidationError as e:
message = "JSON schema error with API request '{}': {}".format(request.path_qs, e.message) message = "JSON schema error with API request '{}' and JSON data '{}': {}".format(request.path_qs,
if "is not valid under any of the given schemas" not in message: request.json,
best_match = jsonschema.exceptions.best_match(validator.iter_errors(request.json)) e.message)
message += " (best matched error: {})".format(best_match.message)
log.error(message) log.error(message)
log.debug("Input schema: {}".format(json.dumps(input_schema))) log.debug("Input schema: {}".format(json.dumps(input_schema)))
raise aiohttp.web.HTTPBadRequest(text=message) raise aiohttp.web.HTTPBadRequest(text=message)
return request return request
# if set_input_schema_defaults:
# validator = ValidatorWithDefaults(input_schema)
# else:
# validator = jsonschema.Draft4Validator(input_schema)
# error = jsonschema.exceptions.best_match(validator.iter_errors(request.json))
# if error:
# message = "JSON schema error with API request '{}' while validating JSON data '{}': {}".format(request.path_qs, request.json, error.message)
# log.error(message)
# log.debug("Input schema: {}".format(json.dumps(input_schema)))
# raise aiohttp.web.HTTPBadRequest(text=message)
#
# return request
class Route(object): class Route(object):
@ -176,7 +134,6 @@ class Route(object):
input_schema = kw.get("input", {}) input_schema = kw.get("input", {})
api_version = kw.get("api_version", 2) api_version = kw.get("api_version", 2)
raw = kw.get("raw", False) raw = kw.get("raw", False)
set_input_schema_defaults = kw.get("set_input_schema_defaults", False)
def register(func): def register(func):
# Add the type of server to the route # Add the type of server to the route
@ -225,7 +182,7 @@ class Route(object):
return response return response
# API call # API call
request = await parse_request(request, input_schema, raw, set_input_schema_defaults) request = await parse_request(request, input_schema, raw)
record_file = server_config.get("record") record_file = server_config.get("record")
if record_file: if record_file:
try: try:

View File

@ -15,6 +15,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest
import jsonschema
from gns3server.controller.appliance import Appliance from gns3server.controller.appliance import Appliance
@ -26,42 +29,25 @@ def test_appliance_json():
"category": 0, "category": 0,
"symbol": "qemu.svg", "symbol": "qemu.svg",
"server": "local", "server": "local",
"platform": None "platform": "i386"
}) })
assert a.__json__() == { settings = a.__json__()
"appliance_id": a.id, assert settings["appliance_id"] == a.id
"appliance_type": "qemu", assert settings["appliance_type"] == "qemu"
"builtin": False, assert settings["builtin"] == False
"name": "Test",
"default_name_format": "{name}-{0}",
"category": "router",
"symbol": "qemu.svg",
"compute_id": "local",
"platform": None
}
def test_appliance_json_with_not_known_category(): def test_appliance_json_with_not_known_category():
a = Appliance(None, { with pytest.raises(jsonschema.ValidationError):
"node_type": "qemu", a = Appliance(None, {
"name": "Test", "node_type": "qemu",
"default_name_format": "{name}-{0}", "name": "Test",
"category": 'Not known', "default_name_format": "{name}-{0}",
"symbol": "qemu.svg", "category": 'Not known',
"server": "local", "symbol": "qemu.svg",
"platform": None "server": "local",
}) "platform": "i386"
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
}
def test_appliance_json_with_platform(): def test_appliance_json_with_platform():
@ -71,20 +57,15 @@ def test_appliance_json_with_platform():
"default_name_format": "{name}-{0}", "default_name_format": "{name}-{0}",
"category": 0, "category": 0,
"symbol": "dynamips.svg", "symbol": "dynamips.svg",
"image": "IOS_image.bin",
"server": "local", "server": "local",
"platform": "c3725" "platform": "c3725"
}) })
assert a.__json__() == { settings = a.__json__()
"appliance_id": a.id, assert settings["appliance_id"] == a.id
"appliance_type": "dynamips", assert settings["appliance_type"] == "dynamips"
"builtin": False, assert settings["builtin"] == False
"name": "Test", assert settings["platform"] == "c3725"
"default_name_format": "{name}-{0}",
"category": "router",
"symbol": "dynamips.svg",
"compute_id": "local",
"platform": "c3725"
}
def test_appliance_fix_linked_base(): def test_appliance_fix_linked_base():

View File

@ -213,14 +213,10 @@ def test_add_node_from_appliance(async_run, controller):
project = Project(controller=controller, name="Test") project = Project(controller=controller, name="Test")
controller._notification = MagicMock() controller._notification = MagicMock()
appliance = Appliance(str(uuid.uuid4()), { appliance = Appliance(str(uuid.uuid4()), {
"server": "local", "compute_id": "local",
"name": "Test", "name": "Test",
"default_name_format": "{name}-{0}",
"appliance_type": "vpcs", "appliance_type": "vpcs",
"builtin": False, "builtin": False,
"properties": {
"a": 1
}
}) })
controller._appliances[appliance.id] = appliance controller._appliances[appliance.id] = appliance
controller._computes["local"] = compute controller._computes["local"] = compute
@ -230,30 +226,15 @@ def test_add_node_from_appliance(async_run, controller):
compute.post = AsyncioMagicMock(return_value=response) compute.post = AsyncioMagicMock(return_value=response)
node = async_run(project.add_node_from_appliance(appliance.id, x=23, y=12)) node = async_run(project.add_node_from_appliance(appliance.id, x=23, y=12))
compute.post.assert_any_call('/projects', data={ compute.post.assert_any_call('/projects', data={
"name": project._name, "name": project._name,
"project_id": project._id, "project_id": project._id,
"path": project._path "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 assert compute in project._project_created_on_compute
controller.notification.project_emit.assert_any_call("node.created", node.__json__()) 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): def test_delete_node(async_run, controller):
""" """

View File

@ -400,7 +400,6 @@ def test_c3600_dynamips_appliance_create_wrong_chassis(http_controller):
response = http_controller.post("/appliances", params) response = http_controller.post("/appliances", params)
assert response.status == 400 assert response.status == 400
assert "is not valid under any of the given schemas" in response.json["message"]
def test_c2691_dynamips_appliance_create(http_controller): def test_c2691_dynamips_appliance_create(http_controller):
@ -504,7 +503,6 @@ def test_c2600_dynamips_appliance_create_wrong_chassis(http_controller):
response = http_controller.post("/appliances", params) response = http_controller.post("/appliances", params)
assert response.status == 400 assert response.status == 400
assert "is not valid under any of the given schemas" in response.json["message"]
def test_c1700_dynamips_appliance_create(http_controller): def test_c1700_dynamips_appliance_create(http_controller):
@ -564,7 +562,6 @@ def test_c1700_dynamips_appliance_create_wrong_chassis(http_controller):
response = http_controller.post("/appliances", params) response = http_controller.post("/appliances", params)
assert response.status == 400 assert response.status == 400
assert "is not valid under any of the given schemas" in response.json["message"]
def test_dynamips_appliance_create_wrong_platform(http_controller): def test_dynamips_appliance_create_wrong_platform(http_controller):
@ -577,7 +574,6 @@ def test_dynamips_appliance_create_wrong_platform(http_controller):
response = http_controller.post("/appliances", params) response = http_controller.post("/appliances", params)
assert response.status == 400 assert response.status == 400
assert "is not valid under any of the given schemas" in response.json["message"]
def test_iou_appliance_create(http_controller): def test_iou_appliance_create(http_controller):