diff --git a/conf/gns3_server.conf b/conf/gns3_server.conf index bd76b87e..bd4d8991 100644 --- a/conf/gns3_server.conf +++ b/conf/gns3_server.conf @@ -11,9 +11,13 @@ certkey=/home/gns3/.config/GNS3/ssl/server.key ; Path where devices images are stored images_path = /home/gns3/GNS3/images + ; Path where user projects are stored projects_path = /home/gns3/GNS3/projects +; Path where user appliances are stored +appliances_path = /home/gns3/GNS3/appliances + ; Option to automatically send crash reports to the GNS3 team report_errors = True diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index b818f43a..1b985ea4 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -66,13 +66,17 @@ class Controller: def load_appliances(self): self._appliance_templates = {} - for file in os.listdir(get_resource('appliances')): - path = os.path.join(get_resource('appliances'), file) - appliance_id = uuid.uuid3(uuid.NAMESPACE_URL, path) # Generate the UUID from path to avoid change between reboots - with open(path, 'r', encoding='utf-8') as f: - appliance = ApplianceTemplate(appliance_id, json.load(f)) - if appliance.status != 'broken': - self._appliance_templates[appliance.id] = appliance + for directory, builtin in ( + (get_resource('appliances'), True,), (self.appliances_path(), False,) + ): + if os.path.isdir(directory): + for file in os.listdir(directory): + path = os.path.join(directory, file) + appliance_id = uuid.uuid3(uuid.NAMESPACE_URL, path) # Generate the UUID from path to avoid change between reboots + with open(path, 'r', encoding='utf-8') as f: + appliance = ApplianceTemplate(appliance_id, json.load(f), builtin=builtin) + if appliance.status != 'broken': + self._appliance_templates[appliance.id] = appliance self._appliances = {} vms = [] @@ -304,6 +308,15 @@ class Controller: os.makedirs(images_path, exist_ok=True) return images_path + def appliances_path(self): + """ + Get the image storage directory + """ + server_config = Config.instance().get_section_config("Server") + appliances_path = os.path.expanduser(server_config.get("appliances_path", "~/GNS3/projects")) + os.makedirs(appliances_path, exist_ok=True) + return appliances_path + @asyncio.coroutine def _import_gns3_gui_conf(self): """ diff --git a/gns3server/controller/appliance_template.py b/gns3server/controller/appliance_template.py index de7e98c6..caf18f7e 100644 --- a/gns3server/controller/appliance_template.py +++ b/gns3server/controller/appliance_template.py @@ -21,7 +21,7 @@ import uuid class ApplianceTemplate: - def __init__(self, appliance_id, data): + def __init__(self, appliance_id, data, builtin=True): if appliance_id is None: self._id = str(uuid.uuid4()) elif isinstance(appliance_id, uuid.UUID): @@ -29,6 +29,7 @@ class ApplianceTemplate: else: self._id = appliance_id self._data = data.copy() + self._builtin = builtin if "appliance_id" in self._data: del self._data["appliance_id"] @@ -44,4 +45,6 @@ class ApplianceTemplate: """ Appliance data (a hash) """ - return copy.deepcopy(self._data) + data = copy.deepcopy(self._data) + data["builtin"] = self._builtin + return data diff --git a/scripts/remote-install.sh b/scripts/remote-install.sh index 8e9c741e..b126bf39 100644 --- a/scripts/remote-install.sh +++ b/scripts/remote-install.sh @@ -206,6 +206,8 @@ host = 0.0.0.0 port = 3080 images_path = /opt/gns3/images projects_path = /opt/gns3/projects +appliances_path = /opt/gns3/appliances +configs_path = /opt/gns3/configs report_errors = True [Qemu] diff --git a/tests/conftest.py b/tests/conftest.py index 2fbc9074..6e0b72b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -205,6 +205,7 @@ def run_around_tests(monkeypatch, port_manager, controller, config): config.set("Server", "projects_path", os.path.join(tmppath, 'projects')) config.set("Server", "symbols_path", os.path.join(tmppath, 'symbols')) config.set("Server", "images_path", os.path.join(tmppath, 'images')) + config.set("Server", "appliances_path", os.path.join(tmppath, 'appliances')) config.set("Server", "ubridge_path", os.path.join(tmppath, 'bin', 'ubridge')) config.set("Server", "auth", False) @@ -227,7 +228,7 @@ def run_around_tests(monkeypatch, port_manager, controller, config): # An helper should not raise Exception try: shutil.rmtree(tmppath) - except: + except BaseException: pass diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index 9f342224..5c7240ff 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -21,7 +21,7 @@ import json import pytest import socket import aiohttp -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch from tests.utils import AsyncioMagicMock, asyncio_patch from gns3server.controller.compute import Compute @@ -460,12 +460,6 @@ def test_get_free_project_name(controller, async_run): assert controller.get_free_project_name("Hello") == "Hello" -def test_appliance_templates(controller): - assert len(controller.appliance_templates) > 0 - for appliance in controller.appliance_templates.values(): - assert appliance.__json__()["status"] != "broken" - - def test_load_base_files(controller, config, tmpdir): config.set_section_config("Server", {"configs_path": str(tmpdir)}) @@ -480,10 +474,28 @@ def test_load_base_files(controller, config, tmpdir): assert f.read() == 'test' -def test_appliance_templates(controller, async_run): - controller.load_appliances() +def test_appliance_templates(controller, async_run, tmpdir): + my_appliance = { + "name": "My Appliance", + "status": "stable" + } + with open(str(tmpdir / "my_appliance.gns3a"), 'w+') as f: + json.dump(my_appliance, f) + + with patch("gns3server.config.Config.get_section_config", return_value={"appliances_path": str(tmpdir)}): + controller.load_appliances() assert len(controller.appliance_templates) > 0 + for appliance in controller.appliance_templates.values(): + assert appliance.__json__()["status"] != "broken" assert "Alpine Linux" in [c.__json__()["name"] for c in controller.appliance_templates.values()] + assert "My Appliance" in [c.__json__()["name"] for c in controller.appliance_templates.values()] + + for c in controller.appliance_templates.values(): + j = c.__json__() + if j["name"] == "Alpine Linux": + assert j["builtin"] + elif j["name"] == "My Appliance": + assert not j["builtin"] def test_load_appliances(controller): @@ -526,4 +538,3 @@ def test_autoidlepc(controller, async_run): async_run(controller.autoidlepc("local", "c7200", "test.bin")) assert node_mock.dynamips_auto_idlepc.called assert len(controller.projects) == 0 -