1
0
mirror of https://github.com/GNS3/gns3-server synced 2025-02-17 18:42:00 +00:00

Close, commit and delete supported for projects in controller

This commit is contained in:
Julien Duponchelle 2016-03-10 10:32:07 +01:00
parent 3296b97f59
commit 58f1abff35
No known key found for this signature in database
GPG Key ID: F1E2485547D4595D
19 changed files with 560 additions and 117 deletions

View File

@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import asyncio
import aiohttp
from ..config import Config from ..config import Config
@ -24,6 +26,7 @@ class Controller:
def __init__(self): def __init__(self):
self._hypervisors = {} self._hypervisors = {}
self._projects = {}
def isEnabled(self): def isEnabled(self):
""" """
@ -36,6 +39,7 @@ class Controller:
""" """
Add a server to the dictionnary of hypervisors controlled by GNS3 Add a server to the dictionnary of hypervisors controlled by GNS3
""" """
if hypervisor.id not in self._hypervisors:
self._hypervisors[hypervisor.id] = hypervisor self._hypervisors[hypervisor.id] = hypervisor
@property @property
@ -45,6 +49,35 @@ class Controller:
""" """
return self._hypervisors 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 @staticmethod
def instance(): def instance():
""" """

View File

@ -15,6 +15,9 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import aiohttp
import asyncio
import json
from ..controller.controller_error import ControllerError from ..controller.controller_error import ControllerError
from ..config import Config from ..config import Config
@ -49,7 +52,7 @@ class Hypervisor:
# If the hypervisor is local but the hypervisor id is local # If the hypervisor is local but the hypervisor id is local
# it's a configuration issue # 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") raise HypervisorError("The local hypervisor is started without --local")
@property @property
@ -76,3 +79,23 @@ class Hypervisor:
"connected": self._connected, "connected": self._connected,
"version": self._version "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)

View File

@ -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 <http://www.gnu.org/licenses/>.
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
}

View File

@ -16,4 +16,5 @@
# 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 .hypervisor_handler import HypervisorHandler from .hypervisor_handler import HypervisorHandler
from .project_handler import ProjectHandler
from .version_handler import VersionHandler from .version_handler import VersionHandler

View File

@ -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 <http://www.gnu.org/licenses/>.
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)

View File

@ -38,28 +38,18 @@ class Project:
:param project_id: force project identifier (None by default auto generate an UUID) :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 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) :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 self._name = name
if project_id is None:
self._id = str(uuid4())
else:
try: try:
UUID(project_id, version=4) UUID(project_id, version=4)
except ValueError: except ValueError:
raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id)) raise aiohttp.web.HTTPBadRequest(text="{} is not a valid UUID".format(project_id))
self._id = 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
self._vms = set() self._vms = set()
self._vms_to_destroy = set() self._vms_to_destroy = set()
self.temporary = temporary self.temporary = temporary
@ -70,7 +60,8 @@ class Project:
self._listeners = set() self._listeners = set()
if path is None: 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: try:
os.makedirs(path, exist_ok=True) os.makedirs(path, exist_ok=True)
except OSError as e: except OSError as e:
@ -84,9 +75,7 @@ class Project:
return { return {
"name": self._name, "name": self._name,
"project_id": self._id, "project_id": self._id,
"location": self._location, "temporary": self._temporary
"temporary": self._temporary,
"path": self._path,
} }
def _config(self): def _config(self):
@ -118,19 +107,6 @@ class Project:
return self._id 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 @property
def path(self): def path(self):

View File

@ -79,8 +79,6 @@ class ProjectManager:
if project_id is not None and project_id in self._projects: if project_id is not None and project_id in self._projects:
return self._projects[project_id] 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) project = Project(name=name, project_id=project_id, path=path, temporary=temporary)
self._projects[project.id] = project self._projects[project.id] = project
return project return project

View File

