mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +00:00
parent
017202d151
commit
12735ff36e
@ -32,6 +32,7 @@ from .topology import project_to_topology, load_topology
|
|||||||
from .udp_link import UDPLink
|
from .udp_link import UDPLink
|
||||||
from ..config import Config
|
from ..config import Config
|
||||||
from ..utils.path import check_path_allowed, get_default_project_directory
|
from ..utils.path import check_path_allowed, get_default_project_directory
|
||||||
|
from ..utils.asyncio.pool import Pool
|
||||||
from .export_project import export_project
|
from .export_project import export_project
|
||||||
from .import_project import import_project
|
from .import_project import import_project
|
||||||
|
|
||||||
@ -61,14 +62,14 @@ class Project:
|
|||||||
:param status: Status of the project (opened / closed)
|
:param status: Status of the project (opened / closed)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened", filename=None, auto_start=False):
|
def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened", filename=None, auto_start=False, auto_open=False, auto_close=False):
|
||||||
|
|
||||||
self._controller = controller
|
self._controller = controller
|
||||||
assert name is not None
|
assert name is not None
|
||||||
self._name = name
|
self._name = name
|
||||||
self._auto_start = False
|
self._auto_start = auto_start
|
||||||
self._auto_close = False
|
self._auto_close = auto_close
|
||||||
self._auto_open = False
|
self._auto_open = auto_open
|
||||||
self._status = status
|
self._status = status
|
||||||
|
|
||||||
# Disallow overwrite of existing project
|
# Disallow overwrite of existing project
|
||||||
@ -103,9 +104,8 @@ class Project:
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def update(self, **kwargs):
|
def update(self, **kwargs):
|
||||||
"""
|
"""
|
||||||
Update the node on the compute server
|
Update the project
|
||||||
|
:param kwargs: Project properties
|
||||||
:param kwargs: Node properties
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
old_json = self.__json__()
|
old_json = self.__json__()
|
||||||
@ -492,6 +492,7 @@ class Project:
|
|||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def close(self, ignore_notification=False):
|
def close(self, ignore_notification=False):
|
||||||
|
yield from self.stop_all()
|
||||||
for compute in self._project_created_on_compute:
|
for compute in self._project_created_on_compute:
|
||||||
yield from compute.post("/projects/{}/close".format(self._id))
|
yield from compute.post("/projects/{}/close".format(self._id))
|
||||||
self._cleanPictures()
|
self._cleanPictures()
|
||||||
@ -580,6 +581,10 @@ class Project:
|
|||||||
for drawing_data in topology.get("drawings", []):
|
for drawing_data in topology.get("drawings", []):
|
||||||
drawing = yield from self.add_drawing(**drawing_data)
|
drawing = yield from self.add_drawing(**drawing_data)
|
||||||
|
|
||||||
|
# Should we start the nodes when project is open
|
||||||
|
if self._auto_start:
|
||||||
|
yield from self.start_all()
|
||||||
|
|
||||||
@open_required
|
@open_required
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def duplicate(self, name=None, location=None):
|
def duplicate(self, name=None, location=None):
|
||||||
@ -626,6 +631,36 @@ class Project:
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise aiohttp.web.HTTPInternalServerError(text="Could not write topology: {}".format(e))
|
raise aiohttp.web.HTTPInternalServerError(text="Could not write topology: {}".format(e))
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def start_all(self):
|
||||||
|
"""
|
||||||
|
Start all nodes
|
||||||
|
"""
|
||||||
|
pool = Pool(concurrency=3)
|
||||||
|
for node in self.nodes.values():
|
||||||
|
pool.append(node.start)
|
||||||
|
yield from pool.join()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def stop_all(self):
|
||||||
|
"""
|
||||||
|
Stop all nodes
|
||||||
|
"""
|
||||||
|
pool = Pool(concurrency=3)
|
||||||
|
for node in self.nodes.values():
|
||||||
|
pool.append(node.stop)
|
||||||
|
yield from pool.join()
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def suspend_all(self):
|
||||||
|
"""
|
||||||
|
Suspend all nodes
|
||||||
|
"""
|
||||||
|
pool = Pool(concurrency=3)
|
||||||
|
for node in self.nodes.values():
|
||||||
|
pool.append(node.suspend)
|
||||||
|
yield from pool.join()
|
||||||
|
|
||||||
def __json__(self):
|
def __json__(self):
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -53,6 +53,8 @@ def project_to_topology(project):
|
|||||||
"project_id": project.id,
|
"project_id": project.id,
|
||||||
"name": project.name,
|
"name": project.name,
|
||||||
"auto_start": project.auto_start,
|
"auto_start": project.auto_start,
|
||||||
|
"auto_open": project.auto_open,
|
||||||
|
"auto_close": project.auto_close,
|
||||||
"topology": {
|
"topology": {
|
||||||
"nodes": [],
|
"nodes": [],
|
||||||
"links": [],
|
"links": [],
|
||||||
|
@ -20,7 +20,6 @@ import aiohttp
|
|||||||
|
|
||||||
from gns3server.web.route import Route
|
from gns3server.web.route import Route
|
||||||
from gns3server.controller import Controller
|
from gns3server.controller import Controller
|
||||||
from gns3server.utils.asyncio.pool import Pool
|
|
||||||
|
|
||||||
from gns3server.schemas.node import (
|
from gns3server.schemas.node import (
|
||||||
NODE_OBJECT_SCHEMA,
|
NODE_OBJECT_SCHEMA,
|
||||||
@ -107,10 +106,7 @@ class NodeHandler:
|
|||||||
def start_all(request, response):
|
def start_all(request, response):
|
||||||
|
|
||||||
project = Controller.instance().get_project(request.match_info["project_id"])
|
project = Controller.instance().get_project(request.match_info["project_id"])
|
||||||
pool = Pool(concurrency=3)
|
yield from project.start_all()
|
||||||
for node in project.nodes.values():
|
|
||||||
pool.append(node.start)
|
|
||||||
yield from pool.join()
|
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
@ -128,10 +124,7 @@ class NodeHandler:
|
|||||||
def stop_all(request, response):
|
def stop_all(request, response):
|
||||||
|
|
||||||
project = Controller.instance().get_project(request.match_info["project_id"])
|
project = Controller.instance().get_project(request.match_info["project_id"])
|
||||||
pool = Pool(concurrency=3)
|
yield from project.stop_all()
|
||||||
for node in project.nodes.values():
|
|
||||||
pool.append(node.stop)
|
|
||||||
yield from pool.join()
|
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
@ -149,10 +142,7 @@ class NodeHandler:
|
|||||||
def suspend_all(request, response):
|
def suspend_all(request, response):
|
||||||
|
|
||||||
project = Controller.instance().get_project(request.match_info["project_id"])
|
project = Controller.instance().get_project(request.match_info["project_id"])
|
||||||
pool = Pool(concurrency=3)
|
yield from project.suspend_all()
|
||||||
for node in project.nodes.values():
|
|
||||||
pool.append(node.suspend)
|
|
||||||
yield from pool.join()
|
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
@ -170,13 +160,8 @@ class NodeHandler:
|
|||||||
def reload_all(request, response):
|
def reload_all(request, response):
|
||||||
|
|
||||||
project = Controller.instance().get_project(request.match_info["project_id"])
|
project = Controller.instance().get_project(request.match_info["project_id"])
|
||||||
pool = Pool(concurrency=3)
|
yield from project.stop_all()
|
||||||
for node in project.nodes.values():
|
yield from project.start_all()
|
||||||
pool.append(node.stop)
|
|
||||||
yield from pool.join()
|
|
||||||
for node in project.nodes.values():
|
|
||||||
pool.append(node.start)
|
|
||||||
yield from pool.join()
|
|
||||||
response.set_status(204)
|
response.set_status(204)
|
||||||
|
|
||||||
@Route.post(
|
@Route.post(
|
||||||
|
@ -45,6 +45,14 @@ TOPOLOGY_SCHEMA = {
|
|||||||
"description": "Start the topology when opened",
|
"description": "Start the topology when opened",
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"auto_close": {
|
||||||
|
"description": "Close the topology when no client is connected",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"auto_open": {
|
||||||
|
"description": "Open the topology with GNS3",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"revision": {
|
"revision": {
|
||||||
"description": "Version of the .gns3 specification.",
|
"description": "Version of the .gns3 specification.",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
@ -27,6 +27,7 @@ in futur GNS3 versions.
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>ID</th>
|
<th>ID</th>
|
||||||
|
<th>Status</th>
|
||||||
<th>Compute</th>
|
<th>Compute</th>
|
||||||
<th>Console</th>
|
<th>Console</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -34,6 +35,7 @@ in futur GNS3 versions.
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{{node.name}}</td>
|
<td>{{node.name}}</td>
|
||||||
<td>{{node.id}}</td>
|
<td>{{node.id}}</td>
|
||||||
|
<td>{{node.status}}</td>
|
||||||
<td>{{node.compute.id}}</td>
|
<td>{{node.compute.id}}</td>
|
||||||
<td><a href="{{node.console_type}}://{{node.host}}:{{node.console}}">Console</a>
|
<td><a href="{{node.console_type}}://{{node.host}}:{{node.console}}">Console</a>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -23,7 +23,7 @@ import pytest
|
|||||||
import aiohttp
|
import aiohttp
|
||||||
import zipfile
|
import zipfile
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
from tests.utils import AsyncioMagicMock
|
from tests.utils import AsyncioMagicMock, asyncio_patch
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
||||||
@ -349,7 +349,9 @@ def test_dump():
|
|||||||
def test_open_close(async_run, controller):
|
def test_open_close(async_run, controller):
|
||||||
project = Project(controller=controller, status="closed", name="Test")
|
project = Project(controller=controller, status="closed", name="Test")
|
||||||
assert project.status == "closed"
|
assert project.status == "closed"
|
||||||
|
project.start_all = AsyncioMagicMock()
|
||||||
async_run(project.open())
|
async_run(project.open())
|
||||||
|
assert not project.start_all.called
|
||||||
assert project.status == "opened"
|
assert project.status == "opened"
|
||||||
controller._notification = MagicMock()
|
controller._notification = MagicMock()
|
||||||
async_run(project.close())
|
async_run(project.close())
|
||||||
@ -357,6 +359,14 @@ def test_open_close(async_run, controller):
|
|||||||
controller.notification.emit.assert_any_call("project.closed", project.__json__())
|
controller.notification.emit.assert_any_call("project.closed", project.__json__())
|
||||||
|
|
||||||
|
|
||||||
|
def test_open_auto_start(async_run, controller):
|
||||||
|
project = Project(controller=controller, status="closed", name="Test")
|
||||||
|
project.auto_start = True
|
||||||
|
project.start_all = AsyncioMagicMock()
|
||||||
|
async_run(project.open())
|
||||||
|
assert project.start_all.called
|
||||||
|
|
||||||
|
|
||||||
def test_is_running(project, async_run, node):
|
def test_is_running(project, async_run, node):
|
||||||
"""
|
"""
|
||||||
If a node is started or paused return True
|
If a node is started or paused return True
|
||||||
@ -451,3 +461,49 @@ def test_snapshot(project, async_run):
|
|||||||
# Raise a conflict if name is already use
|
# Raise a conflict if name is already use
|
||||||
with pytest.raises(aiohttp.web_exceptions.HTTPConflict):
|
with pytest.raises(aiohttp.web_exceptions.HTTPConflict):
|
||||||
snapshot = async_run(project.snapshot("test1"))
|
snapshot = async_run(project.snapshot("test1"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_start_all(project, async_run):
|
||||||
|
compute = MagicMock()
|
||||||
|
compute.id = "local"
|
||||||
|
response = MagicMock()
|
||||||
|
response.json = {"console": 2048}
|
||||||
|
compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
|
||||||
|
for node_i in range(0, 10):
|
||||||
|
async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
||||||
|
|
||||||
|
compute.post = AsyncioMagicMock()
|
||||||
|
async_run(project.start_all())
|
||||||
|
assert len(compute.post.call_args_list) == 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_stop_all(project, async_run):
|
||||||
|
compute = MagicMock()
|
||||||
|
compute.id = "local"
|
||||||
|
response = MagicMock()
|
||||||
|
response.json = {"console": 2048}
|
||||||
|
compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
|
||||||
|
for node_i in range(0, 10):
|
||||||
|
async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
||||||
|
|
||||||
|
compute.post = AsyncioMagicMock()
|
||||||
|
async_run(project.stop_all())
|
||||||
|
assert len(compute.post.call_args_list) == 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_suspend_all(project, async_run):
|
||||||
|
compute = MagicMock()
|
||||||
|
compute.id = "local"
|
||||||
|
response = MagicMock()
|
||||||
|
response.json = {"console": 2048}
|
||||||
|
compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
|
||||||
|
for node_i in range(0, 10):
|
||||||
|
async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
||||||
|
|
||||||
|
compute.post = AsyncioMagicMock()
|
||||||
|
async_run(project.suspend_all())
|
||||||
|
assert len(compute.post.call_args_list) == 10
|
||||||
|
|
||||||
|
@ -35,6 +35,8 @@ def test_project_to_topology_empty(tmpdir):
|
|||||||
"project_id": project.id,
|
"project_id": project.id,
|
||||||
"name": "Test",
|
"name": "Test",
|
||||||
"auto_start": False,
|
"auto_start": False,
|
||||||
|
"auto_close": False,
|
||||||
|
"auto_open": False,
|
||||||
"revision": 5,
|
"revision": 5,
|
||||||
"topology": {
|
"topology": {
|
||||||
"nodes": [],
|
"nodes": [],
|
||||||
|
Loading…
Reference in New Issue
Block a user