From bd657c01675a22be9e0cee0bfdae0026ff90f65c Mon Sep 17 00:00:00 2001 From: grossmj Date: Sun, 18 Nov 2018 17:05:16 +0700 Subject: [PATCH] Support to duplicate an appliance. --- gns3server/controller/__init__.py | 15 ++++++++++ .../api/controller/appliance_handler.py | 21 +++++++++++++- .../handlers/api/controller/test_appliance.py | 29 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 0d3235d9..196446ec 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -24,6 +24,7 @@ import shutil import asyncio import aiohttp import jsonschema +import copy from ..config import Config from .project import Project @@ -181,6 +182,20 @@ class Controller: self.save() self.notification.controller_emit("appliance.deleted", appliance.__json__()) + def duplicate_appliance(self, appliance_id): + """ + Duplicates an appliance. + + :param appliance_id: appliance identifier + """ + + appliance = self.get_appliance(appliance_id) + if appliance.builtin: + raise aiohttp.web.HTTPConflict(text="Appliance ID {} cannot be duplicated because it is a builtin".format(appliance_id)) + appliance_settings = copy.deepcopy(appliance.settings) + del appliance_settings["appliance_id"] + return self.add_appliance(appliance_settings) + def load_appliances(self): #self._appliances = {} diff --git a/gns3server/handlers/api/controller/appliance_handler.py b/gns3server/handlers/api/controller/appliance_handler.py index 4c3115f7..b97117eb 100644 --- a/gns3server/handlers/api/controller/appliance_handler.py +++ b/gns3server/handlers/api/controller/appliance_handler.py @@ -115,7 +115,7 @@ class ApplianceHandler: @Route.delete( r"/appliances/{appliance_id}", parameters={ - "appliance_id": "Node UUID" + "appliance_id": "appliance UUID" }, status_codes={ 204: "Appliance deleted", @@ -140,6 +140,25 @@ class ApplianceHandler: controller = Controller.instance() response.json([c for c in controller.appliances.values()]) + @Route.post( + r"/appliances/{appliance_id}/duplicate", + parameters={ + "appliance_id": "Appliance UUID" + }, + status_codes={ + 201: "Appliance duplicated", + 400: "Invalid request", + 404: "Appliance doesn't exist" + }, + description="Duplicate an appliance", + output=APPLIANCE_OBJECT_SCHEMA) + async def duplicate(request, response): + + controller = Controller.instance() + appliance = controller.duplicate_appliance(request.match_info["appliance_id"]) + response.set_status(201) + response.json(appliance) + @Route.post( r"/projects/{project_id}/appliances/{appliance_id}", description="Create a node from an appliance", diff --git a/tests/handlers/api/controller/test_appliance.py b/tests/handlers/api/controller/test_appliance.py index 890ad94e..c6cd5dfc 100644 --- a/tests/handlers/api/controller/test_appliance.py +++ b/tests/handlers/api/controller/test_appliance.py @@ -210,6 +210,35 @@ def test_appliance_delete(http_controller, controller): assert len(controller.appliances) == 0 +def test_appliance_duplicate(http_controller, controller): + + appliance_id = str(uuid.uuid4()) + params = {"appliance_id": appliance_id, + "base_script_file": "vpcs_base_config.txt", + "category": "guest", + "console_auto_start": False, + "console_type": "telnet", + "default_name_format": "PC{0}", + "name": "VPCS_TEST", + "compute_id": "local", + "symbol": ":/symbols/vpcs_guest.svg", + "appliance_type": "vpcs"} + + response = http_controller.post("/appliances", params) + assert response.status == 201 + + response = http_controller.post("/appliances/{}/duplicate".format(appliance_id), example=True) + assert response.status == 201 + assert response.json["appliance_id"] != appliance_id + params.pop("appliance_id") + for param, value in params.items(): + assert response.json[param] == value + + response = http_controller.get("/appliances") + assert len(response.json) == 2 + assert len(controller.appliances) == 2 + + def test_c7200_dynamips_appliance_create(http_controller): params = {"name": "Cisco c7200 appliance",