@ -78,16 +78,6 @@ PROJECT_OBJECT_SCHEMA = {
"type": ["string", "null"], "type": ["string", "null"],
"minLength": 1 "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": { "project_id": {
"description": "Project UUID", "description": "Project UUID",
"type": "string", "type": "string",
@ -99,9 +89,14 @@ PROJECT_OBJECT_SCHEMA = {
"description": "If project is a temporary project", "description": "If project is a temporary project",
"type": "boolean" "type": "boolean"
}, },
"path": {
"description": "Project directory",
"type": ["string", "null"],
"minLength": 1
}
}, },
"additionalProperties": False, "additionalProperties": False,
"required": ["location", "project_id", "temporary"] "required": ["project_id", "temporary"]
} }
PROJECT_LIST_SCHEMA = { PROJECT_LIST_SCHEMA = {

View File

@ -240,3 +240,11 @@ def linux_platform():
sys.platform = "linuxdebian" sys.platform = "linuxdebian"
yield yield
sys.plaform = old_platform 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))

View File

View File

@ -16,9 +16,14 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
import pytest import pytest
import uuid
import aiohttp
from unittest.mock import MagicMock
from gns3server.controller import Controller from gns3server.controller import Controller
from gns3server.controller.hypervisor import Hypervisor from gns3server.controller.hypervisor import Hypervisor
from gns3server.controller.project import Project
from gns3server.config import Config from gns3server.config import Config
@ -38,3 +43,49 @@ def test_addHypervisor(controller):
assert len(controller.hypervisors) == 1 assert len(controller.hypervisors) == 1
controller.addHypervisor(Hypervisor("test2")) controller.addHypervisor(Hypervisor("test2"))
assert len(controller.hypervisors) == 2 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")

View File

