mirror of
https://github.com/GNS3/gns3-server.git
synced 2026-11-05 23:01:15 +02:00
Merge branch '2.1' into embed_appliances
This commit is contained in:
68 files changed
+1527
-484
No files matched your search
@@ -515,25 +515,12 @@ class Dynamips(BaseManager):
|
||||
default_startup_config_path = os.path.join(module_workdir, vm.id, "configs", "i{}_startup-config.cfg".format(vm.dynamips_id))
|
||||
default_private_config_path = os.path.join(module_workdir, vm.id, "configs", "i{}_private-config.cfg".format(vm.dynamips_id))
|
||||
|
||||
startup_config_path = settings.get("startup_config")
|
||||
startup_config_content = settings.get("startup_config_content")
|
||||
if startup_config_path:
|
||||
yield from vm.set_configs(startup_config_path)
|
||||
elif startup_config_content:
|
||||
startup_config_path = self._create_config(vm, default_startup_config_path, startup_config_content)
|
||||
yield from vm.set_configs(startup_config_path)
|
||||
elif os.path.isfile(default_startup_config_path) and os.path.getsize(default_startup_config_path) == 0:
|
||||
# An empty startup-config may crash Dynamips
|
||||
startup_config_path = self._create_config(vm, default_startup_config_path, "!\n")
|
||||
yield from vm.set_configs(startup_config_path)
|
||||
|
||||
private_config_path = settings.get("private_config")
|
||||
if startup_config_content:
|
||||
self._create_config(vm, default_startup_config_path, startup_config_content)
|
||||
private_config_content = settings.get("private_config_content")
|
||||
if private_config_path:
|
||||
yield from vm.set_configs(vm.startup_config, private_config_path)
|
||||
elif private_config_content:
|
||||
private_config_path = self._create_config(vm, default_private_config_path, private_config_content)
|
||||
yield from vm.set_configs(vm.startup_config, private_config_path)
|
||||
if private_config_content:
|
||||
self._create_config(vm, default_private_config_path, private_config_content)
|
||||
|
||||
def _create_config(self, vm, path, content=None):
|
||||
"""
|
||||
@@ -553,6 +540,11 @@ class Dynamips(BaseManager):
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not create Dynamips configs directory: {}".format(e))
|
||||
|
||||
if content is None or len(content) == 0:
|
||||
content = "!\n"
|
||||
if os.path.exists(path):
|
||||
return
|
||||
|
||||
try:
|
||||
with open(path, "wb") as f:
|
||||
if content:
|
||||
|
||||
@@ -211,7 +211,8 @@ class EthernetSwitch(Device):
|
||||
nio = self._nios[port_number]
|
||||
if isinstance(nio, NIOUDP):
|
||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
||||
yield from self._hypervisor.send('ethsw remove_nio "{name}" {nio}'.format(name=self._name, nio=nio))
|
||||
if self._hypervisor:
|
||||
yield from self._hypervisor.send('ethsw remove_nio "{name}" {nio}'.format(name=self._name, nio=nio))
|
||||
|
||||
log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||
id=self._id,
|
||||
|
||||
@@ -24,6 +24,7 @@ import asyncio
|
||||
import time
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
import shlex
|
||||
import base64
|
||||
@@ -78,8 +79,6 @@ class Router(BaseNode):
|
||||
self._dynamips_id = dynamips_id
|
||||
self._platform = platform
|
||||
self._image = ""
|
||||
self._startup_config = ""
|
||||
self._private_config = ""
|
||||
self._ram = 128 # Megabytes
|
||||
self._nvram = 128 # Kilobytes
|
||||
self._mmap = True
|
||||
@@ -102,8 +101,6 @@ class Router(BaseNode):
|
||||
self._slots = []
|
||||
self._ghost_flag = ghost_flag
|
||||
self._memory_watcher = None
|
||||
self._startup_config_content = ""
|
||||
self._private_config_content = ""
|
||||
|
||||
if not ghost_flag:
|
||||
if not dynamips_id:
|
||||
@@ -152,8 +149,6 @@ class Router(BaseNode):
|
||||
"platform": self._platform,
|
||||
"image": self._image,
|
||||
"image_md5sum": md5sum(self._image),
|
||||
"startup_config": self._startup_config,
|
||||
"private_config": self._private_config,
|
||||
"ram": self._ram,
|
||||
"nvram": self._nvram,
|
||||
"mmap": self._mmap,
|
||||
@@ -171,9 +166,7 @@ class Router(BaseNode):
|
||||
"console_type": "telnet",
|
||||
"aux": self.aux,
|
||||
"mac_addr": self._mac_addr,
|
||||
"system_id": self._system_id,
|
||||
"startup_config_content": self._startup_config_content,
|
||||
"private_config_content": self._private_config_content}
|
||||
"system_id": self._system_id}
|
||||
|
||||
# return the relative path if the IOS image is in the images_path directory
|
||||
router_info["image"] = self.manager.get_relative_image_path(self._image)
|
||||
@@ -289,6 +282,16 @@ class Router(BaseNode):
|
||||
if not self._ghost_flag:
|
||||
self.check_available_ram(self.ram)
|
||||
|
||||
startup_config_path = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id))
|
||||
private_config_path = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
|
||||
|
||||
if not os.path.exists(private_config_path) or not os.path.getsize(private_config_path):
|
||||
# an empty private-config can prevent a router to boot.
|
||||
private_config_path = ''
|
||||
yield from self._hypervisor.send('vm set_config "{name}" "{startup}" "{private}"'.format(
|
||||
name=self._name,
|
||||
startup=startup_config_path,
|
||||
private=private_config_path))
|
||||
yield from self._hypervisor.send('vm start "{name}"'.format(name=self._name))
|
||||
self.status = "started"
|
||||
log.info('router "{name}" [{id}] has been started'.format(name=self._name, id=self._id))
|
||||
@@ -1458,26 +1461,6 @@ class Router(BaseNode):
|
||||
|
||||
return self._slots
|
||||
|
||||
@property
|
||||
def startup_config(self):
|
||||
"""
|
||||
Returns the startup-config for this router.
|
||||
|
||||
:returns: path to startup-config file
|
||||
"""
|
||||
|
||||
return self._startup_config
|
||||
|
||||
@property
|
||||
def private_config(self):
|
||||
"""
|
||||
Returns the private-config for this router.
|
||||
|
||||
:returns: path to private-config file
|
||||
"""
|
||||
|
||||
return self._private_config
|
||||
|
||||
@asyncio.coroutine
|
||||
def set_name(self, new_name):
|
||||
"""
|
||||
@@ -1486,89 +1469,34 @@ class Router(BaseNode):
|
||||
:param new_name: new name string
|
||||
"""
|
||||
|
||||
if self._startup_config:
|
||||
# change the hostname in the startup-config
|
||||
startup_config_path = os.path.join(self._working_directory, "configs", "i{}_startup-config.cfg".format(self._dynamips_id))
|
||||
if os.path.isfile(startup_config_path):
|
||||
try:
|
||||
with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
||||
old_config = f.read()
|
||||
new_config = old_config.replace(self.name, new_name)
|
||||
f.seek(0)
|
||||
self._startup_config_content = new_config
|
||||
f.write(new_config)
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e))
|
||||
# change the hostname in the startup-config
|
||||
startup_config_path = os.path.join(self._working_directory, "configs", "i{}_startup-config.cfg".format(self._dynamips_id))
|
||||
if os.path.isfile(startup_config_path):
|
||||
try:
|
||||
with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
||||
old_config = f.read()
|
||||
new_config = re.sub(r"^hostname .+$", "hostname " + new_name, old_config, flags=re.MULTILINE)
|
||||
f.seek(0)
|
||||
f.write(new_config)
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e))
|
||||
|
||||
if self._private_config:
|
||||
# change the hostname in the private-config
|
||||
private_config_path = os.path.join(self._working_directory, "configs", "i{}_private-config.cfg".format(self._dynamips_id))
|
||||
if os.path.isfile(private_config_path):
|
||||
try:
|
||||
with open(private_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
||||
old_config = f.read()
|
||||
new_config = old_config.replace(self.name, new_name)
|
||||
f.seek(0)
|
||||
self._private_config_content = new_config
|
||||
f.write(new_config)
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e))
|
||||
# change the hostname in the private-config
|
||||
private_config_path = os.path.join(self._working_directory, "configs", "i{}_private-config.cfg".format(self._dynamips_id))
|
||||
if os.path.isfile(private_config_path):
|
||||
try:
|
||||
with open(private_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
||||
old_config = f.read()
|
||||
new_config = old_config.replace(self.name, new_name)
|
||||
f.seek(0)
|
||||
f.write(new_config)
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e))
|
||||
|
||||
yield from self._hypervisor.send('vm rename "{name}" "{new_name}"'.format(name=self._name, new_name=new_name))
|
||||
log.info('Router "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name, id=self._id, new_name=new_name))
|
||||
self._name = new_name
|
||||
|
||||
@asyncio.coroutine
|
||||
def set_configs(self, startup_config, private_config=''):
|
||||
"""
|
||||
Sets the config files that are pushed to startup-config and
|
||||
private-config in NVRAM when the instance is started.
|
||||
|
||||
:param startup_config: path to statup-config file
|
||||
:param private_config: path to private-config file
|
||||
(keep existing data when if an empty string)
|
||||
"""
|
||||
|
||||
startup_config = startup_config.replace("\\", '/')
|
||||
private_config = private_config.replace("\\", '/')
|
||||
|
||||
if self._startup_config != startup_config or self._private_config != private_config:
|
||||
self._startup_config = startup_config
|
||||
self._private_config = private_config
|
||||
|
||||
if private_config:
|
||||
private_config_path = os.path.join(self._working_directory, private_config)
|
||||
try:
|
||||
if not os.path.getsize(private_config_path):
|
||||
# an empty private-config can prevent a router to boot.
|
||||
private_config = ''
|
||||
self._private_config_content = ""
|
||||
else:
|
||||
with open(private_config_path) as f:
|
||||
self._private_config_content = f.read()
|
||||
except OSError as e:
|
||||
raise DynamipsError("Cannot access the private-config {}: {}".format(private_config_path, e))
|
||||
|
||||
try:
|
||||
startup_config_path = os.path.join(self._working_directory, startup_config)
|
||||
with open(startup_config_path) as f:
|
||||
self._startup_config_content = f.read()
|
||||
except OSError as e:
|
||||
raise DynamipsError("Cannot access the startup-config {}: {}".format(startup_config_path, e))
|
||||
|
||||
yield from self._hypervisor.send('vm set_config "{name}" "{startup}" "{private}"'.format(name=self._name,
|
||||
startup=startup_config,
|
||||
private=private_config))
|
||||
|
||||
log.info('Router "{name}" [{id}]: has a new startup-config set: "{startup}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
startup=startup_config))
|
||||
|
||||
if private_config:
|
||||
log.info('Router "{name}" [{id}]: has a new private-config set: "{private}"'.format(name=self._name,
|
||||
id=self._id,
|
||||
private=private_config))
|
||||
|
||||
@asyncio.coroutine
|
||||
def extract_config(self):
|
||||
"""
|
||||
@@ -1594,41 +1522,35 @@ class Router(BaseNode):
|
||||
Saves the startup-config and private-config to files.
|
||||
"""
|
||||
|
||||
if self.startup_config or self.private_config:
|
||||
try:
|
||||
config_path = os.path.join(self._working_directory, "configs")
|
||||
os.makedirs(config_path, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could could not create configuration directory {}: {}".format(config_path, e))
|
||||
|
||||
startup_config_base64, private_config_base64 = yield from self.extract_config()
|
||||
if startup_config_base64:
|
||||
startup_config = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id))
|
||||
try:
|
||||
config_path = os.path.join(self._working_directory, "configs")
|
||||
os.makedirs(config_path, exist_ok=True)
|
||||
except OSError as e:
|
||||
raise DynamipsError("Could could not create configuration directory {}: {}".format(config_path, e))
|
||||
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
|
||||
config = "!\n" + config.replace("\r", "")
|
||||
config_path = os.path.join(self._working_directory, startup_config)
|
||||
with open(config_path, "wb") as f:
|
||||
log.info("saving startup-config to {}".format(startup_config))
|
||||
f.write(config.encode("utf-8"))
|
||||
except (binascii.Error, OSError) as e:
|
||||
raise DynamipsError("Could not save the startup configuration {}: {}".format(config_path, e))
|
||||
|
||||
startup_config_base64, private_config_base64 = yield from self.extract_config()
|
||||
if startup_config_base64:
|
||||
if not self.startup_config:
|
||||
self._startup_config = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id))
|
||||
try:
|
||||
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
|
||||
config = "!\n" + config.replace("\r", "")
|
||||
config_path = os.path.join(self._working_directory, self.startup_config)
|
||||
with open(config_path, "wb") as f:
|
||||
log.info("saving startup-config to {}".format(self.startup_config))
|
||||
self._startup_config_content = config
|
||||
f.write(config.encode("utf-8"))
|
||||
except (binascii.Error, OSError) as e:
|
||||
raise DynamipsError("Could not save the startup configuration {}: {}".format(config_path, e))
|
||||
|
||||
if private_config_base64 and base64.b64decode(private_config_base64) != b'\nkerberos password \nend\n':
|
||||
if not self.private_config:
|
||||
self._private_config = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
|
||||
try:
|
||||
config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace")
|
||||
config_path = os.path.join(self._working_directory, self.private_config)
|
||||
with open(config_path, "wb") as f:
|
||||
log.info("saving private-config to {}".format(self.private_config))
|
||||
self._private_config_content = config
|
||||
f.write(config.encode("utf-8"))
|
||||
except (binascii.Error, OSError) as e:
|
||||
raise DynamipsError("Could not save the private configuration {}: {}".format(config_path, e))
|
||||
if private_config_base64 and base64.b64decode(private_config_base64) != b'\nkerberos password \nend\n':
|
||||
private_config = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
|
||||
try:
|
||||
config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace")
|
||||
config_path = os.path.join(self._working_directory, private_config)
|
||||
with open(config_path, "wb") as f:
|
||||
log.info("saving private-config to {}".format(private_config))
|
||||
f.write(config.encode("utf-8"))
|
||||
except (binascii.Error, OSError) as e:
|
||||
raise DynamipsError("Could not save the private configuration {}: {}".format(config_path, e))
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
|
||||
@@ -26,8 +26,6 @@ import re
|
||||
import asyncio
|
||||
import subprocess
|
||||
import shutil
|
||||
import argparse
|
||||
import threading
|
||||
import configparser
|
||||
import struct
|
||||
import hashlib
|
||||
@@ -207,10 +205,6 @@ class IOUVM(BaseNode):
|
||||
"ram": self._ram,
|
||||
"nvram": self._nvram,
|
||||
"l1_keepalives": self._l1_keepalives,
|
||||
"startup_config": self.relative_startup_config_file,
|
||||
"startup_config_content": self.startup_config_content,
|
||||
"private_config_content": self.private_config_content,
|
||||
"private_config": self.relative_private_config_file,
|
||||
"use_default_iou_values": self._use_default_iou_values,
|
||||
"command_line": self.command_line}
|
||||
|
||||
@@ -307,7 +301,7 @@ class IOUVM(BaseNode):
|
||||
|
||||
if self.startup_config_file:
|
||||
content = self.startup_config_content
|
||||
content = content.replace(self._name, new_name)
|
||||
content = re.sub(r"^hostname .+$", "hostname " + new_name, content, flags=re.MULTILINE)
|
||||
self.startup_config_content = content
|
||||
|
||||
super(IOUVM, IOUVM).name.__set__(self, new_name)
|
||||
@@ -1167,7 +1161,7 @@ class IOUVM(BaseNode):
|
||||
bay=adapter_number,
|
||||
unit=port_number,
|
||||
output_file=output_file,
|
||||
data_link_type=data_link_type))
|
||||
data_link_type=re.sub("^DLT_", "", data_link_type)))
|
||||
|
||||
@asyncio.coroutine
|
||||
def stop_capture(self, adapter_number, port_number):
|
||||
|
||||
@@ -23,12 +23,13 @@ order to run a QEMU VM.
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import math
|
||||
import shutil
|
||||
import subprocess
|
||||
import shlex
|
||||
import asyncio
|
||||
import socket
|
||||
import gns3server
|
||||
import subprocess
|
||||
|
||||
from gns3server.utils import parse_version
|
||||
from .qemu_error import QemuError
|
||||
@@ -1446,6 +1447,20 @@ class QemuVM(BaseNode):
|
||||
# this is a patched Qemu if version is below 1.1.0
|
||||
patched_qemu = True
|
||||
|
||||
# Each 32 PCI device we need to add a PCI bridge with max 9 bridges
|
||||
pci_devices = 4 + len(self._ethernet_adapters) # 4 PCI devices are use by default by qemu
|
||||
bridge_id = 0
|
||||
for bridge_id in range(1, math.floor(pci_devices / 32) + 1):
|
||||
network_options.extend(["-device", "i82801b11-bridge,id=dmi_pci_bridge{bridge_id}".format(bridge_id=bridge_id)])
|
||||
network_options.extend(["-device", "pci-bridge,id=pci-bridge{bridge_id},bus=dmi_pci_bridge{bridge_id},chassis_nr=0x1,addr=0x{bridge_id},shpc=off".format(bridge_id=bridge_id)])
|
||||
|
||||
if bridge_id > 1:
|
||||
qemu_version = yield from self.manager.get_qemu_version(self.qemu_path)
|
||||
if qemu_version and parse_version(qemu_version) < parse_version("2.4.0"):
|
||||
raise QemuError("Qemu version 2.4 or later is required to run this VM with a large number of network adapters")
|
||||
|
||||
pci_device_id = 4 + bridge_id # Bridge consume PCI ports
|
||||
|
||||
for adapter_number, adapter in enumerate(self._ethernet_adapters):
|
||||
mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
|
||||
|
||||
@@ -1483,8 +1498,14 @@ class QemuVM(BaseNode):
|
||||
|
||||
else:
|
||||
# newer QEMU networking syntax
|
||||
device_string = "{},mac={}".format(self._adapter_type, mac)
|
||||
bridge_id = math.floor(pci_device_id / 32)
|
||||
if bridge_id > 0:
|
||||
addr = pci_device_id % 32
|
||||
device_string = "{},bus=pci-bridge{bridge_id},addr=0x{addr:02x}".format(device_string, bridge_id=bridge_id, addr=addr)
|
||||
pci_device_id += 1
|
||||
if nio:
|
||||
network_options.extend(["-device", "{},mac={},netdev=gns3-{}".format(self._adapter_type, mac, adapter_number)])
|
||||
network_options.extend(["-device", "{},netdev=gns3-{}".format(device_string, adapter_number)])
|
||||
if isinstance(nio, NIOUDP):
|
||||
network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
|
||||
nio.rhost,
|
||||
@@ -1494,7 +1515,7 @@ class QemuVM(BaseNode):
|
||||
elif isinstance(nio, NIOTAP):
|
||||
network_options.extend(["-netdev", "tap,id=gns3-{},ifname={},script=no,downscript=no".format(adapter_number, nio.tap_device)])
|
||||
else:
|
||||
network_options.extend(["-device", "{},mac={}".format(self._adapter_type, mac)])
|
||||
network_options.extend(["-device", device_string])
|
||||
|
||||
return network_options
|
||||
|
||||
|
||||
@@ -19,14 +19,16 @@
|
||||
VirtualBox VM instance.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import shlex
|
||||
import re
|
||||
import os
|
||||
import tempfile
|
||||
import sys
|
||||
import json
|
||||
import uuid
|
||||
import shlex
|
||||
import shutil
|
||||
import socket
|
||||
import asyncio
|
||||
import tempfile
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from gns3server.utils import parse_version
|
||||
@@ -209,7 +211,16 @@ class VirtualBoxVM(BaseNode):
|
||||
if os.path.exists(self._linked_vbox_file()):
|
||||
tree = ET.parse(self._linked_vbox_file())
|
||||
machine = tree.getroot().find("{http://www.virtualbox.org/}Machine")
|
||||
if machine is not None:
|
||||
if machine is not None and machine.get("uuid") != "{" + self.id + "}":
|
||||
|
||||
for image in tree.getroot().findall("{http://www.virtualbox.org/}Image"):
|
||||
currentSnapshot = machine.get("currentSnapshot")
|
||||
if currentSnapshot:
|
||||
newSnapshot = re.sub("\{.*\}", "{" + str(uuid.uuid4()) + "}", currentSnapshot)
|
||||
shutil.move(os.path.join(self.working_dir, self._vmname, "Snapshots", currentSnapshot) + ".vdi",
|
||||
os.path.join(self.working_dir, self._vmname, "Snapshots", newSnapshot) + ".vdi")
|
||||
image.set("uuid", newSnapshot)
|
||||
|
||||
machine.set("uuid", "{" + self.id + "}")
|
||||
tree.write(self._linked_vbox_file())
|
||||
|
||||
@@ -292,6 +303,16 @@ class VirtualBoxVM(BaseNode):
|
||||
if self.acpi_shutdown:
|
||||
# use ACPI to shutdown the VM
|
||||
result = yield from self._control_vm("acpipowerbutton")
|
||||
trial = 0
|
||||
while True:
|
||||
vm_state = yield from self._get_vm_state()
|
||||
if vm_state == "poweroff":
|
||||
break
|
||||
yield from asyncio.sleep(1)
|
||||
trial += 1
|
||||
if trial >= 120:
|
||||
yield from self._control_vm("poweroff")
|
||||
break
|
||||
self.status = "stopped"
|
||||
log.debug("ACPI shutdown result: {}".format(result))
|
||||
else:
|
||||
|
||||
@@ -104,12 +104,12 @@ class VPCSVM(BaseNode):
|
||||
Check if VPCS is available with the correct version.
|
||||
"""
|
||||
|
||||
path = self.vpcs_path
|
||||
path = self._vpcs_path()
|
||||
if not path:
|
||||
raise VPCSError("No path to a VPCS executable has been set")
|
||||
|
||||
# This raise an error if ubridge is not available
|
||||
ubridge_path = self.ubridge_path
|
||||
self.ubridge_path
|
||||
|
||||
if not os.path.isfile(path):
|
||||
raise VPCSError("VPCS program '{}' is not accessible".format(path))
|
||||
@@ -128,8 +128,6 @@ class VPCSVM(BaseNode):
|
||||
"console": self._console,
|
||||
"console_type": "telnet",
|
||||
"project_id": self.project.id,
|
||||
"startup_script": self.startup_script,
|
||||
"startup_script_path": self.relative_startup_script,
|
||||
"command_line": self.command_line}
|
||||
|
||||
@property
|
||||
@@ -146,8 +144,7 @@ class VPCSVM(BaseNode):
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def vpcs_path(self):
|
||||
def _vpcs_path(self):
|
||||
"""
|
||||
Returns the VPCS executable path.
|
||||
|
||||
@@ -172,6 +169,7 @@ class VPCSVM(BaseNode):
|
||||
if self.script_file:
|
||||
content = self.startup_script
|
||||
content = content.replace(self._name, new_name)
|
||||
content = re.sub(r"^set pcname .+$", "set pcname " + new_name, content, flags=re.MULTILINE)
|
||||
self.startup_script = content
|
||||
|
||||
super(VPCSVM, VPCSVM).name.__set__(self, new_name)
|
||||
@@ -217,7 +215,7 @@ class VPCSVM(BaseNode):
|
||||
Checks if the VPCS executable version is >= 0.8b or == 0.6.1.
|
||||
"""
|
||||
try:
|
||||
output = yield from subprocess_check_output(self.vpcs_path, "-v", cwd=self.working_dir)
|
||||
output = yield from subprocess_check_output(self._vpcs_path(), "-v", cwd=self.working_dir)
|
||||
match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output)
|
||||
if match:
|
||||
version = match.group(1)
|
||||
@@ -225,7 +223,7 @@ class VPCSVM(BaseNode):
|
||||
if self._vpcs_version < parse_version("0.6.1"):
|
||||
raise VPCSError("VPCS executable version must be >= 0.6.1 but not a 0.8")
|
||||
else:
|
||||
raise VPCSError("Could not determine the VPCS version for {}".format(self.vpcs_path))
|
||||
raise VPCSError("Could not determine the VPCS version for {}".format(self._vpcs_path()))
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
|
||||
|
||||
@@ -270,8 +268,8 @@ class VPCSVM(BaseNode):
|
||||
self.status = "started"
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
vpcs_stdout = self.read_vpcs_stdout()
|
||||
log.error("Could not start VPCS {}: {}\n{}".format(self.vpcs_path, e, vpcs_stdout))
|
||||
raise VPCSError("Could not start VPCS {}: {}\n{}".format(self.vpcs_path, e, vpcs_stdout))
|
||||
log.error("Could not start VPCS {}: {}\n{}".format(self._vpcs_path(), e, vpcs_stdout))
|
||||
raise VPCSError("Could not start VPCS {}: {}\n{}".format(self._vpcs_path(), e, vpcs_stdout))
|
||||
|
||||
def _termination_callback(self, returncode):
|
||||
"""
|
||||
@@ -514,7 +512,7 @@ class VPCSVM(BaseNode):
|
||||
|
||||
"""
|
||||
|
||||
command = [self.vpcs_path]
|
||||
command = [self._vpcs_path()]
|
||||
command.extend(["-p", str(self._internal_console_port)]) # listen to console port
|
||||
command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset
|
||||
command.extend(["-i", "1"]) # option to start only one VPC instance
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
!
|
||||
service timestamps debug datetime msec
|
||||
service timestamps log datetime msec
|
||||
no service password-encryption
|
||||
!
|
||||
hostname %h
|
||||
!
|
||||
ip cef
|
||||
no ip domain-lookup
|
||||
no ip icmp rate-limit unreachable
|
||||
ip tcp synwait 5
|
||||
no cdp log mismatch duplex
|
||||
!
|
||||
line con 0
|
||||
exec-timeout 0 0
|
||||
logging synchronous
|
||||
privilege level 15
|
||||
no login
|
||||
line aux 0
|
||||
exec-timeout 0 0
|
||||
logging synchronous
|
||||
privilege level 15
|
||||
no login
|
||||
!
|
||||
!
|
||||
end
|
||||
@@ -0,0 +1,181 @@
|
||||
!
|
||||
service timestamps debug datetime msec
|
||||
service timestamps log datetime msec
|
||||
no service password-encryption
|
||||
no service dhcp
|
||||
!
|
||||
hostname %h
|
||||
!
|
||||
ip cef
|
||||
no ip routing
|
||||
no ip domain-lookup
|
||||
no ip icmp rate-limit unreachable
|
||||
ip tcp synwait 5
|
||||
no cdp log mismatch duplex
|
||||
vtp file nvram:vlan.dat
|
||||
!
|
||||
!
|
||||
interface FastEthernet0/0
|
||||
description *** Unused for Layer2 EtherSwitch ***
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface FastEthernet0/1
|
||||
description *** Unused for Layer2 EtherSwitch ***
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface FastEthernet1/0
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/1
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/2
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/3
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/4
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/5
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/6
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/7
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/8
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/9
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/10
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/11
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/12
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/13
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/14
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface FastEthernet1/15
|
||||
no shutdown
|
||||
duplex full
|
||||
speed 100
|
||||
!
|
||||
interface Vlan1
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
!
|
||||
line con 0
|
||||
exec-timeout 0 0
|
||||
logging synchronous
|
||||
privilege level 15
|
||||
no login
|
||||
line aux 0
|
||||
exec-timeout 0 0
|
||||
logging synchronous
|
||||
privilege level 15
|
||||
no login
|
||||
!
|
||||
!
|
||||
banner exec $
|
||||
|
||||
***************************************************************
|
||||
This is a normal Router with a SW module inside (NM-16ESW)
|
||||
It has been preconfigured with hard coded speed and duplex
|
||||
|
||||
To create vlans use the command "vlan database" from exec mode
|
||||
After creating all desired vlans use "exit" to apply the config
|
||||
|
||||
To view existing vlans use the command "show vlan-switch brief"
|
||||
|
||||
Warning: You are using an old IOS image for this router.
|
||||
Please update the IOS to enable the "macro" command!
|
||||
***************************************************************
|
||||
|
||||
$
|
||||
!
|
||||
!Warning: If the IOS is old and doesn't support macro, it will stop the configuration loading from this point!
|
||||
!
|
||||
macro name add_vlan
|
||||
end
|
||||
vlan database
|
||||
vlan $v
|
||||
exit
|
||||
@
|
||||
macro name del_vlan
|
||||
end
|
||||
vlan database
|
||||
no vlan $v
|
||||
exit
|
||||
@
|
||||
!
|
||||
!
|
||||
banner exec $
|
||||
|
||||
***************************************************************
|
||||
This is a normal Router with a Switch module inside (NM-16ESW)
|
||||
It has been pre-configured with hard-coded speed and duplex
|
||||
|
||||
To create vlans use the command "vlan database" in exec mode
|
||||
After creating all desired vlans use "exit" to apply the config
|
||||
|
||||
To view existing vlans use the command "show vlan-switch brief"
|
||||
|
||||
Alias(exec) : vl - "show vlan-switch brief" command
|
||||
Alias(configure): va X - macro to add vlan X
|
||||
Alias(configure): vd X - macro to delete vlan X
|
||||
***************************************************************
|
||||
|
||||
$
|
||||
!
|
||||
alias configure va macro global trace add_vlan $v
|
||||
alias configure vd macro global trace del_vlan $v
|
||||
alias exec vl show vlan-switch brief
|
||||
!
|
||||
!
|
||||
end
|
||||
@@ -0,0 +1,132 @@
|
||||
!
|
||||
service timestamps debug datetime msec
|
||||
service timestamps log datetime msec
|
||||
no service password-encryption
|
||||
!
|
||||
hostname %h
|
||||
!
|
||||
!
|
||||
!
|
||||
logging discriminator EXCESS severity drops 6 msg-body drops EXCESSCOLL
|
||||
logging buffered 50000
|
||||
logging console discriminator EXCESS
|
||||
!
|
||||
no ip icmp rate-limit unreachable
|
||||
!
|
||||
ip cef
|
||||
no ip domain-lookup
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
ip tcp synwait-time 5
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
interface Ethernet0/0
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet0/1
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet0/2
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet0/3
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet1/0
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet1/1
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet1/2
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet1/3
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet2/0
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet2/1
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet2/2
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet2/3
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet3/0
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet3/1
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet3/2
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Ethernet3/3
|
||||
no ip address
|
||||
no shutdown
|
||||
duplex auto
|
||||
!
|
||||
interface Vlan1
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
line con 0
|
||||
exec-timeout 0 0
|
||||
privilege level 15
|
||||
logging synchronous
|
||||
line aux 0
|
||||
exec-timeout 0 0
|
||||
privilege level 15
|
||||
logging synchronous
|
||||
!
|
||||
end
|
||||
@@ -0,0 +1,108 @@
|
||||
!
|
||||
service timestamps debug datetime msec
|
||||
service timestamps log datetime msec
|
||||
no service password-encryption
|
||||
!
|
||||
hostname %h
|
||||
!
|
||||
!
|
||||
!
|
||||
no ip icmp rate-limit unreachable
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
ip cef
|
||||
no ip domain-lookup
|
||||
!
|
||||
!
|
||||
ip tcp synwait-time 5
|
||||
!
|
||||
!
|
||||
!
|
||||
!
|
||||
interface Ethernet0/0
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Ethernet0/1
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Ethernet0/2
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Ethernet0/3
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Ethernet1/0
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Ethernet1/1
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Ethernet1/2
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Ethernet1/3
|
||||
no ip address
|
||||
shutdown
|
||||
!
|
||||
interface Serial2/0
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
interface Serial2/1
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
interface Serial2/2
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
interface Serial2/3
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
interface Serial3/0
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
interface Serial3/1
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
interface Serial3/2
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
interface Serial3/3
|
||||
no ip address
|
||||
shutdown
|
||||
serial restart-delay 0
|
||||
!
|
||||
!
|
||||
no cdp log mismatch duplex
|
||||
!
|
||||
line con 0
|
||||
exec-timeout 0 0
|
||||
privilege level 15
|
||||
logging synchronous
|
||||
line aux 0
|
||||
exec-timeout 0 0
|
||||
privilege level 15
|
||||
logging synchronous
|
||||
!
|
||||
end
|
||||
@@ -0,0 +1 @@
|
||||
set pcname %h
|
||||
@@ -18,6 +18,7 @@
|
||||
import os
|
||||
import json
|
||||
import socket
|
||||
import shutil
|
||||
import asyncio
|
||||
import aiohttp
|
||||
|
||||
@@ -68,16 +69,22 @@ class Controller:
|
||||
@asyncio.coroutine
|
||||
def start(self):
|
||||
log.info("Start controller")
|
||||
yield from self.load()
|
||||
self.load_base_files()
|
||||
server_config = Config.instance().get_section_config("Server")
|
||||
host = server_config.get("host", "localhost")
|
||||
|
||||
# If console_host is 0.0.0.0 client will use the ip they use
|
||||
# to connect to the controller
|
||||
console_host = host
|
||||
if host == "0.0.0.0":
|
||||
host = "127.0.0.1"
|
||||
|
||||
name = socket.gethostname()
|
||||
if name == "gns3vm":
|
||||
name = "Main server"
|
||||
|
||||
yield from self.add_compute(compute_id="local",
|
||||
name=socket.gethostname(),
|
||||
name=name,
|
||||
protocol=server_config.get("protocol", "http"),
|
||||
host=host,
|
||||
console_host=console_host,
|
||||
@@ -85,6 +92,7 @@ class Controller:
|
||||
user=server_config.get("user", ""),
|
||||
password=server_config.get("password", ""),
|
||||
force=True)
|
||||
yield from self._load_controller_settings()
|
||||
yield from self.load_projects()
|
||||
yield from self.gns3vm.auto_start_vm()
|
||||
yield from self._project_auto_open()
|
||||
@@ -131,7 +139,7 @@ class Controller:
|
||||
json.dump(data, f, indent=4)
|
||||
|
||||
@asyncio.coroutine
|
||||
def load(self):
|
||||
def _load_controller_settings(self):
|
||||
"""
|
||||
Reload the controller configuration from disk
|
||||
"""
|
||||
@@ -177,6 +185,20 @@ class Controller:
|
||||
except OSError as e:
|
||||
log.error(str(e))
|
||||
|
||||
def load_base_files(self):
|
||||
"""
|
||||
At startup we copy base file to the user location to allow
|
||||
them to customize it
|
||||
"""
|
||||
dst_path = self.configs_path()
|
||||
src_path = get_resource('configs')
|
||||
try:
|
||||
for file in os.listdir(src_path):
|
||||
if not os.path.exists(os.path.join(dst_path, file)):
|
||||
shutil.copy(os.path.join(src_path, file), os.path.join(dst_path, file))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
def images_path(self):
|
||||
"""
|
||||
Get the image storage directory
|
||||
@@ -186,6 +208,15 @@ class Controller:
|
||||
os.makedirs(images_path, exist_ok=True)
|
||||
return images_path
|
||||
|
||||
def configs_path(self):
|
||||
"""
|
||||
Get the configs storage directory
|
||||
"""
|
||||
server_config = Config.instance().get_section_config("Server")
|
||||
images_path = os.path.expanduser(server_config.get("configs_path", "~/GNS3/projects"))
|
||||
os.makedirs(images_path, exist_ok=True)
|
||||
return images_path
|
||||
|
||||
@asyncio.coroutine
|
||||
def _import_gns3_gui_conf(self):
|
||||
"""
|
||||
@@ -269,7 +300,7 @@ class Controller:
|
||||
return None
|
||||
|
||||
for compute in self._computes.values():
|
||||
if name and compute.name == name:
|
||||
if name and compute.name == name and not force:
|
||||
raise aiohttp.web.HTTPConflict(text='Compute name "{}" already exists'.format(name))
|
||||
|
||||
compute = Compute(compute_id=compute_id, controller=self, name=name, **kwargs)
|
||||
@@ -332,7 +363,6 @@ class Controller:
|
||||
try:
|
||||
return self._computes[compute_id]
|
||||
except KeyError:
|
||||
server_config = Config.instance().get_section_config("Server")
|
||||
if compute_id == "vm":
|
||||
raise aiohttp.web.HTTPNotFound(text="You try to use a node on the GNS3 VM server but the GNS3 VM is not configured")
|
||||
raise aiohttp.web.HTTPNotFound(text="Compute ID {} doesn't exist".format(compute_id))
|
||||
@@ -383,7 +413,8 @@ class Controller:
|
||||
return project
|
||||
|
||||
def remove_project(self, project):
|
||||
del self._projects[project.id]
|
||||
if project.id in self._projects:
|
||||
del self._projects[project.id]
|
||||
|
||||
@asyncio.coroutine
|
||||
def load_project(self, path, load=True):
|
||||
@@ -394,7 +425,7 @@ class Controller:
|
||||
:param load: Load the topology
|
||||
"""
|
||||
topo_data = load_topology(path)
|
||||
topology = topo_data.pop("topology")
|
||||
topo_data.pop("topology")
|
||||
topo_data.pop("version")
|
||||
topo_data.pop("revision")
|
||||
topo_data.pop("type")
|
||||
|
||||
@@ -22,14 +22,12 @@ import socket
|
||||
import json
|
||||
import uuid
|
||||
import sys
|
||||
import os
|
||||
import io
|
||||
|
||||
from ..utils import parse_version
|
||||
from ..utils.images import list_images, md5sum
|
||||
from ..utils.images import list_images
|
||||
from ..utils.asyncio import locked_coroutine
|
||||
from ..controller.controller_error import ControllerError
|
||||
from ..config import Config
|
||||
from ..version import __version__
|
||||
|
||||
|
||||
@@ -216,7 +214,10 @@ class Compute:
|
||||
"""
|
||||
Return the IP associated to the host
|
||||
"""
|
||||
return socket.gethostbyname(self._host)
|
||||
try:
|
||||
return socket.gethostbyname(self._host)
|
||||
except socket.gaierror:
|
||||
return '0.0.0.0'
|
||||
|
||||
@host.setter
|
||||
def host(self, host):
|
||||
@@ -360,6 +361,16 @@ class Compute:
|
||||
response = yield from self._run_http_query(method, path, data=data, **kwargs)
|
||||
return response
|
||||
|
||||
@asyncio.coroutine
|
||||
def _try_reconnect(self):
|
||||
"""
|
||||
We catch error during reconnect
|
||||
"""
|
||||
try:
|
||||
yield from self.connect()
|
||||
except aiohttp.web.HTTPConflict:
|
||||
pass
|
||||
|
||||
@locked_coroutine
|
||||
def connect(self):
|
||||
"""
|
||||
@@ -374,14 +385,18 @@ class Compute:
|
||||
self._connection_failure += 1
|
||||
# After 5 failure we close the project using the compute to avoid sync issues
|
||||
if self._connection_failure == 5:
|
||||
log.warning("Can't connect to compute %s", self._id)
|
||||
yield from self._controller.close_compute_projects(self)
|
||||
asyncio.get_event_loop().call_later(2, lambda: asyncio.async(self.connect()))
|
||||
|
||||
asyncio.get_event_loop().call_later(2, lambda: asyncio.async(self._try_reconnect()))
|
||||
|
||||
return
|
||||
except aiohttp.web.HTTPNotFound:
|
||||
raise aiohttp.web.HTTPConflict(text="The server {} is not a GNS3 server or it's a 1.X server".format(self._id))
|
||||
except aiohttp.web.HTTPUnauthorized:
|
||||
raise aiohttp.web.HTTPConflict(text="Invalid auth for server {} ".format(self._id))
|
||||
raise aiohttp.web.HTTPConflict(text="Invalid auth for server {}".format(self._id))
|
||||
except aiohttp.web.HTTPServiceUnavailable:
|
||||
raise aiohttp.web.HTTPConflict(text="The server {} is unavailable".format(self._id))
|
||||
|
||||
if "version" not in response.json:
|
||||
self._http_session.close()
|
||||
@@ -411,7 +426,7 @@ class Compute:
|
||||
except aiohttp.errors.WSServerHandshakeError:
|
||||
self._ws = None
|
||||
break
|
||||
if response.tp == aiohttp.MsgType.closed or response.tp == aiohttp.MsgType.error:
|
||||
if response.tp == aiohttp.MsgType.closed or response.tp == aiohttp.MsgType.error or response.data is None:
|
||||
self._connected = False
|
||||
break
|
||||
msg = json.loads(response.data)
|
||||
|
||||
@@ -43,6 +43,7 @@ class Drawing:
|
||||
self._id = str(uuid.uuid4())
|
||||
else:
|
||||
self._id = drawing_id
|
||||
self._svg = "<svg></svg>"
|
||||
self.svg = svg
|
||||
self._x = x
|
||||
self._y = y
|
||||
|
||||
@@ -47,6 +47,9 @@ def export_project(project, temporary_dir, include_images=False, keep_compute_id
|
||||
if project.is_running():
|
||||
raise aiohttp.web.HTTPConflict(text="Running topology could not be exported")
|
||||
|
||||
# Make sure we save the project
|
||||
project.dump()
|
||||
|
||||
z = zipstream.ZipFile(allowZip64=True)
|
||||
|
||||
if not os.path.exists(project._path):
|
||||
@@ -136,6 +139,8 @@ def _export_project_file(project, path, z, include_images, keep_compute_id, allo
|
||||
if "topology" in topology:
|
||||
if "nodes" in topology["topology"]:
|
||||
for node in topology["topology"]["nodes"]:
|
||||
if node["node_type"] == "virtualbox" and node.get("properties", {}).get("linked_clone"):
|
||||
raise aiohttp.web.HTTPConflict(text="Topology with a linked {} clone could not be exported. Use qemu instead.".format(node["node_type"]))
|
||||
if not allow_all_nodes and node["node_type"] in ["virtualbox", "vmware", "cloud"]:
|
||||
raise aiohttp.web.HTTPConflict(text="Topology with a {} could not be exported".format(node["node_type"]))
|
||||
|
||||
|
||||
@@ -222,8 +222,14 @@ class GNS3VM:
|
||||
"""
|
||||
engine = self._get_engine(engine)
|
||||
vms = []
|
||||
for vm in (yield from engine.list()):
|
||||
vms.append({"vmname": vm["vmname"]})
|
||||
try:
|
||||
for vm in (yield from engine.list()):
|
||||
vms.append({"vmname": vm["vmname"]})
|
||||
except GNS3VMError as e:
|
||||
# We raise error only if user activated the GNS3 VM
|
||||
# otherwise you have noise when VMware is not installed
|
||||
if self.enable:
|
||||
raise e
|
||||
return vms
|
||||
|
||||
@asyncio.coroutine
|
||||
@@ -267,6 +273,7 @@ class GNS3VM:
|
||||
engine.vmname = self._settings["vmname"]
|
||||
engine.ram = self._settings["ram"]
|
||||
engine.vpcus = self._settings["vcpus"]
|
||||
engine.headless = self._settings["headless"]
|
||||
compute = yield from self._controller.add_compute(compute_id="vm",
|
||||
name="GNS3 VM is starting ({})".format(engine.vmname),
|
||||
host=None,
|
||||
@@ -277,6 +284,7 @@ class GNS3VM:
|
||||
except Exception as e:
|
||||
yield from self._controller.delete_compute("vm")
|
||||
log.error("Can't start the GNS3 VM: {}", str(e))
|
||||
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname))
|
||||
raise e
|
||||
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname),
|
||||
protocol=self.protocol,
|
||||
|
||||
@@ -221,7 +221,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM):
|
||||
second to a GNS3 endpoint in order to get the list of the interfaces and
|
||||
their IP and after that match it with VirtualBox host only.
|
||||
"""
|
||||
remaining_try = 240
|
||||
remaining_try = 300
|
||||
while remaining_try > 0:
|
||||
json_data = None
|
||||
session = aiohttp.ClientSession()
|
||||
|
||||
@@ -52,9 +52,11 @@ class Link:
|
||||
return self._created
|
||||
|
||||
@asyncio.coroutine
|
||||
def add_node(self, node, adapter_number, port_number, label=None):
|
||||
def add_node(self, node, adapter_number, port_number, label=None, dump=True):
|
||||
"""
|
||||
Add a node to the link
|
||||
|
||||
:param dump: Dump project on disk
|
||||
"""
|
||||
|
||||
port = node.get_port(adapter_number, port_number)
|
||||
@@ -101,7 +103,8 @@ class Link:
|
||||
self._created = True
|
||||
self._project.controller.notification.emit("link.created", self.__json__())
|
||||
|
||||
self._project.dump()
|
||||
if dump:
|
||||
self._project.dump()
|
||||
|
||||
@asyncio.coroutine
|
||||
def update_nodes(self, nodes):
|
||||
|
||||
@@ -146,6 +146,15 @@ class Node:
|
||||
def properties(self, val):
|
||||
self._properties = val
|
||||
|
||||
def _base_config_file_content(self, path):
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(self.project.controller.configs_path(), path)
|
||||
try:
|
||||
with open(path) as f:
|
||||
return f.read()
|
||||
except (PermissionError, OSError):
|
||||
return None
|
||||
|
||||
@property
|
||||
def project(self):
|
||||
return self._project
|
||||
@@ -366,8 +375,12 @@ class Node:
|
||||
self._console_type = value
|
||||
elif key == "name":
|
||||
self.name = value
|
||||
elif key in ["node_id", "project_id", "console_host"]:
|
||||
pass
|
||||
elif key in ["node_id", "project_id", "console_host",
|
||||
"startup_config_content",
|
||||
"private_config_content",
|
||||
"startup_script"]:
|
||||
if key in self._properties:
|
||||
del self._properties[key]
|
||||
else:
|
||||
self._properties[key] = value
|
||||
self._list_ports()
|
||||
@@ -384,6 +397,17 @@ class Node:
|
||||
data = copy.copy(properties)
|
||||
else:
|
||||
data = copy.copy(self._properties)
|
||||
# We replace the startup script name by the content of the file
|
||||
mapping = {
|
||||
"base_script_file": "startup_script",
|
||||
"startup_config": "startup_config_content",
|
||||
"private_config": "private_config_content",
|
||||
}
|
||||
for k, v in mapping.items():
|
||||
if k in list(self._properties.keys()):
|
||||
data[v] = self._base_config_file_content(self._properties[k])
|
||||
del data[k]
|
||||
del self._properties[k] # We send the file only one time
|
||||
data["name"] = self._name
|
||||
if self._console:
|
||||
# console is optional for builtin nodes
|
||||
@@ -585,17 +609,6 @@ class Node:
|
||||
return False
|
||||
return self.id == other.id and other.project.id == self.project.id
|
||||
|
||||
def _filter_properties(self):
|
||||
"""
|
||||
Some properties are private and should not be exposed
|
||||
"""
|
||||
PRIVATE_PROPERTIES = ("iourc_content", )
|
||||
prop = copy.copy(self._properties)
|
||||
for k in list(prop.keys()):
|
||||
if k in PRIVATE_PROPERTIES:
|
||||
del prop[k]
|
||||
return prop
|
||||
|
||||
def __json__(self, topology_dump=False):
|
||||
"""
|
||||
:param topology_dump: Filter to keep only properties require for saving on disk
|
||||
@@ -608,7 +621,7 @@ class Node:
|
||||
"name": self._name,
|
||||
"console": self._console,
|
||||
"console_type": self._console_type,
|
||||
"properties": self._filter_properties(),
|
||||
"properties": self._properties,
|
||||
"label": self._label,
|
||||
"x": self._x,
|
||||
"y": self._y,
|
||||
@@ -631,7 +644,7 @@ class Node:
|
||||
"console_host": str(self._compute.console_host),
|
||||
"console_type": self._console_type,
|
||||
"command_line": self._command_line,
|
||||
"properties": self._filter_properties(),
|
||||
"properties": self._properties,
|
||||
"status": self._status,
|
||||
"label": self._label,
|
||||
"x": self._x,
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
# 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
|
||||
|
||||
from .atm_port import ATMPort
|
||||
from .frame_relay_port import FrameRelayPort
|
||||
from .gigabitethernet_port import GigabitEthernetPort
|
||||
@@ -64,11 +66,14 @@ class StandardPortFactory:
|
||||
port_name = first_port_name
|
||||
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
||||
else:
|
||||
port_name = port_name_format.format(
|
||||
interface_number,
|
||||
segment_number,
|
||||
adapter=adapter_number,
|
||||
**cls._generate_replacement(interface_number, segment_number))
|
||||
try:
|
||||
port_name = port_name_format.format(
|
||||
interface_number,
|
||||
segment_number,
|
||||
adapter=adapter_number,
|
||||
**cls._generate_replacement(interface_number, segment_number))
|
||||
except (ValueError, KeyError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Invalid port name format {}: {}".format(port_name_format, str(e)))
|
||||
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
||||
interface_number += 1
|
||||
if port_segment_size:
|
||||
|
||||
@@ -289,7 +289,12 @@ class Project:
|
||||
if '{0}' in base_name or '{id}' in base_name:
|
||||
# base name is a template, replace {0} or {id} by an unique identifier
|
||||
for number in range(1, 1000000):
|
||||
name = base_name.format(number, id=number)
|
||||
try:
|
||||
name = base_name.format(number, id=number, name="Node")
|
||||
except KeyError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="{" + e.args[0] + "} is not a valid replacement string in the node name")
|
||||
except ValueError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="{} is not a valid replacement string in the node name".format(base_name))
|
||||
if name not in self._allocated_node_names:
|
||||
self._allocated_node_names.add(name)
|
||||
return name
|
||||
@@ -314,10 +319,11 @@ class Project:
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def add_node(self, compute, name, node_id, node_type=None, **kwargs):
|
||||
def add_node(self, compute, name, node_id, dump=True, node_type=None, **kwargs):
|
||||
"""
|
||||
Create a node or return an existing node
|
||||
|
||||
:param dump: Dump topology to disk
|
||||
:param kwargs: See the documentation of node
|
||||
"""
|
||||
if node_id in self._nodes:
|
||||
@@ -349,7 +355,8 @@ class Project:
|
||||
yield from node.create()
|
||||
self._nodes[node.id] = node
|
||||
self.controller.notification.emit("node.created", node.__json__())
|
||||
self.dump()
|
||||
if dump:
|
||||
self.dump()
|
||||
return node
|
||||
|
||||
@locked_coroutine
|
||||
@@ -401,17 +408,19 @@ class Project:
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def add_drawing(self, drawing_id=None, **kwargs):
|
||||
def add_drawing(self, drawing_id=None, dump=True, **kwargs):
|
||||
"""
|
||||
Create an drawing or return an existing drawing
|
||||
|
||||
:param dump: Dump the topology to disk
|
||||
:param kwargs: See the documentation of drawing
|
||||
"""
|
||||
if drawing_id not in self._drawings:
|
||||
drawing = Drawing(self, drawing_id=drawing_id, **kwargs)
|
||||
self._drawings[drawing.id] = drawing
|
||||
self.controller.notification.emit("drawing.created", drawing.__json__())
|
||||
self.dump()
|
||||
if dump:
|
||||
self.dump()
|
||||
return drawing
|
||||
return self._drawings[drawing_id]
|
||||
|
||||
@@ -435,15 +444,18 @@ class Project:
|
||||
|
||||
@open_required
|
||||
@asyncio.coroutine
|
||||
def add_link(self, link_id=None):
|
||||
def add_link(self, link_id=None, dump=True):
|
||||
"""
|
||||
Create a link. By default the link is empty
|
||||
|
||||
:param dump: Dump topology to disk
|
||||
"""
|
||||
if link_id and link_id in self._links:
|
||||
return self._links[link.id]
|
||||
return self._links[link_id]
|
||||
link = UDPLink(self, link_id=link_id)
|
||||
self._links[link.id] = link
|
||||
self.dump()
|
||||
if dump:
|
||||
self.dump()
|
||||
return link
|
||||
|
||||
@open_required
|
||||
@@ -526,7 +538,7 @@ class Project:
|
||||
@asyncio.coroutine
|
||||
def close(self, ignore_notification=False):
|
||||
yield from self.stop_all()
|
||||
for compute in self._project_created_on_compute:
|
||||
for compute in list(self._project_created_on_compute):
|
||||
try:
|
||||
yield from compute.post("/projects/{}/close".format(self._id), dont_connect=True)
|
||||
# We don't care if a compute is down at this step
|
||||
@@ -626,15 +638,16 @@ class Project:
|
||||
compute = self.controller.get_compute(node.pop("compute_id"))
|
||||
name = node.pop("name")
|
||||
node_id = node.pop("node_id")
|
||||
yield from self.add_node(compute, name, node_id, **node)
|
||||
yield from self.add_node(compute, name, node_id, dump=False, **node)
|
||||
for link_data in topology.get("links", []):
|
||||
link = yield from self.add_link(link_id=link_data["link_id"])
|
||||
for node_link in link_data["nodes"]:
|
||||
node = self.get_node(node_link["node_id"])
|
||||
yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"], label=node_link.get("label"))
|
||||
yield from link.add_node(node, node_link["adapter_number"], node_link["port_number"], label=node_link.get("label"), dump=False)
|
||||
for drawing_data in topology.get("drawings", []):
|
||||
drawing = yield from self.add_drawing(**drawing_data)
|
||||
yield from self.add_drawing(dump=False, **drawing_data)
|
||||
|
||||
self.dump()
|
||||
# We catch all error to be able to rollback the .gns3 to the previous state
|
||||
except Exception as e:
|
||||
for compute in self._project_created_on_compute:
|
||||
|
||||
@@ -20,6 +20,7 @@ import os
|
||||
import uuid
|
||||
import shutil
|
||||
import asyncio
|
||||
import aiohttp.web
|
||||
from datetime import datetime, timezone
|
||||
|
||||
|
||||
@@ -80,10 +81,13 @@ class Snapshot:
|
||||
# We don't send close notif to clients because the close / open dance is purely internal
|
||||
yield from self._project.close(ignore_notification=True)
|
||||
self._project.controller.notification.emit("snapshot.restored", self.__json__())
|
||||
if os.path.exists(os.path.join(self._project.path, "project-files")):
|
||||
shutil.rmtree(os.path.join(self._project.path, "project-files"))
|
||||
with open(self._path, "rb") as f:
|
||||
project = yield from import_project(self._project.controller, self._project.id, f, location=self._project.path)
|
||||
try:
|
||||
if os.path.exists(os.path.join(self._project.path, "project-files")):
|
||||
shutil.rmtree(os.path.join(self._project.path, "project-files"))
|
||||
with open(self._path, "rb") as f:
|
||||
project = yield from import_project(self._project.controller, self._project.id, f, location=self._project.path)
|
||||
except (OSError, PermissionError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text=str(e))
|
||||
yield from project.open()
|
||||
return project
|
||||
|
||||
|
||||
@@ -39,16 +39,17 @@ class Symbols:
|
||||
def list(self):
|
||||
self._symbols_path = {}
|
||||
symbols = []
|
||||
for file in os.listdir(get_resource("symbols")):
|
||||
if file.startswith('.'):
|
||||
continue
|
||||
symbol_id = ':/symbols/' + file
|
||||
symbols.append({
|
||||
'symbol_id': symbol_id,
|
||||
'filename': file,
|
||||
'builtin': True,
|
||||
})
|
||||
self._symbols_path[symbol_id] = os.path.join(get_resource("symbols"), file)
|
||||
if get_resource("symbols"):
|
||||
for file in os.listdir(get_resource("symbols")):
|
||||
if file.startswith('.'):
|
||||
continue
|
||||
symbol_id = ':/symbols/' + file
|
||||
symbols.append({
|
||||
'symbol_id': symbol_id,
|
||||
'filename': file,
|
||||
'builtin': True,
|
||||
})
|
||||
self._symbols_path[symbol_id] = os.path.join(get_resource("symbols"), file)
|
||||
directory = self.symbols_path()
|
||||
if directory:
|
||||
for file in os.listdir(directory):
|
||||
|
||||
@@ -23,7 +23,6 @@ import glob
|
||||
import shutil
|
||||
import zipfile
|
||||
import aiohttp
|
||||
import platform
|
||||
import jsonschema
|
||||
|
||||
|
||||
@@ -37,7 +36,7 @@ import logging
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
GNS3_FILE_FORMAT_REVISION = 7
|
||||
GNS3_FILE_FORMAT_REVISION = 8
|
||||
|
||||
|
||||
def _check_topology_schema(topo):
|
||||
@@ -117,34 +116,65 @@ def load_topology(path):
|
||||
topo = json.load(f)
|
||||
except (OSError, UnicodeDecodeError, ValueError) as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Could not load topology {}: {}".format(path, str(e)))
|
||||
if "revision" not in topo or topo["revision"] < 5:
|
||||
|
||||
if topo.get("revision", 0) > GNS3_FILE_FORMAT_REVISION:
|
||||
raise aiohttp.web.HTTPConflict(text="This project is designed for a more recent version of GNS3 please update GNS3 to version {} or later".format(topo["version"]))
|
||||
|
||||
changed = False
|
||||
if "revision" not in topo or topo["revision"] < GNS3_FILE_FORMAT_REVISION:
|
||||
# If it's an old GNS3 file we need to convert it
|
||||
# first we backup the file
|
||||
shutil.copy(path, path + ".backup{}".format(topo.get("revision", 0)))
|
||||
changed = True
|
||||
|
||||
if "revision" not in topo or topo["revision"] < 5:
|
||||
topo = _convert_1_3_later(topo, path)
|
||||
_check_topology_schema(topo)
|
||||
with open(path, "w+", encoding="utf-8") as f:
|
||||
json.dump(topo, f, indent=4, sort_keys=True)
|
||||
|
||||
# Version before GNS3 2.0 alpha 4
|
||||
if topo["revision"] < 6:
|
||||
shutil.copy(path, path + ".backup{}".format(topo.get("revision", 0)))
|
||||
topo = _convert_2_0_0_alpha(topo, path)
|
||||
_check_topology_schema(topo)
|
||||
with open(path, "w+", encoding="utf-8") as f:
|
||||
json.dump(topo, f, indent=4, sort_keys=True)
|
||||
|
||||
# Version before GNS3 2.0 beta 3
|
||||
if topo["revision"] < 7:
|
||||
shutil.copy(path, path + ".backup{}".format(topo.get("revision", 0)))
|
||||
topo = _convert_2_0_0_beta_2(topo, path)
|
||||
_check_topology_schema(topo)
|
||||
|
||||
# Version before GNS3 2.1
|
||||
if topo["revision"] < 8:
|
||||
topo = _convert_2_0_0(topo, path)
|
||||
|
||||
_check_topology_schema(topo)
|
||||
|
||||
if changed:
|
||||
with open(path, "w+", encoding="utf-8") as f:
|
||||
json.dump(topo, f, indent=4, sort_keys=True)
|
||||
return topo
|
||||
|
||||
if topo["revision"] > GNS3_FILE_FORMAT_REVISION:
|
||||
raise aiohttp.web.HTTPConflict(text="This project is designed for a more recent version of GNS3 please update GNS3 to version {} or later".format(topo["version"]))
|
||||
_check_topology_schema(topo)
|
||||
|
||||
def _convert_2_0_0(topo, topo_path):
|
||||
"""
|
||||
Convert topologies from GNS3 2.0.0 to 2.1
|
||||
|
||||
Changes:
|
||||
* Remove startup_script_path from VPCS and base config file for IOU and Dynamips
|
||||
"""
|
||||
topo["revision"] = 8
|
||||
|
||||
for node in topo.get("topology", {}).get("nodes", []):
|
||||
if "properties" in node:
|
||||
if node["node_type"] == "vpcs":
|
||||
if "startup_script_path" in node["properties"]:
|
||||
del node["properties"]["startup_script_path"]
|
||||
if "startup_script" in node["properties"]:
|
||||
del node["properties"]["startup_script"]
|
||||
elif node["node_type"] == "dynamips" or node["node_type"] == "iou":
|
||||
if "startup_config" in node["properties"]:
|
||||
del node["properties"]["startup_config"]
|
||||
if "private_config" in node["properties"]:
|
||||
del node["properties"]["private_config"]
|
||||
if "startup_config_content" in node["properties"]:
|
||||
del node["properties"]["startup_config_content"]
|
||||
if "private_config_content" in node["properties"]:
|
||||
del node["properties"]["private_config_content"]
|
||||
return topo
|
||||
|
||||
|
||||
@@ -165,11 +195,14 @@ def _convert_2_0_0_beta_2(topo, topo_path):
|
||||
|
||||
dynamips_dir = os.path.join(topo_dir, "project-files", "dynamips")
|
||||
node_dir = os.path.join(dynamips_dir, node_id)
|
||||
os.makedirs(os.path.join(node_dir, "configs"), exist_ok=True)
|
||||
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))):
|
||||
shutil.move(path, os.path.join(node_dir, os.path.basename(path)))
|
||||
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "configs", "i{}_*".format(dynamips_id))):
|
||||
shutil.move(path, os.path.join(node_dir, "configs", os.path.basename(path)))
|
||||
try:
|
||||
os.makedirs(os.path.join(node_dir, "configs"), exist_ok=True)
|
||||
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))):
|
||||
shutil.move(path, os.path.join(node_dir, os.path.basename(path)))
|
||||
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "configs", "i{}_*".format(dynamips_id))):
|
||||
shutil.move(path, os.path.join(node_dir, "configs", os.path.basename(path)))
|
||||
except OSError as e:
|
||||
raise aiohttp.web.HTTPConflict(text="Can't convert project {}: {}".format(topo_path, str(e)))
|
||||
return topo
|
||||
|
||||
|
||||
@@ -320,14 +353,24 @@ def _convert_1_3_later(topo, topo_path):
|
||||
node["properties"]["ram"] = PLATFORMS_DEFAULT_RAM[old_node["type"].lower()]
|
||||
elif old_node["type"] == "VMwareVM":
|
||||
node["node_type"] = "vmware"
|
||||
node["properties"]["linked_clone"] = old_node.get("linked_clone", False)
|
||||
if node["symbol"] is None:
|
||||
node["symbol"] = ":/symbols/vmware_guest.svg"
|
||||
elif old_node["type"] == "VirtualBoxVM":
|
||||
node["node_type"] = "virtualbox"
|
||||
node["properties"]["linked_clone"] = old_node.get("linked_clone", False)
|
||||
if node["symbol"] is None:
|
||||
node["symbol"] = ":/symbols/vbox_guest.svg"
|
||||
elif old_node["type"] == "IOUDevice":
|
||||
node["node_type"] = "iou"
|
||||
node["port_name_format"] = old_node.get("port_name_format", "Ethernet{segment0}/{port0}")
|
||||
node["port_segment_size"] = int(old_node.get("port_segment_size", "4"))
|
||||
if node["symbol"] is None:
|
||||
if "l2" in node["properties"].get("path", ""):
|
||||
node["symbol"] = ":/symbols/multilayer_switch.svg"
|
||||
else:
|
||||
node["symbol"] = ":/symbols/router.svg"
|
||||
|
||||
elif old_node["type"] == "Cloud":
|
||||
old_node["ports"] = _create_cloud(node, old_node, ":/symbols/cloud.svg")
|
||||
elif old_node["type"] == "Host":
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
import os
|
||||
import sys
|
||||
import struct
|
||||
import aiohttp
|
||||
import platform
|
||||
|
||||
|
||||
@@ -53,7 +54,7 @@ class CrashReport:
|
||||
Report crash to a third party service
|
||||
"""
|
||||
|
||||
DSN = "sync+https://b7430bad849c4b88b3a928032d6cce5e:f140bfdd2ebb4bf4b929c002b45b2357@sentry.io/38482"
|
||||
DSN = "sync+https://83564b27a6f6475488a3eb74c78f1760:ed5ac7c6d3f7428d960a84da98450b69@sentry.io/38482"
|
||||
if hasattr(sys, "frozen"):
|
||||
cacert = get_resource("cacert.pem")
|
||||
if cacert is not None and os.path.isfile(cacert):
|
||||
@@ -94,6 +95,7 @@ class CrashReport:
|
||||
"os:win_32": " ".join(platform.win32_ver()),
|
||||
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
|
||||
"os:linux": " ".join(platform.linux_distribution()),
|
||||
"aiohttp:version": aiohttp.__version__,
|
||||
"python:version": "{}.{}.{}".format(sys.version_info[0],
|
||||
sys.version_info[1],
|
||||
sys.version_info[2]),
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
import os
|
||||
import sys
|
||||
import base64
|
||||
|
||||
from gns3server.web.route import Route
|
||||
from gns3server.schemas.nio import NIO_SCHEMA
|
||||
@@ -78,7 +77,6 @@ class DynamipsVMHandler:
|
||||
aux=request.json.get("aux"),
|
||||
chassis=request.json.pop("chassis", default_chassis),
|
||||
node_type="dynamips")
|
||||
|
||||
yield from dynamips_manager.update_vm_settings(vm, request.json)
|
||||
response.set_status(201)
|
||||
response.json(vm)
|
||||
|
||||
@@ -30,8 +30,7 @@ from gns3server.schemas.node import (
|
||||
from gns3server.schemas.iou import (
|
||||
IOU_CREATE_SCHEMA,
|
||||
IOU_START_SCHEMA,
|
||||
IOU_OBJECT_SCHEMA,
|
||||
IOU_CONFIGS_SCHEMA,
|
||||
IOU_OBJECT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -344,10 +344,7 @@ class NodeHandler:
|
||||
raise aiohttp.web.HTTPForbidden
|
||||
|
||||
node_type = node.node_type
|
||||
if node_type == "dynamips":
|
||||
path = "/project-files/{}/{}".format(node_type, path)
|
||||
else:
|
||||
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||
|
||||
res = yield from node.compute.http_query("GET", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), timeout=None, raw=True)
|
||||
response.set_status(200)
|
||||
@@ -384,12 +381,9 @@ class NodeHandler:
|
||||
raise aiohttp.web.HTTPForbidden
|
||||
|
||||
node_type = node.node_type
|
||||
if node_type == "dynamips":
|
||||
path = "/project-files/{}/{}".format(node_type, path)
|
||||
else:
|
||||
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||
|
||||
data = yield from request.content.read()
|
||||
|
||||
res = yield from node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True)
|
||||
yield from node.compute.http_query("POST", "/projects/{project_id}/files{path}".format(project_id=project.id, path=path), data=data, timeout=None, raw=True)
|
||||
response.set_status(201)
|
||||
@@ -114,7 +114,10 @@ class ServerHandler:
|
||||
def write_settings(request, response):
|
||||
controller = Controller.instance()
|
||||
controller.settings = request.json
|
||||
controller.save()
|
||||
try:
|
||||
controller.save()
|
||||
except (OSError, PermissionError) as e:
|
||||
raise HTTPConflict(text="Can't save the settings {}".format(str(e)))
|
||||
response.json(controller.settings)
|
||||
response.set_status(201)
|
||||
|
||||
|
||||
@@ -62,18 +62,10 @@ VM_CREATE_SCHEMA = {
|
||||
"type": ["string", "null"],
|
||||
"minLength": 1,
|
||||
},
|
||||
"startup_config": {
|
||||
"description": "Path to the IOS startup configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"startup_config_content": {
|
||||
"description": "Content of IOS startup configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"private_config": {
|
||||
"description": "Path to the IOS private configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"private_config_content": {
|
||||
"description": "Content of IOS private configuration file",
|
||||
"type": "string",
|
||||
@@ -296,22 +288,6 @@ VM_UPDATE_SCHEMA = {
|
||||
"description": "Dynamips ID",
|
||||
"type": "integer"
|
||||
},
|
||||
"startup_config": {
|
||||
"description": "Path to the IOS startup configuration file.",
|
||||
"type": "string",
|
||||
},
|
||||
"private_config": {
|
||||
"description": "Path to the IOS private configuration file.",
|
||||
"type": "string",
|
||||
},
|
||||
"startup_config_content": {
|
||||
"description": "Content of IOS startup configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"private_config_content": {
|
||||
"description": "Content of IOS private configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"ram": {
|
||||
"description": "Amount of RAM in MB",
|
||||
"type": "integer"
|
||||
@@ -552,14 +528,6 @@ VM_OBJECT_SCHEMA = {
|
||||
"type": ["string", "null"],
|
||||
"minLength": 1,
|
||||
},
|
||||
"startup_config": {
|
||||
"description": "Path to the IOS startup configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"private_config": {
|
||||
"description": "Path to the IOS private configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"ram": {
|
||||
"description": "Amount of RAM in MB",
|
||||
"type": "integer"
|
||||
@@ -706,14 +674,6 @@ VM_OBJECT_SCHEMA = {
|
||||
{"type": "null"}
|
||||
]
|
||||
},
|
||||
"startup_config_content": {
|
||||
"description": "Content of IOS startup configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
"private_config_content": {
|
||||
"description": "Content of IOS private configuration file",
|
||||
"type": "string",
|
||||
},
|
||||
# C7200 properties
|
||||
"npe": {
|
||||
"description": "NPE model",
|
||||
|
||||
@@ -78,14 +78,6 @@ IOU_CREATE_SCHEMA = {
|
||||
"description": "Use default IOU values",
|
||||
"type": ["boolean", "null"]
|
||||
},
|
||||
"startup_config": {
|
||||
"description": "Path to the startup-config of IOU",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"private_config": {
|
||||
"description": "Path to the private-config of IOU",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"startup_config_content": {
|
||||
"description": "Startup-config of IOU",
|
||||
"type": ["string", "null"]
|
||||
@@ -94,10 +86,6 @@ IOU_CREATE_SCHEMA = {
|
||||
"description": "Private-config of IOU",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"iourc_content": {
|
||||
"description": "Content of the iourc file. Ignored if Null",
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name", "path"]
|
||||
@@ -187,30 +175,10 @@ IOU_OBJECT_SCHEMA = {
|
||||
"description": "Always up ethernet interface",
|
||||
"type": "boolean"
|
||||
},
|
||||
"startup_config": {
|
||||
"description": "Path of the startup-config content relative to project directory",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"private_config": {
|
||||
"description": "Path of the private-config content relative to project directory",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"use_default_iou_values": {
|
||||
"description": "Use default IOU values",
|
||||
"type": ["boolean", "null"]
|
||||
},
|
||||
"startup_config_content": {
|
||||
"description": "Startup-config of IOU",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"private_config_content": {
|
||||
"description": "Private-config of IOU",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"iourc_content": {
|
||||
"description": "Content of the iourc file. Ignored if Null",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"command_line": {
|
||||
"description": "Last command line used by GNS3 to start QEMU",
|
||||
"type": "string"
|
||||
@@ -218,23 +186,3 @@ IOU_OBJECT_SCHEMA = {
|
||||
},
|
||||
"additionalProperties": False
|
||||
}
|
||||
|
||||
|
||||
IOU_CONFIGS_SCHEMA = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"description": "Request validation to get the startup and private configuration file",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"startup_config_content": {
|
||||
"description": "Content of the startup configuration file",
|
||||
"type": ["string", "null"],
|
||||
"minLength": 1,
|
||||
},
|
||||
"private_config_content": {
|
||||
"description": "Content of the private configuration file",
|
||||
"type": ["string", "null"],
|
||||
"minLength": 1,
|
||||
},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
}
|
||||
@@ -147,7 +147,7 @@ QEMU_CREATE_SCHEMA = {
|
||||
"description": "Number of adapters",
|
||||
"type": ["integer", "null"],
|
||||
"minimum": 0,
|
||||
"maximum": 32,
|
||||
"maximum": 275,
|
||||
},
|
||||
"adapter_type": {
|
||||
"description": "QEMU adapter type",
|
||||
@@ -332,7 +332,7 @@ QEMU_UPDATE_SCHEMA = {
|
||||
"description": "Number of adapters",
|
||||
"type": ["integer", "null"],
|
||||
"minimum": 0,
|
||||
"maximum": 32,
|
||||
"maximum": 275,
|
||||
},
|
||||
"adapter_type": {
|
||||
"description": "QEMU adapter type",
|
||||
@@ -520,7 +520,7 @@ QEMU_OBJECT_SCHEMA = {
|
||||
"description": "Number of adapters",
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 32,
|
||||
"maximum": 275,
|
||||
},
|
||||
"adapter_type": {
|
||||
"description": "QEMU adapter type",
|
||||
|
||||
@@ -50,10 +50,6 @@ VPCS_CREATE_SCHEMA = {
|
||||
"description": "Content of the VPCS startup script",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"startup_script_path": {
|
||||
"description": "Path of the VPCS startup script relative to project directory (IGNORED)",
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name"]
|
||||
@@ -79,14 +75,6 @@ VPCS_UPDATE_SCHEMA = {
|
||||
"description": "Console type",
|
||||
"enum": ["telnet"]
|
||||
},
|
||||
"startup_script": {
|
||||
"description": "Content of the VPCS startup script",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"startup_script_path": {
|
||||
"description": "Path of the VPCS startup script relative to project directory (IGNORED)",
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": False,
|
||||
}
|
||||
@@ -133,19 +121,11 @@ VPCS_OBJECT_SCHEMA = {
|
||||
"maxLength": 36,
|
||||
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
|
||||
},
|
||||
"startup_script": {
|
||||
"description": "Content of the VPCS startup script",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"startup_script_path": {
|
||||
"description": "Path of the VPCS startup script relative to project directory",
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"command_line": {
|
||||
"description": "Last command line used by GNS3 to start QEMU",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": False,
|
||||
"required": ["name", "node_id", "status", "console", "console_type", "project_id", "startup_script_path", "command_line"]
|
||||
"required": ["name", "node_id", "status", "console", "console_type", "project_id", "command_line"]
|
||||
}
|
||||
@@ -34,6 +34,7 @@ class SerialReaderWriterProtocol(asyncio.Protocol):
|
||||
|
||||
def __init__(self):
|
||||
self._output = asyncio.StreamReader()
|
||||
self._closed = False
|
||||
self.transport = None
|
||||
|
||||
def read(self, n=-1):
|
||||
@@ -54,9 +55,11 @@ class SerialReaderWriterProtocol(asyncio.Protocol):
|
||||
self.transport = transport
|
||||
|
||||
def data_received(self, data):
|
||||
self._output.feed_data(data)
|
||||
if not self._closed:
|
||||
self._output.feed_data(data)
|
||||
|
||||
def close(self):
|
||||
self._closed = True
|
||||
self._output.feed_eof()
|
||||
|
||||
|
||||
@@ -122,7 +125,10 @@ def _asyncio_open_serial_unix(path):
|
||||
raise NodeError('Pipe file "{}" is missing'.format(path))
|
||||
|
||||
output = SerialReaderWriterProtocol()
|
||||
con = yield from asyncio.get_event_loop().create_unix_connection(lambda: output, path)
|
||||
try:
|
||||
yield from asyncio.get_event_loop().create_unix_connection(lambda: output, path)
|
||||
except ConnectionRefusedError:
|
||||
raise NodeError('Can\'t open pipe file "{}"'.format(path))
|
||||
return output
|
||||
|
||||
|
||||
|
||||
@@ -120,7 +120,8 @@ def _svg_convert_size(size):
|
||||
"pc": 15,
|
||||
"mm": 3.543307,
|
||||
"cm": 35.43307,
|
||||
"in": 90
|
||||
"in": 90,
|
||||
"px": 1
|
||||
}
|
||||
if len(size) > 3:
|
||||
if size[-2:] in conversion_table:
|
||||
|
||||
@@ -25,3 +25,14 @@
|
||||
|
||||
__version__ = "2.1.0dev1"
|
||||
__version_info__ = (2, 1, 0, -99)
|
||||
|
||||
# If it's a git checkout try to add the commit
|
||||
if "dev" in __version__:
|
||||
try:
|
||||
import os
|
||||
import subprocess
|
||||
if os.path.exists(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".git")):
|
||||
r = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"]).decode().strip("\n")
|
||||
__version__ += "-" + r
|
||||
except Exception as e:
|
||||
print(e)
|
||||
@@ -39,6 +39,7 @@ class Response(aiohttp.web.Response):
|
||||
self._route = route
|
||||
self._output_schema = output_schema
|
||||
self._request = request
|
||||
headers['Connection'] = "close" # Disable keep alive because create trouble with old Qt (5.2, 5.3 and 5.4)
|
||||
headers['X-Route'] = self._route
|
||||
headers['Server'] = "Python/{0[0]}.{0[1]} GNS3/{1}".format(sys.version_info, __version__)
|
||||
super().__init__(headers=headers, **kwargs)
|
||||
|
||||
Reference in new issue
Block a user