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:
commit
da58a65075
14
CHANGELOG
14
CHANGELOG
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user