Merge branch '1.5' into 2.0

This commit is contained in:
Julien Duponchelle committed 2016-04-08 17:40:27 +02:00
commit 9e8fcab65c
34 files changed
+895 -188

No files matched your search

@@ -73,6 +73,7 @@ class DockerHandler:
adapters=request.json.get("adapters"),
console=request.json.get("console"),
console_type=request.json.get("console_type"),
console_resolution=request.json.get("console_resolution", "1024x768"),
aux=request.json.get("aux")
)
for name, value in request.json.items():
@@ -277,8 +278,11 @@ class DockerHandler:
vm = docker_manager.get_vm(request.match_info["vm_id"], project_id=request.match_info["project_id"])
vm.name = request.json.get("name", vm.name)
vm.console = request.json.get("console", vm.console)
vm.aux = request.json.get("aux", vm.aux)
vm.console_resolution = request.json.get("console_resolution", vm.console_resolution)
vm.start_command = request.json.get("start_command", vm.start_command)
vm.environment = request.json.get("environment", vm.environment)
vm.adapters = request.json.get("adapters", vm.adapters)
yield from vm.update()
response.json(vm)
@@ -20,6 +20,7 @@ import asyncio
import json
import os
import psutil
import tempfile
from ....web.route import Route
from ....schemas.project import PROJECT_OBJECT_SCHEMA, PROJECT_CREATE_SCHEMA, PROJECT_UPDATE_SCHEMA, PROJECT_FILE_LIST_SCHEMA, PROJECT_LIST_SCHEMA
@@ -56,6 +57,7 @@ class ProjectHandler:
description="Create a new project on the server",
status_codes={
201: "Project created",
403: "You are not allowed to modify this property",
409: "Project already created"
},
output=PROJECT_OBJECT_SCHEMA,
@@ -301,4 +303,111 @@ class ProjectHandler:
except FileNotFoundError:
raise aiohttp.web.HTTPNotFound()
except PermissionError:
raise aiohttp.web.HTTPForbidden()
@classmethod
@Route.post(
r"/projects/{project_id}/files/{path:.+}",
description="Get a file of a project",
parameters={
"project_id": "The UUID of the project",
},
raw=True,
status_codes={
200: "Return the file",
403: "Permission denied",
404: "The path doesn't exist"
})
def write_file(request, response):
pm = ProjectManager.instance()
project = pm.get_project(request.match_info["project_id"])
path = request.match_info["path"]
path = os.path.normpath(path)
# Raise error if user try to escape
if path[0] == ".":
raise aiohttp.web.HTTPForbidden
path = os.path.join(project.path, path)
response.set_status(200)
try:
with open(path, 'wb+') as f:
while True:
packet = yield from request.content.read(512)
if not packet:
break
f.write(packet)
except FileNotFoundError:
raise aiohttp.web.HTTPNotFound()
except PermissionError:
raise aiohttp.web.HTTPForbidden()
@classmethod
@Route.get(
r"/projects/{project_id}/export",
description="Export a project as a portable archive",
parameters={
"project_id": "The UUID of the project",
},
raw=True,
status_codes={
200: "Return the file",
404: "The project doesn't exist"
})
def export_project(request, response):
pm = ProjectManager.instance()
project = pm.get_project(request.match_info["project_id"])
response.content_type = 'application/gns3z'
response.headers['CONTENT-DISPOSITION'] = 'attachment; filename="{}.gns3z"'.format(project.name)
response.enable_chunked_encoding()
# Very important: do not send a content length otherwise QT close the connection but curl can consume the Feed
response.content_length = None
response.start(request)
for data in project.export():
response.write(data)
yield from response.drain()
yield from response.write_eof()
@classmethod
@Route.post(
r"/projects/{project_id}/import",
description="Import a project from a portable archive",
parameters={
"project_id": "The UUID of the project",
},
raw=True,
output=PROJECT_OBJECT_SCHEMA,
status_codes={
200: "Project imported",
403: "You are not allowed to modify this property"
})
def import_project(request, response):
pm = ProjectManager.instance()
project_id = request.match_info["project_id"]
project = pm.create_project(project_id=project_id)
# We write the content to a temporary location
# and after extract all. It could be more optimal to stream
# this but it's not implemented in Python.
# 
# Spooled mean the file is temporary keep in ram until max_size
try:
with tempfile.SpooledTemporaryFile(max_size=10000) as temp:
while True:
packet = yield from request.content.read(512)
if not packet:
break
temp.write(packet)
project.import_zip(temp, gns3vm=bool(request.GET.get("gns3vm", "1")))
except OSError as e:
raise aiohttp.web.HTTPInternalServerError(text="Could not import the project: {}".format(e))
response.json(project)
response.set_status(201)
+7 -3
View File
@@ -340,13 +340,17 @@ class BaseVM:
return
if self._console_type == "vnc" and console is not None and console < 5900:
raise VMError("VNC console require a port superior or equal to 5900")
raise VMError("VNC console require a port superior or equal to 5900 currently it's {}".format(console))
if self._console:
self._manager.port_manager.release_tcp_port(self._console, self._project)
self._console = None
if console is not None:
self._console = self._manager.port_manager.reserve_tcp_port(console, self._project)
if self.console_type == "vnc":
self._console = self._manager.port_manager.reserve_tcp_port(console, self._project, port_range_start=5900, port_range_end=6000)
else:
self._console = self._manager.port_manager.reserve_tcp_port(console, self._project)
log.info("{module}: '{name}' [{id}]: console port set to {port}".format(module=self.manager.module_name,
name=self.name,
id=self.id,
@@ -401,7 +405,7 @@ class BaseVM:
if path == "ubridge":
path = shutil.which("ubridge")
if path is None:
if path is None or len(path) == 0:
raise VMError("uBridge is not installed")
return path
+71 -13
View File
@@ -53,11 +53,13 @@ class DockerVM(BaseVM):
:param console: TCP console port
:param console_type: Console type
:param aux: TCP aux console port
:param console_resolution: Resolution of the VNC display
"""
def __init__(self, name, vm_id, project, manager, image,
console=None, aux=None, start_command=None,
adapters=None, environment=None, console_type="telnet"):
adapters=None, environment=None, console_type="telnet",
console_resolution="1024x768"):
super().__init__(name, vm_id, project, manager, console=console, aux=aux, allocate_aux=True, console_type=console_type)
self._image = image
@@ -68,6 +70,8 @@ class DockerVM(BaseVM):
self._ubridge_hypervisor = None
self._temporary_directory = None
self._telnet_servers = []
self._x11vnc_process = None
self._console_resolution = console_resolution
if adapters is None:
self.adapters = 1
@@ -90,6 +94,7 @@ class DockerVM(BaseVM):
"adapters": self.adapters,
"console": self.console,
"console_type": self.console_type,
"console_resolution": self.console_resolution,
"aux": self.aux,
"start_command": self.start_command,
"environment": self.environment,
@@ -120,6 +125,14 @@ class DockerVM(BaseVM):
else:
self._start_command = command
@property
def console_resolution(self):
return self._console_resolution
@console_resolution.setter
def console_resolution(self, resolution):
self._console_resolution = resolution
@property
def environment(self):
return self._environment
@@ -159,6 +172,10 @@ class DockerVM(BaseVM):
binds.append("{}:/gns3:ro".format(get_resource("hypervisor/docker/resources")))
# We mount our own etc/network
network_config = self._create_network_config()
binds.append("{}:/etc/network:rw".format(network_config))
volumes = image_infos.get("ContainerConfig", {}).get("Volumes")
if volumes is None:
return binds
@@ -169,6 +186,39 @@ class DockerVM(BaseVM):
return binds
def _create_network_config(self):
"""
If network config is empty we create a sample config
"""
path = os.path.join(self.working_dir, "etc", "network")
os.makedirs(path, exist_ok=True)
os.makedirs(os.path.join(path, "if-up.d"), exist_ok=True)
os.makedirs(os.path.join(path, "if-down.d"), exist_ok=True)
os.makedirs(os.path.join(path, "if-pre-up.d"), exist_ok=True)
os.makedirs(os.path.join(path, "if-post-down.d"), exist_ok=True)
if not os.path.exists(os.path.join(path, "interfaces")):
with open(os.path.join(path, "interfaces"), "w+") as f:
f.write("""#
# This is a sample network config uncomment lines to configure the network
#
""")
for adapter in range(0, self.adapters):
f.write("""
# Static config for eth{adapter}
#auto eth{adapter}
#iface eth{adapter} inet static
#\taddress 192.168.{adapter}.2
#\tnetmask 255.255.255.0
#\tgateway 192.168.{adapter}.1
#\tup echo nameserver 192.168.{adapter}.1 > /etc/resolv.conf
# DHCP config for eth{adapter}
# auto eth{adapter}
# iface eth{adapter} inet dhcp""".format(adapter=adapter))
return path
@asyncio.coroutine
def create(self):
"""Creates the Docker container."""
@@ -199,7 +249,6 @@ class DockerVM(BaseVM):
"Entrypoint": image_infos.get("Config", {"Entrypoint": []})["Entrypoint"]
}
if params["Entrypoint"] is None:
params["Entrypoint"] = []
if self._start_command:
@@ -233,11 +282,13 @@ class DockerVM(BaseVM):
"""
# We need to save the console and state and restore it
console = self.console
aux = self.aux
state = yield from self._get_container_state()
yield from self.close()
yield from self.create()
self.console = console
self.aux = aux
if state == "running":
yield from self.start()
@@ -287,7 +338,7 @@ class DockerVM(BaseVM):
# We can not use the API because docker doesn't expose a websocket api for exec
# https://github.com/GNS3/gns3-gui/issues/1039
process = yield from asyncio.subprocess.create_subprocess_exec(
"docker", "exec", "-i", self._cid, "/bin/sh", "-i",
"docker", "exec", "-i", self._cid, "/gns3/bin/busybox", "sh", "-i",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.STDOUT,
stdin=asyncio.subprocess.PIPE)
@@ -304,8 +355,8 @@ class DockerVM(BaseVM):
self._display = self._get_free_display_port()
if shutil.which("Xvfb") is None or shutil.which("x11vnc") is None:
raise DockerError("Please install Xvfb and x11vnc before using the VNC support")
self._xvfb_process = yield from asyncio.create_subprocess_exec("Xvfb", "-nolisten", "tcp", ":{}".format(self._display), "-screen", "0", "1024x768x16")
self._x11vnc_process = yield from asyncio.create_subprocess_exec("x11vnc", "-forever", "-nopw", "-display", "WAIT:{}".format(self._display), "-rfbport", str(self.console), "-noncache", "-listen", self._manager.port_manager.console_host)
self._xvfb_process = yield from asyncio.create_subprocess_exec("Xvfb", "-nolisten", "tcp", ":{}".format(self._display), "-screen", "0", self._console_resolution + "x16")
self._x11vnc_process = yield from asyncio.create_subprocess_exec("x11vnc", "-forever", "-nopw", "-shared", "-geometry", self._console_resolution, "-display", "WAIT:{}".format(self._display), "-rfbport", str(self.console), "-noncache", "-listen", self._manager.port_manager.console_host)
x11_socket = os.path.join("/tmp/.X11-unix/", "X{}".format(self._display))
yield from wait_for_file_creation(x11_socket)
@@ -433,10 +484,11 @@ class DockerVM(BaseVM):
try:
if self.console_type == "vnc":
self._x11vnc_process.terminate()
self._xvfb_process.terminate()
yield from self._x11vnc_process.wait()
yield from self._xvfb_process.wait()
if self._x11vnc_process:
self._x11vnc_process.terminate()
self._xvfb_process.terminate()
yield from self._x11vnc_process.wait()
yield from self._xvfb_process.wait()
state = yield from self._get_container_state()
if state == "paused" or state == "running":
@@ -521,11 +573,17 @@ class DockerVM(BaseVM):
if not self._ubridge_hypervisor or not self._ubridge_hypervisor.is_running():
return
yield from self._ubridge_hypervisor.send("bridge delete bridge{name}".format(
name=adapter_number))
adapter = self._ethernet_adapters[adapter_number]
yield from self._ubridge_hypervisor.send('docker delete_veth {hostif}'.format(hostif=adapter.host_ifc))
try:
yield from self._ubridge_hypervisor.send("bridge delete bridge{name}".format(
name=adapter_number))
except UbridgeError as e:
log.debug(str(e))
try:
yield from self._ubridge_hypervisor.send('docker delete_veth {hostif}'.format(hostif=adapter.host_ifc))
except UbridgeError as e:
log.debug(str(e))
@asyncio.coroutine
def _get_namespace(self):
+14 -2
View File
@@ -1,4 +1,4 @@
#!/bin/sh
#!/gns3/bin/busybox sh
#
# Copyright (C) 2016 GNS3 Technologies Inc.
#
@@ -19,6 +19,14 @@
# This script is injected into the container and launch before
# the start command of the container
#
OLD_PATH="$PATH"
PATH=/gns3/bin:/tmp/gns3/bin
# bootstrap busybox commands
if [ ! -d /tmp/gns3/bin ]; then
busybox mkdir -p /tmp/gns3/bin
/gns3/bin/busybox --install -s /tmp/gns3/bin
fi
# Wait 2 seconds to settle the network interfaces
sleep 2
@@ -37,10 +45,14 @@ __EOF__
# configure loopback interface
ip link set dev lo up
# configure eth interfaces
# activate eth interfaces
sed -n 's/^ *\(eth[0-9]*\):.*/\1/p' < /proc/net/dev | while read dev; do
ip link set dev $dev up
done
# configure network interfaces
ifup -a -f
# continue normal docker startup
PATH="$OLD_PATH"
exec "$@"
+3 -3
View File
@@ -32,7 +32,7 @@ import glob
log = logging.getLogger(__name__)
from gns3server.utils.interfaces import get_windows_interfaces, is_interface_up
from gns3server.utils.interfaces import interfaces, is_interface_up
from gns3server.utils.asyncio import wait_run_in_executor
from pkg_resources import parse_version
from uuid import UUID, uuid4
@@ -439,9 +439,9 @@ class Dynamips(BaseManager):
ethernet_device = nio_settings["ethernet_device"]
if sys.platform.startswith("win"):
# replace the interface name by the GUID on Windows
interfaces = get_windows_interfaces()
windows_interfaces = interfaces()
npf_interface = None
for interface in interfaces:
for interface in windows_interfaces:
if interface["name"] == ethernet_device:
npf_interface = interface["id"]
if not npf_interface:
@@ -895,7 +895,7 @@ class Router(BaseVM):
"""
self.console = console
yield from self._hypervisor.send('vm set_con_tcp_port "{name}" {console}'.format(name=self._name, console=console))
yield from self._hypervisor.send('vm set_con_tcp_port "{name}" {console}'.format(name=self._name, console=self.console))
@asyncio.coroutine
def set_aux(self, aux):
+9 -9
View File
@@ -42,8 +42,8 @@ class PortManager:
server_config = Config.instance().get_section_config("Server")
remote_console_connections = server_config.getboolean("allow_remote_console")
console_start_port_range = server_config.getint("console_start_port_range", 2001)
console_end_port_range = server_config.getint("console_end_port_range", 7000)
console_start_port_range = server_config.getint("console_start_port_range", 5000)
console_end_port_range = server_config.getint("console_end_port_range", 10000)
self._console_port_range = (console_start_port_range, console_end_port_range)
log.debug("Console port range is {}-{}".format(console_start_port_range, console_end_port_range))
@@ -225,15 +225,15 @@ class PortManager:
old_port = port
port = self.get_free_tcp_port(project, port_range_start=port_range_start, port_range_end=port_range_end)
msg = "TCP port {} already in use on host {}. Port has been replaced by {}".format(old_port, self._console_host, port)
log.warning(msg)
project.emit("log.warning", {"message": msg})
log.debug(msg)
#project.emit("log.warning", {"message": msg})
return port
if port < self._console_port_range[0] or port > self._console_port_range[1]:
if port < port_range_start or port > port_range_end:
old_port = port
port = self.get_free_tcp_port(project, port_range_start=port_range_start, port_range_end=port_range_end)
msg = "TCP port {} is outside the range {}-{} on host {}. Port has been replaced by {}".format(old_port, port_range_start, port_range_end, self._console_host, port)
log.warning(msg)
project.emit("log.warning", {"message": msg})
log.debug(msg)
#project.emit("log.warning", {"message": msg})
return port
try:
PortManager._check_port(self._console_host, port, "TCP")
@@ -241,8 +241,8 @@ class PortManager:
old_port = port
port = self.get_free_tcp_port(project, port_range_start=port_range_start, port_range_end=port_range_end)
msg = "TCP port {} already in use on host {}. Port has been replaced by {}".format(old_port, self._console_host, port)
log.warning(msg)
project.emit("log.warning", {"message": msg})
log.debug(msg)
#project.emit("log.warning", {"message": msg})
return port
self._used_tcp_ports.add(port)
+111 -1
View File
@@ -15,11 +15,14 @@
# 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 os
import aiohttp
import shutil
import asyncio
import hashlib
import zipstream
import zipfile
import json
from uuid import UUID, uuid4
from .port_manager import PortManager
@@ -143,6 +146,8 @@ class Project:
@name.setter
def name(self, name):
if "/" in name or "\\" in name:
raise aiohttp.web.HTTPForbidden(text="Name can not contain path separator")
self._name = name
@property
@@ -460,3 +465,108 @@ class Project:
break
m.update(buf)
return m.hexdigest()
def export(self):
"""
Export the project as zip. It's a ZipStream object.
The file will be read chunk by chunk when you iterate on
the zip.
It will ignore some files like snapshots and
:returns: ZipStream object
"""
z = zipstream.ZipFile()
# topdown allo to modify the list of directory in order to ignore
# directory
for root, dirs, files in os.walk(self._path, topdown=True):
# Remove snapshots
if os.path.split(root)[-1:][0] == "project-files":
dirs[:] = [d for d in dirs if d != "snapshots"]
# Ignore log files and OS noise
files = [f for f in files if not f.endswith('_log.txt') and not f.endswith('.log') and f != '.DS_Store']
for file in files:
path = os.path.join(root, file)
# We rename the .gns3 project.gns3 to avoid the task to the client to guess the file name
if file.endswith(".gns3"):
z.write(path, "project.gns3")
else:
# We merge the data from all server in the same project-files directory
vm_directory = os.path.join(self._path, "servers", "vm")
if os.path.commonprefix([root, vm_directory]) == vm_directory:
z.write(path, os.path.relpath(path, vm_directory))
else:
z.write(path, os.path.relpath(path, self._path))
return z
def import_zip(self, stream, gns3vm=True):
"""
Import a project contain in a zip file
:param stream: A io.BytesIO of the zipfile
:param gns3vm: True move docker, iou and qemu to the GNS3 VM
"""
with zipfile.ZipFile(stream) as myzip:
myzip.extractall(self.path)
project_file = os.path.join(self.path, "project.gns3")
if os.path.exists(project_file):
with open(project_file) as f:
topology = json.load(f)
topology["project_id"] = self.id
topology["name"] = self.name
topology.setdefault("topology", {})
topology["topology"].setdefault("nodes", [])
topology["topology"]["servers"] = [
{
"id": 1,
"local": True,
"vm": False
}
]
# By default all node run on local server
for node in topology["topology"]["nodes"]:
node["server_id"] = 1
if gns3vm:
# Move to servers/vm directory the data that should be import on remote server
modules_to_vm = {
"qemu": "QemuVM",
"iou": "IOUDevice",
"docker": "DockerVM"
}
vm_directory = os.path.join(self.path, "servers", "vm", "project-files")
vm_server_use = False
for module, device_type in modules_to_vm.items():
module_directory = os.path.join(self.path, "project-files", module)
if os.path.exists(module_directory):
os.makedirs(vm_directory, exist_ok=True)
shutil.move(module_directory, os.path.join(vm_directory, module))
# Patch node to use the GNS3 VM
for node in topology["topology"]["nodes"]:
if node["type"] == device_type:
node["server_id"] = 2
vm_server_use = True
# We use the GNS3 VM. We need to add the server to the list
if vm_server_use:
topology["topology"]["servers"].append({
"id": 2,
"vm": True,
"local": False
})
# Write the modified topology
with open(project_file, "w") as f:
json.dump(topology, f, indent=4)
# Rename to a human distinctive name
shutil.move(project_file, os.path.join(self.path, self.name + ".gns3"))
+28 -72
View File
@@ -40,6 +40,7 @@ from ..base_vm import BaseVM
from ...schemas.qemu import QEMU_OBJECT_SCHEMA, QEMU_PLATFORMS
from ...utils.asyncio import monitor_process
from ...utils.images import md5sum
from .qcow2 import Qcow2, Qcow2Error
import logging
log = logging.getLogger(__name__)
@@ -1233,90 +1234,45 @@ class QemuVM(BaseVM):
options = []
qemu_img_path = self._get_qemu_img()
if self._hda_disk_image:
if not os.path.isfile(self._hda_disk_image) or not os.path.exists(self._hda_disk_image):
if os.path.islink(self._hda_disk_image):
raise QemuError("hda disk image '{}' linked to '{}' is not accessible".format(self._hda_disk_image, os.path.realpath(self._hda_disk_image)))
drives = ["a", "b", "c", "d"]
for disk_index, drive in enumerate(drives):
disk_image = getattr(self, "_hd{}_disk_image".format(drive))
interface = getattr(self, "hd{}_disk_interface".format(drive))
if not disk_image:
continue
disk_name = "hd" + drive
if not os.path.isfile(disk_image) or not os.path.exists(disk_image):
if os.path.islink(disk_image):
raise QemuError("{} disk image '{}' linked to '{}' is not accessible".format(disk_name, disk_image, os.path.realpath(disk_image)))
else:
raise QemuError("hda disk image '{}' is not accessible".format(self._hda_disk_image))
raise QemuError("{} disk image '{}' is not accessible".format(disk_name, disk_image))
if self._linked_clone:
hda_disk = os.path.join(self.working_dir, "hda_disk.qcow2")
if not os.path.exists(hda_disk):
disk = os.path.join(self.working_dir, "{}_disk.qcow2".format(disk_name))
if not os.path.exists(disk):
# create the disk
try:
process = yield from asyncio.create_subprocess_exec(qemu_img_path, "create", "-o",
"backing_file={}".format(self._hda_disk_image),
"-f", "qcow2", hda_disk)
"backing_file={}".format(disk_image),
"-f", "qcow2", disk)
retcode = yield from process.wait()
log.info("{} returned with {}".format(qemu_img_path, retcode))
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Could not create hda disk image {}".format(e))
else:
hda_disk = self._hda_disk_image
options.extend(["-drive", 'file={},if={},index=0,media=disk'.format(hda_disk, self.hda_disk_interface)])
if self._hdb_disk_image:
if not os.path.isfile(self._hdb_disk_image) or not os.path.exists(self._hdb_disk_image):
if os.path.islink(self._hdb_disk_image):
raise QemuError("hdb disk image '{}' linked to '{}' is not accessible".format(self._hdb_disk_image, os.path.realpath(self._hdb_disk_image)))
raise QemuError("Could not create {} disk image {}".format(disk_name, e))
else:
raise QemuError("hdb disk image '{}' is not accessible".format(self._hdb_disk_image))
if self._linked_clone:
hdb_disk = os.path.join(self.working_dir, "hdb_disk.qcow2")
if not os.path.exists(hdb_disk):
# The disk exists we check if the clone work
try:
process = yield from asyncio.create_subprocess_exec(qemu_img_path, "create", "-o",
"backing_file={}".format(self._hdb_disk_image),
"-f", "qcow2", hdb_disk)
retcode = yield from process.wait()
log.info("{} returned with {}".format(qemu_img_path, retcode))
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Could not create hdb disk image {}".format(e))
else:
hdb_disk = self._hdb_disk_image
options.extend(["-drive", 'file={},if={},index=1,media=disk'.format(hdb_disk, self.hdb_disk_interface)])
qcow2 = Qcow2(disk)
yield from qcow2.rebase(qemu_img_path, disk_image)
except (Qcow2Error, OSError) as e:
raise QemuError("Could not use qcow2 disk image {} for {} {}".format(disk_image, disk_name, e))
if self._hdc_disk_image:
if not os.path.isfile(self._hdc_disk_image) or not os.path.exists(self._hdc_disk_image):
if os.path.islink(self._hdc_disk_image):
raise QemuError("hdc disk image '{}' linked to '{}' is not accessible".format(self._hdc_disk_image, os.path.realpath(self._hdc_disk_image)))
else:
raise QemuError("hdc disk image '{}' is not accessible".format(self._hdc_disk_image))
if self._linked_clone:
hdc_disk = os.path.join(self.working_dir, "hdc_disk.qcow2")
if not os.path.exists(hdc_disk):
try:
process = yield from asyncio.create_subprocess_exec(qemu_img_path, "create", "-o",
"backing_file={}".format(self._hdc_disk_image),
"-f", "qcow2", hdc_disk)
retcode = yield from process.wait()
log.info("{} returned with {}".format(qemu_img_path, retcode))
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Could not create hdc disk image {}".format(e))
else:
hdc_disk = self._hdc_disk_image
options.extend(["-drive", 'file={},if={},index=2,media=disk'.format(hdc_disk, self.hdc_disk_interface)])
if self._hdd_disk_image:
if not os.path.isfile(self._hdd_disk_image) or not os.path.exists(self._hdd_disk_image):
if os.path.islink(self._hdd_disk_image):
raise QemuError("hdd disk image '{}' linked to '{}' is not accessible".format(self._hdd_disk_image, os.path.realpath(self._hdd_disk_image)))
else:
raise QemuError("hdd disk image '{}' is not accessible".format(self._hdd_disk_image))
if self._linked_clone:
hdd_disk = os.path.join(self.working_dir, "hdd_disk.qcow2")
if not os.path.exists(hdd_disk):
try:
process = yield from asyncio.create_subprocess_exec(qemu_img_path, "create", "-o",
"backing_file={}".format(self._hdd_disk_image),
"-f", "qcow2", hdd_disk)
retcode = yield from process.wait()
log.info("{} returned with {}".format(qemu_img_path, retcode))
except (OSError, subprocess.SubprocessError) as e:
raise QemuError("Could not create hdd disk image {}".format(e))
else:
hdd_disk = self._hdd_disk_image
options.extend(["-drive", 'file={},if={},index=3,media=disk'.format(hdd_disk, self.hdd_disk_interface)])
disk = disk_image
options.extend(["-drive", 'file={},if={},index={},media=disk'.format(disk, interface, disk_index)])
return options
+4 -2
View File
@@ -594,8 +594,10 @@ class VMware(BaseManager):
"""
if sys.platform.startswith("win"):
from win32com.shell import shell, shellcon
documents_folder = shell.SHGetSpecialFolderPath(None, shellcon.CSIDL_PERSONAL)
import ctypes
path = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH)
ctypes.windll.shell32.SHGetFolderPathW(None, 5, None, 0, path)
documents_folder = path.value
windows_type = sys.getwindowsversion().product_type
if windows_type == 2 or windows_type == 3:
return '{}\My Virtual Machines'.format(documents_folder)
+4 -2
View File
@@ -26,7 +26,7 @@ import asyncio
import tempfile
from gns3server.utils.telnet_server import TelnetServer
from gns3server.utils.interfaces import interfaces, get_windows_interfaces
from gns3server.utils.interfaces import interfaces
from gns3server.utils.asyncio import wait_for_file_creation, wait_for_named_pipe_creation
from collections import OrderedDict
from .vmware_error import VMwareError
@@ -144,6 +144,8 @@ class VMwareVM(BaseVM):
yield from self.manager.check_vmrun_version()
if self._linked_clone and not os.path.exists(os.path.join(self.working_dir, os.path.basename(self._vmx_path))):
if self.manager.host_type == "player":
raise VMwareError("Linked clones are not supported by VMware Player")
# create the base snapshot for linked clones
base_snapshot_name = "GNS3 Linked Base for clones"
vmsd_path = os.path.splitext(self._vmx_path)[0] + ".vmsd"
@@ -320,7 +322,7 @@ class VMwareVM(BaseVM):
yield from self._ubridge_hypervisor.send('bridge add_nio_linux_raw {name} "{interface}"'.format(name=vnet,
interface=vmnet_interface))
elif sys.platform.startswith("win"):
windows_interfaces = get_windows_interfaces()
windows_interfaces = interfaces()
npf = None
source_mac = None
for interface in windows_interfaces:
Binary file not shown.
@@ -0,0 +1,138 @@
#!/tmp/gns3/bin/sh
# script for udhcpc
# Copyright (c) 2008 Natanael Copa <natanael.copa@gmail.com>
UDHCPC="/gns3/etc/udhcpc"
UDHCPC_CONF="$UDHCPC/udhcpc.conf"
RESOLV_CONF="/etc/resolv.conf"
[ -f $UDHCPC_CONF ] && . $UDHCPC_CONF
export broadcast
export dns
export domain
export interface
export ip
export mask
export metric
export router
export subnet
#export PATH=/usr/bin:/bin:/usr/sbin:/sbin
run_scripts() {
local dir=$1
if [ -d $dir ]; then
for i in $dir/*; do
[ -f $i ] && $i
done
fi
}
deconfig() {
ip addr flush dev $interface
}
is_wifi() {
test -e /sys/class/net/$interface/phy80211
}
if_index() {
if [ -e /sys/class/net/$interface/ifindex ]; then
cat /sys/class/net/$interface/ifindex
else
ip link show dev $interface | head -n1 | cut -d: -f1
fi
}
calc_metric() {
local base=
if is_wifi; then
base=300
else
base=200
fi
echo $(( $base + $(if_index) ))
}
routes() {
[ -z "$router" ] && return
local gw= num=
while ip route del default via dev $interface 2>/dev/null; do
:
done
num=0
for gw in $router; do
ip route add 0.0.0.0/0 via $gw dev $interface \
metric $(( $num + ${IF_METRIC:-$(calc_metric)} ))
num=$(( $num + 1 ))
done
}
resolvconf() {
local i
[ -n "$IF_PEER_DNS" ] && [ "$IF_PEER_DNS" != "yes" ] && return
if [ "$RESOLV_CONF" = "no" ] || [ "$RESOLV_CONF" = "NO" ] \
|| [ -z "$RESOLV_CONF" ]; then
return
fi
echo -n > "$RESOLV_CONF"
[ -n "$domain" ] && echo "search $domain" >> "$RESOLV_CONF"
for i in $dns; do
echo "nameserver $i" >> "$RESOLV_CONF"
done
}
bound() {
ip addr add $ip/$mask ${broadcast:+broadcast $broadcast} dev $interface
ip link set dev $interface up
routes
resolvconf
}
renew() {
if ! ip addr show dev $interface | grep $ip/$mask; then
ip addr flush dev $interface
ip addr add $ip/$mask ${broadcast:+broadcast $broadcast} dev $interface
fi
local i
for i in $router; do
if ! ip route show | grep ^default | grep $i; then
routes
break
fi
done
if ! grep "^search $domain"; then
resolvconf
return
fi
for i in $dns; do
if ! grep "^nameserver $i"; then
resolvconf
return
fi
done
}
case "$1" in
deconfig|renew|bound)
run_scripts $UDHCPC/pre-$1
$1
run_scripts $UDHCPC/post-$1
;;
leasefail)
echo "udhcpc failed to get a DHCP lease" >&2
;;
nak)
echo "udhcpc received DHCP NAK" >&2
;;
*)
echo "Error: this script should be called from udhcpc" >&2
exit 1
;;
esac
exit 0
+1 -1
View File
@@ -112,7 +112,7 @@ def parse_arguments(argv):
config = Config.instance().get_section_config("Server")
defaults = {
"host": config.get("host", "0.0.0.0"),
"port": config.get("port", 8000),
"port": config.get("port", 3080),
"ssl": config.getboolean("ssl", False),
"certfile": config.get("certfile", ""),
"certkey": config.get("certkey", ""),
+18 -3
View File
@@ -43,6 +43,11 @@ DOCKER_CREATE_SCHEMA = {
"description": "console type",
"enum": ["telnet", "vnc"]
},
"console_resolution": {
"description": "console resolution for VNC",
"type": ["string", "null"],
"pattern": "^[0-9]+x[0-9]+$"
},
"aux": {
"description": "auxilary TCP port",
"minimum": 1,
@@ -92,6 +97,11 @@ DOCKER_UPDATE_SCHEMA = {
"maximum": 65535,
"type": ["integer", "null"]
},
"console_resolution": {
"description": "console resolution for VNC",
"type": ["string", "null"],
"pattern": "^[0-9]+x[0-9]+$"
},
"console_type": {
"description": "console type",
"enum": ["telnet", "vnc"]
@@ -143,13 +153,18 @@ DOCKER_OBJECT_SCHEMA = {
"description": "auxilary TCP port",
"minimum": 1,
"maximum": 65535,
"type": ["integer", "null"]
"type": "integer"
},
"console": {
"description": "console TCP port",
"minimum": 1,
"maximum": 65535,
"type": ["integer", "null"]
"type": "integer"
},
"console_resolution": {
"description": "console resolution for VNC",
"type": "string",
"pattern": "^[0-9]+x[0-9]+$"
},
"console_type": {
"description": "console type",
@@ -196,7 +211,7 @@ DOCKER_OBJECT_SCHEMA = {
}
},
"additionalProperties": False,
"required": ["vm_id", "project_id", "image", "container_id", "adapters", "aux", "console", "console_type", "start_command", "environment", "vm_directory"]
"required": ["vm_id", "project_id", "image", "container_id", "adapters", "aux", "console", "console_type", "console_resolution", "start_command", "environment", "vm_directory"]
}
+6 -3
View File
@@ -148,11 +148,11 @@ class AsyncioTelnetServer:
return_when=asyncio.FIRST_COMPLETED)
for coro in done:
data = coro.result()
# Console is closed
if len(data) == 0:
raise ConnectionResetError()
if coro == network_read:
if network_reader.at_eof():
raise ConnectionResetError()
network_read = asyncio.async(network_reader.read(READ_SIZE))
if IAC in data:
@@ -167,6 +167,9 @@ class AsyncioTelnetServer:
self._writer.write(data)
yield from self._writer.drain()
elif coro == reader_read:
if self._reader.at_eof():
raise ConnectionResetError()
reader_read = yield from self._get_reader(network_reader)
# Replicate the output on all clients
+13
View File
@@ -163,6 +163,19 @@ def interfaces():
"mac_address": mac_address})
else:
try:
import pywintypes
import win32service
import win32serviceutil
try:
if win32serviceutil.QueryServiceStatus("npf", None)[1] != win32service.SERVICE_RUNNING:
raise aiohttp.web.HTTPInternalServerError(text="The NPF service is not running")
except pywintypes.error as e:
if e[0] == 1060:
raise aiohttp.web.HTTPInternalServerError(text="The NPF service is not installed")
else:
raise aiohttp.web.HTTPInternalServerError(text="Could not check if the NPF service is running: {}".format(e[2]))
results = get_windows_interfaces()
except ImportError:
message = "pywin32 module is not installed, please install it on the server to get the available interface names"
+1
View File
@@ -19,6 +19,7 @@ import json
import jsonschema
import asyncio
import aiohttp.web
import asyncio
import logging
import sys
import jinja2