@ -17,10 +17,13 @@
import pytest 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.controller.hypervisor import Hypervisor, HypervisorError
from gns3server.version import __version__ from gns3server.version import __version__
from tests.utils import asyncio_patch
@pytest.fixture @pytest.fixture
@ -46,6 +49,25 @@ def test_hypervisor_local(hypervisor):
s = Hypervisor("test") 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): def test_json(hypervisor):
assert hypervisor.__json__() == { assert hypervisor.__json__() == {
"hypervisor_id": "my_hypervisor_id", "hypervisor_id": "my_hypervisor_id",

View File

@ -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 <http://www.gnu.org/licenses/>.
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}

View File

@ -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 <http://www.gnu.org/licenses/>.
"""
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

View File

@ -33,74 +33,72 @@ from gns3server.hypervisor.project_manager import ProjectManager
def test_create_project_with_path(http_hypervisor, tmpdir): def test_create_project_with_path(http_hypervisor, tmpdir):
with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): 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.status == 201
assert response.json["path"] == str(tmpdir)
assert response.json["name"] == "test" assert response.json["name"] == "test"
assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e0f"
def test_create_project_without_dir(http_hypervisor): 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) response = http_hypervisor.post("/projects", query, example=True)
assert response.status == 201 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["temporary"] is False
assert response.json["name"] == "test" assert response.json["name"] == "test"
def test_create_temporary_project(http_hypervisor): 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) response = http_hypervisor.post("/projects", query)
assert response.status == 201 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["temporary"] is True
assert response.json["name"] == "test" assert response.json["name"] == "test"
def test_create_project_with_uuid(http_hypervisor): 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) response = http_hypervisor.post("/projects", query)
assert response.status == 201 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" assert response.json["name"] == "test"
def test_show_project(http_hypervisor): 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) response = http_hypervisor.post("/projects", query)
assert response.status == 201 assert response.status == 201
response = http_hypervisor.get("/projects/00010203-0405-0607-0809-0a0b0c0d0e02", example=True) response = http_hypervisor.get("/projects/40010203-0405-0607-0809-0a0b0c0d0e02", example=True)
assert len(response.json.keys()) == 5 assert len(response.json.keys()) == 3
assert len(response.json["location"]) > 0 assert response.json["project_id"] == "40010203-0405-0607-0809-0a0b0c0d0e02"
assert response.json["project_id"] == "00010203-0405-0607-0809-0a0b0c0d0e02"
assert response.json["temporary"] is False assert response.json["temporary"] is False
assert response.json["name"] == "test" assert response.json["name"] == "test"
def test_show_project_invalid_uuid(http_hypervisor): 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 assert response.status == 404
def test_list_projects(http_hypervisor): def test_list_projects(http_hypervisor):
ProjectManager.instance()._projects = {} 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) response = http_hypervisor.post("/projects", query)
assert response.status == 201 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) response = http_hypervisor.post("/projects", query)
assert response.status == 201 assert response.status == 201
response = http_hypervisor.get("/projects", example=True) response = http_hypervisor.get("/projects", example=True)
assert response.status == 200 assert response.status == 200
print(response.json)
assert len(response.json) == 2 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): 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) response = http_hypervisor.post("/projects", query)
assert response.status == 201 assert response.status == 201
query = {"name": "test", "temporary": False} query = {"name": "test", "temporary": False}
@ -115,13 +113,12 @@ def test_update_path_project_temporary(http_hypervisor, tmpdir):
os.makedirs(str(tmpdir / "b")) os.makedirs(str(tmpdir / "b"))
with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): 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.status == 201
assert response.json["name"] == "first_name" assert response.json["name"] == "first_name"
query = {"name": "second_name", "path": str(tmpdir / "b")} 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) response = http_hypervisor.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True)
assert response.status == 200 assert response.status == 200
assert response.json["path"] == str(tmpdir / "b")
assert response.json["name"] == "second_name" assert response.json["name"] == "second_name"
assert not os.path.exists(str(tmpdir / "a")) 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")) os.makedirs(str(tmpdir / "b"))
with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): 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.status == 201
assert response.json["name"] == "first_name" assert response.json["name"] == "first_name"
query = {"name": "second_name", "path": str(tmpdir / "b")} 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) response = http_hypervisor.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True)
assert response.status == 200 assert response.status == 200
assert response.json["path"] == str(tmpdir / "b")
assert response.json["name"] == "second_name" assert response.json["name"] == "second_name"
assert os.path.exists(str(tmpdir / "a")) 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): def test_update_path_project_non_local(http_hypervisor, tmpdir):
with patch("gns3server.hypervisor.project.Project.is_local", return_value=False): 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 assert response.status == 201
query = {"name": "second_name", "path": str(tmpdir)} query = {"name": "second_name", "path": str(tmpdir)}
response = http_hypervisor.put("/projects/{project_id}".format(project_id=response.json["project_id"]), query, example=True) 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): def test_get_file(http_hypervisor, tmpdir):
with patch("gns3server.config.Config.get_section_config", return_value={"project_directory": str(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: with open(os.path.join(project.path, "hello"), "w+") as f:
f.write("world") f.write("world")

View File

@ -58,8 +58,8 @@ def test_get_application_id_multiple_project(loop, iou):
vm1_id = str(uuid.uuid4()) vm1_id = str(uuid.uuid4())
vm2_id = str(uuid.uuid4()) vm2_id = str(uuid.uuid4())
vm3_id = str(uuid.uuid4()) vm3_id = str(uuid.uuid4())
project1 = ProjectManager.instance().create_project() project1 = ProjectManager.instance().create_project(project_id=str(uuid.uuid4()))
project2 = ProjectManager.instance().create_project() 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 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", project1.id, vm2_id))
loop.run_until_complete(iou.create_vm("PC 2", project2.id, vm3_id)) loop.run_until_complete(iou.create_vm("PC 2", project2.id, vm3_id))

View File

@ -18,14 +18,16 @@
import aiohttp import aiohttp
import pytest import pytest
import sys import sys
import uuid
from unittest.mock import patch from unittest.mock import patch
from gns3server.hypervisor.port_manager import PortManager from gns3server.hypervisor.port_manager import PortManager
from gns3server.hypervisor.project import Project from gns3server.hypervisor.project import Project
def test_reserve_tcp_port(): def test_reserve_tcp_port():
pm = PortManager() pm = PortManager()
project = Project() project = Project(project_id=str(uuid.uuid4()))
pm.reserve_tcp_port(2001, project) pm.reserve_tcp_port(2001, project)
with patch("gns3server.hypervisor.project.Project.emit") as mock_emit: with patch("gns3server.hypervisor.project.Project.emit") as mock_emit:
port = pm.reserve_tcp_port(2001, project) port = pm.reserve_tcp_port(2001, project)
@ -35,7 +37,7 @@ def test_reserve_tcp_port():
def test_reserve_tcp_port_outside_range(): def test_reserve_tcp_port_outside_range():
pm = PortManager() pm = PortManager()
project = Project() project = Project(project_id=str(uuid.uuid4()))
with patch("gns3server.hypervisor.project.Project.emit") as mock_emit: with patch("gns3server.hypervisor.project.Project.emit") as mock_emit:
port = pm.reserve_tcp_port(80, project) port = pm.reserve_tcp_port(80, project)
assert port != 80 assert port != 80
@ -49,7 +51,7 @@ def test_reserve_tcp_port_already_used_by_another_program():
""" """
pm = PortManager() pm = PortManager()
project = Project() project = Project(project_id=str(uuid.uuid4()))
with patch("gns3server.hypervisor.port_manager.PortManager._check_port") as mock_check: with patch("gns3server.hypervisor.port_manager.PortManager._check_port") as mock_check:
def execute_mock(host, port, *args): def execute_mock(host, port, *args):
@ -73,7 +75,7 @@ def test_reserve_tcp_port_already_used():
""" """
pm = PortManager() pm = PortManager()
project = Project() project = Project(project_id=str(uuid.uuid4()))
with patch("gns3server.hypervisor.port_manager.PortManager._check_port") as mock_check: with patch("gns3server.hypervisor.port_manager.PortManager._check_port") as mock_check:
def execute_mock(host, port, *args): def execute_mock(host, port, *args):
@ -92,7 +94,7 @@ def test_reserve_tcp_port_already_used():
def test_reserve_udp_port(): def test_reserve_udp_port():
pm = PortManager() pm = PortManager()
project = Project() project = Project(project_id=str(uuid.uuid4()))
pm.reserve_udp_port(10000, project) pm.reserve_udp_port(10000, project)
with pytest.raises(aiohttp.web.HTTPConflict): with pytest.raises(aiohttp.web.HTTPConflict):
pm.reserve_udp_port(10000, project) pm.reserve_udp_port(10000, project)
@ -100,14 +102,14 @@ def test_reserve_udp_port():
def test_reserve_udp_port_outside_range(): def test_reserve_udp_port_outside_range():
pm = PortManager() pm = PortManager()
project = Project() project = Project(project_id=str(uuid.uuid4()))
with pytest.raises(aiohttp.web.HTTPConflict): with pytest.raises(aiohttp.web.HTTPConflict):
pm.reserve_udp_port(80, project) pm.reserve_udp_port(80, project)
def test_release_udp_port(): def test_release_udp_port():
pm = PortManager() pm = PortManager()
project = Project() project = Project(project_id=str(uuid.uuid4()))
pm.reserve_udp_port(10000, project) pm.reserve_udp_port(10000, project)
pm.release_udp_port(10000, project) pm.release_udp_port(10000, project)
pm.reserve_udp_port(10000, project) pm.reserve_udp_port(10000, project)

View File

@ -26,6 +26,7 @@ from unittest.mock import patch
from tests.utils import asyncio_patch from tests.utils import asyncio_patch
from gns3server.hypervisor.project import Project from gns3server.hypervisor.project import Project
from gns3server.hypervisor.vpcs import VPCS, VPCSVM from gns3server.hypervisor.vpcs import VPCS, VPCSVM
from gns3server.config import Config
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
@ -42,32 +43,33 @@ def vm(project, manager, loop):
def test_affect_uuid(): def test_affect_uuid():
p = Project()
assert len(p.id) == 36
p = Project(project_id='00010203-0405-0607-0809-0a0b0c0d0e0f') p = Project(project_id='00010203-0405-0607-0809-0a0b0c0d0e0f')
assert p.id == '00010203-0405-0607-0809-0a0b0c0d0e0f' assert p.id == '00010203-0405-0607-0809-0a0b0c0d0e0f'
def test_path(tmpdir): 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): with patch("gns3server.hypervisor.project.Project.is_local", return_value=True):
p = Project(location=str(tmpdir)) with patch("gns3server.hypervisor.project.Project._get_default_project_directory", return_value=directory):
assert p.path == os.path.join(str(tmpdir), p.id) p = Project(project_id=str(uuid4()))
assert os.path.exists(os.path.join(str(tmpdir), p.id)) 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")) assert not os.path.exists(os.path.join(p.path, ".gns3_temporary"))
def test_init_path(tmpdir): def test_init_path(tmpdir):
with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): 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) assert p.path == str(tmpdir)
def test_changing_path_temporary_flag(tmpdir): def test_changing_path_temporary_flag(tmpdir):
with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): 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) assert os.path.exists(p.path)
original_path = p.path original_path = p.path
assert os.path.exists(os.path.join(p.path, ".gns3_temporary")) 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(): 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(p.path)
assert os.path.exists(os.path.join(p.path, ".gns3_temporary")) assert os.path.exists(os.path.join(p.path, ".gns3_temporary"))
def test_remove_temporary_flag(): 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(p.path)
assert os.path.exists(os.path.join(p.path, ".gns3_temporary")) assert os.path.exists(os.path.join(p.path, ".gns3_temporary"))
p.temporary = False p.temporary = False
assert not os.path.exists(os.path.join(p.path, ".gns3_temporary")) 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): def test_changing_path_not_allowed(tmpdir):
with patch("gns3server.hypervisor.project.Project.is_local", return_value=False): with patch("gns3server.hypervisor.project.Project.is_local", return_value=False):
with pytest.raises(aiohttp.web.HTTPForbidden): with pytest.raises(aiohttp.web.HTTPForbidden):
p = Project() p = Project(project_id=str(uuid4()))
p.path = str(tmpdir) p.path = str(tmpdir)
def test_changing_path_with_quote_not_allowed(tmpdir): def test_changing_path_with_quote_not_allowed(tmpdir):
with patch("gns3server.hypervisor.project.Project.is_local", return_value=True): with patch("gns3server.hypervisor.project.Project.is_local", return_value=True):
with pytest.raises(aiohttp.web.HTTPForbidden): with pytest.raises(aiohttp.web.HTTPForbidden):
p = Project() p = Project(project_id=str(uuid4()))
p.path = str(tmpdir / "project\"53") p.path = str(tmpdir / "project\"53")
def test_json(tmpdir): def test_json(tmpdir):
p = Project() p = Project(project_id=str(uuid4()))
assert p.__json__() == {"name": p.name, "location": p.location, "path": p.path, "project_id": p.id, "temporary": False} assert p.__json__() == {"name": p.name, "project_id": p.id, "temporary": False}
def test_vm_working_directory(tmpdir, vm): 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): with patch("gns3server.hypervisor.project.Project.is_local", return_value=True):
p = Project(location=str(tmpdir)) p = Project(project_id=str(uuid4()))
assert p.vm_working_directory(vm) == os.path.join(str(tmpdir), p.id, 'project-files', vm.module_name, vm.id) 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)) assert os.path.exists(p.vm_working_directory(vm))
def test_mark_vm_for_destruction(vm): def test_mark_vm_for_destruction(vm):
project = Project() project = Project(project_id=str(uuid4()))
project.add_vm(vm) project.add_vm(vm)
project.mark_vm_for_destruction(vm) project.mark_vm_for_destruction(vm)
assert len(project._vms_to_destroy) == 1 assert len(project._vms_to_destroy) == 1
@ -130,7 +128,7 @@ def test_mark_vm_for_destruction(vm):
def test_commit(manager, loop): def test_commit(manager, loop):
project = Project() project = Project(project_id=str(uuid4()))
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
project.add_vm(vm) project.add_vm(vm)
directory = project.vm_working_directory(vm) directory = project.vm_working_directory(vm)
@ -144,7 +142,7 @@ def test_commit(manager, loop):
def test_commit_permission_issue(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) vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
project.add_vm(vm) project.add_vm(vm)
directory = project.vm_working_directory(vm) directory = project.vm_working_directory(vm)
@ -158,7 +156,7 @@ def test_commit_permission_issue(manager, loop):
def test_project_delete(loop): def test_project_delete(loop):
project = Project() project = Project(project_id=str(uuid4()))
directory = project.path directory = project.path
assert os.path.exists(directory) assert os.path.exists(directory)
loop.run_until_complete(asyncio.async(project.delete())) loop.run_until_complete(asyncio.async(project.delete()))
@ -166,7 +164,7 @@ def test_project_delete(loop):
def test_project_delete_permission_issue(loop): def test_project_delete_permission_issue(loop):
project = Project() project = Project(project_id=str(uuid4()))
directory = project.path directory = project.path
assert os.path.exists(directory) assert os.path.exists(directory)
os.chmod(directory, 0) os.chmod(directory, 0)
@ -176,7 +174,7 @@ def test_project_delete_permission_issue(loop):
def test_project_add_vm(manager): def test_project_add_vm(manager):
project = Project() project = Project(project_id=str(uuid4()))
vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager)
project.add_vm(vm) project.add_vm(vm)
assert len(project.vms) == 1 assert len(project.vms) == 1
@ -193,7 +191,7 @@ def test_project_close(loop, vm, project):
def test_project_close_temporary_project(loop, manager): def test_project_close_temporary_project(loop, manager):
"""A temporary project is deleted when closed""" """A temporary project is deleted when closed"""
project = Project(temporary=True) project = Project(temporary=True, project_id=str(uuid4()))
directory = project.path directory = project.path
assert os.path.exists(directory) assert os.path.exists(directory)
loop.run_until_complete(asyncio.async(project.close())) 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): def test_get_default_project_directory(monkeypatch):
monkeypatch.undo() monkeypatch.undo()
project = Project() project = Project(project_id=str(uuid4()))
path = os.path.normpath(os.path.expanduser("~/GNS3/projects")) path = os.path.normpath(os.path.expanduser("~/GNS3/projects"))
assert project._get_default_project_directory() == path assert project._get_default_project_directory() == path
assert os.path.exists(path) assert os.path.exists(path)
@ -237,7 +235,7 @@ def test_clean_project_directory(tmpdir):
def test_list_files(tmpdir, loop): def test_list_files(tmpdir, loop):
with patch("gns3server.config.Config.get_section_config", return_value={"project_directory": str(tmpdir)}): 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 path = project.path
os.makedirs(os.path.join(path, "vm-1", "dynamips")) os.makedirs(os.path.join(path, "vm-1", "dynamips"))
with open(os.path.join(path, "vm-1", "dynamips", "test.bin"), "w+") as f: with open(os.path.join(path, "vm-1", "dynamips", "test.bin"), "w+") as f:

View File

@ -51,8 +51,8 @@ def test_get_mac_id_multiple_project(loop, port_manager):
vm1_id = str(uuid.uuid4()) vm1_id = str(uuid.uuid4())
vm2_id = str(uuid.uuid4()) vm2_id = str(uuid.uuid4())
vm3_id = str(uuid.uuid4()) vm3_id = str(uuid.uuid4())
project1 = ProjectManager.instance().create_project() project1 = ProjectManager.instance().create_project(project_id=str(uuid.uuid4()))
project2 = ProjectManager.instance().create_project() 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 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", project1.id, vm2_id))
loop.run_until_complete(vpcs.create_vm("PC 2", project2.id, vm3_id)) loop.run_until_complete(vpcs.create_vm("PC 2", project2.id, vm3_id))