mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Merge pull request #1332 from GNS3/global-vars
Docker `ExtraHosts`, global variables for project and supplier support, Fixes: #2482
This commit is contained in:
commit
c52342907a
1
.gitignore
vendored
1
.gitignore
vendored
@ -39,6 +39,7 @@ nosetests.xml
|
||||
.project
|
||||
.pydevproject
|
||||
.settings
|
||||
.vscode
|
||||
|
||||
# Pycharm
|
||||
.idea
|
||||
|
@ -61,11 +61,12 @@ class DockerVM(BaseNode):
|
||||
:param console_resolution: Resolution of the VNC display
|
||||
:param console_http_port: Port to redirect HTTP queries
|
||||
:param console_http_path: Url part with the path of the web interface
|
||||
:param extra_hosts: Hosts which will be written into /etc/hosts into docker conainer
|
||||
"""
|
||||
|
||||
def __init__(self, name, node_id, project, manager, image, console=None, aux=None, start_command=None,
|
||||
adapters=None, environment=None, console_type="telnet", console_resolution="1024x768",
|
||||
console_http_port=80, console_http_path="/"):
|
||||
console_http_port=80, console_http_path="/", extra_hosts=None):
|
||||
|
||||
super().__init__(name, node_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type)
|
||||
|
||||
@ -84,6 +85,8 @@ class DockerVM(BaseNode):
|
||||
self._console_http_path = console_http_path
|
||||
self._console_http_port = console_http_port
|
||||
self._console_websocket = None
|
||||
self._extra_hosts = extra_hosts
|
||||
|
||||
self._volumes = []
|
||||
# Keep a list of created bridge
|
||||
self._bridges = set()
|
||||
@ -114,7 +117,8 @@ class DockerVM(BaseNode):
|
||||
"start_command": self.start_command,
|
||||
"status": self.status,
|
||||
"environment": self.environment,
|
||||
"node_directory": self.working_path
|
||||
"node_directory": self.working_path,
|
||||
"extra_hosts": self.extra_hosts
|
||||
}
|
||||
|
||||
def _get_free_display_port(self):
|
||||
@ -178,6 +182,14 @@ class DockerVM(BaseNode):
|
||||
def environment(self, command):
|
||||
self._environment = command
|
||||
|
||||
@property
|
||||
def extra_hosts(self):
|
||||
return self._extra_hosts
|
||||
|
||||
@extra_hosts.setter
|
||||
def extra_hosts(self, extra_hosts):
|
||||
self._extra_hosts = extra_hosts
|
||||
|
||||
@asyncio.coroutine
|
||||
def _get_container_state(self):
|
||||
"""Returns the container state (e.g. running, paused etc.)
|
||||
@ -288,7 +300,7 @@ class DockerVM(BaseNode):
|
||||
"HostConfig": {
|
||||
"CapAdd": ["ALL"],
|
||||
"Privileged": True,
|
||||
"Binds": self._mount_binds(image_infos)
|
||||
"Binds": self._mount_binds(image_infos),
|
||||
},
|
||||
"Volumes": {},
|
||||
"Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573
|
||||
@ -313,11 +325,20 @@ class DockerVM(BaseNode):
|
||||
# Give the information to the container the list of volume path mounted
|
||||
params["Env"].append("GNS3_VOLUMES={}".format(":".join(self._volumes)))
|
||||
|
||||
variables = self.project.variables
|
||||
if not variables:
|
||||
variables = []
|
||||
|
||||
for var in variables:
|
||||
formatted = self._format_env(variables, var.get('value', ''))
|
||||
params["Env"].append("{}={}".format(var["name"], formatted))
|
||||
|
||||
if self._environment:
|
||||
for e in self._environment.strip().split("\n"):
|
||||
e = e.strip()
|
||||
if not e.startswith("GNS3_"):
|
||||
params["Env"].append(e)
|
||||
formatted = self._format_env(variables, e)
|
||||
params["Env"].append(formatted)
|
||||
|
||||
if self._console_type == "vnc":
|
||||
yield from self._start_vnc()
|
||||
@ -325,12 +346,36 @@ class DockerVM(BaseNode):
|
||||
params["Env"].append("DISPLAY=:{}".format(self._display))
|
||||
params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/")
|
||||
|
||||
if self._extra_hosts:
|
||||
extra_hosts = self._format_extra_hosts(self._extra_hosts)
|
||||
if extra_hosts:
|
||||
params["Env"].append("GNS3_EXTRA_HOSTS={}".format(extra_hosts))
|
||||
|
||||
result = yield from self.manager.query("POST", "containers/create", data=params)
|
||||
self._cid = result['Id']
|
||||
log.info("Docker container '{name}' [{id}] created".format(
|
||||
name=self._name, id=self._id))
|
||||
return True
|
||||
|
||||
def _format_env(self, variables, env):
|
||||
for variable in variables:
|
||||
env = env.replace('${' + variable["name"] + '}', variable.get("value", ""))
|
||||
return env
|
||||
|
||||
def _format_extra_hosts(self, extra_hosts):
|
||||
lines = [h.strip() for h in self._extra_hosts.split("\n") if h.strip() != ""]
|
||||
hosts = []
|
||||
try:
|
||||
for host in lines:
|
||||
hostname, ip = host.split(":")
|
||||
hostname = hostname.strip()
|
||||
ip = ip.strip()
|
||||
if hostname and ip:
|
||||
hosts.append((hostname, ip))
|
||||
except ValueError:
|
||||
raise DockerError("Can't apply `ExtraHosts`, wrong format: {}".format(extra_hosts))
|
||||
return "\n".join(["{}\t{}".format(h[1], h[0]) for h in hosts])
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self):
|
||||
"""
|
||||
|
@ -60,6 +60,14 @@ ff02::1 ip6-allnodes
|
||||
ff02::2 ip6-allrouters
|
||||
__EOF__
|
||||
|
||||
# imitate docker's `ExtraHosts` behaviour
|
||||
sed -i '/GNS3_EXTRA_HOSTS_START/,/GNS3_EXTRA_HOSTS_END/d' /etc/hosts
|
||||
[ -n "$GNS3_EXTRA_HOSTS" ] && cat >> /etc/hosts << __EOF__
|
||||
# GNS3_EXTRA_HOSTS_START
|
||||
$GNS3_EXTRA_HOSTS
|
||||
# GNS3_EXTRA_HOSTS_END
|
||||
__EOF__
|
||||
|
||||
# configure loopback interface
|
||||
ip link set dev lo up
|
||||
|
||||
|
@ -25,13 +25,13 @@ import zipfile
|
||||
import json
|
||||
|
||||
from uuid import UUID, uuid4
|
||||
|
||||
from .port_manager import PortManager
|
||||
from .notification_manager import NotificationManager
|
||||
from ..config import Config
|
||||
from ..utils.asyncio import wait_run_in_executor
|
||||
from ..utils.path import check_path_allowed, get_default_project_directory
|
||||
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
@ -46,7 +46,7 @@ class Project:
|
||||
:param path: path of the project. (None use the standard directory)
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, project_id=None, path=None):
|
||||
def __init__(self, name=None, project_id=None, path=None, variables=None):
|
||||
|
||||
self._name = name
|
||||
if project_id:
|
||||
@ -61,6 +61,7 @@ class Project:
|
||||
self._nodes = set()
|
||||
self._used_tcp_ports = set()
|
||||
self._used_udp_ports = set()
|
||||
self._variables = variables
|
||||
|
||||
if path is None:
|
||||
location = get_default_project_directory()
|
||||
@ -83,7 +84,8 @@ class Project:
|
||||
|
||||
return {
|
||||
"name": self._name,
|
||||
"project_id": self._id
|
||||
"project_id": self._id,
|
||||
"variables": self._variables
|
||||
}
|
||||
|
||||
def _config(self):
|
||||
@ -131,6 +133,14 @@ class Project:
|
||||
|
||||
return self._nodes
|
||||
|
||||
@property
|
||||
def variables(self):
|
||||
return self._variables
|
||||
|
||||
@variables.setter
|
||||
def variables(self, variables):
|
||||
self._variables = variables
|
||||
|
||||
def record_tcp_port(self, port):
|
||||
"""
|
||||
Associate a reserved TCP port number with this project.
|
||||
@ -287,6 +297,17 @@ class Project:
|
||||
yield from node.delete()
|
||||
self._nodes.remove(node)
|
||||
|
||||
@asyncio.coroutine
|
||||
def update(self, variables=None, **kwargs):
|
||||
original_variables = self.variables
|
||||
self.variables = variables
|
||||
|
||||
# we need to update docker nodes when variables changes
|
||||
if original_variables != variables:
|
||||
for node in self.nodes:
|
||||
if hasattr(node, 'update'):
|
||||
yield from node.update()
|
||||
|
||||
@asyncio.coroutine
|
||||
def close(self):
|
||||
"""
|
||||
|
@ -16,6 +16,7 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import aiohttp
|
||||
import asyncio
|
||||
import psutil
|
||||
import platform
|
||||
from .project import Project
|
||||
@ -95,16 +96,16 @@ class ProjectManager:
|
||||
log.warning(message)
|
||||
project.emit("log.warning", {"message": message})
|
||||
|
||||
def create_project(self, name=None, project_id=None, path=None):
|
||||
def create_project(self, name=None, project_id=None, path=None, variables=None):
|
||||
"""
|
||||
Create a project and keep a references to it in project manager.
|
||||
|
||||
See documentation of Project for arguments
|
||||
"""
|
||||
|
||||
if project_id is not None and project_id in self._projects:
|
||||
return self._projects[project_id]
|
||||
project = Project(name=name, project_id=project_id, path=path)
|
||||
project = Project(name=name, project_id=project_id,
|
||||
path=path, variables=variables)
|
||||
self._check_available_disk_space(project)
|
||||
self._projects[project.id] = project
|
||||
return project
|
||||
|
@ -460,7 +460,6 @@ class Compute:
|
||||
msg = json.loads(response.data)
|
||||
action = msg.pop("action")
|
||||
event = msg.pop("event")
|
||||
|
||||
if action == "ping":
|
||||
self._cpu_usage_percent = event["cpu_usage_percent"]
|
||||
self._memory_usage_percent = event["memory_usage_percent"]
|
||||
|
@ -69,7 +69,7 @@ class Project:
|
||||
def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened",
|
||||
filename=None, auto_start=False, auto_open=False, auto_close=True,
|
||||
scene_height=1000, scene_width=2000, zoom=100, show_layers=False, snap_to_grid=False, show_grid=False,
|
||||
grid_size=0, show_interface_labels=False):
|
||||
grid_size=0, show_interface_labels=False, variables=None, supplier=None):
|
||||
|
||||
self._controller = controller
|
||||
assert name is not None
|
||||
@ -86,6 +86,9 @@ class Project:
|
||||
self._show_grid = show_grid
|
||||
self._grid_size = grid_size
|
||||
self._show_interface_labels = show_interface_labels
|
||||
self._variables = variables
|
||||
self._supplier = supplier
|
||||
|
||||
self._loading = False
|
||||
|
||||
# Disallow overwrite of existing project
|
||||
@ -134,6 +137,15 @@ class Project:
|
||||
self.controller.notification.emit("project.updated", self.__json__())
|
||||
self.dump()
|
||||
|
||||
# update on computes
|
||||
for compute in list(self._project_created_on_compute):
|
||||
yield from compute.put(
|
||||
"/projects/{}".format(self._id), {
|
||||
"variables": self.variables
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Called when open/close a project. Cleanup internal stuff
|
||||
@ -267,6 +279,36 @@ class Project:
|
||||
"""
|
||||
self._show_interface_labels = show_interface_labels
|
||||
|
||||
@property
|
||||
def variables(self):
|
||||
"""
|
||||
Variables applied to the project
|
||||
:return: list
|
||||
"""
|
||||
return self._variables
|
||||
|
||||
@variables.setter
|
||||
def variables(self, variables):
|
||||
"""
|
||||
Setter for variables applied to the project
|
||||
"""
|
||||
self._variables = variables
|
||||
|
||||
@property
|
||||
def supplier(self):
|
||||
"""
|
||||
Supplier of the project
|
||||
:return: dict
|
||||
"""
|
||||
return self._supplier
|
||||
|
||||
@supplier.setter
|
||||
def supplier(self, supplier):
|
||||
"""
|
||||
Setter for supplier of the project
|
||||
"""
|
||||
self._supplier = supplier
|
||||
|
||||
@property
|
||||
def auto_start(self):
|
||||
"""
|
||||
@ -461,12 +503,14 @@ class Project:
|
||||
yield from compute.post("/projects", data={
|
||||
"name": self._name,
|
||||
"project_id": self._id,
|
||||
"path": self._path
|
||||
"path": self._path,
|
||||
"variables": self._variables
|
||||
})
|
||||
else:
|
||||
yield from compute.post("/projects", data={
|
||||
"name": self._name,
|
||||
"project_id": self._id,
|
||||
"variables": self._variables
|
||||
})
|
||||
|
||||
self._project_created_on_compute.add(compute)
|
||||
@ -676,6 +720,15 @@ class Project:
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# don't remove supplier's logo
|
||||
if self.supplier:
|
||||
try:
|
||||
logo = self.supplier['logo']
|
||||
pictures.remove(logo)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
for pict in pictures:
|
||||
os.remove(os.path.join(self.pictures_directory, pict))
|
||||
except OSError as e:
|
||||
@ -1004,7 +1057,9 @@ class Project:
|
||||
"snap_to_grid": self._snap_to_grid,
|
||||
"show_grid": self._show_grid,
|
||||
"grid_size": self._grid_size,
|
||||
"show_interface_labels": self._show_interface_labels
|
||||
"show_interface_labels": self._show_interface_labels,
|
||||
"supplier": self._supplier,
|
||||
"variables": self._variables
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -85,6 +85,8 @@ def project_to_topology(project):
|
||||
"show_grid": project.show_grid,
|
||||
"grid_size": project.grid_size,
|
||||
"show_interface_labels": project.show_interface_labels,
|
||||
"variables": project.variables,
|
||||
"supplier": project.supplier,
|
||||
"topology": {
|
||||
"nodes": [],
|
||||
"links": [],
|
||||
|
@ -60,7 +60,8 @@ class DockerHandler:
|
||||
console_resolution=request.json.get("console_resolution", "1024x768"),
|
||||
console_http_port=request.json.get("console_http_port", 80),
|
||||
console_http_path=request.json.get("console_http_path", "/"),
|
||||
aux=request.json.get("aux"))
|
||||
aux=request.json.get("aux"),
|
||||
extra_hosts=request.json.get("extra_hosts"))
|
||||
for name, value in request.json.items():
|
||||
if name != "node_id":
|
||||
if hasattr(container, name) and getattr(container, name) != value:
|
||||
@ -312,7 +313,7 @@ class DockerHandler:
|
||||
props = [
|
||||
"name", "console", "aux", "console_type", "console_resolution",
|
||||
"console_http_port", "console_http_path", "start_command",
|
||||
"environment", "adapters"
|
||||
"environment", "adapters", "extra_hosts"
|
||||
]
|
||||
|
||||
changed = False
|
||||
|
@ -73,11 +73,31 @@ class ProjectHandler:
|
||||
p = pm.create_project(
|
||||
name=request.json.get("name"),
|
||||
path=request.json.get("path"),
|
||||
project_id=request.json.get("project_id")
|
||||
project_id=request.json.get("project_id"),
|
||||
variables=request.json.get("variables", None)
|
||||
)
|
||||
response.set_status(201)
|
||||
response.json(p)
|
||||
|
||||
@Route.put(
|
||||
r"/projects/{project_id}",
|
||||
description="Update the project on the server",
|
||||
status_codes={
|
||||
201: "Project updated",
|
||||
403: "Forbidden to update a project"
|
||||
},
|
||||
output=PROJECT_OBJECT_SCHEMA,
|
||||
input=PROJECT_UPDATE_SCHEMA)
|
||||
def update_project(request, response):
|
||||
|
||||
pm = ProjectManager.instance()
|
||||
project = pm.get_project(request.match_info["project_id"])
|
||||
yield from project.update(
|
||||
variables=request.json.get("variables", None)
|
||||
)
|
||||
response.set_status(200)
|
||||
response.json(project)
|
||||
|
||||
@Route.get(
|
||||
r"/projects/{project_id}",
|
||||
description="Get project information",
|
||||
|
@ -87,6 +87,11 @@ DOCKER_CREATE_SCHEMA = {
|
||||
"type": ["string", "null"],
|
||||
"minLength": 0,
|
||||
},
|
||||
"extra_hosts": {
|
||||
"description": "Docker extra hosts (added to /etc/hosts)",
|
||||
"type": ["string", "null"],
|
||||
"minLength": 0,
|
||||
},
|
||||
"container_id": {
|
||||
"description": "Docker container ID Read only",
|
||||
"type": "string",
|
||||
@ -184,6 +189,11 @@ DOCKER_OBJECT_SCHEMA = {
|
||||
"type": ["string", "null"],
|
||||
"minLength": 0,
|
||||
},
|
||||
"extra_hosts": {
|
||||
"description": "Docker extra hosts (added to /etc/hosts)",
|
||||
"type": ["string", "null"],
|
||||
"minLength": 0,
|
||||
},
|
||||
"node_directory": {
|
||||
"description": "Path to the node working directory Read only",
|
||||
"type": "string"
|
||||
|
@ -15,6 +15,40 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
SUPPLIER_OBJECT_SCHEMA = {
|
||||
"type": ["object", "null"],
|
||||
"description": "Supplier of the project",
|
||||
"properties": {
|
||||
"logo": {
|
||||
"type": "string",
|
||||
"description": "Path to the project supplier logo"
|
||||
},
|
||||
"url": {
|
||||
"type": "string",
|
||||
"description": "URL to the project supplier site"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
VARIABLES_OBJECT_SCHEMA = {
|
||||
"type": ["array", "null"],
|
||||
"description": "Variables required to run the project",
|
||||
"items": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Variable name"
|
||||
},
|
||||
"value": {
|
||||
"type": "string",
|
||||
"description": "Variable value"
|
||||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
PROJECT_CREATE_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
@ -73,7 +107,9 @@ PROJECT_CREATE_SCHEMA = {
|
||||
"show_interface_labels": {
|
||||
"type": "boolean",
|
||||
"description": "Show interface labels on the drawing area"
|
||||
}
|
||||
},
|
||||
"supplier": SUPPLIER_OBJECT_SCHEMA,
|
||||
"variables": VARIABLES_OBJECT_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name"]
|
||||
@ -136,7 +172,9 @@ PROJECT_UPDATE_SCHEMA = {
|
||||
"show_interface_labels": {
|
||||
"type": "boolean",
|
||||
"description": "Show interface labels on the drawing area"
|
||||
}
|
||||
},
|
||||
"supplier": SUPPLIER_OBJECT_SCHEMA,
|
||||
"variables": VARIABLES_OBJECT_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
}
|
||||
@ -215,7 +253,9 @@ PROJECT_OBJECT_SCHEMA = {
|
||||
"show_interface_labels": {
|
||||
"type": "boolean",
|
||||
"description": "Show interface labels on the drawing area"
|
||||
}
|
||||
},
|
||||
"supplier": SUPPLIER_OBJECT_SCHEMA,
|
||||
"variables": VARIABLES_OBJECT_SCHEMA
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["project_id"]
|
||||
|
@ -23,6 +23,8 @@ from gns3server.schemas.compute import COMPUTE_OBJECT_SCHEMA
|
||||
from gns3server.schemas.drawing import DRAWING_OBJECT_SCHEMA
|
||||
from gns3server.schemas.link import LINK_OBJECT_SCHEMA
|
||||
from gns3server.schemas.node import NODE_OBJECT_SCHEMA
|
||||
from gns3server.schemas.project import VARIABLES_OBJECT_SCHEMA
|
||||
from gns3server.schemas.project import SUPPLIER_OBJECT_SCHEMA
|
||||
|
||||
|
||||
TOPOLOGY_SCHEMA = {
|
||||
@ -97,6 +99,8 @@ TOPOLOGY_SCHEMA = {
|
||||
"type": "boolean",
|
||||
"description": "Show interface labels on the drawing area"
|
||||
},
|
||||
"supplier": SUPPLIER_OBJECT_SCHEMA,
|
||||
"variables": VARIABLES_OBJECT_SCHEMA,
|
||||
"topology": {
|
||||
"description": "The topology content",
|
||||
"type": "object",
|
||||
|
@ -61,6 +61,7 @@ def test_json(vm, project):
|
||||
'console_resolution': '1024x768',
|
||||
'console_http_port': 80,
|
||||
'console_http_path': '/',
|
||||
'extra_hosts': None,
|
||||
'aux': vm.aux,
|
||||
'start_command': vm.start_command,
|
||||
'environment': vm.environment,
|
||||
@ -202,6 +203,77 @@ def test_create_vnc(loop, project, manager):
|
||||
assert vm._console_type == "vnc"
|
||||
|
||||
|
||||
def test_create_with_extra_hosts(loop, project, manager):
|
||||
extra_hosts = "test:199.199.199.1\ntest2:199.199.199.1"
|
||||
|
||||
response = {
|
||||
"Id": "e90e34656806",
|
||||
"Warnings": []
|
||||
}
|
||||
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), project, manager, "ubuntu", extra_hosts=extra_hosts)
|
||||
loop.run_until_complete(asyncio.async(vm.create()))
|
||||
called_kwargs = mock.call_args[1]
|
||||
assert "GNS3_EXTRA_HOSTS=199.199.199.1\ttest\n199.199.199.1\ttest2" in called_kwargs["data"]["Env"]
|
||||
assert vm._extra_hosts == extra_hosts
|
||||
|
||||
|
||||
def test_create_with_extra_hosts_wrong_format(loop, project, manager):
|
||||
extra_hosts = "test"
|
||||
|
||||
response = {
|
||||
"Id": "e90e34656806",
|
||||
"Warnings": []
|
||||
}
|
||||
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response):
|
||||
vm = DockerVM("test", str(uuid.uuid4()), project, manager, "ubuntu", extra_hosts=extra_hosts)
|
||||
with pytest.raises(DockerError):
|
||||
loop.run_until_complete(asyncio.async(vm.create()))
|
||||
|
||||
|
||||
def test_create_with_empty_extra_hosts(loop, project, manager):
|
||||
extra_hosts = "test:\n"
|
||||
|
||||
response = {
|
||||
"Id": "e90e34656806",
|
||||
"Warnings": []
|
||||
}
|
||||
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), project, manager, "ubuntu", extra_hosts=extra_hosts)
|
||||
loop.run_until_complete(asyncio.async(vm.create()))
|
||||
called_kwargs = mock.call_args[1]
|
||||
assert len([ e for e in called_kwargs["data"]["Env"] if "GNS3_EXTRA_HOSTS" in e]) == 0
|
||||
|
||||
|
||||
def test_create_with_project_variables(loop, project, manager):
|
||||
response = {
|
||||
"Id": "e90e34656806",
|
||||
"Warnings": []
|
||||
}
|
||||
|
||||
project.variables = [
|
||||
{"name": "VAR1"},
|
||||
{"name": "VAR2", "value": "VAL1"},
|
||||
{"name": "VAR3", "value": "2x${VAR2}"}
|
||||
]
|
||||
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.list_images", return_value=[{"image": "ubuntu"}]):
|
||||
with asyncio_patch("gns3server.compute.docker.Docker.query", return_value=response) as mock:
|
||||
vm = DockerVM("test", str(uuid.uuid4()), project, manager, "ubuntu")
|
||||
loop.run_until_complete(asyncio.async(vm.create()))
|
||||
called_kwargs = mock.call_args[1]
|
||||
assert "VAR1=" in called_kwargs["data"]["Env"]
|
||||
assert "VAR2=VAL1" in called_kwargs["data"]["Env"]
|
||||
assert "VAR3=2xVAL1" in called_kwargs["data"]["Env"]
|
||||
project.variables = None
|
||||
|
||||
|
||||
def test_create_start_cmd(loop, project, manager):
|
||||
|
||||
response = {
|
||||
|
@ -92,9 +92,29 @@ def test_changing_path_not_allowed(tmpdir):
|
||||
p.path = str(tmpdir)
|
||||
|
||||
|
||||
def test_variables(tmpdir):
|
||||
variables = [{"name": "VAR1", "value": "VAL1"}]
|
||||
p = Project(project_id=str(uuid4()), variables=variables)
|
||||
assert p.variables == variables
|
||||
|
||||
|
||||
def test_json(tmpdir):
|
||||
p = Project(project_id=str(uuid4()))
|
||||
assert p.__json__() == {"name": p.name, "project_id": p.id}
|
||||
assert p.__json__() == {
|
||||
"name": p.name,
|
||||
"project_id": p.id,
|
||||
"variables": None
|
||||
}
|
||||
|
||||
|
||||
def test_json_with_variables(tmpdir):
|
||||
variables = [{"name": "VAR1", "value": "VAL1"}]
|
||||
p = Project(project_id=str(uuid4()), variables=variables)
|
||||
assert p.__json__() == {
|
||||
"name": p.name,
|
||||
"project_id": p.id,
|
||||
"variables": variables
|
||||
}
|
||||
|
||||
|
||||
def test_node_working_directory(tmpdir, node):
|
||||
@ -185,3 +205,10 @@ def test_emit(async_run):
|
||||
(action, event, context) = async_run(queue.get(0.5))
|
||||
assert action == "test"
|
||||
assert context["project_id"] == project.id
|
||||
|
||||
|
||||
def test_update_project(loop):
|
||||
variables = [{"name": "TEST", "value": "VAL"}]
|
||||
project = Project(project_id=str(uuid.uuid4()))
|
||||
loop.run_until_complete(asyncio.async(project.update(variables=variables)))
|
||||
assert project.variables == variables
|
||||
|
@ -77,19 +77,35 @@ def test_json(tmpdir):
|
||||
"show_layers": False,
|
||||
"snap_to_grid": False,
|
||||
"grid_size": 0,
|
||||
"supplier": None,
|
||||
"variables": None
|
||||
}
|
||||
|
||||
|
||||
def test_update(controller, async_run):
|
||||
project = Project(controller=controller, name="Hello")
|
||||
controller._notification = MagicMock()
|
||||
|
||||
assert project.name == "Hello"
|
||||
async_run(project.update(name="World"))
|
||||
assert project.name == "World"
|
||||
controller.notification.emit.assert_any_call("project.updated", project.__json__())
|
||||
|
||||
|
||||
def test_update_on_compute(controller, async_run):
|
||||
variables = [{"name": "TEST", "value": "VAL1"}]
|
||||
compute = MagicMock()
|
||||
compute.id = "local"
|
||||
project = Project(controller=controller, name="Test")
|
||||
project._project_created_on_compute = [compute]
|
||||
controller._notification = MagicMock()
|
||||
|
||||
async_run(project.update(variables=variables))
|
||||
|
||||
compute.put.assert_any_call('/projects/{}'.format(project.id), {
|
||||
"variables": variables
|
||||
})
|
||||
|
||||
|
||||
def test_path(tmpdir):
|
||||
|
||||
directory = Config.instance().get_section_config("Server").get("projects_path")
|
||||
@ -148,7 +164,8 @@ def test_add_node_local(async_run, controller):
|
||||
compute.post.assert_any_call('/projects', data={
|
||||
"name": project._name,
|
||||
"project_id": project._id,
|
||||
"path": project._path
|
||||
"path": project._path,
|
||||
"variables": None
|
||||
})
|
||||
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
|
||||
data={'node_id': node.id,
|
||||
@ -176,7 +193,8 @@ def test_add_node_non_local(async_run, controller):
|
||||
|
||||
compute.post.assert_any_call('/projects', data={
|
||||
"name": project._name,
|
||||
"project_id": project._id
|
||||
"project_id": project._id,
|
||||
"variables": None
|
||||
})
|
||||
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
|
||||
data={'node_id': node.id,
|
||||
@ -216,7 +234,8 @@ def test_add_node_from_appliance(async_run, controller):
|
||||
compute.post.assert_any_call('/projects', data={
|
||||
"name": project._name,
|
||||
"project_id": project._id,
|
||||
"path": project._path
|
||||
"path": project._path,
|
||||
"variables": None
|
||||
})
|
||||
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
|
||||
data={'node_id': node.id,
|
||||
@ -395,6 +414,26 @@ def test_clean_pictures(async_run, project, controller):
|
||||
assert not os.path.exists(os.path.join(project.pictures_directory, "test2.png"))
|
||||
|
||||
|
||||
def test_clean_pictures_and_keep_supplier_logo(async_run, project, controller):
|
||||
"""
|
||||
When a project is close old pictures should be removed
|
||||
"""
|
||||
project.supplier = {
|
||||
'logo': 'logo.png'
|
||||
}
|
||||
|
||||
drawing = async_run(project.add_drawing())
|
||||
drawing._svg = "test.png"
|
||||
open(os.path.join(project.pictures_directory, "test.png"), "w+").close()
|
||||
open(os.path.join(project.pictures_directory, "test2.png"), "w+").close()
|
||||
open(os.path.join(project.pictures_directory, "logo.png"), "w+").close()
|
||||
|
||||
async_run(project.close())
|
||||
assert os.path.exists(os.path.join(project.pictures_directory, "test.png"))
|
||||
assert not os.path.exists(os.path.join(project.pictures_directory, "test2.png"))
|
||||
assert os.path.exists(os.path.join(project.pictures_directory, "logo.png"))
|
||||
|
||||
|
||||
def test_delete(async_run, project, controller):
|
||||
assert os.path.exists(project.path)
|
||||
async_run(project.delete())
|
||||
|
@ -53,6 +53,8 @@ def test_project_to_topology_empty(tmpdir):
|
||||
"drawings": []
|
||||
},
|
||||
"type": "topology",
|
||||
"supplier": None,
|
||||
"variables": None,
|
||||
"version": __version__
|
||||
}
|
||||
|
||||
@ -81,6 +83,26 @@ def test_basic_topology(tmpdir, async_run, controller):
|
||||
assert topo["topology"]["drawings"][0] == drawing.__json__(topology_dump=True)
|
||||
|
||||
|
||||
def test_project_to_topology(tmpdir, controller):
|
||||
variables = [
|
||||
{"name": "TEST1"},
|
||||
{"name": "TEST2", "value": "value1"}
|
||||
]
|
||||
supplier = {
|
||||
'logo': 'logo.png',
|
||||
'url': 'http://example.com'
|
||||
}
|
||||
|
||||
project = Project(name="Test", controller=controller)
|
||||
compute = Compute("my_compute", controller)
|
||||
compute.http_query = MagicMock()
|
||||
project.variables = variables
|
||||
project.supplier = supplier
|
||||
topo = project_to_topology(project)
|
||||
assert topo["variables"] == variables
|
||||
assert topo["supplier"] == supplier
|
||||
|
||||
|
||||
def test_load_topology(tmpdir):
|
||||
data = {
|
||||
"project_id": "69f26504-7aa3-48aa-9f29-798d44841211",
|
||||
@ -137,3 +159,55 @@ def test_load_newer_topology(tmpdir):
|
||||
json.dump(data, f)
|
||||
with pytest.raises(aiohttp.web.HTTPConflict):
|
||||
topo = load_topology(path)
|
||||
|
||||
|
||||
def test_load_topology_with_variables(tmpdir):
|
||||
variables = [
|
||||
{"name": "TEST1"},
|
||||
{"name": "TEST2", "value": "value1"}
|
||||
]
|
||||
data = {
|
||||
"project_id": "69f26504-7aa3-48aa-9f29-798d44841211",
|
||||
"name": "Test",
|
||||
"revision": GNS3_FILE_FORMAT_REVISION,
|
||||
"topology": {
|
||||
"nodes": [],
|
||||
"links": [],
|
||||
"computes": [],
|
||||
"drawings": []
|
||||
},
|
||||
"variables": variables,
|
||||
"type": "topology",
|
||||
"version": __version__}
|
||||
|
||||
path = str(tmpdir / "test.gns3")
|
||||
with open(path, "w+") as f:
|
||||
json.dump(data, f)
|
||||
topo = load_topology(path)
|
||||
assert topo == data
|
||||
|
||||
|
||||
def test_load_topology_with_supplier(tmpdir):
|
||||
supplier = {
|
||||
'logo': 'logo.png',
|
||||
'url': 'http://example.com'
|
||||
}
|
||||
data = {
|
||||
"project_id": "69f26504-7aa3-48aa-9f29-798d44841211",
|
||||
"name": "Test",
|
||||
"revision": GNS3_FILE_FORMAT_REVISION,
|
||||
"topology": {
|
||||
"nodes": [],
|
||||
"links": [],
|
||||
"computes": [],
|
||||
"drawings": []
|
||||
},
|
||||
"supplier": supplier,
|
||||
"type": "topology",
|
||||
"version": __version__}
|
||||
|
||||
path = str(tmpdir / "test.gns3")
|
||||
with open(path, "w+") as f:
|
||||
json.dump(data, f)
|
||||
topo = load_topology(path)
|
||||
assert topo == data
|
@ -32,7 +32,7 @@ pytestmark = pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supp
|
||||
@pytest.fixture
|
||||
def base_params():
|
||||
"""Return standard parameters"""
|
||||
return {"name": "PC TEST 1", "image": "nginx", "start_command": "nginx-daemon", "adapters": 2, "environment": "YES=1\nNO=0", "console_type": "telnet", "console_resolution": "1280x1024"}
|
||||
return {"name": "PC TEST 1", "image": "nginx", "start_command": "nginx-daemon", "adapters": 2, "environment": "YES=1\nNO=0", "console_type": "telnet", "console_resolution": "1280x1024", "extra_hosts": "test:127.0.0.1"}
|
||||
|
||||
|
||||
@pytest.yield_fixture(autouse=True)
|
||||
@ -69,7 +69,7 @@ def test_docker_create(http_compute, project, base_params):
|
||||
assert response.json["adapters"] == 2
|
||||
assert response.json["environment"] == "YES=1\nNO=0"
|
||||
assert response.json["console_resolution"] == "1280x1024"
|
||||
|
||||
assert response.json["extra_hosts"] == "test:127.0.0.1"
|
||||
|
||||
def test_docker_start(http_compute, vm):
|
||||
with asyncio_patch("gns3server.compute.docker.docker_vm.DockerVM.start", return_value=True) as mock:
|
||||
@ -150,7 +150,8 @@ def test_docker_update(http_compute, vm, tmpdir, free_console_port):
|
||||
response = http_compute.put("/projects/{project_id}/docker/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), {"name": "test",
|
||||
"console": free_console_port,
|
||||
"start_command": "yes",
|
||||
"environment": "GNS3=1\nGNS4=0"},
|
||||
"environment": "GNS3=1\nGNS4=0",
|
||||
"extra_hosts": "test:127.0.0.1"},
|
||||
example=True)
|
||||
assert mock.called
|
||||
assert response.status == 200
|
||||
@ -158,6 +159,7 @@ def test_docker_update(http_compute, vm, tmpdir, free_console_port):
|
||||
assert response.json["console"] == free_console_port
|
||||
assert response.json["start_command"] == "yes"
|
||||
assert response.json["environment"] == "GNS3=1\nGNS4=0"
|
||||
assert response.json["extra_hosts"] == "test:127.0.0.1"
|
||||
|
||||
|
||||
def test_docker_start_capture(http_compute, vm, tmpdir, project):
|
||||
|
@ -21,9 +21,6 @@ This test suite check /project endpoint
|
||||
|
||||
import uuid
|
||||
import os
|
||||
import asyncio
|
||||
import aiohttp
|
||||
import zipfile
|
||||
|
||||
from unittest.mock import patch
|
||||
from tests.utils import asyncio_patch
|
||||
@ -60,9 +57,10 @@ def test_show_project(http_compute):
|
||||
response = http_compute.post("/projects", query)
|
||||
assert response.status == 201
|
||||
response = http_compute.get("/projects/40010203-0405-0607-0809-0a0b0c0d0e02", example=True)
|
||||
assert len(response.json.keys()) == 2
|
||||
assert len(response.json.keys()) == 3
|
||||
assert response.json["project_id"] == "40010203-0405-0607-0809-0a0b0c0d0e02"
|
||||
assert response.json["name"] == "test"
|
||||
assert response.json["variables"] is None
|
||||
|
||||
|
||||
def test_show_project_invalid_uuid(http_compute):
|
||||
@ -93,6 +91,23 @@ def test_delete_project(http_compute, project):
|
||||
assert mock.called
|
||||
|
||||
|
||||
def test_update_project(http_compute):
|
||||
query = {"name": "test", "project_id": "51010203-0405-0607-0809-0a0b0c0d0e0f"}
|
||||
response = http_compute.post("/projects", query)
|
||||
assert response.status == 201
|
||||
|
||||
query = {
|
||||
"variables": [{"name": "TEST1", "value": "VAL1"}]
|
||||
}
|
||||
response = http_compute.put(
|
||||
"/projects/{project_id}".format(project_id="51010203-0405-0607-0809-0a0b0c0d0e0f"),
|
||||
query,
|
||||
example=True
|
||||
)
|
||||
assert response.status == 200
|
||||
assert response.json["variables"] == [{"name": "TEST1", "value": "VAL1"}]
|
||||
|
||||
|
||||
def test_delete_project_invalid_uuid(http_compute):
|
||||
response = http_compute.delete("/projects/{project_id}".format(project_id=uuid.uuid4()))
|
||||
assert response.status == 404
|
||||
|
@ -67,6 +67,31 @@ def test_create_project_with_uuid(http_controller):
|
||||
assert response.json["name"] == "test"
|
||||
|
||||
|
||||
def test_create_project_with_variables(http_controller):
|
||||
variables = [
|
||||
{"name": "TEST1"},
|
||||
{"name": "TEST2", "value": "value1"}
|
||||
]
|
||||
query = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f", "variables": variables}
|
||||
response = http_controller.post("/projects", query)
|
||||
assert response.status == 201
|
||||
assert response.json["variables"] == [
|
||||
{"name": "TEST1"},
|
||||
{"name": "TEST2", "value": "value1"}
|
||||
]
|
||||
|
||||
|
||||
def test_create_project_with_supplier(http_controller):
|
||||
supplier = {
|
||||
'logo': 'logo.png',
|
||||
'url': 'http://example.com'
|
||||
}
|
||||
query = {"name": "test", "project_id": "30010203-0405-0607-0809-0a0b0c0d0e0f", "supplier": supplier}
|
||||
response = http_controller.post("/projects", query)
|
||||
assert response.status == 201
|
||||
assert response.json["supplier"] == supplier
|
||||
|
||||
|
||||
def test_update_project(http_controller):
|
||||
query = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f"}
|
||||
response = http_controller.post("/projects", query)
|
||||
@ -79,6 +104,20 @@ def test_update_project(http_controller):
|
||||
assert response.json["name"] == "test2"
|
||||
|
||||
|
||||
def test_update_project_with_variables(http_controller):
|
||||
variables = [
|
||||
{"name": "TEST1"},
|
||||
{"name": "TEST2", "value": "value1"}
|
||||
]
|
||||
query = {"name": "test", "project_id": "10010203-0405-0607-0809-0a0b0c0d0e0f", "variables": variables}
|
||||
response = http_controller.post("/projects", query)
|
||||
assert response.status == 201
|
||||
query = {"name": "test2"}
|
||||
response = http_controller.put("/projects/10010203-0405-0607-0809-0a0b0c0d0e0f", query, example=True)
|
||||
assert response.status == 200
|
||||
assert response.json["variables"] == variables
|
||||
|
||||
|
||||
def test_list_projects(http_controller, tmpdir):
|
||||
http_controller.post("/projects", {"name": "test", "path": str(tmpdir), "project_id": "00010203-0405-0607-0809-0a0b0c0d0e0f"})
|
||||
response = http_controller.get("/projects", example=True)
|
||||
|
Loading…
Reference in New Issue
Block a user