1
0
mirror of https://github.com/GNS3/gns3-server synced 2024-12-01 04:38:12 +00:00

Merge remote-tracking branch 'origin/2.0' into 2.0

Conflicts:
	gns3server/web/web_server.py
This commit is contained in:
grossmj 2016-06-16 11:19:03 -06:00
commit da58a65075
12 changed files with 69 additions and 31 deletions

View File

@ -1,5 +1,19 @@
# Change Log # Change Log
## 1.5.0rc2 15/06/2016
* Fix black screen with Qt app in Docker container
* Detect when command in the container exit
* Docker when the aux console exit and restart it
* Pass by default the environment variable container=docker
* Fix busybox binary location
* Avoid loosing console port for Docker
* Workaround a crash in x11vnc
* Delete volume when dropping the container
* Catch connection reset in ioucon
* Delete vlan.dat for L2IOL during config import. Fixes #1285.
* Copy original ressources from VOLUMES
## 1.5.0rc1 01/06/2016 ## 1.5.0rc1 01/06/2016
* Save an restore docker permission * Save an restore docker permission

View File

@ -18,6 +18,10 @@ it on https://github.com/GNS3/gns3-gui we will take care of the triage.
For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm For bugs specific to the GNS3 VM, please report on https://github.com/GNS3/gns3-vm
## Security issues
For security issues please keep it private and send an email to developers@gns3.net
## Asking for new features ## Asking for new features
The best is to start a discussion on the community website in order to get feedback The best is to start a discussion on the community website in order to get feedback

View File

@ -274,5 +274,6 @@ Previous versions
API version 1 API version 1
------------- -------------
Shipped with GNS3 1.3, 1.4 and 1.5. This API doesn't support the controller system. Shipped with GNS3 1.3, 1.4 and 1.5.
This API doesn't support the controller system and save used a commit system instead of live save.

View File

@ -279,7 +279,7 @@ class DockerVM(BaseNode):
"Binds": self._mount_binds(image_infos) "Binds": self._mount_binds(image_infos)
}, },
"Volumes": {}, "Volumes": {},
"Env": [], "Env": ["container=docker"], # Systemd compliant: https://github.com/GNS3/gns3-server/issues/573
"Cmd": [], "Cmd": [],
"Entrypoint": image_infos.get("Config", {"Entrypoint": []})["Entrypoint"] "Entrypoint": image_infos.get("Config", {"Entrypoint": []})["Entrypoint"]
} }
@ -306,6 +306,7 @@ class DockerVM(BaseNode):
if self._console_type == "vnc": if self._console_type == "vnc":
yield from self._start_vnc() yield from self._start_vnc()
params["Env"].append("QT_GRAPHICSSYSTEM=native") # To fix a Qt issue: https://github.com/GNS3/gns3-server/issues/556
params["Env"].append("DISPLAY=:{}".format(self._display)) params["Env"].append("DISPLAY=:{}".format(self._display))
params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/") params["HostConfig"]["Binds"].append("/tmp/.X11-unix/:/tmp/.X11-unix/")
@ -384,7 +385,7 @@ class DockerVM(BaseNode):
# We can not use the API because docker doesn't expose a websocket api for exec # We can not use the API because docker doesn't expose a websocket api for exec
# https://github.com/GNS3/gns3-gui/issues/1039 # https://github.com/GNS3/gns3-gui/issues/1039
process = yield from asyncio.subprocess.create_subprocess_exec( process = yield from asyncio.subprocess.create_subprocess_exec(
"docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "/gns3/bin/busybox sh", "/dev/null", "docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "script", "-qfc", "while true; do /gns3/bin/busybox sh; done", "/dev/null",
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT, stderr=asyncio.subprocess.STDOUT,
stdin=asyncio.subprocess.PIPE) stdin=asyncio.subprocess.PIPE)
@ -493,7 +494,9 @@ class DockerVM(BaseNode):
out.feed_eof() out.feed_eof()
ws.close() ws.close()
break break
yield from self.stop()
@asyncio.coroutine
def is_running(self): def is_running(self):
"""Checks if the container is running. """Checks if the container is running.
@ -535,21 +538,21 @@ class DockerVM(BaseNode):
if state == "paused": if state == "paused":
yield from self.unpause() yield from self.unpause()
if state != "stopped":
yield from self._fix_permissions() yield from self._fix_permissions()
# t=5 number of seconds to wait before killing the container # t=5 number of seconds to wait before killing the container
try: try:
yield from self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5}) yield from self.manager.query("POST", "containers/{}/stop".format(self._cid), params={"t": 5})
log.info("Docker container '{name}' [{image}] stopped".format(name=self._name, image=self._image)) log.info("Docker container '{name}' [{image}] stopped".format(
name=self._name, image=self._image))
except DockerHttp304Error: except DockerHttp304Error:
# Container is already stopped # Container is already stopped
pass pass
finally:
self.status = "stopped"
# Ignore runtime error because when closing the server # Ignore runtime error because when closing the server
except RuntimeError as e: except RuntimeError as e:
log.debug("Docker runtime error when closing: {}".format(str(e))) log.debug("Docker runtime error when closing: {}".format(str(e)))
return return
self.status = "stopped"
@asyncio.coroutine @asyncio.coroutine
def pause(self): def pause(self):

