From 58f1abff35f5d61224c492a861863890782c87ad Mon Sep 17 00:00:00 2001 From: Julien Duponchelle Date: Thu, 10 Mar 2016 10:32:07 +0100 Subject: [PATCH] Close, commit and delete supported for projects in controller --- gns3server/controller/__init__.py | 35 +++++- gns3server/controller/hypervisor.py | 25 +++- gns3server/controller/project.py | 89 ++++++++++++++ .../handlers/api/controller/__init__.py | 1 + .../api/controller/project_handler.py | 109 ++++++++++++++++++ gns3server/hypervisor/project.py | 42 ++----- gns3server/hypervisor/project_manager.py | 2 - gns3server/schemas/project.py | 17 +-- tests/conftest.py | 8 ++ tests/controller/__init__.py | 0 tests/controller/test_controller.py | 51 ++++++++ tests/controller/test_hypervisor.py | 24 +++- tests/controller/test_project.py | 33 ++++++ tests/handlers/api/controller/test_project.py | 109 ++++++++++++++++++ tests/handlers/api/hypervisor/test_project.py | 46 ++++---- tests/hypervisor/iou/test_iou_manager.py | 4 +- tests/hypervisor/test_port_manager.py | 16 +-- tests/hypervisor/test_project.py | 62 +++++----- tests/hypervisor/vpcs/test_vpcs_manager.py | 4 +- 19 files changed, 560 insertions(+), 117 deletions(-) create mode 100644 gns3server/controller/project.py create mode 100644 gns3server/handlers/api/controller/project_handler.py create mode 100644 tests/controller/__init__.py create mode 100644 tests/controller/test_project.py create mode 100644 tests/handlers/api/controller/test_project.py diff --git a/gns3server/controller/__init__.py b/gns3server/controller/__init__.py index c95c7c7b..989bc9e3 100644 --- a/gns3server/controller/__init__.py +++ b/gns3server/controller/__init__.py @@ -15,6 +15,8 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import asyncio +import aiohttp from ..config import Config @@ -24,6 +26,7 @@ class Controller: def __init__(self): self._hypervisors = {} + self._projects = {} def isEnabled(self): """ @@ -36,7 +39,8 @@ class Controller: """ Add a server to the dictionnary of hypervisors controlled by GNS3 """ - self._hypervisors[hypervisor.id] = hypervisor + if hypervisor.id not in self._hypervisors: + self._hypervisors[hypervisor.id] = hypervisor @property def hypervisors(self): @@ -45,6 +49,35 @@ class Controller: """ return self._hypervisors + @asyncio.coroutine + def addProject(self, project): + """ + Add a server to the dictionnary of projects controlled by GNS3 + """ + if project.id not in self._projects: + self._projects[project.id] = project + for hypervisor in self._hypervisors.values(): + yield from project.addHypervisor(hypervisor) + + def getProject(self, project_id): + """ + Return a server or raise a 404 + """ + try: + return self._projects[project_id] + except KeyError: + raise aiohttp.web.HTTPNotFound(text="Project ID {} doesn't exist".format(project_id)) + + def removeProject(self, project): + del self._projects[project.id] + + @property + def projects(self): + """ + :returns: The dictionnary of hypervisors managed by GNS3 + """ + return self._projects + @staticmethod def instance(): """ diff --git a/gns3server/controller/hypervisor.py b/gns3server/controller/hypervisor.py index ace64cc7..02d54765 100644 --- a/gns3server/controller/hypervisor.py +++ b/gns3server/controller/hypervisor.py @@ -15,6 +15,9 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +import aiohttp +import asyncio +import json from ..controller.controller_error import ControllerError from ..config import Config @@ -49,7 +52,7 @@ class Hypervisor: # If the hypervisor is local but the hypervisor id is local # it's a configuration issue - if hypervisor_id == "local" and Config.instance().get_section_config("Hypervisor")["local"] is False: + if hypervisor_id == "local" and Config.instance().get_section_config("Server")["local"] is False: raise HypervisorError("The local hypervisor is started without --local") @property @@ -76,3 +79,23 @@ class Hypervisor: "connected": self._connected, "version": self._version } + + @asyncio.coroutine + def _httpQuery(self, method, path, data=None): + with aiohttp.Timeout(10): + with aiohttp.ClientSession() as session: + url = "{}://{}:{}/v2/hypervisor{}".format(self._protocol, self._host, self._port, path) + headers = {'content-type': 'application/json'} + if hasattr(data, '__json__'): + data = data.__json__() + data = json.dumps(data) + response = yield from session.request(method, url, headers=headers, data=data) + print(response.status) + assert response.status < 300 + body = yield from response.read() + yield from response.release() + return body + + @asyncio.coroutine + def post(self, path, data): + yield from self._httpQuery("POST", path, data) diff --git a/gns3server/controller/project.py b/gns3server/controller/project.py new file mode 100644 index 00000000..f8f26dc1 --- /dev/null +++ b/gns3server/controller/project.py @@ -0,0 +1,89 @@ +#!/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 asyncio +from uuid import UUID, uuid4 + + +class Project: + """ + A project inside controller + + :param project_id: force project identifier (None by default auto generate an UUID) + :param path: path of the project. (None use the standard directory) + :param temporary: boolean to tell if the project is a temporary project (destroy when closed) + """ + + def __init__(self, name=None, project_id=None, path=None, temporary=False): + + self._name = name + if project_id is None: + self._id = str(uuid4()) + else: + try: + UUID(project_id, version=4) + except ValueError: + raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id)) + self._id = project_id + self._path = path + self._temporary = temporary + self._hypervisors = set() + + @property + def name(self): + return self._name + + @property + def id(self): + return self._id + + @property + def temporary(self): + return self._temporary + + @property + def path(self): + return self._path + + @asyncio.coroutine + def addHypervisor(self, hypervisor): + self._hypervisors.add(hypervisor) + yield from hypervisor.post("/projects", self) + + @asyncio.coroutine + def close(self): + for hypervisor in self._hypervisors: + yield from hypervisor.post("/projects/{}/close".format(self._id)) + + @asyncio.coroutine + def commit(self): + for hypervisor in self._hypervisors: + yield from hypervisor.post("/projects/{}/commit".format(self._id)) + + @asyncio.coroutine + def delete(self): + for hypervisor in self._hypervisors: + yield from hypervisor.delete("/projects/{}".format(self._id)) + + def __json__(self): + + return { + "name": self._name, + "project_id": self._id, + "temporary": self._temporary, + "path": self._path + } diff --git a/gns3server/handlers/api/controller/__init__.py b/gns3server/handlers/api/controller/__init__.py index 7bd43799..a195ab7f 100644 --- a/gns3server/handlers/api/controller/__init__.py +++ b/gns3server/handlers/api/controller/__init__.py @@ -16,4 +16,5 @@ # along with this program. If not, see . from .hypervisor_handler import HypervisorHandler +from .project_handler import ProjectHandler from .version_handler import VersionHandler diff --git a/gns3server/handlers/api/controller/project_handler.py b/gns3server/handlers/api/controller/project_handler.py new file mode 100644 index 00000000..7b526c7d --- /dev/null +++ b/gns3server/handlers/api/controller/project_handler.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 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 aiohttp +import asyncio + + +from ....web.route import Route +from ....schemas.project import PROJECT_OBJECT_SCHEMA, PROJECT_CREATE_SCHEMA +from ....controller import Controller +from ....controller.project import Project + + +import logging +log = logging.getLogger() + + +class ProjectHandler: + + @classmethod + @Route.post( + r"/projects", + description="Create a new project on the server", + status_codes={ + 201: "Project created", + 409: "Project already created" + }, + output=PROJECT_OBJECT_SCHEMA, + input=PROJECT_CREATE_SCHEMA) + def create_project(request, response): + + controller = Controller.instance() + project = Project(name=request.json.get("name"), + path=request.json.get("path"), + project_id=request.json.get("project_id"), + temporary=request.json.get("temporary", False)) + yield from controller.addProject(project) + response.set_status(201) + response.json(project) + + @classmethod + @Route.post( + r"/projects/{project_id}/commit", + description="Write changes on disk", + parameters={ + "project_id": "The UUID of the project", + }, + status_codes={ + 204: "Changes have been written on disk", + 404: "The project doesn't exist" + }) + def commit(request, response): + + controller = Controller.instance() + project = controller.getProject(request.match_info["project_id"]) + yield from project.commit() + response.set_status(204) + + @classmethod + @Route.post( + r"/projects/{project_id}/close", + description="Close a project", + parameters={ + "project_id": "The UUID of the project", + }, + status_codes={ + 204: "The project has been closed", + 404: "The project doesn't exist" + }) + def close(request, response): + + controller = Controller.instance() + project = controller.getProject(request.match_info["project_id"]) + yield from project.close() + controller.removeProject(project) + response.set_status(204) + + @classmethod + @Route.delete( + r"/projects/{project_id}", + description="Delete a project from disk", + parameters={ + "project_id": "The UUID of the project", + }, + status_codes={ + 204: "Changes have been written on disk", + 404: "The project doesn't exist" + }) + def delete(request, response): + + controller = Controller.instance() + project = controller.getProject(request.match_info["project_id"]) + yield from project.delete() + controller.removeProject(project) + response.set_status(204) diff --git a/gns3server/hypervisor/project.py b/gns3server/hypervisor/project.py index 2b6dfea8..322512ac 100644 --- a/gns3server/hypervisor/project.py +++ b/gns3server/hypervisor/project.py @@ -38,27 +38,17 @@ class Project: :param project_id: force project identifier (None by default auto generate an UUID) :param path: path of the project. (None use the standard directory) - :param location: parent path of the project. (None should create a tmp directory) :param temporary: boolean to tell if the project is a temporary project (destroy when closed) """ - def __init__(self, name=None, project_id=None, path=None, location=None, temporary=False): + def __init__(self, name=None, project_id=None, path=None, temporary=False): self._name = name - if project_id is None: - self._id = str(uuid4()) - else: - try: - UUID(project_id, version=4) - except ValueError: - raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id)) - self._id = project_id - - self._location = None - if location is None: - self._location = self._config().get("project_directory", self._get_default_project_directory()) - else: - self.location = location + try: + UUID(project_id, version=4) + except ValueError: + raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id)) + self._id = project_id self._vms = set() self._vms_to_destroy = set() @@ -70,7 +60,8 @@ class Project: self._listeners = set() if path is None: - path = os.path.join(self._location, self._id) + location = self._config().get("project_directory", self._get_default_project_directory()) + path = os.path.join(location, self._id) try: os.makedirs(path, exist_ok=True) except OSError as e: @@ -84,9 +75,7 @@ class Project: return { "name": self._name, "project_id": self._id, - "location": self._location, - "temporary": self._temporary, - "path": self._path, + "temporary": self._temporary } def _config(self): @@ -118,19 +107,6 @@ class Project: return self._id - @property - def location(self): - - return self._location - - @location.setter - def location(self, location): - - if location != self._location and self.is_local() is False: - raise aiohttp.web.HTTPForbidden(text="You are not allowed to modify the project directory location") - - self._location = location - @property def path(self): diff --git a/gns3server/hypervisor/project_manager.py b/gns3server/hypervisor/project_manager.py index 7ecf4e54..cb4011de 100644 --- a/gns3server/hypervisor/project_manager.py +++ b/gns3server/hypervisor/project_manager.py @@ -79,8 +79,6 @@ class ProjectManager: if project_id is not None and project_id in self._projects: return self._projects[project_id] - # FIXME: should we have an error? - #raise aiohttp.web.HTTPConflict(text="Project ID {} is already in use on this server".format(project_id)) project = Project(name=name, project_id=project_id, path=path, temporary=temporary) self._projects[project.id] = project return project diff --git a/gns3server/schemas/project.py b/gns3server/schemas/project.py index ec2b9636..4f00ab67 100644 --- a/gns3server/schemas/project.py +++ b/gns3server/schemas/project.py @@ -78,16 +78,6 @@ PROJECT_OBJECT_SCHEMA = { "type": ["string", "null"], "minLength": 1 }, - "location": { - "description": "Base directory where the project should be created on remote server", - "type": "string", - "minLength": 1 - }, - "path": { - "description": "Directory of the project on the server", - "type": "string", - "minLength": 1 - }, "project_id": { "description": "Project UUID", "type": "string", @@ -99,9 +89,14 @@ PROJECT_OBJECT_SCHEMA = { "description": "If project is a temporary project", "type": "boolean" }, + "path": { + "description": "Project directory", + "type": ["string", "null"], + "minLength": 1 + } }, "additionalProperties": False, - "required": ["location", "project_id", "temporary"] + "required": ["project_id", "temporary"] } PROJECT_LIST_SCHEMA = { diff --git a/tests/conftest.py b/tests/conftest.py index 90f437f2..d97688c3 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -240,3 +240,11 @@ def linux_platform(): sys.platform = "linuxdebian" yield sys.plaform = old_platform + + +@pytest.fixture +def async_run(loop): + """ + Shortcut for running in asyncio loop + """ + return lambda x: loop.run_until_complete(asyncio.async(x)) diff --git a/tests/controller/__init__.py b/tests/controller/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/controller/test_controller.py b/tests/controller/test_controller.py index 870dc291..7f78ea8a 100644 --- a/tests/controller/test_controller.py +++ b/tests/controller/test_controller.py @@ -16,9 +16,14 @@ # along with this program. If not, see . import pytest +import uuid +import aiohttp +from unittest.mock import MagicMock + from gns3server.controller import Controller from gns3server.controller.hypervisor import Hypervisor +from gns3server.controller.project import Project from gns3server.config import Config @@ -38,3 +43,49 @@ def test_addHypervisor(controller): assert len(controller.hypervisors) == 1 controller.addHypervisor(Hypervisor("test2")) assert len(controller.hypervisors) == 2 + + +def test_addProject(controller, async_run): + uuid1 = str(uuid.uuid4()) + project1 = Project(project_id=uuid1) + uuid2 = str(uuid.uuid4()) + + async_run(controller.addProject(project1)) + assert len(controller.projects) == 1 + async_run(controller.addProject(Project(project_id=uuid1))) + assert len(controller.projects) == 1 + async_run(controller.addProject(Project(project_id=uuid2))) + assert len(controller.projects) == 2 + + +def test_removeProject(controller, async_run): + uuid1 = str(uuid.uuid4()) + project1 = Project(project_id=uuid1) + + async_run(controller.addProject(project1)) + assert len(controller.projects) == 1 + + controller.removeProject(project1) + assert len(controller.projects) == 0 + + +def test_addProject_with_hypervisor(controller, async_run): + uuid1 = str(uuid.uuid4()) + project1 = Project(project_id=uuid1) + + hypervisor = Hypervisor("test1") + hypervisor.post = MagicMock() + controller.addHypervisor(hypervisor) + + async_run(controller.addProject(project1)) + hypervisor.post.assert_called_with("/projects", project1) + + +def test_getProject(controller, async_run): + uuid1 = str(uuid.uuid4()) + project = Project(project_id=uuid1) + + async_run(controller.addProject(project)) + assert controller.getProject(uuid1) == project + with pytest.raises(aiohttp.web.HTTPNotFound): + assert controller.getProject("dsdssd") diff --git a/tests/controller/test_hypervisor.py b/tests/controller/test_hypervisor.py index d97976fd..4512f975 100644 --- a/tests/controller/test_hypervisor.py +++ b/tests/controller/test_hypervisor.py @@ -17,10 +17,13 @@ import pytest -from unittest.mock import patch +import json +from unittest.mock import patch, MagicMock +from gns3server.controller.project import Project from gns3server.controller.hypervisor import Hypervisor, HypervisorError from gns3server.version import __version__ +from tests.utils import asyncio_patch @pytest.fixture @@ -46,6 +49,25 @@ def test_hypervisor_local(hypervisor): s = Hypervisor("test") +def test_hypervisor_httpQuery(hypervisor, async_run): + response = MagicMock() + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + response.status = 200 + + async_run(hypervisor.post("/projects", {"a": "b"})) + mock.assert_called_with("POST", "https://example.com:84/v2/hypervisor/projects", data='{"a": "b"}', headers={'content-type': 'application/json'}) + + +def test_hypervisor_httpQuery_project(hypervisor, async_run): + response = MagicMock() + with asyncio_patch("aiohttp.ClientSession.request", return_value=response) as mock: + response.status = 200 + + project = Project() + async_run(hypervisor.post("/projects", project)) + mock.assert_called_with("POST", "https://example.com:84/v2/hypervisor/projects", data=json.dumps(project.__json__()), headers={'content-type': 'application/json'}) + + def test_json(hypervisor): assert hypervisor.__json__() == { "hypervisor_id": "my_hypervisor_id", diff --git a/tests/controller/test_project.py b/tests/controller/test_project.py new file mode 100644 index 00000000..2ab6e466 --- /dev/null +++ b/tests/controller/test_project.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 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 gns3server.controller.project import Project + + +def test_affect_uuid(): + p = Project() + assert len(p.id) == 36 + + p = Project(project_id='00010203-0405-0607-0809-0a0b0c0d0e0f') + assert p.id == '00010203-0405-0607-0809-0a0b0c0d0e0f' + + +def test_json(tmpdir): + p = Project() + assert p.__json__() == {"name": p.name, "project_id": p.id, "temporary": False, "path": None} diff --git a/tests/handlers/api/controller/test_project.py b/tests/handlers/api/controller/test_project.py new file mode 100644 index 00000000..e3a49028 --- /dev/null +++ b/tests/handlers/api/controller/test_project.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 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 . + +""" +This test suite check /project endpoint +""" + +import uuid +import os +import asyncio +import aiohttp +import pytest + + +from unittest.mock import patch +from tests.utils import asyncio_patch + +from gns3server.handlers.api.controller.project_handler import ProjectHandler +from gns3server.controller import Controller + + +@pytest.fixture +def project(http_controller): + u = str(uuid.uuid4()) + query = {"name": "test", "project_id": u} + response = http_controller.post("/projects", query) + return Controller.instance().getProject(u) + + +def test_create_project_with_path(http_controller, tmpdir): + with asyncio_patch("gns3server.controller.Controller.addProject") as mock: + response = http_controller.post("/projects", {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}) + assert response.status == 201 + assert response.json["name"] == "test" + assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f" + assert mock.called + + +def test_create_project_without_dir(http_controller): + query = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"} + response = http_controller.post("/projects", query, example=True) + assert response.status == 201 + assert response.json["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f" + assert response.json["temporary"] is False + assert response.json["name"] == "test" + + +def test_create_temporary_project(http_controller): + query = {"name": "test", "temporary": True, "project_id": "20010203-0405-0607-0809-0a0b0c0d0e0f"} + response = http_controller.post("/projects", query) + assert response.status == 201 + assert response.json["project_id"] == "20010203-0405-0607-0809-0a0b0c0d0e0f" + assert response.json["temporary"] is True + assert response.json["name"] == "test" + + +def test_create_project_with_uuid(http_controller): + query = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f"} + response = http_controller.post("/projects", query) + assert response.status == 201 + assert response.json["project_id"] == "30010203-0405-0607-0809-0a0b0c0d0e0f" + assert response.json["name"] == "test" + + +def test_commit_project(http_controller, project): + with asyncio_patch("gns3server.controller.project.Project.commit", return_value=True) as mock: + response = http_controller.post("/projects/{project_id}/commit".format(project_id=project.id), example=True) + assert response.status == 204 + assert mock.called + + +def test_commit_project_invalid_uuid(http_controller): + response = http_controller.post("/projects/{project_id}/commit".format(project_id=uuid.uuid4())) + assert response.status == 404 + + +def test_delete_project(http_controller, project): + with asyncio_patch("gns3server.controller.project.Project.delete", return_value=True) as mock: + response = http_controller.delete("/projects/{project_id}".format(project_id=project.id), example=True) + assert response.status == 204 + assert mock.called + assert project not in Controller.instance().projects + + +def test_delete_project_invalid_uuid(http_controller): + response = http_controller.delete("/projects/{project_id}".format(project_id=uuid.uuid4())) + assert response.status == 404 + + +def test_close_project(http_controller, project): + with asyncio_patch("gns3server.controller.project.Project.close", return_value=True) as mock: + response = http_controller.post("/projects/{project_id}/close".format(project_id=project.id), example=True) + assert response.status == 204 + assert mock.called + assert project not in Controller.instance().projects diff --git a/tests/handlers/api/hypervisor/test_project.py b/tests/handlers/api/hypervisor/test_project.py index dcf12ced..df8f2644 100644 --- a/tests/handlers/api/hypervisor/test_project.py +++ b/tests/handlers/api/hypervisor/test_project.py @@ -33,74 +33,72 @@ from gns3server.hypervisor.project_manager import ProjectManager def test_create_project_with_path(http_hypervisor, tmpdir): with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): - response = http_hypervisor.post("/projects", {"name": "test", "path": str(tmpdir)}) + response = http_hypervisor.post("/projects", {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"}) assert response.status == 201 - assert response.json["path"] == str(tmpdir) assert response.json["name"] == "test" + assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f" def test_create_project_without_dir(http_hypervisor): - query = {"name": "test"} + query = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"} response = http_hypervisor.post("/projects", query, example=True) assert response.status == 201 - assert response.json["project_id"] is not None + assert response.json["project_id"] == "10010203-0405-0607-0809-0a0b0c0d0e0f" assert response.json["temporary"] is False assert response.json["name"] == "test" def test_create_temporary_project(http_hypervisor): - query = {"name": "test", "temporary": True} + query = {"name": "test", "temporary": True, "project_id": "20010203-0405-0607-0809-0a0b0c0d0e0f"} response = http_hypervisor.post("/projects", query) assert response.status == 201 - assert response.json["project_id"] is not None + assert response.json["project_id"] == "20010203-0405-0607-0809-0a0b0c0d0e0f" assert response.json["temporary"] is True assert response.json["name"] == "test" def test_create_project_with_uuid(http_hypervisor): - query = {"name": "test", "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"} + query = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f"} response = http_hypervisor.post("/projects", query) assert response.status == 201 - assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f" + assert response.json["project_id"] == "30010203-0405-0607-0809-0a0b0c0d0e0f" assert response.json["name"] == "test" def test_show_project(http_hypervisor): - query = {"name": "test", "project_id": "00010203-0405-0607-0809-0a0b0c0d0e02", "temporary": False} + query = {"name": "test", "project_id": "40010203-0405-0607-0809-0a0b0c0d0e02", "temporary": False} response = http_hypervisor.post("/projects", query) assert response.status == 201 - response = http_hypervisor.get("/projects/00010203-0405-0607-0809-0a0b0c0d0e02", example=True) - assert len(response.json.keys()) == 5 - assert len(response.json["location"]) > 0 - assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e02" + response = http_hypervisor.get("/projects/40010203-0405-0607-0809-0a0b0c0d0e02", example=True) + assert len(response.json.keys()) == 3 + assert response.json["project_id"] == "40010203-0405-0607-0809-0a0b0c0d0e02" assert response.json["temporary"] is False assert response.json["name"] == "test" def test_show_project_invalid_uuid(http_hypervisor): - response = http_hypervisor.get("/projects/00010203-0405-0607-0809-0a0b0c0d0e42") + response = http_hypervisor.get("/projects/50010203-0405-0607-0809-0a0b0c0d0e42") assert response.status == 404 def test_list_projects(http_hypervisor): ProjectManager.instance()._projects = {} - query = {"name": "test", "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"} + query = {"name": "test", "project_id": "51010203-0405-0607-0809-0a0b0c0d0e0f"} response = http_hypervisor.post("/projects", query) assert response.status == 201 - query = {"name": "test", "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0b"} + query = {"name": "test", "project_id": "52010203-0405-0607-0809-0a0b0c0d0e0b"} response = http_hypervisor.post("/projects", query) assert response.status == 201 response = http_hypervisor.get("/projects", example=True) assert response.status == 200 - print(response.json) assert len(response.json) == 2 - assert response.json[0]["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0b" or response.json[1]["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0b" + assert "51010203-0405-0607-0809-0a0b0c0d0e0f" in [p["project_id"] for p in response.json] def test_update_temporary_project(http_hypervisor): - query = {"name": "test", "temporary": True} + query = {"name": "test", "temporary": True, "project_id": "60010203-0405-0607-0809-0a0b0c0d0e0b"} response = http_hypervisor.post("/projects", query) assert response.status == 201 query = {"name": "test", "temporary": False} @@ -115,13 +113,12 @@ def test_update_path_project_temporary(http_hypervisor, tmpdir): os.makedirs(str(tmpdir / "b")) with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): - response = http_hypervisor.post("/projects", {"name": "first_name", "path": str(tmpdir / "a"), "temporary": True}) + response = http_hypervisor.post("/projects", {"name": "first_name", "path": str(tmpdir / "a"), "temporary": True, "project_id": "70010203-0405-0607-0809-0a0b0c0d0e0b"}) assert response.status == 201 assert response.json["name"] == "first_name" query = {"name": "second_name", "path": str(tmpdir / "b")} response = http_hypervisor.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) assert response.status == 200 - assert response.json["path"] == str(tmpdir / "b") assert response.json["name"] == "second_name" assert not os.path.exists(str(tmpdir / "a")) @@ -134,13 +131,12 @@ def test_update_path_project_non_temporary(http_hypervisor, tmpdir): os.makedirs(str(tmpdir / "b")) with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): - response = http_hypervisor.post("/projects", {"name": "first_name", "path": str(tmpdir / "a")}) + response = http_hypervisor.post("/projects", {"name": "first_name", "path": str(tmpdir / "a"), "project_id": "80010203-0405-0607-0809-0a0b0c0d0e0b"}) assert response.status == 201 assert response.json["name"] == "first_name" query = {"name": "second_name", "path": str(tmpdir / "b")} response = http_hypervisor.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) assert response.status == 200 - assert response.json["path"] == str(tmpdir / "b") assert response.json["name"] == "second_name" assert os.path.exists(str(tmpdir / "a")) @@ -150,7 +146,7 @@ def test_update_path_project_non_temporary(http_hypervisor, tmpdir): def test_update_path_project_non_local(http_hypervisor, tmpdir): with patch("gns3server.hypervisor.project.Project.is_local", return_value=False): - response = http_hypervisor.post("/projects", {"name": "first_name"}) + response = http_hypervisor.post("/projects", {"name": "first_name", "project_id": "90010203-0405-0607-0809-0a0b0c0d0e0b"}) assert response.status == 201 query = {"name": "second_name", "path": str(tmpdir)} response = http_hypervisor.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) @@ -247,7 +243,7 @@ def test_list_files(http_hypervisor, project): def test_get_file(http_hypervisor, tmpdir): with patch("gns3server.config.Config.get_section_config", return_value={"project_directory": str(tmpdir)}): - project = ProjectManager.instance().create_project() + project = ProjectManager.instance().create_project(project_id="01010203-0405-0607-0809-0a0b0c0d0e0b") with open(os.path.join(project.path, "hello"), "w+") as f: f.write("world") diff --git a/tests/hypervisor/iou/test_iou_manager.py b/tests/hypervisor/iou/test_iou_manager.py index 384f435d..68b47026 100644 --- a/tests/hypervisor/iou/test_iou_manager.py +++ b/tests/hypervisor/iou/test_iou_manager.py @@ -58,8 +58,8 @@ def test_get_application_id_multiple_project(loop, iou): vm1_id = str(uuid.uuid4()) vm2_id = str(uuid.uuid4()) vm3_id = str(uuid.uuid4()) - project1 = ProjectManager.instance().create_project() - project2 = ProjectManager.instance().create_project() + project1 = ProjectManager.instance().create_project(project_id=str(uuid.uuid4())) + project2 = ProjectManager.instance().create_project(project_id=str(uuid.uuid4())) loop.run_until_complete(iou.create_vm("PC 1", project1.id, vm1_id)) loop.run_until_complete(iou.create_vm("PC 2", project1.id, vm2_id)) loop.run_until_complete(iou.create_vm("PC 2", project2.id, vm3_id)) diff --git a/tests/hypervisor/test_port_manager.py b/tests/hypervisor/test_port_manager.py index 7d7d6e5e..c2b26751 100644 --- a/tests/hypervisor/test_port_manager.py +++ b/tests/hypervisor/test_port_manager.py @@ -18,14 +18,16 @@ import aiohttp import pytest import sys +import uuid from unittest.mock import patch + from gns3server.hypervisor.port_manager import PortManager from gns3server.hypervisor.project import Project def test_reserve_tcp_port(): pm = PortManager() - project = Project() + project = Project(project_id=str(uuid.uuid4())) pm.reserve_tcp_port(2001, project) with patch("gns3server.hypervisor.project.Project.emit") as mock_emit: port = pm.reserve_tcp_port(2001, project) @@ -35,7 +37,7 @@ def test_reserve_tcp_port(): def test_reserve_tcp_port_outside_range(): pm = PortManager() - project = Project() + project = Project(project_id=str(uuid.uuid4())) with patch("gns3server.hypervisor.project.Project.emit") as mock_emit: port = pm.reserve_tcp_port(80, project) assert port != 80 @@ -49,7 +51,7 @@ def test_reserve_tcp_port_already_used_by_another_program(): """ pm = PortManager() - project = Project() + project = Project(project_id=str(uuid.uuid4())) with patch("gns3server.hypervisor.port_manager.PortManager._check_port") as mock_check: def execute_mock(host, port, *args): @@ -73,7 +75,7 @@ def test_reserve_tcp_port_already_used(): """ pm = PortManager() - project = Project() + project = Project(project_id=str(uuid.uuid4())) with patch("gns3server.hypervisor.port_manager.PortManager._check_port") as mock_check: def execute_mock(host, port, *args): @@ -92,7 +94,7 @@ def test_reserve_tcp_port_already_used(): def test_reserve_udp_port(): pm = PortManager() - project = Project() + project = Project(project_id=str(uuid.uuid4())) pm.reserve_udp_port(10000, project) with pytest.raises(aiohttp.web.HTTPConflict): pm.reserve_udp_port(10000, project) @@ -100,14 +102,14 @@ def test_reserve_udp_port(): def test_reserve_udp_port_outside_range(): pm = PortManager() - project = Project() + project = Project(project_id=str(uuid.uuid4())) with pytest.raises(aiohttp.web.HTTPConflict): pm.reserve_udp_port(80, project) def test_release_udp_port(): pm = PortManager() - project = Project() + project = Project(project_id=str(uuid.uuid4())) pm.reserve_udp_port(10000, project) pm.release_udp_port(10000, project) pm.reserve_udp_port(10000, project) diff --git a/tests/hypervisor/test_project.py b/tests/hypervisor/test_project.py index 8ca7a935..12304230 100644 --- a/tests/hypervisor/test_project.py +++ b/tests/hypervisor/test_project.py @@ -26,6 +26,7 @@ from unittest.mock import patch from tests.utils import asyncio_patch from gns3server.hypervisor.project import Project from gns3server.hypervisor.vpcs import VPCS, VPCSVM +from gns3server.config import Config @pytest.fixture(scope="module") @@ -42,32 +43,33 @@ def vm(project, manager, loop): def test_affect_uuid(): - p = Project() - assert len(p.id) == 36 - p = Project(project_id='00010203-0405-0607-0809-0a0b0c0d0e0f') assert p.id == '00010203-0405-0607-0809-0a0b0c0d0e0f' def test_path(tmpdir): + + directory = Config.instance().get_section_config("Server").get("project_directory") + with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): - p = Project(location=str(tmpdir)) - assert p.path == os.path.join(str(tmpdir), p.id) - assert os.path.exists(os.path.join(str(tmpdir), p.id)) - assert not os.path.exists(os.path.join(p.path, ".gns3_temporary")) + with patch("gns3server.hypervisor.project.Project._get_default_project_directory", return_value=directory): + p = Project(project_id=str(uuid4())) + assert p.path == os.path.join(directory, p.id) + assert os.path.exists(os.path.join(directory, p.id)) + assert not os.path.exists(os.path.join(p.path, ".gns3_temporary")) def test_init_path(tmpdir): with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): - p = Project(path=str(tmpdir)) + p = Project(path=str(tmpdir), project_id=str(uuid4())) assert p.path == str(tmpdir) def test_changing_path_temporary_flag(tmpdir): with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): - p = Project(temporary=True) + p = Project(temporary=True, project_id=str(uuid4())) assert os.path.exists(p.path) original_path = p.path assert os.path.exists(os.path.join(p.path, ".gns3_temporary")) @@ -76,53 +78,49 @@ def test_changing_path_temporary_flag(tmpdir): def test_temporary_path(): - p = Project(temporary=True) + p = Project(temporary=True, project_id=str(uuid4())) assert os.path.exists(p.path) assert os.path.exists(os.path.join(p.path, ".gns3_temporary")) def test_remove_temporary_flag(): - p = Project(temporary=True) + p = Project(temporary=True, project_id=str(uuid4())) assert os.path.exists(p.path) assert os.path.exists(os.path.join(p.path, ".gns3_temporary")) p.temporary = False assert not os.path.exists(os.path.join(p.path, ".gns3_temporary")) -def test_changing_location_not_allowed(tmpdir): - with patch("gns3server.hypervisor.project.Project.is_local", return_value=False): - with pytest.raises(aiohttp.web.HTTPForbidden): - p = Project(location=str(tmpdir)) - - def test_changing_path_not_allowed(tmpdir): with patch("gns3server.hypervisor.project.Project.is_local", return_value=False): with pytest.raises(aiohttp.web.HTTPForbidden): - p = Project() + p = Project(project_id=str(uuid4())) p.path = str(tmpdir) def test_changing_path_with_quote_not_allowed(tmpdir): with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): with pytest.raises(aiohttp.web.HTTPForbidden): - p = Project() + p = Project(project_id=str(uuid4())) p.path = str(tmpdir / "project\"53") def test_json(tmpdir): - p = Project() - assert p.__json__() == {"name": p.name, "location": p.location, "path": p.path, "project_id": p.id, "temporary": False} + p = Project(project_id=str(uuid4())) + assert p.__json__() == {"name": p.name, "project_id": p.id, "temporary": False} def test_vm_working_directory(tmpdir, vm): + directory = Config.instance().get_section_config("Server").get("project_directory") + with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): - p = Project(location=str(tmpdir)) - assert p.vm_working_directory(vm) == os.path.join(str(tmpdir), p.id, 'project-files', vm.module_name, vm.id) + p = Project(project_id=str(uuid4())) + assert p.vm_working_directory(vm) == os.path.join(directory, p.id, 'project-files', vm.module_name, vm.id) assert os.path.exists(p.vm_working_directory(vm)) def test_mark_vm_for_destruction(vm): - project = Project() + project = Project(project_id=str(uuid4())) project.add_vm(vm) project.mark_vm_for_destruction(vm) assert len(project._vms_to_destroy) == 1 @@ -130,7 +128,7 @@ def test_mark_vm_for_destruction(vm): def test_commit(manager, loop): - project = Project() + project = Project(project_id=str(uuid4())) vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) project.add_vm(vm) directory = project.vm_working_directory(vm) @@ -144,7 +142,7 @@ def test_commit(manager, loop): def test_commit_permission_issue(manager, loop): - project = Project() + project = Project(project_id=str(uuid4())) vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) project.add_vm(vm) directory = project.vm_working_directory(vm) @@ -158,7 +156,7 @@ def test_commit_permission_issue(manager, loop): def test_project_delete(loop): - project = Project() + project = Project(project_id=str(uuid4())) directory = project.path assert os.path.exists(directory) loop.run_until_complete(asyncio.async(project.delete())) @@ -166,7 +164,7 @@ def test_project_delete(loop): def test_project_delete_permission_issue(loop): - project = Project() + project = Project(project_id=str(uuid4())) directory = project.path assert os.path.exists(directory) os.chmod(directory, 0) @@ -176,7 +174,7 @@ def test_project_delete_permission_issue(loop): def test_project_add_vm(manager): - project = Project() + project = Project(project_id=str(uuid4())) vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) project.add_vm(vm) assert len(project.vms) == 1 @@ -193,7 +191,7 @@ def test_project_close(loop, vm, project): def test_project_close_temporary_project(loop, manager): """A temporary project is deleted when closed""" - project = Project(temporary=True) + project = Project(temporary=True, project_id=str(uuid4())) directory = project.path assert os.path.exists(directory) loop.run_until_complete(asyncio.async(project.close())) @@ -203,7 +201,7 @@ def test_project_close_temporary_project(loop, manager): def test_get_default_project_directory(monkeypatch): monkeypatch.undo() - project = Project() + project = Project(project_id=str(uuid4())) path = os.path.normpath(os.path.expanduser("~/GNS3/projects")) assert project._get_default_project_directory() == path assert os.path.exists(path) @@ -237,7 +235,7 @@ def test_clean_project_directory(tmpdir): def test_list_files(tmpdir, loop): with patch("gns3server.config.Config.get_section_config", return_value={"project_directory": str(tmpdir)}): - project = Project() + project = Project(project_id=str(uuid4())) path = project.path os.makedirs(os.path.join(path, "vm-1", "dynamips")) with open(os.path.join(path, "vm-1", "dynamips", "test.bin"), "w+") as f: diff --git a/tests/hypervisor/vpcs/test_vpcs_manager.py b/tests/hypervisor/vpcs/test_vpcs_manager.py index ba45a06c..cabc4fab 100644 --- a/tests/hypervisor/vpcs/test_vpcs_manager.py +++ b/tests/hypervisor/vpcs/test_vpcs_manager.py @@ -51,8 +51,8 @@ def test_get_mac_id_multiple_project(loop, port_manager): vm1_id = str(uuid.uuid4()) vm2_id = str(uuid.uuid4()) vm3_id = str(uuid.uuid4()) - project1 = ProjectManager.instance().create_project() - project2 = ProjectManager.instance().create_project() + project1 = ProjectManager.instance().create_project(project_id=str(uuid.uuid4())) + project2 = ProjectManager.instance().create_project(project_id=str(uuid.uuid4())) loop.run_until_complete(vpcs.create_vm("PC 1", project1.id, vm1_id)) loop.run_until_complete(vpcs.create_vm("PC 2", project1.id, vm2_id)) loop.run_until_complete(vpcs.create_vm("PC 2", project2.id, vm3_id))