mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 19:28:07 +00:00
Refactor template management to use database.
This commit is contained in:
parent
b417bc4dec
commit
d730c591b3
@ -21,18 +21,25 @@ API routes for templates.
|
|||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
import json
|
import json
|
||||||
|
import pydantic
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
from fastapi import APIRouter, Request, Response, HTTPException, status
|
from fastapi import APIRouter, Request, Response, HTTPException, Depends, status
|
||||||
from fastapi.encoders import jsonable_encoder
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from gns3server import schemas
|
from gns3server import schemas
|
||||||
from gns3server.controller import Controller
|
from gns3server.controller import Controller
|
||||||
|
from gns3server.db.repositories.templates import TemplatesRepository
|
||||||
|
from gns3server.controller.controller_error import (
|
||||||
|
ControllerBadRequestError,
|
||||||
|
ControllerNotFoundError,
|
||||||
|
ControllerForbiddenError
|
||||||
|
)
|
||||||
|
|
||||||
|
from .dependencies.database import get_repository
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
@ -41,107 +48,141 @@ responses = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@router.post("/templates",
|
@router.post("/templates", response_model=schemas.Template, status_code=status.HTTP_201_CREATED)
|
||||||
status_code=status.HTTP_201_CREATED,
|
async def create_template(
|
||||||
response_model=schemas.Template)
|
new_template: schemas.TemplateCreate,
|
||||||
def create_template(template_data: schemas.TemplateCreate):
|
template_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Create a new template.
|
Create a new template.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
controller = Controller.instance()
|
try:
|
||||||
template = controller.template_manager.add_template(jsonable_encoder(template_data, exclude_unset=True))
|
return await template_repo.create_template(new_template)
|
||||||
# Reset the symbol list
|
except pydantic.ValidationError as e:
|
||||||
controller.symbols.list()
|
raise ControllerBadRequestError(f"JSON schema error received while creating new template: {e}")
|
||||||
return template.__json__()
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/templates/{template_id}",
|
@router.get("/templates/{template_id}",
|
||||||
response_model=schemas.Template,
|
response_model=schemas.Template,
|
||||||
response_model_exclude_unset=True,
|
response_model_exclude_unset=True,
|
||||||
responses=responses)
|
responses=responses)
|
||||||
def get_template(template_id: UUID, request: Request, response: Response):
|
async def get_template(
|
||||||
|
template_id: UUID,
|
||||||
|
request: Request,
|
||||||
|
response: Response,
|
||||||
|
template_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Return a template.
|
Return a template.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
request_etag = request.headers.get("If-None-Match", "")
|
request_etag = request.headers.get("If-None-Match", "")
|
||||||
controller = Controller.instance()
|
template = await template_repo.get_template(template_id)
|
||||||
template = controller.template_manager.get_template(str(template_id))
|
if not template:
|
||||||
data = json.dumps(template.__json__())
|
raise ControllerNotFoundError(f"Template '{template_id}' not found")
|
||||||
|
data = json.dumps(template)
|
||||||
template_etag = '"' + hashlib.md5(data.encode()).hexdigest() + '"'
|
template_etag = '"' + hashlib.md5(data.encode()).hexdigest() + '"'
|
||||||
if template_etag == request_etag:
|
if template_etag == request_etag:
|
||||||
raise HTTPException(status_code=status.HTTP_304_NOT_MODIFIED)
|
raise HTTPException(status_code=status.HTTP_304_NOT_MODIFIED)
|
||||||
else:
|
else:
|
||||||
response.headers["ETag"] = template_etag
|
response.headers["ETag"] = template_etag
|
||||||
return template.__json__()
|
return template
|
||||||
|
|
||||||
|
|
||||||
@router.put("/templates/{template_id}",
|
@router.put("/templates/{template_id}",
|
||||||
response_model=schemas.Template,
|
response_model=schemas.Template,
|
||||||
response_model_exclude_unset=True,
|
response_model_exclude_unset=True,
|
||||||
responses=responses)
|
responses=responses)
|
||||||
def update_template(template_id: UUID, template_data: schemas.TemplateUpdate):
|
async def update_template(
|
||||||
|
template_id: UUID,
|
||||||
|
template_data: schemas.TemplateUpdate,
|
||||||
|
template_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Update a template.
|
Update a template.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
controller = Controller.instance()
|
if template_repo.get_builtin_template(template_id):
|
||||||
template = controller.template_manager.get_template(str(template_id))
|
raise ControllerForbiddenError(f"Template '{template_id}' cannot be updated because it is built-in")
|
||||||
template.update(**jsonable_encoder(template_data, exclude_unset=True))
|
template = await template_repo.update_template(template_id, template_data)
|
||||||
return template.__json__()
|
if not template:
|
||||||
|
raise ControllerNotFoundError(f"Template '{template_id}' not found")
|
||||||
|
return template
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/templates/{template_id}",
|
@router.delete("/templates/{template_id}",
|
||||||
status_code=status.HTTP_204_NO_CONTENT,
|
status_code=status.HTTP_204_NO_CONTENT,
|
||||||
responses=responses)
|
responses=responses)
|
||||||
def delete_template(template_id: UUID):
|
async def delete_template(
|
||||||
|
template_id: UUID,
|
||||||
|
template_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||||
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Delete a template.
|
Delete a template.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
controller = Controller.instance()
|
if template_repo.get_builtin_template(template_id):
|
||||||
controller.template_manager.delete_template(str(template_id))
|
raise ControllerForbiddenError(f"Template '{template_id}' cannot be deleted because it is built-in")
|
||||||
|
success = await template_repo.delete_template(template_id)
|
||||||
|
if not success:
|
||||||
|
raise ControllerNotFoundError(f"Template '{template_id}' not found")
|
||||||
|
|
||||||
|
|
||||||
@router.get("/templates",
|
@router.get("/templates",
|
||||||
response_model=List[schemas.Template],
|
response_model=List[schemas.Template],
|
||||||
response_model_exclude_unset=True)
|
response_model_exclude_unset=True)
|
||||||
def get_templates():
|
async def get_templates(
|
||||||
|
template_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||||
|
) -> List[dict]:
|
||||||
"""
|
"""
|
||||||
Return all templates.
|
Return all templates.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
controller = Controller.instance()
|
templates = await template_repo.get_templates()
|
||||||
return [c.__json__() for c in controller.template_manager.templates.values()]
|
return templates
|
||||||
|
|
||||||
|
|
||||||
@router.post("/templates/{template_id}/duplicate",
|
@router.post("/templates/{template_id}/duplicate",
|
||||||
response_model=schemas.Template,
|
response_model=schemas.Template,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses=responses)
|
responses=responses)
|
||||||
async def duplicate_template(template_id: UUID):
|
async def duplicate_template(
|
||||||
|
template_id: UUID,
|
||||||
|
template_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||||
|
) -> dict:
|
||||||
"""
|
"""
|
||||||
Duplicate a template.
|
Duplicate a template.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
controller = Controller.instance()
|
if template_repo.get_builtin_template(template_id):
|
||||||
template = controller.template_manager.duplicate_template(str(template_id))
|
raise ControllerForbiddenError(f"Template '{template_id}' cannot be duplicated because it is built-in")
|
||||||
return template.__json__()
|
template = await template_repo.duplicate_template(template_id)
|
||||||
|
if not template:
|
||||||
|
raise ControllerNotFoundError(f"Template '{template_id}' not found")
|
||||||
|
return template
|
||||||
|
|
||||||
|
|
||||||
@router.post("/projects/{project_id}/templates/{template_id}",
|
@router.post("/projects/{project_id}/templates/{template_id}",
|
||||||
response_model=schemas.Node,
|
response_model=schemas.Node,
|
||||||
status_code=status.HTTP_201_CREATED,
|
status_code=status.HTTP_201_CREATED,
|
||||||
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or template"}})
|
responses={404: {"model": schemas.ErrorMessage, "description": "Could not find project or template"}})
|
||||||
async def create_node_from_template(project_id: UUID, template_id: UUID, template_usage: schemas.TemplateUsage):
|
async def create_node_from_template(
|
||||||
|
project_id: UUID,
|
||||||
|
template_id: UUID,
|
||||||
|
template_usage: schemas.TemplateUsage,
|
||||||
|
template_repo: TemplatesRepository = Depends(get_repository(TemplatesRepository))
|
||||||
|
) -> schemas.Node:
|
||||||
"""
|
"""
|
||||||
Create a new node from a template.
|
Create a new node from a template.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
template = await template_repo.get_template(template_id)
|
||||||
|
if not template:
|
||||||
|
raise ControllerNotFoundError(f"Template '{template_id}' not found")
|
||||||
controller = Controller.instance()
|
controller = Controller.instance()
|
||||||
project = controller.get_project(str(project_id))
|
project = controller.get_project(str(project_id))
|
||||||
node = await project.add_node_from_template(str(template_id),
|
node = await project.add_node_from_template(template,
|
||||||
x=template_usage.x,
|
x=template_usage.x,
|
||||||
y=template_usage.y,
|
y=template_usage.y,
|
||||||
compute_id=template_usage.compute_id)
|
compute_id=template_usage.compute_id)
|
||||||
|
@ -25,10 +25,8 @@ import asyncio
|
|||||||
|
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
from .project import Project
|
from .project import Project
|
||||||
from .template import Template
|
|
||||||
from .appliance import Appliance
|
from .appliance import Appliance
|
||||||
from .appliance_manager import ApplianceManager
|
from .appliance_manager import ApplianceManager
|
||||||
from .template_manager import TemplateManager
|
|
||||||
from .compute import Compute, ComputeError
|
from .compute import Compute, ComputeError
|
||||||
from .notification import Notification
|
from .notification import Notification
|
||||||
from .symbols import Symbols
|
from .symbols import Symbols
|
||||||
@ -55,7 +53,6 @@ class Controller:
|
|||||||
self.gns3vm = GNS3VM(self)
|
self.gns3vm = GNS3VM(self)
|
||||||
self.symbols = Symbols()
|
self.symbols = Symbols()
|
||||||
self._appliance_manager = ApplianceManager()
|
self._appliance_manager = ApplianceManager()
|
||||||
self._template_manager = TemplateManager()
|
|
||||||
self._iou_license_settings = {"iourc_content": "",
|
self._iou_license_settings = {"iourc_content": "",
|
||||||
"license_check": True}
|
"license_check": True}
|
||||||
self._config_loaded = False
|
self._config_loaded = False
|
||||||
@ -208,10 +205,6 @@ class Controller:
|
|||||||
"appliances_etag": self._appliance_manager.appliances_etag,
|
"appliances_etag": self._appliance_manager.appliances_etag,
|
||||||
"version": __version__}
|
"version": __version__}
|
||||||
|
|
||||||
for template in self._template_manager.templates.values():
|
|
||||||
if not template.builtin:
|
|
||||||
controller_settings["templates"].append(template.__json__())
|
|
||||||
|
|
||||||
for compute in self._computes.values():
|
for compute in self._computes.values():
|
||||||
if compute.id != "local" and compute.id != "vm":
|
if compute.id != "local" and compute.id != "vm":
|
||||||
controller_settings["computes"].append({"host": compute.host,
|
controller_settings["computes"].append({"host": compute.host,
|
||||||
@ -259,7 +252,6 @@ class Controller:
|
|||||||
|
|
||||||
self._appliance_manager.appliances_etag = controller_settings.get("appliances_etag")
|
self._appliance_manager.appliances_etag = controller_settings.get("appliances_etag")
|
||||||
self._appliance_manager.load_appliances()
|
self._appliance_manager.load_appliances()
|
||||||
self._template_manager.load_templates(controller_settings.get("templates"))
|
|
||||||
self._config_loaded = True
|
self._config_loaded = True
|
||||||
return controller_settings.get("computes", [])
|
return controller_settings.get("computes", [])
|
||||||
|
|
||||||
@ -546,14 +538,6 @@ class Controller:
|
|||||||
|
|
||||||
return self._appliance_manager
|
return self._appliance_manager
|
||||||
|
|
||||||
@property
|
|
||||||
def template_manager(self):
|
|
||||||
"""
|
|
||||||
:returns: Template Manager instance
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._template_manager
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def iou_license(self):
|
def iou_license(self):
|
||||||
"""
|
"""
|
||||||
|
@ -612,7 +612,6 @@ class Node:
|
|||||||
if the image exists
|
if the image exists
|
||||||
"""
|
"""
|
||||||
|
|
||||||
print("UPLOAD MISSING IMAGE")
|
|
||||||
for directory in images_directories(type):
|
for directory in images_directories(type):
|
||||||
image = os.path.join(directory, img)
|
image = os.path.join(directory, img)
|
||||||
if os.path.exists(image):
|
if os.path.exists(image):
|
||||||
|
@ -500,16 +500,11 @@ class Project:
|
|||||||
return new_name
|
return new_name
|
||||||
|
|
||||||
@open_required
|
@open_required
|
||||||
async def add_node_from_template(self, template_id, x=0, y=0, name=None, compute_id=None):
|
async def add_node_from_template(self, template, x=0, y=0, name=None, compute_id=None):
|
||||||
"""
|
"""
|
||||||
Create a node from a template.
|
Create a node from a template.
|
||||||
"""
|
"""
|
||||||
try:
|
|
||||||
template = copy.deepcopy(self.controller.template_manager.templates[template_id].settings)
|
|
||||||
except KeyError:
|
|
||||||
msg = "Template {} doesn't exist".format(template_id)
|
|
||||||
log.error(msg)
|
|
||||||
raise ControllerNotFoundError(msg)
|
|
||||||
template["x"] = x
|
template["x"] = x
|
||||||
template["y"] = y
|
template["y"] = y
|
||||||
node_type = template.pop("template_type")
|
node_type = template.pop("template_type")
|
||||||
|
@ -1,168 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright (C) 2020 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
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from pydantic import ValidationError
|
|
||||||
from fastapi.encoders import jsonable_encoder
|
|
||||||
from gns3server import schemas
|
|
||||||
|
|
||||||
import logging
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
ID_TO_CATEGORY = {
|
|
||||||
3: "firewall",
|
|
||||||
2: "guest",
|
|
||||||
1: "switch",
|
|
||||||
0: "router"
|
|
||||||
}
|
|
||||||
|
|
||||||
TEMPLATE_TYPE_TO_SHEMA = {
|
|
||||||
"cloud": schemas.CloudTemplate,
|
|
||||||
"ethernet_hub": schemas.EthernetHubTemplate,
|
|
||||||
"ethernet_switch": schemas.EthernetSwitchTemplate,
|
|
||||||
"docker": schemas.DockerTemplate,
|
|
||||||
"dynamips": schemas.DynamipsTemplate,
|
|
||||||
"vpcs": schemas.VPCSTemplate,
|
|
||||||
"virtualbox": schemas.VirtualBoxTemplate,
|
|
||||||
"vmware": schemas.VMwareTemplate,
|
|
||||||
"iou": schemas.IOUTemplate,
|
|
||||||
"qemu": schemas.QemuTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
DYNAMIPS_PLATFORM_TO_SHEMA = {
|
|
||||||
"c7200": schemas.C7200DynamipsTemplate,
|
|
||||||
"c3745": schemas.C3745DynamipsTemplate,
|
|
||||||
"c3725": schemas.C3725DynamipsTemplate,
|
|
||||||
"c3600": schemas.C3600DynamipsTemplate,
|
|
||||||
"c2691": schemas.C2691DynamipsTemplate,
|
|
||||||
"c2600": schemas.C2600DynamipsTemplate,
|
|
||||||
"c1700": schemas.C1700DynamipsTemplate
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class Template:
|
|
||||||
|
|
||||||
def __init__(self, template_id, settings, builtin=False):
|
|
||||||
|
|
||||||
if template_id is None:
|
|
||||||
self._id = str(uuid.uuid4())
|
|
||||||
elif isinstance(template_id, uuid.UUID):
|
|
||||||
self._id = str(template_id)
|
|
||||||
else:
|
|
||||||
self._id = template_id
|
|
||||||
|
|
||||||
self._settings = copy.deepcopy(settings)
|
|
||||||
|
|
||||||
# Version of the gui before 2.1 use linked_base
|
|
||||||
# and the server linked_clone
|
|
||||||
if "linked_base" in self.settings:
|
|
||||||
linked_base = self._settings.pop("linked_base")
|
|
||||||
if "linked_clone" not in self._settings:
|
|
||||||
self._settings["linked_clone"] = linked_base
|
|
||||||
|
|
||||||
# Convert old GUI category to text category
|
|
||||||
try:
|
|
||||||
self._settings["category"] = ID_TO_CATEGORY[self._settings["category"]]
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# The "server" setting has been replaced by "compute_id" setting in version 2.2
|
|
||||||
if "server" in self._settings:
|
|
||||||
self._settings["compute_id"] = self._settings.pop("server")
|
|
||||||
|
|
||||||
# The "node_type" setting has been replaced by "template_type" setting in version 2.2
|
|
||||||
if "node_type" in self._settings:
|
|
||||||
self._settings["template_type"] = self._settings.pop("node_type")
|
|
||||||
|
|
||||||
# Remove an old IOU setting
|
|
||||||
if self._settings["template_type"] == "iou" and "image" in self._settings:
|
|
||||||
del self._settings["image"]
|
|
||||||
|
|
||||||
self._builtin = builtin
|
|
||||||
|
|
||||||
if builtin is False:
|
|
||||||
try:
|
|
||||||
template_schema = TEMPLATE_TYPE_TO_SHEMA[self.template_type]
|
|
||||||
template_settings_with_defaults = template_schema.parse_obj(self.__json__())
|
|
||||||
self._settings = jsonable_encoder(template_settings_with_defaults.dict())
|
|
||||||
if self.template_type == "dynamips":
|
|
||||||
# special case for Dynamips to cover all platform types that contain specific settings
|
|
||||||
dynamips_template_schema = DYNAMIPS_PLATFORM_TO_SHEMA[self._settings["platform"]]
|
|
||||||
dynamips_template_settings_with_defaults = dynamips_template_schema.parse_obj(self.__json__())
|
|
||||||
self._settings = jsonable_encoder(dynamips_template_settings_with_defaults.dict())
|
|
||||||
except ValidationError as e:
|
|
||||||
print(e) #TODO: handle errors
|
|
||||||
raise
|
|
||||||
|
|
||||||
log.debug('Template "{name}" [{id}] loaded'.format(name=self.name, id=self._id))
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def settings(self):
|
|
||||||
return self._settings
|
|
||||||
|
|
||||||
@settings.setter
|
|
||||||
def settings(self, settings):
|
|
||||||
self._settings.update(settings)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._settings["name"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def compute_id(self):
|
|
||||||
return self._settings["compute_id"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def template_type(self):
|
|
||||||
return self._settings["template_type"]
|
|
||||||
|
|
||||||
@property
|
|
||||||
def builtin(self):
|
|
||||||
return self._builtin
|
|
||||||
|
|
||||||
def update(self, **kwargs):
|
|
||||||
|
|
||||||
from gns3server.controller import Controller
|
|
||||||
controller = Controller.instance()
|
|
||||||
Controller.instance().check_can_write_config()
|
|
||||||
self._settings.update(kwargs)
|
|
||||||
controller.notification.controller_emit("template.updated", self.__json__())
|
|
||||||
controller.save()
|
|
||||||
|
|
||||||
def __json__(self):
|
|
||||||
"""
|
|
||||||
Template settings.
|
|
||||||
"""
|
|
||||||
|
|
||||||
settings = self._settings
|
|
||||||
settings.update({"template_id": self._id,
|
|
||||||
"builtin": self.builtin})
|
|
||||||
|
|
||||||
if self.builtin:
|
|
||||||
# builin templates have compute_id set to None to tell clients
|
|
||||||
# to select a compute
|
|
||||||
settings["compute_id"] = None
|
|
||||||
else:
|
|
||||||
settings["compute_id"] = self.compute_id
|
|
||||||
|
|
||||||
return settings
|
|
@ -1,148 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# Copyright (C) 2019 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
|
|
||||||
import uuid
|
|
||||||
import pydantic
|
|
||||||
|
|
||||||
from .controller_error import ControllerError, ControllerNotFoundError
|
|
||||||
from .template import Template
|
|
||||||
|
|
||||||
import logging
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TemplateManager:
|
|
||||||
"""
|
|
||||||
Manages templates.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
|
|
||||||
self._templates = {}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def templates(self):
|
|
||||||
"""
|
|
||||||
:returns: The dictionary of templates managed by GNS3
|
|
||||||
"""
|
|
||||||
|
|
||||||
return self._templates
|
|
||||||
|
|
||||||
def load_templates(self, template_settings=None):
|
|
||||||
"""
|
|
||||||
Loads templates from controller settings.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if template_settings:
|
|
||||||
for template_settings in template_settings:
|
|
||||||
try:
|
|
||||||
template = Template(template_settings.get("template_id"), template_settings)
|
|
||||||
self._templates[template.id] = template
|
|
||||||
except pydantic.ValidationError as e:
|
|
||||||
message = "Cannot load template with JSON data '{}': {}".format(template_settings, e)
|
|
||||||
log.warning(message)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Add builtins
|
|
||||||
builtins = []
|
|
||||||
builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "cloud"), {"template_type": "cloud", "name": "Cloud", "default_name_format": "Cloud{0}", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
|
|
||||||
builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "nat"), {"template_type": "nat", "name": "NAT", "default_name_format": "NAT{0}", "category": 2, "symbol": ":/symbols/cloud.svg"}, builtin=True))
|
|
||||||
builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "vpcs"), {"template_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(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_switch"), {"template_type": "ethernet_switch", "console_type": "none", "name": "Ethernet switch", "default_name_format": "Switch{0}", "category": 1, "symbol": ":/symbols/ethernet_switch.svg"}, builtin=True))
|
|
||||||
builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"), {"template_type": "ethernet_hub", "name": "Ethernet hub", "default_name_format": "Hub{0}", "category": 1, "symbol": ":/symbols/hub.svg"}, builtin=True))
|
|
||||||
builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"), {"template_type": "frame_relay_switch", "name": "Frame Relay switch", "default_name_format": "FRSW{0}", "category": 1, "symbol": ":/symbols/frame_relay_switch.svg"}, builtin=True))
|
|
||||||
builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"), {"template_type": "atm_switch", "name": "ATM switch", "default_name_format": "ATMSW{0}", "category": 1, "symbol": ":/symbols/atm_switch.svg"}, builtin=True))
|
|
||||||
|
|
||||||
#FIXME: disable TraceNG
|
|
||||||
#if sys.platform.startswith("win"):
|
|
||||||
# builtins.append(Template(uuid.uuid3(uuid.NAMESPACE_DNS, "traceng"), {"template_type": "traceng", "name": "TraceNG", "default_name_format": "TraceNG-{0}", "category": 2, "symbol": ":/symbols/traceng.svg", "properties": {}}, builtin=True))
|
|
||||||
for b in builtins:
|
|
||||||
self._templates[b.id] = b
|
|
||||||
|
|
||||||
def add_template(self, settings):
|
|
||||||
"""
|
|
||||||
Adds a new template.
|
|
||||||
|
|
||||||
:param settings: template settings
|
|
||||||
|
|
||||||
:returns: Template object
|
|
||||||
"""
|
|
||||||
|
|
||||||
template_id = settings.get("template_id", "")
|
|
||||||
if template_id in self._templates:
|
|
||||||
raise ControllerError("Template ID '{}' already exists".format(template_id))
|
|
||||||
else:
|
|
||||||
template_id = settings.setdefault("template_id", str(uuid.uuid4()))
|
|
||||||
try:
|
|
||||||
template = Template(template_id, settings)
|
|
||||||
except pydantic.ValidationError as e:
|
|
||||||
message = "JSON schema error adding template with JSON data '{}': {}".format(settings, e)
|
|
||||||
raise ControllerError(message)
|
|
||||||
|
|
||||||
from . import Controller
|
|
||||||
Controller.instance().check_can_write_config()
|
|
||||||
self._templates[template.id] = template
|
|
||||||
Controller.instance().save()
|
|
||||||
Controller.instance().notification.controller_emit("template.created", template.__json__())
|
|
||||||
return template
|
|
||||||
|
|
||||||
def get_template(self, template_id):
|
|
||||||
"""
|
|
||||||
Gets a template.
|
|
||||||
|
|
||||||
:param template_id: template identifier
|
|
||||||
|
|
||||||
:returns: Template object
|
|
||||||
"""
|
|
||||||
|
|
||||||
template = self._templates.get(template_id)
|
|
||||||
if not template:
|
|
||||||
raise ControllerNotFoundError("Template ID {} doesn't exist".format(template_id))
|
|
||||||
return template
|
|
||||||
|
|
||||||
def delete_template(self, template_id):
|
|
||||||
"""
|
|
||||||
Deletes a template.
|
|
||||||
|
|
||||||
:param template_id: template identifier
|
|
||||||
"""
|
|
||||||
|
|
||||||
template = self.get_template(template_id)
|
|
||||||
if template.builtin:
|
|
||||||
raise ControllerError("Template ID {} cannot be deleted because it is a builtin".format(template_id))
|
|
||||||
from . import Controller
|
|
||||||
Controller.instance().check_can_write_config()
|
|
||||||
self._templates.pop(template_id)
|
|
||||||
Controller.instance().save()
|
|
||||||
Controller.instance().notification.controller_emit("template.deleted", template.__json__())
|
|
||||||
|
|
||||||
def duplicate_template(self, template_id):
|
|
||||||
"""
|
|
||||||
Duplicates a template.
|
|
||||||
|
|
||||||
:param template_id: template identifier
|
|
||||||
"""
|
|
||||||
|
|
||||||
template = self.get_template(template_id)
|
|
||||||
if template.builtin:
|
|
||||||
raise ControllerError("Template ID {} cannot be duplicated because it is a builtin".format(template_id))
|
|
||||||
template_settings = copy.deepcopy(template.settings)
|
|
||||||
del template_settings["template_id"]
|
|
||||||
return self.add_template(template_settings)
|
|
||||||
|
|
||||||
|
|
32
gns3server/db/models/__init__.py
Normal file
32
gns3server/db/models/__init__.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 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/>.
|
||||||
|
|
||||||
|
from .base import Base
|
||||||
|
from .users import User
|
||||||
|
from .templates import (
|
||||||
|
Template,
|
||||||
|
CloudTemplate,
|
||||||
|
DockerTemplate,
|
||||||
|
DynamipsTemplate,
|
||||||
|
EthernetHubTemplate,
|
||||||
|
EthernetSwitchTemplate,
|
||||||
|
IOUTemplate,
|
||||||
|
QemuTemplate,
|
||||||
|
VirtualBoxTemplate,
|
||||||
|
VMwareTemplate,
|
||||||
|
VPCSTemplate
|
||||||
|
)
|
@ -17,21 +17,33 @@
|
|||||||
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, func
|
from fastapi.encoders import jsonable_encoder
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy import Column, DateTime, func, inspect
|
||||||
from sqlalchemy.types import TypeDecorator, CHAR
|
from sqlalchemy.types import TypeDecorator, CHAR
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
from sqlalchemy.ext.declarative import as_declarative
|
||||||
|
|
||||||
from sqlalchemy.orm import declarative_base
|
|
||||||
|
|
||||||
Base = declarative_base()
|
@as_declarative()
|
||||||
|
class Base:
|
||||||
|
|
||||||
|
def _asdict(self):
|
||||||
|
|
||||||
|
return {c.key: getattr(self, c.key)
|
||||||
|
for c in inspect(self).mapper.column_attrs}
|
||||||
|
|
||||||
|
def _asjson(self):
|
||||||
|
|
||||||
|
return jsonable_encoder(self._asdict())
|
||||||
|
|
||||||
|
|
||||||
class GUID(TypeDecorator):
|
class GUID(TypeDecorator):
|
||||||
"""Platform-independent GUID type.
|
"""
|
||||||
|
Platform-independent GUID type.
|
||||||
Uses PostgreSQL's UUID type, otherwise uses
|
Uses PostgreSQL's UUID type, otherwise uses
|
||||||
CHAR(32), storing as stringified hex values.
|
CHAR(32), storing as stringified hex values.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
impl = CHAR
|
impl = CHAR
|
||||||
|
|
||||||
def load_dialect_impl(self, dialect):
|
def load_dialect_impl(self, dialect):
|
||||||
@ -73,30 +85,3 @@ class BaseTable(Base):
|
|||||||
|
|
||||||
def generate_uuid():
|
def generate_uuid():
|
||||||
return str(uuid.uuid4())
|
return str(uuid.uuid4())
|
||||||
|
|
||||||
|
|
||||||
class User(BaseTable):
|
|
||||||
|
|
||||||
__tablename__ = "users"
|
|
||||||
|
|
||||||
user_id = Column(GUID, primary_key=True, default=generate_uuid)
|
|
||||||
username = Column(String, unique=True, index=True)
|
|
||||||
email = Column(String, unique=True, index=True)
|
|
||||||
full_name = Column(String)
|
|
||||||
hashed_password = Column(String)
|
|
||||||
is_active = Column(Boolean, default=True)
|
|
||||||
is_superuser = Column(Boolean, default=False)
|
|
||||||
|
|
||||||
|
|
||||||
# items = relationship("Item", back_populates="owner")
|
|
||||||
#
|
|
||||||
#
|
|
||||||
# class Item(Base):
|
|
||||||
# __tablename__ = "items"
|
|
||||||
#
|
|
||||||
# id = Column(Integer, primary_key=True, index=True)
|
|
||||||
# title = Column(String, index=True)
|
|
||||||
# description = Column(String, index=True)
|
|
||||||
# owner_id = Column(Integer, ForeignKey("users.id"))
|
|
||||||
#
|
|
||||||
# owner = relationship("User", back_populates="items")
|
|
286
gns3server/db/models/templates.py
Normal file
286
gns3server/db/models/templates.py
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 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/>.
|
||||||
|
|
||||||
|
|
||||||
|
from sqlalchemy import Boolean, Column, String, Integer, ForeignKey, PickleType
|
||||||
|
|
||||||
|
from .base import BaseTable, generate_uuid, GUID
|
||||||
|
|
||||||
|
|
||||||
|
class Template(BaseTable):
|
||||||
|
|
||||||
|
__tablename__ = "templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||||
|
name = Column(String, index=True)
|
||||||
|
category = Column(String)
|
||||||
|
default_name_format = Column(String)
|
||||||
|
symbol = Column(String)
|
||||||
|
builtin = Column(Boolean, default=False)
|
||||||
|
compute_id = Column(String)
|
||||||
|
usage = Column(String)
|
||||||
|
template_type = Column(String)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "templates",
|
||||||
|
"polymorphic_on": template_type
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class CloudTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "cloud_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
ports_mapping = Column(PickleType)
|
||||||
|
remote_console_host = Column(String)
|
||||||
|
remote_console_port = Column(Integer)
|
||||||
|
remote_console_type = Column(String)
|
||||||
|
remote_console_http_path = Column(String)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "cloud"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DockerTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "docker_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
image = Column(String)
|
||||||
|
adapters = Column(Integer)
|
||||||
|
start_command = Column(String)
|
||||||
|
environment = Column(String)
|
||||||
|
console_type = Column(String)
|
||||||
|
aux_type = Column(String)
|
||||||
|
console_auto_start = Column(Boolean)
|
||||||
|
console_http_port = Column(Integer)
|
||||||
|
console_http_path = Column(String)
|
||||||
|
console_resolution = Column(String)
|
||||||
|
extra_hosts = Column(String)
|
||||||
|
extra_volumes = Column(PickleType)
|
||||||
|
memory = Column(Integer)
|
||||||
|
cpus = Column(Integer)
|
||||||
|
custom_adapters = Column(PickleType)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "docker"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class DynamipsTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "dynamips_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
platform = Column(String)
|
||||||
|
chassis = Column(String)
|
||||||
|
image = Column(String)
|
||||||
|
exec_area = Column(Integer)
|
||||||
|
mmap = Column(Boolean)
|
||||||
|
mac_addr = Column(String)
|
||||||
|
system_id = Column(String)
|
||||||
|
startup_config = Column(String)
|
||||||
|
private_config = Column(String)
|
||||||
|
idlepc = Column(String)
|
||||||
|
idlemax = Column(Integer)
|
||||||
|
idlesleep = Column(Integer)
|
||||||
|
disk0 = Column(Integer)
|
||||||
|
disk1 = Column(Integer)
|
||||||
|
auto_delete_disks = Column(Boolean)
|
||||||
|
console_type = Column(String)
|
||||||
|
console_auto_start = Column(Boolean)
|
||||||
|
aux_type = Column(String)
|
||||||
|
ram = Column(Integer)
|
||||||
|
nvram = Column(Integer)
|
||||||
|
npe = Column(String)
|
||||||
|
midplane = Column(String)
|
||||||
|
sparsemem = Column(Boolean)
|
||||||
|
iomem = Column(Integer)
|
||||||
|
slot0 = Column(String)
|
||||||
|
slot1 = Column(String)
|
||||||
|
slot2 = Column(String)
|
||||||
|
slot3 = Column(String)
|
||||||
|
slot4 = Column(String)
|
||||||
|
slot5 = Column(String)
|
||||||
|
slot6 = Column(String)
|
||||||
|
wic0 = Column(String)
|
||||||
|
wic1 = Column(String)
|
||||||
|
wic2 = Column(String)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "dynamips"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EthernetHubTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "ethernet_hub_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
ports_mapping = Column(PickleType)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "ethernet_hub"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class EthernetSwitchTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "ethernet_switch_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
ports_mapping = Column(PickleType)
|
||||||
|
console_type = Column(String)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "ethernet_switch"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class IOUTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "iou_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
path = Column(String)
|
||||||
|
ethernet_adapters = Column(Integer)
|
||||||
|
serial_adapters = Column(Integer)
|
||||||
|
ram = Column(Integer)
|
||||||
|
nvram = Column(Integer)
|
||||||
|
use_default_iou_values = Column(Boolean)
|
||||||
|
startup_config = Column(String)
|
||||||
|
private_config = Column(String)
|
||||||
|
l1_keepalives = Column(Boolean)
|
||||||
|
console_type = Column(String)
|
||||||
|
console_auto_start = Column(Boolean)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "iou"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class QemuTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "qemu_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
qemu_path = Column(String)
|
||||||
|
platform = Column(String)
|
||||||
|
linked_clone = Column(Boolean)
|
||||||
|
ram = Column(Integer)
|
||||||
|
cpus = Column(Integer)
|
||||||
|
maxcpus = Column(Integer)
|
||||||
|
adapters = Column(Integer)
|
||||||
|
adapter_type = Column(String)
|
||||||
|
mac_address = Column(String)
|
||||||
|
first_port_name = Column(String)
|
||||||
|
port_name_format = Column(String)
|
||||||
|
port_segment_size = Column(Integer)
|
||||||
|
console_type = Column(String)
|
||||||
|
console_auto_start = Column(Boolean)
|
||||||
|
aux_type = Column(String)
|
||||||
|
boot_priority = Column(String)
|
||||||
|
hda_disk_image = Column(String)
|
||||||
|
hda_disk_interface = Column(String)
|
||||||
|
hdb_disk_image = Column(String)
|
||||||
|
hdb_disk_interface = Column(String)
|
||||||
|
hdc_disk_image = Column(String)
|
||||||
|
hdc_disk_interface = Column(String)
|
||||||
|
hdd_disk_image = Column(String)
|
||||||
|
hdd_disk_interface = Column(String)
|
||||||
|
cdrom_image = Column(String)
|
||||||
|
initrd = Column(String)
|
||||||
|
kernel_image = Column(String)
|
||||||
|
bios_image = Column(String)
|
||||||
|
kernel_command_line = Column(String)
|
||||||
|
legacy_networking = Column(Boolean)
|
||||||
|
replicate_network_connection_state = Column(Boolean)
|
||||||
|
create_config_disk = Column(Boolean)
|
||||||
|
on_close = Column(String)
|
||||||
|
cpu_throttling = Column(Integer)
|
||||||
|
process_priority = Column(String)
|
||||||
|
options = Column(String)
|
||||||
|
custom_adapters = Column(PickleType)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "qemu"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VirtualBoxTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "virtualbox_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
vmname = Column(String)
|
||||||
|
ram = Column(Integer)
|
||||||
|
linked_clone = Column(Boolean)
|
||||||
|
adapters = Column(Integer)
|
||||||
|
use_any_adapter = Column(Boolean)
|
||||||
|
adapter_type = Column(String)
|
||||||
|
first_port_name = Column(String)
|
||||||
|
port_name_format = Column(String)
|
||||||
|
port_segment_size = Column(Integer)
|
||||||
|
headless = Column(Boolean)
|
||||||
|
on_close = Column(String)
|
||||||
|
console_type = Column(String)
|
||||||
|
console_auto_start = Column(Boolean)
|
||||||
|
custom_adapters = Column(PickleType)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "virtualbox"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VMwareTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "vmware_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
vmx_path = Column(String)
|
||||||
|
linked_clone = Column(Boolean)
|
||||||
|
first_port_name = Column(String)
|
||||||
|
port_name_format = Column(String)
|
||||||
|
port_segment_size = Column(Integer)
|
||||||
|
adapters = Column(Integer)
|
||||||
|
adapter_type = Column(String)
|
||||||
|
use_any_adapter = Column(Boolean)
|
||||||
|
headless = Column(Boolean)
|
||||||
|
on_close = Column(String)
|
||||||
|
console_type = Column(String)
|
||||||
|
console_auto_start = Column(Boolean)
|
||||||
|
custom_adapters = Column(PickleType)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "vmware"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class VPCSTemplate(Template):
|
||||||
|
|
||||||
|
__tablename__ = "vpcs_templates"
|
||||||
|
|
||||||
|
template_id = Column(GUID, ForeignKey("templates.template_id"), primary_key=True)
|
||||||
|
base_script_file = Column(String)
|
||||||
|
console_type = Column(String)
|
||||||
|
console_auto_start = Column(Boolean, default=False)
|
||||||
|
|
||||||
|
__mapper_args__ = {
|
||||||
|
"polymorphic_identity": "vpcs"
|
||||||
|
}
|
48
gns3server/db/models/users.py
Normal file
48
gns3server/db/models/users.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 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/>.
|
||||||
|
|
||||||
|
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, DateTime, func
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
|
|
||||||
|
from .base import BaseTable, generate_uuid, GUID
|
||||||
|
|
||||||
|
|
||||||
|
class User(BaseTable):
|
||||||
|
|
||||||
|
__tablename__ = "users"
|
||||||
|
|
||||||
|
user_id = Column(GUID, primary_key=True, default=generate_uuid)
|
||||||
|
username = Column(String, unique=True, index=True)
|
||||||
|
email = Column(String, unique=True, index=True)
|
||||||
|
full_name = Column(String)
|
||||||
|
hashed_password = Column(String)
|
||||||
|
is_active = Column(Boolean, default=True)
|
||||||
|
is_superuser = Column(Boolean, default=False)
|
||||||
|
|
||||||
|
|
||||||
|
# items = relationship("Item", back_populates="owner")
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# class Item(Base):
|
||||||
|
# __tablename__ = "items"
|
||||||
|
#
|
||||||
|
# id = Column(Integer, primary_key=True, index=True)
|
||||||
|
# title = Column(String, index=True)
|
||||||
|
# description = Column(String, index=True)
|
||||||
|
# owner_id = Column(Integer, ForeignKey("users.id"))
|
||||||
|
#
|
||||||
|
# owner = relationship("User", back_populates="items")
|
@ -16,6 +16,7 @@
|
|||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from gns3server.controller import Controller
|
||||||
|
|
||||||
|
|
||||||
class BaseRepository:
|
class BaseRepository:
|
||||||
@ -23,3 +24,4 @@ class BaseRepository:
|
|||||||
def __init__(self, db_session: AsyncSession) -> None:
|
def __init__(self, db_session: AsyncSession) -> None:
|
||||||
|
|
||||||
self._db_session = db_session
|
self._db_session = db_session
|
||||||
|
self._controller = Controller.instance()
|
||||||
|
243
gns3server/db/repositories/templates.py
Normal file
243
gns3server/db/repositories/templates.py
Normal file
@ -0,0 +1,243 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Copyright (C) 2021 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 uuid
|
||||||
|
|
||||||
|
from uuid import UUID
|
||||||
|
from typing import List
|
||||||
|
from fastapi.encoders import jsonable_encoder
|
||||||
|
from sqlalchemy import select, update, delete
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
from sqlalchemy.orm.session import make_transient
|
||||||
|
|
||||||
|
from .base import BaseRepository
|
||||||
|
|
||||||
|
import gns3server.db.models as models
|
||||||
|
from gns3server import schemas
|
||||||
|
|
||||||
|
TEMPLATE_TYPE_TO_SHEMA = {
|
||||||
|
"cloud": schemas.CloudTemplate,
|
||||||
|
"ethernet_hub": schemas.EthernetHubTemplate,
|
||||||
|
"ethernet_switch": schemas.EthernetSwitchTemplate,
|
||||||
|
"docker": schemas.DockerTemplate,
|
||||||
|
"dynamips": schemas.DynamipsTemplate,
|
||||||
|
"vpcs": schemas.VPCSTemplate,
|
||||||
|
"virtualbox": schemas.VirtualBoxTemplate,
|
||||||
|
"vmware": schemas.VMwareTemplate,
|
||||||
|
"iou": schemas.IOUTemplate,
|
||||||
|
"qemu": schemas.QemuTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
DYNAMIPS_PLATFORM_TO_SHEMA = {
|
||||||
|
"c7200": schemas.C7200DynamipsTemplate,
|
||||||
|
"c3745": schemas.C3745DynamipsTemplate,
|
||||||
|
"c3725": schemas.C3725DynamipsTemplate,
|
||||||
|
"c3600": schemas.C3600DynamipsTemplate,
|
||||||
|
"c2691": schemas.C2691DynamipsTemplate,
|
||||||
|
"c2600": schemas.C2600DynamipsTemplate,
|
||||||
|
"c1700": schemas.C1700DynamipsTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
TEMPLATE_TYPE_TO_MODEL = {
|
||||||
|
"cloud": models.CloudTemplate,
|
||||||
|
"docker": models.DockerTemplate,
|
||||||
|
"dynamips": models.DynamipsTemplate,
|
||||||
|
"ethernet_hub": models.EthernetHubTemplate,
|
||||||
|
"ethernet_switch": models.EthernetSwitchTemplate,
|
||||||
|
"iou": models.IOUTemplate,
|
||||||
|
"qemu": models.QemuTemplate,
|
||||||
|
"virtualbox": models.VirtualBoxTemplate,
|
||||||
|
"vmware": models.VMwareTemplate,
|
||||||
|
"vpcs": models.VPCSTemplate
|
||||||
|
}
|
||||||
|
|
||||||
|
# built-in templates have their compute_id set to None to tell clients to select a compute
|
||||||
|
BUILTIN_TEMPLATES = [
|
||||||
|
{
|
||||||
|
"template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "cloud"),
|
||||||
|
"template_type": "cloud",
|
||||||
|
"name": "Cloud",
|
||||||
|
"default_name_format": "Cloud{0}",
|
||||||
|
"category": "guest",
|
||||||
|
"symbol": ":/symbols/cloud.svg",
|
||||||
|
"compute_id": None,
|
||||||
|
"builtin": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "nat"),
|
||||||
|
"template_type": "nat",
|
||||||
|
"name": "NAT",
|
||||||
|
"default_name_format": "NAT{0}",
|
||||||
|
"category": "guest",
|
||||||
|
"symbol": ":/symbols/cloud.svg",
|
||||||
|
"compute_id": None,
|
||||||
|
"builtin": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "vpcs"),
|
||||||
|
"template_type": "vpcs",
|
||||||
|
"name": "VPCS",
|
||||||
|
"default_name_format": "PC{0}",
|
||||||
|
"category": "guest",
|
||||||
|
"symbol": ":/symbols/vpcs_guest.svg",
|
||||||
|
"base_script_file": "vpcs_base_config.txt",
|
||||||
|
"compute_id": None,
|
||||||
|
"builtin": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_switch"),
|
||||||
|
"template_type": "ethernet_switch",
|
||||||
|
"name": "Ethernet switch",
|
||||||
|
"console_type": "none",
|
||||||
|
"default_name_format": "Switch{0}",
|
||||||
|
"category": "switch",
|
||||||
|
"symbol": ":/symbols/ethernet_switch.svg",
|
||||||
|
"compute_id": None,
|
||||||
|
"builtin": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "ethernet_hub"),
|
||||||
|
"template_type": "ethernet_hub",
|
||||||
|
"name": "Ethernet hub",
|
||||||
|
"default_name_format": "Hub{0}",
|
||||||
|
"category": "switch",
|
||||||
|
"symbol": ":/symbols/hub.svg",
|
||||||
|
"compute_id": None,
|
||||||
|
"builtin": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "frame_relay_switch"),
|
||||||
|
"template_type": "frame_relay_switch",
|
||||||
|
"name": "Frame Relay switch",
|
||||||
|
"default_name_format": "FRSW{0}",
|
||||||
|
"category": "switch",
|
||||||
|
"symbol": ":/symbols/frame_relay_switch.svg",
|
||||||
|
"compute_id": None,
|
||||||
|
"builtin": True
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"template_id": uuid.uuid3(uuid.NAMESPACE_DNS, "atm_switch"),
|
||||||
|
"template_type": "atm_switch",
|
||||||
|
"name": "ATM switch",
|
||||||
|
"default_name_format": "ATMSW{0}",
|
||||||
|
"category": "switch",
|
||||||
|
"symbol": ":/symbols/atm_switch.svg",
|
||||||
|
"compute_id": None,
|
||||||
|
"builtin": True
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class TemplatesRepository(BaseRepository):
|
||||||
|
|
||||||
|
def __init__(self, db_session: AsyncSession) -> None:
|
||||||
|
|
||||||
|
super().__init__(db_session)
|
||||||
|
|
||||||
|
def get_builtin_template(self, template_id: UUID) -> dict:
|
||||||
|
|
||||||
|
for builtin_template in BUILTIN_TEMPLATES:
|
||||||
|
if builtin_template["template_id"] == template_id:
|
||||||
|
return jsonable_encoder(builtin_template)
|
||||||
|
|
||||||
|
async def get_template(self, template_id: UUID) -> dict:
|
||||||
|
|
||||||
|
query = select(models.Template).where(models.Template.template_id == template_id)
|
||||||
|
result = (await self._db_session.execute(query)).scalars().first()
|
||||||
|
if result:
|
||||||
|
return result._asjson()
|
||||||
|
else:
|
||||||
|
return self.get_builtin_template(template_id)
|
||||||
|
|
||||||
|
async def get_templates(self) -> List[dict]:
|
||||||
|
|
||||||
|
templates = []
|
||||||
|
query = select(models.Template)
|
||||||
|
result = await self._db_session.execute(query)
|
||||||
|
for db_template in result.scalars().all():
|
||||||
|
templates.append(db_template._asjson())
|
||||||
|
for builtin_template in BUILTIN_TEMPLATES:
|
||||||
|
templates.append(jsonable_encoder(builtin_template))
|
||||||
|
return templates
|
||||||
|
|
||||||
|
async def create_template(self, template_create: schemas.TemplateCreate) -> dict:
|
||||||
|
|
||||||
|
# get the default template settings
|
||||||
|
template_settings = jsonable_encoder(template_create, exclude_unset=True)
|
||||||
|
template_schema = TEMPLATE_TYPE_TO_SHEMA[template_create.template_type]
|
||||||
|
template_settings_with_defaults = template_schema.parse_obj(template_settings)
|
||||||
|
settings = template_settings_with_defaults.dict()
|
||||||
|
if template_create.template_type == "dynamips":
|
||||||
|
# special case for Dynamips to cover all platform types that contain specific settings
|
||||||
|
dynamips_template_schema = DYNAMIPS_PLATFORM_TO_SHEMA[settings["platform"]]
|
||||||
|
dynamips_template_settings_with_defaults = dynamips_template_schema.parse_obj(template_settings)
|
||||||
|
settings = dynamips_template_settings_with_defaults.dict()
|
||||||
|
|
||||||
|
model = TEMPLATE_TYPE_TO_MODEL[template_create.template_type]
|
||||||
|
db_template = model(**settings)
|
||||||
|
self._db_session.add(db_template)
|
||||||
|
await self._db_session.commit()
|
||||||
|
await self._db_session.refresh(db_template)
|
||||||
|
template = db_template._asjson()
|
||||||
|
self._controller.notification.controller_emit("template.created", template)
|
||||||
|
return template
|
||||||
|
|
||||||
|
async def update_template(
|
||||||
|
self,
|
||||||
|
template_id: UUID,
|
||||||
|
template_update: schemas.TemplateUpdate) -> dict:
|
||||||
|
|
||||||
|
update_values = template_update.dict(exclude_unset=True)
|
||||||
|
|
||||||
|
query = update(models.Template) \
|
||||||
|
.where(models.Template.template_id == template_id) \
|
||||||
|
.values(update_values)
|
||||||
|
|
||||||
|
await self._db_session.execute(query)
|
||||||
|
await self._db_session.commit()
|
||||||
|
template = await self.get_template(template_id)
|
||||||
|
if template:
|
||||||
|
self._controller.notification.controller_emit("template.updated", template)
|
||||||
|
return template
|
||||||
|
|
||||||
|
async def delete_template(self, template_id: UUID) -> bool:
|
||||||
|
|
||||||
|
query = delete(models.Template).where(models.Template.template_id == template_id)
|
||||||
|
result = await self._db_session.execute(query)
|
||||||
|
await self._db_session.commit()
|
||||||
|
if result.rowcount > 0:
|
||||||
|
self._controller.notification.controller_emit("template.deleted", {"template_id": str(template_id)})
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def duplicate_template(self, template_id: UUID) -> dict:
|
||||||
|
|
||||||
|
query = select(models.Template).where(models.Template.template_id == template_id)
|
||||||
|
db_template = (await self._db_session.execute(query)).scalars().first()
|
||||||
|
if not db_template:
|
||||||
|
return db_template
|
||||||
|
|
||||||
|
# duplicate db object with new primary key (template_id)
|
||||||
|
self._db_session.expunge(db_template)
|
||||||
|
make_transient(db_template)
|
||||||
|
db_template.template_id = None
|
||||||
|
self._db_session.add(db_template)
|
||||||
|
await self._db_session.commit()
|
||||||
|
await self._db_session.refresh(db_template)
|
||||||
|
template = db_template._asjson()
|
||||||
|
self._controller.notification.controller_emit("template.created", template)
|
||||||
|
return template
|
@ -36,22 +36,26 @@ class UsersRepository(BaseRepository):
|
|||||||
|
|
||||||
async def get_user(self, user_id: UUID) -> Optional[models.User]:
|
async def get_user(self, user_id: UUID) -> Optional[models.User]:
|
||||||
|
|
||||||
result = await self._db_session.execute(select(models.User).where(models.User.user_id == user_id))
|
query = select(models.User).where(models.User.user_id == user_id)
|
||||||
|
result = await self._db_session.execute(query)
|
||||||
return result.scalars().first()
|
return result.scalars().first()
|
||||||
|
|
||||||
async def get_user_by_username(self, username: str) -> Optional[models.User]:
|
async def get_user_by_username(self, username: str) -> Optional[models.User]:
|
||||||
|
|
||||||
result = await self._db_session.execute(select(models.User).where(models.User.username == username))
|
query = select(models.User).where(models.User.username == username)
|
||||||
|
result = await self._db_session.execute(query)
|
||||||
return result.scalars().first()
|
return result.scalars().first()
|
||||||
|
|
||||||
async def get_user_by_email(self, email: str) -> Optional[models.User]:
|
async def get_user_by_email(self, email: str) -> Optional[models.User]:
|
||||||
|
|
||||||
result = await self._db_session.execute(select(models.User).where(models.User.email == email))
|
query = select(models.User).where(models.User.email == email)
|
||||||
|
result = await self._db_session.execute(query)
|
||||||
return result.scalars().first()
|
return result.scalars().first()
|
||||||
|
|
||||||
async def get_users(self) -> List[models.User]:
|
async def get_users(self) -> List[models.User]:
|
||||||
|
|
||||||
result = await self._db_session.execute(select(models.User))
|
query = select(models.User)
|
||||||
|
result = await self._db_session.execute(query)
|
||||||
return result.scalars().all()
|
return result.scalars().all()
|
||||||
|
|
||||||
async def create_user(self, user: schemas.UserCreate) -> models.User:
|
async def create_user(self, user: schemas.UserCreate) -> models.User:
|
||||||
|
@ -31,7 +31,7 @@ log = logging.getLogger(__name__)
|
|||||||
async def connect_to_db(app: FastAPI) -> None:
|
async def connect_to_db(app: FastAPI) -> None:
|
||||||
|
|
||||||
db_path = os.path.join(Config.instance().config_dir, "gns3_controller.db")
|
db_path = os.path.join(Config.instance().config_dir, "gns3_controller.db")
|
||||||
db_url = os.environ.get("GNS3_DATABASE_URI", f"sqlite:///{db_path}")
|
db_url = os.environ.get("GNS3_DATABASE_URI", f"sqlite+pysqlite:///{db_path}")
|
||||||
engine = create_async_engine(db_url, connect_args={"check_same_thread": False}, future=True)
|
engine = create_async_engine(db_url, connect_args={"check_same_thread": False}, future=True)
|
||||||
try:
|
try:
|
||||||
async with engine.begin() as conn:
|
async with engine.begin() as conn:
|
||||||
|
@ -18,8 +18,10 @@
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
from typing import Optional, Union
|
from typing import Optional, Union
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
from .nodes import NodeType
|
from .nodes import NodeType
|
||||||
|
from .base import DateTimeModelMixin
|
||||||
|
|
||||||
|
|
||||||
class Category(str, Enum):
|
class Category(str, Enum):
|
||||||
@ -38,7 +40,7 @@ class TemplateBase(BaseModel):
|
|||||||
Common template properties.
|
Common template properties.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
template_id: Optional[str] = None
|
template_id: Optional[UUID] = None
|
||||||
name: Optional[str] = None
|
name: Optional[str] = None
|
||||||
category: Optional[Category] = None
|
category: Optional[Category] = None
|
||||||
default_name_format: Optional[str] = None
|
default_name_format: Optional[str] = None
|
||||||
@ -50,6 +52,7 @@ class TemplateBase(BaseModel):
|
|||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
extra = "allow"
|
extra = "allow"
|
||||||
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class TemplateCreate(TemplateBase):
|
class TemplateCreate(TemplateBase):
|
||||||
@ -67,9 +70,9 @@ class TemplateUpdate(TemplateBase):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Template(TemplateBase):
|
class Template(DateTimeModelMixin, TemplateBase):
|
||||||
|
|
||||||
template_id: str
|
template_id: UUID
|
||||||
name: str
|
name: str
|
||||||
category: Category
|
category: Category
|
||||||
symbol: str
|
symbol: str
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
uvicorn==0.13.3
|
uvicorn==0.13.3
|
||||||
fastapi==0.62.0
|
fastapi==0.63.0
|
||||||
websockets==8.1
|
websockets==8.1
|
||||||
python-multipart==0.0.5
|
python-multipart==0.0.5
|
||||||
aiohttp==3.7.2
|
aiohttp==3.7.2
|
||||||
@ -10,7 +10,7 @@ psutil==5.7.3
|
|||||||
async-timeout==3.0.1
|
async-timeout==3.0.1
|
||||||
distro==1.5.0
|
distro==1.5.0
|
||||||
py-cpuinfo==7.0.0
|
py-cpuinfo==7.0.0
|
||||||
sqlalchemy==1.4.0b1 # beta version with asyncio support
|
sqlalchemy==1.4.0b2 # beta version with asyncio support
|
||||||
passlib[bcrypt]==1.7.2
|
passlib[bcrypt]==1.7.2
|
||||||
python-jose==3.2.0
|
python-jose==3.2.0
|
||||||
email-validator==1.1.2
|
email-validator==1.1.2
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -445,33 +445,6 @@ def test_appliances(controller, tmpdir):
|
|||||||
elif j["name"] == "My Appliance":
|
elif j["name"] == "My Appliance":
|
||||||
assert not j["builtin"]
|
assert not j["builtin"]
|
||||||
|
|
||||||
|
|
||||||
def test_load_templates(controller):
|
|
||||||
|
|
||||||
controller._settings = {}
|
|
||||||
controller.template_manager.load_templates()
|
|
||||||
|
|
||||||
assert "Cloud" in [template.name for template in controller.template_manager.templates.values()]
|
|
||||||
assert "VPCS" in [template.name for template in controller.template_manager.templates.values()]
|
|
||||||
|
|
||||||
for template in controller.template_manager.templates.values():
|
|
||||||
if template.name == "VPCS":
|
|
||||||
assert template._settings["properties"] == {"base_script_file": "vpcs_base_config.txt"}
|
|
||||||
|
|
||||||
# UUID should not change when you run again the function
|
|
||||||
for template in controller.template_manager.templates.values():
|
|
||||||
if template.name == "Test":
|
|
||||||
qemu_uuid = template.id
|
|
||||||
elif template.name == "Cloud":
|
|
||||||
cloud_uuid = template.id
|
|
||||||
controller.template_manager.load_templates()
|
|
||||||
for template in controller.template_manager.templates.values():
|
|
||||||
if template.name == "Test":
|
|
||||||
assert qemu_uuid == template.id
|
|
||||||
elif template.name == "Cloud":
|
|
||||||
assert cloud_uuid == template.id
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_autoidlepc(controller):
|
async def test_autoidlepc(controller):
|
||||||
|
|
||||||
|
@ -26,7 +26,6 @@ from unittest.mock import patch
|
|||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
from gns3server.controller.project import Project
|
from gns3server.controller.project import Project
|
||||||
from gns3server.controller.template import Template
|
|
||||||
from gns3server.controller.node import Node
|
from gns3server.controller.node import Node
|
||||||
from gns3server.controller.ports.ethernet_port import EthernetPort
|
from gns3server.controller.ports.ethernet_port import EthernetPort
|
||||||
from gns3server.controller.controller_error import ControllerError, ControllerNotFoundError, ControllerForbiddenError
|
from gns3server.controller.controller_error import ControllerError, ControllerNotFoundError, ControllerForbiddenError
|
||||||
@ -343,72 +342,72 @@ async def test_add_node_iou_no_id_available(controller):
|
|||||||
await project.add_node(compute, "test1", None, node_type="iou")
|
await project.add_node(compute, "test1", None, node_type="iou")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
# @pytest.mark.asyncio
|
||||||
async def test_add_node_from_template(controller):
|
# async def test_add_node_from_template(controller):
|
||||||
"""
|
# """
|
||||||
For a local server we send the project path
|
# For a local server we send the project path
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
compute = MagicMock()
|
# compute = MagicMock()
|
||||||
compute.id = "local"
|
# compute.id = "local"
|
||||||
project = Project(controller=controller, name="Test")
|
# project = Project(controller=controller, name="Test")
|
||||||
project.emit_notification = MagicMock()
|
# project.emit_notification = MagicMock()
|
||||||
template = Template(str(uuid.uuid4()), {
|
# template = Template(str(uuid.uuid4()), {
|
||||||
"compute_id": "local",
|
# "compute_id": "local",
|
||||||
"name": "Test",
|
# "name": "Test",
|
||||||
"template_type": "vpcs",
|
# "template_type": "vpcs",
|
||||||
"builtin": False,
|
# "builtin": False,
|
||||||
})
|
# })
|
||||||
controller.template_manager.templates[template.id] = template
|
# controller.template_manager.templates[template.id] = template
|
||||||
controller._computes["local"] = compute
|
# controller._computes["local"] = compute
|
||||||
|
#
|
||||||
response = MagicMock()
|
# response = MagicMock()
|
||||||
response.json = {"console": 2048}
|
# response.json = {"console": 2048}
|
||||||
compute.post = AsyncioMagicMock(return_value=response)
|
# compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
#
|
||||||
node = await project.add_node_from_template(template.id, x=23, y=12)
|
# node = await project.add_node_from_template(template.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
|
||||||
})
|
# })
|
||||||
|
#
|
||||||
assert compute in project._project_created_on_compute
|
# assert compute in project._project_created_on_compute
|
||||||
project.emit_notification.assert_any_call("node.created", node.__json__())
|
# project.emit_notification.assert_any_call("node.created", node.__json__())
|
||||||
|
#
|
||||||
|
#
|
||||||
@pytest.mark.asyncio
|
# @pytest.mark.asyncio
|
||||||
async def test_add_builtin_node_from_template(controller):
|
# async def test_add_builtin_node_from_template(controller):
|
||||||
"""
|
# """
|
||||||
For a local server we send the project path
|
# For a local server we send the project path
|
||||||
"""
|
# """
|
||||||
|
#
|
||||||
compute = MagicMock()
|
# compute = MagicMock()
|
||||||
compute.id = "local"
|
# compute.id = "local"
|
||||||
project = Project(controller=controller, name="Test")
|
# project = Project(controller=controller, name="Test")
|
||||||
project.emit_notification = MagicMock()
|
# project.emit_notification = MagicMock()
|
||||||
template = Template(str(uuid.uuid4()), {
|
# template = Template(str(uuid.uuid4()), {
|
||||||
"name": "Builtin-switch",
|
# "name": "Builtin-switch",
|
||||||
"template_type": "ethernet_switch",
|
# "template_type": "ethernet_switch",
|
||||||
}, builtin=True)
|
# }, builtin=True)
|
||||||
|
#
|
||||||
controller.template_manager.templates[template.id] = template
|
# controller.template_manager.templates[template.id] = template
|
||||||
template.__json__()
|
# template.__json__()
|
||||||
controller._computes["local"] = compute
|
# controller._computes["local"] = compute
|
||||||
|
#
|
||||||
response = MagicMock()
|
# response = MagicMock()
|
||||||
response.json = {"console": 2048}
|
# response.json = {"console": 2048}
|
||||||
compute.post = AsyncioMagicMock(return_value=response)
|
# compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
#
|
||||||
node = await project.add_node_from_template(template.id, x=23, y=12, compute_id="local")
|
# node = await project.add_node_from_template(template.id, x=23, y=12, compute_id="local")
|
||||||
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
|
||||||
})
|
# })
|
||||||
|
#
|
||||||
assert compute in project._project_created_on_compute
|
# assert compute in project._project_created_on_compute
|
||||||
project.emit_notification.assert_any_call("node.created", node.__json__())
|
# project.emit_notification.assert_any_call("node.created", node.__json__())
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
#
|
|
||||||
# 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 pytest
|
|
||||||
import pydantic
|
|
||||||
|
|
||||||
from gns3server.controller.template import Template
|
|
||||||
|
|
||||||
|
|
||||||
def test_template_json():
|
|
||||||
a = Template(None, {
|
|
||||||
"node_type": "qemu",
|
|
||||||
"name": "Test",
|
|
||||||
"default_name_format": "{name}-{0}",
|
|
||||||
"category": 0,
|
|
||||||
"symbol": "qemu.svg",
|
|
||||||
"server": "local",
|
|
||||||
"platform": "i386"
|
|
||||||
})
|
|
||||||
settings = a.__json__()
|
|
||||||
assert settings["template_id"] == a.id
|
|
||||||
assert settings["template_type"] == "qemu"
|
|
||||||
assert settings["builtin"] == False
|
|
||||||
|
|
||||||
|
|
||||||
def test_template_json_with_not_known_category():
|
|
||||||
|
|
||||||
with pytest.raises(pydantic.ValidationError):
|
|
||||||
Template(None, {
|
|
||||||
"node_type": "qemu",
|
|
||||||
"name": "Test",
|
|
||||||
"default_name_format": "{name}-{0}",
|
|
||||||
"category": 'Not known',
|
|
||||||
"symbol": "qemu.svg",
|
|
||||||
"server": "local",
|
|
||||||
"platform": "i386"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
def test_template_json_with_platform():
|
|
||||||
|
|
||||||
a = Template(None, {
|
|
||||||
"node_type": "dynamips",
|
|
||||||
"name": "Test",
|
|
||||||
"default_name_format": "{name}-{0}",
|
|
||||||
"category": 0,
|
|
||||||
"symbol": "dynamips.svg",
|
|
||||||
"image": "IOS_image.bin",
|
|
||||||
"server": "local",
|
|
||||||
"platform": "c3725"
|
|
||||||
})
|
|
||||||
settings = a.__json__()
|
|
||||||
assert settings["template_id"] == a.id
|
|
||||||
assert settings["template_type"] == "dynamips"
|
|
||||||
assert settings["builtin"] == False
|
|
||||||
assert settings["platform"] == "c3725"
|
|
||||||
|
|
||||||
|
|
||||||
def test_template_fix_linked_base():
|
|
||||||
"""
|
|
||||||
Version of the gui before 2.1 use linked_base and the server
|
|
||||||
linked_clone
|
|
||||||
"""
|
|
||||||
|
|
||||||
a = Template(None, {
|
|
||||||
"node_type": "qemu",
|
|
||||||
"name": "Test",
|
|
||||||
"default_name_format": "{name}-{0}",
|
|
||||||
"category": 0,
|
|
||||||
"symbol": "qemu.svg",
|
|
||||||
"server": "local",
|
|
||||||
"linked_base": True
|
|
||||||
})
|
|
||||||
assert a.settings["linked_clone"]
|
|
||||||
assert "linked_base" not in a.settings
|
|
Loading…
Reference in New Issue
Block a user