View File

@ -217,9 +217,10 @@ class Controller:
topo_data.pop("revision") topo_data.pop("revision")
topo_data.pop("type") topo_data.pop("type")
project = yield from self.add_project(path=os.path.dirname(path), status="closed", **topo_data) project = yield from self.add_project(path=os.path.dirname(path), status="closed", filename=os.path.basename(path), **topo_data)
if load: if load:
yield from project.open() yield from project.open()
return project
@property @property
def projects(self): def projects(self):

View File

@ -43,7 +43,7 @@ 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"): def __init__(self, name=None, project_id=None, path=None, controller=None, status="opened", filename=None):
self._controller = controller self._controller = controller
self._name = name self._name = name
@ -60,6 +60,13 @@ class Project:
if path is None: if path is None:
path = os.path.join(get_default_project_directory(), self._id) path = os.path.join(get_default_project_directory(), self._id)
self.path = path self.path = path
if filename is None and name is None:
self._filename = "project.gns3"
elif filename is not None:
self._filename = filename
else:
self._filename = self.name + ".gns3"
self.reset() self.reset()
def reset(self): def reset(self):
@ -322,14 +329,8 @@ class Project:
raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e)) raise aiohttp.web.HTTPInternalServerError(text="Could not create project directory: {}".format(e))
return path return path
def _filename(self):
if self.name is None:
return "untitled.gns3"
else:
return self.name + ".gns3"
def _topology_file(self): def _topology_file(self):
return os.path.join(self.path, self._filename()) return os.path.join(self.path, self._filename)
@asyncio.coroutine @asyncio.coroutine
def open(self): def open(self):
@ -373,6 +374,6 @@ class Project:
"name": self._name, "name": self._name,
"project_id": self._id, "project_id": self._id,
"path": self._path, "path": self._path,
"filename": self._filename(), "filename": self._filename,
"status": self._status "status": self._status
} }

View File

@ -49,7 +49,7 @@ class ServerHandler:
# close all the projects first # close all the projects first
controller = Controller.instance() controller = Controller.instance()
projects = controller.projects projects = controller.projects.values()
tasks = [] tasks = []
for project in projects: for project in projects:

View File

