Working dedicated appliance management API. Ref https://github.com/GNS3/gns3-server/issues/1427

This commit is contained in:
grossmj 2018-11-13 14:59:18 +08:00
parent f0fe9d39fa
commit 089fdff4f1
5 changed files with 55 additions and 35 deletions

View File

@ -174,9 +174,12 @@ class Controller:
"""
appliance = self._appliances.get(appliance_id)
if not appliance:
raise aiohttp.web.HTTPNotFound(text="Appliance ID {} doesn't exist".format(appliance_id))
if appliance.builtin:
raise aiohttp.web.HTTPConflict(text="Appliance ID {} cannot be deleted because it is builtin".format(appliance_id))
raise aiohttp.web.HTTPConflict(text="Appliance ID {} cannot be deleted because it is a builtin".format(appliance_id))
self._appliances.pop(appliance_id)
self.save()
def load_appliances(self):
@ -184,17 +187,17 @@ class Controller:
# Add builtins
builtins = []
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "cloud"), {"node_type": "cloud", "name": "Cloud", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "nat"), {"node_type": "nat", "name": "NAT", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "vpcs"), {"node_type": "vpcs", "name": "VPCS", "default_name_format": "PC-{0}", "category": 2, "symbol": ":/symbols/vpcs_guest.svg", "properties": {"base_script_file": "vpcs_base_config.txt"}}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_switch"), {"node_type": "ethernet_switch", "console_type": "telnet", "name": "Ethernet switch", "category": 1, "symbol": ":/symbols/ethernet_switch.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"), {"node_type": "ethernet_hub", "name": "Ethernet hub", "category": 1, "symbol": ":/symbols/hub.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"), {"node_type": "frame_relay_switch", "name": "Frame Relay switch", "category": 1, "symbol": ":/symbols/frame_relay_switch.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"), {"node_type": "atm_switch", "name": "ATM switch", "category": 1, "symbol": ":/symbols/atm_switch.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "cloud"), {"appliance_type": "cloud", "name": "Cloud", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "nat"), {"appliance_type": "nat", "name": "NAT", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "vpcs"), {"appliance_type": "vpcs", "name": "VPCS", "default_name_format": "PC-{0}", "category": 2, "symbol": ":/symbols/vpcs_guest.svg", "properties": {"base_script_file": "vpcs_base_config.txt"}}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_switch"), {"appliance_type": "ethernet_switch", "console_type": "telnet", "name": "Ethernet switch", "category": 1, "symbol": ":/symbols/ethernet_switch.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"), {"appliance_type": "ethernet_hub", "name": "Ethernet hub", "category": 1, "symbol": ":/symbols/hub.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"), {"appliance_type": "frame_relay_switch", "name": "Frame Relay switch", "category": 1, "symbol": ":/symbols/frame_relay_switch.svg"}, builtin=True))
builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"), {"appliance_type": "atm_switch", "name": "ATM switch", "category": 1, "symbol": ":/symbols/atm_switch.svg"}, builtin=True))
#FIXME: disable TraceNG
#if sys.platform.startswith("win"):
# builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "traceng"), {"node_type": "traceng", "name": "TraceNG", "default_name_format": "TraceNG-{0}", "category": 2, "symbol": ":/symbols/traceng.svg", "properties": {}}, builtin=True))
# builtins.append(Appliance(uuid.uuid3(uuid.NAMESPACE_DNS, "traceng"), {"appliance_type": "traceng", "name": "TraceNG", "default_name_format": "TraceNG-{0}", "category": 2, "symbol": ":/symbols/traceng.svg", "properties": {}}, builtin=True))
for b in builtins:
self._appliances[b.id] = b
@ -464,37 +467,37 @@ class Controller:
vms = []
for vm in settings.get("Qemu", {}).get("vms", []):
vm["node_type"] = "qemu"
vm["appliance_type"] = "qemu"
vms.append(vm)
for vm in settings.get("IOU", {}).get("devices", []):
vm["node_type"] = "iou"
vm["appliance_type"] = "iou"
vms.append(vm)
for vm in settings.get("Docker", {}).get("containers", []):
vm["node_type"] = "docker"
vm["appliance_type"] = "docker"
vms.append(vm)
for vm in settings.get("Builtin", {}).get("cloud_nodes", []):
vm["node_type"] = "cloud"
vm["appliance_type"] = "cloud"
vms.append(vm)
for vm in settings.get("Builtin", {}).get("ethernet_switches", []):
vm["node_type"] = "ethernet_switch"
vm["appliance_type"] = "ethernet_switch"
vms.append(vm)
for vm in settings.get("Builtin", {}).get("ethernet_hubs", []):
vm["node_type"] = "ethernet_hub"
vm["appliance_type"] = "ethernet_hub"
vms.append(vm)
for vm in settings.get("Dynamips", {}).get("routers", []):
vm["node_type"] = "dynamips"
vm["appliance_type"] = "dynamips"
vms.append(vm)
for vm in settings.get("VMware", {}).get("vms", []):
vm["node_type"] = "vmware"
vm["appliance_type"] = "vmware"
vms.append(vm)
for vm in settings.get("VirtualBox", {}).get("vms", []):
vm["node_type"] = "virtualbox"
vm["appliance_type"] = "virtualbox"
vms.append(vm)
for vm in settings.get("VPCS", {}).get("nodes", []):
vm["node_type"] = "vpcs"
vm["appliance_type"] = "vpcs"
vms.append(vm)
for vm in settings.get("TraceNG", {}).get("nodes", []):
vm["node_type"] = "traceng"
vm["appliance_type"] = "traceng"
vms.append(vm)
for vm in vms:

View File

@ -56,8 +56,12 @@ class Appliance:
if "server" in self._settings:
self._settings["compute_id"] = self._settings.pop("server")
# The "node_type" setting has been replaced by "appliance_type" setting in version 2.2
if "node_type" in self._settings:
self._settings["appliance_type"] = self._settings.pop("node_type")
# Remove an old IOU setting
if settings["node_type"] == "iou" and "image" in settings:
if self._settings["appliance_type"] == "iou" and "image" in self._settings:
del self._settings["image"]
self._builtin = builtin
@ -72,7 +76,6 @@ class Appliance:
@settings.setter
def settings(self, settings):
self._settings.update(settings)
@property
@ -84,17 +87,21 @@ class Appliance:
return self._settings["compute_id"]
@property
def node_type(self):
return self._settings["node_type"]
def appliance_type(self):
return self._settings["appliance_type"]
@property
def builtin(self):
return self._builtin
def update(self, **kwargs):
self._settings.update(kwargs)
def __json__(self):
"""
Appliance settings.
"""
settings = self._settings
settings.update({"appliance_id": self._id,
"default_name_format": settings.get("default_name_format", "{name}-{0}"),

View File

@ -465,14 +465,14 @@ class Project:
Create a node from an appliance
"""
try:
template = self.controller.appliances[appliance_id].settings
template = copy.deepcopy(self.controller.appliances[appliance_id].settings)
except KeyError:
msg = "Appliance {} doesn't exist".format(appliance_id)
log.error(msg)
raise aiohttp.web.HTTPNotFound(text=msg)
template["x"] = x
template["y"] = y
node_type = template.pop("node_type")
node_type = template.pop("appliance_type")
compute = self.controller.get_compute(template.pop("compute_id", compute_id))
name = template.pop("name")
default_name_format = template.pop("default_name_format", "{name}-{0}")

