diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index 2b15cf75..de3cf17e 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -28,6 +28,8 @@ from .notification import Notification from .symbols import Symbols from ..version import __version__ from .topology import load_topology +from .gns3vm import GNS3VM + import logging log = logging.getLogger(__name__) @@ -40,6 +42,7 @@ class Controller: self._computes = {} self._projects = {} self._notification = Notification(self) + self.gns3vm = GNS3VM(self) self.symbols = Symbols() # Store settings shared by the different GUI will be replace by dedicated API later self._settings = {} @@ -56,15 +59,10 @@ class Controller: self._config_file = os.path.join(config_path, "gns3_controller.conf") log.info("Load controller configuration file {}".format(self._config_file)) - if server_config.getboolean("local", False) is True: - self._computes["local"] = Compute(compute_id="local", - controller=self, - protocol=server_config.get("protocol", "http"), - host=server_config.get("host", "localhost"), - port=server_config.getint("port", 3080), - user=server_config.get("user", ""), - password=server_config.get("password", "")) - + @asyncio.coroutine + def start(self): + log.info("Start controller") + server_config = Config.instance().get_section_config("Server") self._computes["local"] = Compute(compute_id="local", controller=self, protocol=server_config.get("protocol", "http"), @@ -72,6 +70,15 @@ class Controller: port=server_config.getint("port", 3080), user=server_config.get("user", ""), password=server_config.get("password", "")) + yield from self.load() + + @asyncio.coroutine + def stop(self): + log.info("Stop controller") + for compute in self._computes.values(): + yield from compute.close() + self._computes = {} + self._projects = {} def save(self): """ @@ -80,6 +87,7 @@ class Controller: data = { "computes": [], "settings": self._settings, + "gns3vm": self.gns3vm.__json__(), "version": __version__ } @@ -115,6 +123,8 @@ class Controller: return if "settings" in data: self._settings = data["settings"] + if "gns3vm" in data: + self.gns3vm.settings = data["gns3vm"] for c in data["computes"]: yield from self.add_compute(**c) @@ -234,12 +244,6 @@ class Controller: self.save() self.notification.emit("compute.deleted", compute.__json__()) - @asyncio.coroutine - def close(self): - log.info("Close controller") - for compute in self._computes.values(): - yield from compute.close() - @property def notification(self): """ diff --git a/gns3server/controller/gns3vm/__init__.py b/gns3server/controller/gns3vm/__init__.py new file mode 100644 index 00000000..28dcb154 --- /dev/null +++ b/gns3server/controller/gns3vm/__init__.py @@ -0,0 +1,101 @@ +#!/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 . + +import sys +import asyncio + +from .vmware_gns3_vm import VMwareGNS3VM +from .virtualbox_gns3_vm import VirtualBoxGNS3VM + + +class GNS3VM: + """ + Proxy between the controller and the GNS3 VM engine + """ + + def __init__(self, controller, settings={}): + self._controller = controller + # Keep instance of the loaded engines + self._engines = {} + self._settings = { + "vmname": None, + "auto_stop": False, + "headless": False, + "enable": False, + "engine": "vmware" + } + self._settings.update(settings) + + def engine_list(self): + """ + :returns: Return list of engines supported by GNS3 for the GNS3VM + """ + virtualbox_informations = { + "engine_id": "virtualbox", + "name": "VirtualBox", + "description": "VirtualBox doesn't support nested virtualization, this means running Qemu based VM could be very slow." + } + vmware_informations = { + "engine_id": "vmware", + "description": "VMware is the recommended choice for best performances." + } + if sys.platform.startswith("darwin"): + vmware_informations["name"] = "VMware Fusion" + else: + vmware_informations["name"] = "VMware Workstation / Player" + return [ + vmware_informations, + virtualbox_informations + ] + + @property + def settings(self): + return self._settings + + @settings.setter + def settings(self, val): + self._settings.update(val) + self._controller.save() + + def _get_engine(self, engine): + """ + Load an engine + """ + if engine in self._engines: + return self._engines[engine] + + if engine == "vmware": + self._engines["vmware"] = VMwareGNS3VM() + return self._engines["vmware"] + elif engine == "virtualbox": + self._engines["virtualbox"] = VirtualBoxGNS3VM() + return self._engines["virtualbox"] + raise NotImplementedError("The engine {} for the GNS3 VM is not supported".format(engine)) + + def __json__(self): + return self._settings + + @asyncio.coroutine + def list(self, engine): + """ + List VMS for an engine + """ + engine = self._get_engine(engine) + vms = [] + for vm in (yield from engine.list()): + vms.append({"vmname": vm["vmname"]}) + return vms diff --git a/gns3server/controller/base_gns3_vm.py b/gns3server/controller/gns3vm/base_gns3_vm.py similarity index 64% rename from gns3server/controller/base_gns3_vm.py rename to gns3server/controller/gns3vm/base_gns3_vm.py index 458c99e4..a92b0caf 100644 --- a/gns3server/controller/base_gns3_vm.py +++ b/gns3server/controller/gns3vm/base_gns3_vm.py @@ -15,11 +15,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from .gns3_vm_error import GNS3VMError - -import os -import sys -import json import asyncio import psutil @@ -32,7 +27,6 @@ class BaseGNS3VM: def __init__(self): self._vmname = None - self._auto_start = False self._auto_stop = False self._ip_address = None self._port = 3080 @@ -41,12 +35,6 @@ class BaseGNS3VM: self._ram = 1024 self._running = False - if sys.platform.startswith("win"): - config_path = os.path.join(os.path.expandvars("%APPDATA%"), "GNS3") - else: - config_path = os.path.join(os.path.expanduser("~"), ".config", "GNS3") - self._config_file = os.path.join(config_path, "gns3_vm.conf") - # limit the number of vCPUs to the number of physical cores (hyper thread CPUs are excluded) # because this is likely to degrade performances. self._vcpus = psutil.cpu_count(logical=False) @@ -56,51 +44,6 @@ class BaseGNS3VM: ram -= ram % 4 self._ram = ram - self.load() - - def __json__(self): - - settings = {"vmname": self._vmname, - "ip_address": self._ip_address, - "port": self._port, - "headless": self._headless, - "vcpus": self._vcpus, - "ram": self._ram, - "auto_start": self._auto_start, - "auto_stop": self._auto_stop, - "engine": self._engine} - - return settings - - def load(self): - """ - Reload the GNS3 VM configuration from disk - """ - - if not os.path.exists(self._config_file): - self.save() - try: - with open(self._config_file) as f: - data = json.load(f) - except OSError as e: - log.critical("Cannot load %s: %s", self._config_file, str(e)) - return - if "gns3vm" in data: - for name, value in data["gns3vm"].items(): - if hasattr(self, name) and getattr(self, name) != value: - log.debug("GNS3 VM: set {} to {}".format(name, value)) - setattr(self, name, value) - - def save(self): - """ - Save the GNS3 VM configuration on disk - """ - - data = {"gns3vm": self.__json__()} - os.makedirs(os.path.dirname(self._config_file), exist_ok=True) - with open(self._config_file, 'w+') as f: - json.dump(data, f, indent=4) - @property def vmname(self): """ @@ -241,30 +184,10 @@ class BaseGNS3VM: self._ram = new_ram - @property - def auto_start(self): - """ - Returns whether the VM should automatically be started when GNS3 is launched - - :returns: boolean - """ - - return self._auto_start - - @auto_start.setter - def auto_start(self, new_auto_start): - """ - Set whether the VM should automatically be started when GNS3 is launched - - :param new_auto_start: boolean - """ - - self._auto_start = new_auto_start - @property def auto_stop(self): """ - Returns whether the VM should automatically be started when GNS3 is launched + Returns whether the VM should automatically be stopped when GNS3 quit :returns: boolean """ @@ -274,7 +197,7 @@ class BaseGNS3VM: @auto_stop.setter def auto_stop(self, new_auto_stop): """ - Set whether the VM should automatically be stopped when GNS3 is launched + Set whether the VM should automatically be stopped when GNS3 quit :param new_auto_stop: boolean """ @@ -314,15 +237,3 @@ class BaseGNS3VM: """ raise NotImplementedError - - @classmethod - def instance(cls): - """ - Singleton to return only one instance of BaseGNS3VM. - - :returns: instance of BaseGNS3VM - """ - - if not hasattr(cls, "_instance") or cls._instance is None: - cls._instance = cls() - return cls._instance diff --git a/gns3server/controller/gns3_vm_error.py b/gns3server/controller/gns3vm/gns3_vm_error.py similarity index 100% rename from gns3server/controller/gns3_vm_error.py rename to gns3server/controller/gns3vm/gns3_vm_error.py diff --git a/gns3server/controller/virtualbox_gns3_vm.py b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py similarity index 99% rename from gns3server/controller/virtualbox_gns3_vm.py rename to gns3server/controller/gns3vm/virtualbox_gns3_vm.py index 46aeacbb..15be9c85 100644 --- a/gns3server/controller/virtualbox_gns3_vm.py +++ b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py @@ -22,7 +22,7 @@ import socket from .base_gns3_vm import BaseGNS3VM from .gns3_vm_error import GNS3VMError -from ..compute.virtualbox import ( +from ...compute.virtualbox import ( VirtualBox, VirtualBoxError ) diff --git a/gns3server/controller/vmware_gns3_vm.py b/gns3server/controller/gns3vm/vmware_gns3_vm.py similarity index 99% rename from gns3server/controller/vmware_gns3_vm.py rename to gns3server/controller/gns3vm/vmware_gns3_vm.py index c9e71b1c..e17379a8 100644 --- a/gns3server/controller/vmware_gns3_vm.py +++ b/gns3server/controller/gns3vm/vmware_gns3_vm.py @@ -117,7 +117,6 @@ class VMwareGNS3VM(BaseGNS3VM): # check if the VMware guest tools are installed vmware_tools_state = yield from self._execute("checkToolsState", [self._vmx_path]) - print(vmware_tools_state) if vmware_tools_state not in ("installed", "running"): raise GNS3VMError("VMware tools are not installed in {}".format(self.vmname)) diff --git a/gns3server/handlers/api/controller/gns3_vm_handler.py b/gns3server/handlers/api/controller/gns3_vm_handler.py index 3a276e53..d68d943b 100644 --- a/gns3server/handlers/api/controller/gns3_vm_handler.py +++ b/gns3server/handlers/api/controller/gns3_vm_handler.py @@ -17,8 +17,9 @@ from aiohttp.web import HTTPConflict from gns3server.web.route import Route -from gns3server.controller.vmware_gns3_vm import VMwareGNS3VM -from gns3server.controller.virtualbox_gns3_vm import VirtualBoxGNS3VM +from gns3server.controller import Controller +from gns3server.schemas.gns3vm import GNS3VM_SETTINGS_SCHEMA + import logging log = logging.getLogger(__name__) @@ -28,7 +29,18 @@ class GNS3VMHandler: """API entry points for GNS3 VM management.""" @Route.get( - r"/gns3vm/{engine}/vms", + r"/gns3vm/engines", + description="Return the list of engines supported for the GNS3VM", + status_codes={ + 200: "OK" + }) + def list_engines(request, response): + + gns3_vm = Controller().instance().gns3vm + response.json(gns3_vm.engine_list()) + + @Route.get( + r"/gns3vm/engines/{engine}/vms", parameters={ "engine": "Virtualization engine name" }, @@ -39,14 +51,7 @@ class GNS3VMHandler: description="Get all the available VMs for a specific virtualization engine") def get_vms(request, response): - engine = request.match_info["engine"] - if engine == "vmware": - engine_instance = VMwareGNS3VM.instance() - elif engine == "virtualbox": - engine_instance = VirtualBoxGNS3VM.instance() - else: - raise HTTPConflict(text="Unknown engine: '{}'".format(engine)) - vms = yield from engine_instance.list() + vms = yield from Controller.instance().gns3vm.list(request.match_info["engine"]) response.json(vms) @Route.get( @@ -54,51 +59,22 @@ class GNS3VMHandler: description="Get GNS3 VM settings", status_codes={ 200: "GNS3 VM settings returned" - }) + }, + output_schema=GNS3VM_SETTINGS_SCHEMA) def show(request, response): - - gns3_vm = VMwareGNS3VM.instance() - response.json(gns3_vm) + response.json(Controller.instance().gns3vm) @Route.put( r"/gns3vm", description="Update GNS3 VM settings", - #input=GNS3VM_UPDATE_SCHEMA, # TODO: validate settings + input_schema=GNS3VM_SETTINGS_SCHEMA, + output_schema=GNS3VM_SETTINGS_SCHEMA, status_codes={ - 200: "GNS3 VM updated" + 201: "GNS3 VM updated" }) def update(request, response): - gns3_vm = VMwareGNS3VM.instance() - for name, value in request.json.items(): - if hasattr(gns3_vm, name) and getattr(gns3_vm, name) != value: - setattr(gns3_vm, name, value) - gns3_vm.save() + gns3_vm = Controller().instance().gns3vm + gns3_vm.settings = request.json response.json(gns3_vm) - - @Route.post( - r"/gns3vm/start", - status_codes={ - 200: "Instance started", - 400: "Invalid request", - }, - description="Start the GNS3 VM" - ) - def start(request, response): - - gns3_vm = VMwareGNS3VM.instance() - yield from gns3_vm.start() - response.json(gns3_vm) - - @Route.post( - r"/gns3vm/stop", - status_codes={ - 204: "Instance stopped", - 400: "Invalid request", - }, - description="Stop the GNS3 VM") - def stop(request, response): - - gns3_vm = VMwareGNS3VM.instance() - yield from gns3_vm.stop() - response.set_status(204) + response.set_status(201) diff --git a/gns3server/schemas/gns3vm.py b/gns3server/schemas/gns3vm.py new file mode 100644 index 00000000..4d08d8ec --- /dev/null +++ b/gns3server/schemas/gns3vm.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2016 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +GNS3VM_SETTINGS_SCHEMA = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Settings of the GNS3VM", + "type": "object", + "properties": { + "enable": { + "type": "boolean", + "description": "Enable the VM" + }, + "vmname": { + "type": "string", + "description": "The name of the VM" + }, + "auto_stop": { + "type": "boolean", + "description": "The VM auto stop with GNS3" + }, + "headless": { + "type": "boolean", + "description": "Start the VM GUI or not", + }, + "engine": { + "description": "The engine to use for the VM. Null to disable", + "enum": ["vmware", "virtualbox", None] + } + }, + "additionalProperties": False +} diff --git a/gns3server/web/route.py b/gns3server/web/route.py index 80b57dd5..cf3174da 100644 --- a/gns3server/web/route.py +++ b/gns3server/web/route.py @@ -31,7 +31,7 @@ log = logging.getLogger(__name__) from ..compute.error import NodeError, ImageMissingError from ..controller.controller_error import ControllerError from ..ubridge.ubridge_error import UbridgeError -from ..controller.gns3_vm_error import GNS3VMError +from ..controller.gns3vm.gns3_vm_error import GNS3VMError from .response import Response from ..crash_report import CrashReport from ..config import Config diff --git a/gns3server/web/web_server.py b/gns3server/web/web_server.py index c71967a4..36a9850a 100644 --- a/gns3server/web/web_server.py +++ b/gns3server/web/web_server.py @@ -97,7 +97,7 @@ class WebServer: self._handler = None if Config.instance().get_section_config("Server").getboolean("controller"): - yield from Controller.instance().close() + yield from Controller.instance().stop() for module in MODULES: log.debug("Unloading module {}".format(module.__name__)) @@ -272,7 +272,7 @@ class WebServer: self._loop.set_debug(True) if server_config.getboolean("controller"): - asyncio.async(Controller.instance().load()) + asyncio.async(Controller.instance().start()) for key, val in os.environ.items(): log.debug("ENV %s=%s", key, val) diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index 79d470ae..96b9d373 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -38,6 +38,7 @@ def test_save(controller, controller_config_path): assert data["computes"] == [] assert data["version"] == __version__ assert data["settings"] == {} + assert data["gns3vm"] == controller.gns3vm.__json__() def test_load(controller, controller_config_path, async_run): @@ -55,6 +56,7 @@ def test_load(controller, controller_config_path, async_run): } ] data["settings"] = {"IOU": True} + data["gns3vm"] = {"vmname": "Test VM"} with open(controller_config_path, "w+") as f: json.dump(data, f) async_run(controller.load()) @@ -70,6 +72,7 @@ def test_load(controller, controller_config_path, async_run): "cpu_usage_percent": None, "memory_usage_percent": None } + assert controller.gns3vm.settings["vmname"] == "Test VM" def test_import_computes(controller, controller_config_path, async_run): @@ -135,29 +138,29 @@ def test_addCompute(controller, controller_config_path, async_run): controller._notification = MagicMock() c = async_run(controller.add_compute(compute_id="test1")) controller._notification.emit.assert_called_with("compute.created", c.__json__()) - assert len(controller.computes) == 2 + assert len(controller.computes) == 1 async_run(controller.add_compute(compute_id="test1")) controller._notification.emit.assert_called_with("compute.updated", c.__json__()) - assert len(controller.computes) == 2 + assert len(controller.computes) == 1 async_run(controller.add_compute(compute_id="test2")) - assert len(controller.computes) == 3 + assert len(controller.computes) == 2 def test_addDuplicateCompute(controller, controller_config_path, async_run): controller._notification = MagicMock() c = async_run(controller.add_compute(compute_id="test1", name="Test")) - assert len(controller.computes) == 2 + assert len(controller.computes) == 1 with pytest.raises(aiohttp.web.HTTPConflict): async_run(controller.add_compute(compute_id="test2", name="Test")) def test_deleteCompute(controller, controller_config_path, async_run): c = async_run(controller.add_compute(compute_id="test1")) - assert len(controller.computes) == 2 + assert len(controller.computes) == 1 controller._notification = MagicMock() c._connected = True async_run(controller.delete_compute("test1")) - assert len(controller.computes) == 1 + assert len(controller.computes) == 0 controller._notification.emit.assert_called_with("compute.deleted", c.__json__()) with open(controller_config_path) as f: data = json.load(f) @@ -167,7 +170,7 @@ def test_deleteCompute(controller, controller_config_path, async_run): def test_addComputeConfigFile(controller, controller_config_path, async_run): async_run(controller.add_compute(compute_id="test1", name="Test")) - assert len(controller.computes) == 2 + assert len(controller.computes) == 1 with open(controller_config_path) as f: data = json.load(f) assert data["computes"] == [ @@ -249,10 +252,10 @@ def test_getProject(controller, async_run): assert controller.get_project("dsdssd") -def test_close(controller, async_run): +def test_stop(controller, async_run): c = async_run(controller.add_compute(compute_id="test1")) c._connected = True - async_run(controller.close()) + async_run(controller.stop()) assert c.connected is False diff --git a/tests/controller/test_gns3vm.py b/tests/controller/test_gns3vm.py new file mode 100644 index 00000000..85b05e4c --- /dev/null +++ b/tests/controller/test_gns3vm.py @@ -0,0 +1,39 @@ +#!/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 . + +import pytest +from tests.utils import asyncio_patch + +from gns3server.controller.gns3vm import GNS3VM + + +def test_list(async_run, controller): + vm = GNS3VM(controller) + + with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.list", return_value=[{"vmname": "test", "vmx_path": "test"}]): + res = async_run(vm.list("vmware")) + assert res == [{"vmname": "test"}] # Informations specific to vmware are stripped + with asyncio_patch("gns3server.controller.gns3vm.virtualbox_gns3_vm.VirtualBoxGNS3VM.list", return_value=[{"vmname": "test"}]): + res = async_run(vm.list("virtualbox")) + assert res == [{"vmname": "test"}] + with pytest.raises(NotImplementedError): + async_run(vm.list("hyperv")) + + +def test_json(controller): + vm = GNS3VM(controller) + assert vm.__json__() == vm._settings diff --git a/tests/handlers/api/controller/test_compute.py b/tests/handlers/api/controller/test_compute.py index f9a4f35b..bc5b1da1 100644 --- a/tests/handlers/api/controller/test_compute.py +++ b/tests/handlers/api/controller/test_compute.py @@ -35,7 +35,7 @@ def test_compute_create_without_id(http_controller, controller): assert response.json["compute_id"] is not None assert "password" not in response.json - assert len(controller.computes) == 2 + assert len(controller.computes) == 1 assert controller.computes[response.json["compute_id"]].host == "example.com" @@ -55,7 +55,7 @@ def test_compute_create_with_id(http_controller, controller): assert response.json["user"] == "julien" assert "password" not in response.json - assert len(controller.computes) == 2 + assert len(controller.computes) == 1 assert controller.computes["my_compute_id"].host == "example.com" @@ -148,13 +148,13 @@ def test_compute_delete(http_controller, controller): assert response.status == 201 response = http_controller.get("/computes") - assert len(response.json) == 2 + assert len(response.json) == 1 response = http_controller.delete("/computes/my_compute_id") assert response.status == 204 response = http_controller.get("/computes") - assert len(response.json) == 1 + assert len(response.json) == 0 def test_compute_list_images(http_controller, controller): diff --git a/tests/handlers/api/controller/test_gns3vm.py b/tests/handlers/api/controller/test_gns3vm.py new file mode 100644 index 00000000..21e156d2 --- /dev/null +++ b/tests/handlers/api/controller/test_gns3vm.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2016 GNS3 Technologies Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +from tests.utils import asyncio_patch + + +def test_list_vms(http_controller): + with asyncio_patch("gns3server.controller.gns3vm.vmware_gns3_vm.VMwareGNS3VM.list", return_value=[{"vmname": "test"}]): + response = http_controller.get('/gns3vm/engines/vmware/vms', example=True) + assert response.status == 200 + assert response.json == [ + { + "vmname": "test" + } + ] + + +def test_engines(http_controller): + response = http_controller.get('/gns3vm/engines', example=True) + assert response.status == 200 + assert len(response.json) > 0 + + +def test_put_gns3vm(http_controller): + response = http_controller.put('/gns3vm', { + "vmname": "TEST VM" + }, example=True) + assert response.status == 201 + assert response.json["vmname"] == "TEST VM" + + +def test_get_gns3vm(http_controller): + response = http_controller.get('/gns3vm', example=True) + assert response.status == 200 + print(response.json)