@ -102,6 +102,7 @@ def parse_arguments(argv):
parser.add_argument("--log", help="send output to logfile instead of console") parser.add_argument("--log", help="send output to logfile instead of console")
parser.add_argument("--daemon", action="store_true", help="start as a daemon") parser.add_argument("--daemon", action="store_true", help="start as a daemon")
parser.add_argument("--pid", help="store process pid") parser.add_argument("--pid", help="store process pid")
parser.add_argument("--udpdiscovery", action="store_true", help="allow the server to be discover on the network")
args = parser.parse_args(argv) args = parser.parse_args(argv)
if args.config: if args.config:
@ -121,6 +122,7 @@ def parse_arguments(argv):
"quiet": config.getboolean("quiet", False), "quiet": config.getboolean("quiet", False),
"debug": config.getboolean("debug", False), "debug": config.getboolean("debug", False),
"logfile": config.getboolean("logfile", ""), "logfile": config.getboolean("logfile", ""),
"udp_discovery": config.getboolean("udp_discovery", False),
} }
parser.set_defaults(**defaults) parser.set_defaults(**defaults)
@ -142,6 +144,7 @@ def set_config(args):
server_config["record"] = args.record server_config["record"] = args.record
server_config["debug"] = str(args.debug) server_config["debug"] = str(args.debug)
server_config["shell"] = str(args.shell) server_config["shell"] = str(args.shell)
server_config["udp_discovery"] = str(args.udpdiscovery)
config.set_section_config("Server", server_config) config.set_section_config("Server", server_config)

View File

@ -59,6 +59,7 @@ class WebServer:
self._handler = None self._handler = None
self._start_time = time.time() self._start_time = time.time()
self._port_manager = PortManager(host) self._port_manager = PortManager(host)
self._running = False
@staticmethod @staticmethod
def instance(host=None, port=None): def instance(host=None, port=None):
@ -298,7 +299,7 @@ class WebServer:
if server_config.getboolean("shell"): if server_config.getboolean("shell"):
asyncio.async(self.start_shell()) asyncio.async(self.start_shell())
if sys.platform.startswith("linux"): if sys.platform.startswith("linux") and server_config.getboolean("udp_discovery"):
# UDP discovery is only supported on Linux # UDP discovery is only supported on Linux
udp_server_discovery = threading.Thread(target=self._udp_server_discovery, daemon=True) udp_server_discovery = threading.Thread(target=self._udp_server_discovery, daemon=True)
udp_server_discovery.start() udp_server_discovery.start()

View File

@ -105,6 +105,7 @@ def test_create(loop, project, manager):
"Hostname": "test", "Hostname": "test",
"Image": "ubuntu:latest", "Image": "ubuntu:latest",
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network" "GNS3_VOLUMES=/etc/network"
], ],
@ -143,6 +144,7 @@ def test_create_with_tag(loop, project, manager):
"Hostname": "test", "Hostname": "test",
"Image": "ubuntu:16.04", "Image": "ubuntu:16.04",
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network" "GNS3_VOLUMES=/etc/network"
], ],
@ -185,8 +187,10 @@ def test_create_vnc(loop, project, manager):
"Hostname": "test", "Hostname": "test",
"Image": "ubuntu:latest", "Image": "ubuntu:latest",
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network", "GNS3_VOLUMES=/etc/network",
"QT_GRAPHICSSYSTEM=native",
"DISPLAY=:42" "DISPLAY=:42"
], ],
"Entrypoint": ["/gns3/init.sh"], "Entrypoint": ["/gns3/init.sh"],
@ -229,6 +233,7 @@ def test_create_start_cmd(loop, project, manager):
"Hostname": "test", "Hostname": "test",
"Image": "ubuntu:latest", "Image": "ubuntu:latest",
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network" "GNS3_VOLUMES=/etc/network"
] ]
@ -261,6 +266,7 @@ def test_create_environment(loop, project, manager):
"Privileged": True "Privileged": True
}, },
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network", "GNS3_VOLUMES=/etc/network",
"YES=1", "YES=1",
@ -320,6 +326,7 @@ def test_create_image_not_available(loop, project, manager):
"Hostname": "test", "Hostname": "test",
"Image": "ubuntu:latest", "Image": "ubuntu:latest",
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network" "GNS3_VOLUMES=/etc/network"
], ],
@ -540,6 +547,7 @@ def test_update(loop, vm):
"Hostname": "test", "Hostname": "test",
"Image": "ubuntu:latest", "Image": "ubuntu:latest",
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network" "GNS3_VOLUMES=/etc/network"
], ],
@ -608,6 +616,7 @@ def test_update_running(loop, vm):
"Hostname": "test", "Hostname": "test",
"Image": "ubuntu:latest", "Image": "ubuntu:latest",
"Env": [ "Env": [
"container=docker",
"GNS3_MAX_ETHERNET=eth0", "GNS3_MAX_ETHERNET=eth0",
"GNS3_VOLUMES=/etc/network" "GNS3_VOLUMES=/etc/network"
], ],
@ -904,7 +913,7 @@ def test_start_aux(vm, loop):
with asyncio_patch("asyncio.subprocess.create_subprocess_exec", return_value=MagicMock()) as mock_exec: with asyncio_patch("asyncio.subprocess.create_subprocess_exec", return_value=MagicMock()) as mock_exec:
loop.run_until_complete(asyncio.async(vm._start_aux())) loop.run_until_complete(asyncio.async(vm._start_aux()))
mock_exec.assert_called_with('docker', 'exec', '-i', 'e90e34656842', '/gns3/bin/busybox', 'script', '-qfc', '/gns3/bin/busybox sh', '/dev/null', stderr=asyncio.subprocess.STDOUT, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE) mock_exec.assert_called_with('docker', 'exec', '-i', 'e90e34656842', '/gns3/bin/busybox', 'script', '-qfc', 'while true; do /gns3/bin/busybox sh; done', '/dev/null', stderr=asyncio.subprocess.STDOUT, stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE)
def test_create_network_interfaces(vm): def test_create_network_interfaces(vm):