View File

@ -20,6 +20,9 @@ from gns3server.controller import Controller
from gns3server.schemas.node import NODE_OBJECT_SCHEMA
from gns3server.schemas.appliance import APPLIANCE_USAGE_SCHEMA
import hashlib
import json
from gns3server.schemas.appliance import (
APPLIANCE_OBJECT_SCHEMA,
APPLIANCE_UPDATE_SCHEMA,
@ -74,10 +77,17 @@ class ApplianceHandler:
output=APPLIANCE_OBJECT_SCHEMA)
def get(request, response):
request_etag = request.headers.get("If-None-Match", "")
controller = Controller.instance()
appliance = controller.get_appliance(request.match_info["appliance_id"])
response.set_status(200)
response.json(appliance)
data = json.dumps(appliance.__json__())
appliance_etag = '"' + hashlib.md5(data.encode()).hexdigest() + '"'
if appliance_etag == request_etag:
response.set_status(304)
else:
response.headers["ETag"] = appliance_etag
response.set_status(200)
response.json(appliance)
@Route.put(
r"/appliances/{appliance_id}",
@ -93,8 +103,7 @@ class ApplianceHandler:
controller = Controller.instance()
appliance = controller.get_appliance(request.match_info["appliance_id"])
#TODO: update appliance!
#appliance.settings = request.json
appliance.update(**request.json)
response.set_status(200)
response.json(appliance)
@ -123,8 +132,6 @@ class ApplianceHandler:
})
def list(request, response):
#old_etag = request.headers.get('If-None-Match', '')
#print("ETAG => ", old_etag)
controller = Controller.instance()
response.json([c for c in controller.appliances.values()])

View File

@ -18,6 +18,7 @@
import copy
from .node import NODE_TYPE_SCHEMA
APPLIANCE_TYPE_SCHEMA = NODE_TYPE_SCHEMA
APPLIANCE_OBJECT_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",
@ -35,7 +36,7 @@ APPLIANCE_OBJECT_SCHEMA = {
"description": "Compute identifier",
"type": "string"
},
"node_type": NODE_TYPE_SCHEMA,
"appliance_type": APPLIANCE_TYPE_SCHEMA,
"name": {
"description": "Appliance name",
"type": "string",
@ -53,7 +54,7 @@ APPLIANCE_OBJECT_SCHEMA = {
},
},
"additionalProperties": True, #TODO: validate all properties
"required": ["appliance_id", "compute_id", "node_type", "name", "default_name_format", "symbol"]
"required": ["appliance_id", "compute_id", "appliance_type", "name", "default_name_format", "symbol"]
}
APPLIANCE_CREATE_SCHEMA = copy.deepcopy(APPLIANCE_OBJECT_SCHEMA)
@ -62,7 +63,9 @@ APPLIANCE_CREATE_SCHEMA["required"].remove("appliance_id")
APPLIANCE_CREATE_SCHEMA["required"].remove("compute_id")
APPLIANCE_CREATE_SCHEMA["required"].remove("default_name_format")
APPLIANCE_CREATE_SCHEMA["required"].remove("symbol")
APPLIANCE_UPDATE_SCHEMA = copy.deepcopy(APPLIANCE_OBJECT_SCHEMA)
APPLIANCE_UPDATE_SCHEMA = copy.deepcopy(APPLIANCE_CREATE_SCHEMA)
#APPLIANCE_UPDATE_SCHEMA["additionalProperties"] = False
del APPLIANCE_UPDATE_SCHEMA["required"]
APPLIANCE_USAGE_SCHEMA = {
"$schema": "http://json-schema.org/draft-04/schema#",