Merge branch '2.1' into embed_appliances

This commit is contained in:
Julien Duponchelle committed 2017-02-28 14:55:33 +01:00
commit 5a399b90fe
68 files changed
+1527 -484

No files matched your search

+9 -17
View File
@@ -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,
+60 -138
View File
@@ -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):
"""
+2 -8
View File
@@ -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):
+24 -3
View File
@@ -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
+25 -4
View File
@@ -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:
+9 -11
View File
@@ -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
+1
View File
@@ -0,0 +1 @@
set pcname %h
+38 -7
View File
@@ -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 -7
View File
@@ -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)
+1
View File
@@ -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
+5
View File
@@ -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"]))
+10 -2
View File
@@ -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()
+5 -2
View File
@@ -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):
+28 -15
View File
@@ -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,
+10 -5
View File
@@ -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:
+25 -12
View File
@@ -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:
+8 -4
View File
@@ -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
+11 -10
View File
@@ -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):
+63 -20
View File
@@ -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":
+3 -1
View File
@@ -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)
-40
View File
@@ -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",
-52
View File
@@ -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,
}
+3 -3
View File
@@ -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",
+1 -21
View File
@@ -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"]
}
+8 -2
View File
@@ -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
+2 -1
View File
@@ -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:
+11
View File
@@ -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)
+1
View File
@@ -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)