View File

@ -188,7 +188,7 @@ def test_close(controller, async_run):
def test_load_project(controller, async_run, tmpdir): def test_load_project(controller, async_run, tmpdir):
data = { data = {
"name": "Test", "name": "Experience",
"project_id": "c8d07a5a-134f-4c3f-8599-e35eac85eb17", "project_id": "c8d07a5a-134f-4c3f-8599-e35eac85eb17",
"revision": 5, "revision": 5,
"type": "topology", "type": "topology",
@ -242,11 +242,12 @@ def test_load_project(controller, async_run, tmpdir):
controller._computes["my_remote"] = MagicMock() controller._computes["my_remote"] = MagicMock()
with asyncio_patch("gns3server.controller.node.Node.create") as mock_node_create: with asyncio_patch("gns3server.controller.node.Node.create") as mock_node_create:
async_run(controller.load_project(str(tmpdir / "test.gns3"))) project = async_run(controller.load_project(str(tmpdir / "test.gns3")))
assert project._topology_file() == str(tmpdir / "test.gns3")
controller.add_compute.assert_called_with(compute_id='my_remote', host='127.0.0.1', name='My remote', port=3080, protocol='http') controller.add_compute.assert_called_with(compute_id='my_remote', host='127.0.0.1', name='My remote', port=3080, protocol='http')
project = controller.get_project('c8d07a5a-134f-4c3f-8599-e35eac85eb17') project = controller.get_project('c8d07a5a-134f-4c3f-8599-e35eac85eb17')
assert project.name == "Test" assert project.name == "Experience"
assert project.path == str(tmpdir) assert project.path == str(tmpdir)
link = project.get_link("c44331d2-2da4-490d-9aad-7f5c126ae271") link = project.get_link("c44331d2-2da4-490d-9aad-7f5c126ae271")
assert len(link.nodes) == 2 assert len(link.nodes) == 2

View File

@ -43,7 +43,7 @@ def test_affect_uuid():
def test_json(tmpdir): def test_json(tmpdir):
p = Project() p = Project()
assert p.__json__() == {"name": p.name, "project_id": p.id, "path": p.path, "status": "opened", "filename": "untitled.gns3"} assert p.__json__() == {"name": p.name, "project_id": p.id, "path": p.path, "status": "opened", "filename": "project.gns3"}
def test_path(tmpdir): def test_path(tmpdir):