mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-24 17:28:08 +00:00
Merge branch '2.1' into embed_appliances
This commit is contained in:
commit
5a399b90fe
@ -1,6 +1,8 @@
|
|||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
|
- '3.4'
|
||||||
- '3.5'
|
- '3.5'
|
||||||
|
- '3.6'
|
||||||
sudo: false
|
sudo: false
|
||||||
cache: pip
|
cache: pip
|
||||||
install:
|
install:
|
||||||
|
35
CHANGELOG
35
CHANGELOG
@ -1,5 +1,38 @@
|
|||||||
# Change Log
|
# Change Log
|
||||||
|
|
||||||
|
## 2.0.0 beta 4 16/02/2017
|
||||||
|
|
||||||
|
* Lock aiohttp to 1.2.0 because 1.3 create bug with Qt
|
||||||
|
* Avoid a crash in some conditions when reading the serial console
|
||||||
|
* Disallow export of project with VirtualBox linked clone
|
||||||
|
* Fix linked_clone property lost during topology convert
|
||||||
|
* Catch permission error when restoring a snapshot
|
||||||
|
* Fix a rare crash when closing a project
|
||||||
|
* Fix error when you have error on your filesystem during project convertion
|
||||||
|
* Catch error when we can't access to a unix socket
|
||||||
|
* If we can't resolve compute name return 0.0.0.0
|
||||||
|
* Raise an error if you put an invalid key in node name
|
||||||
|
* Improve a lot project loading speed
|
||||||
|
* Fix a potential crash
|
||||||
|
* Fix the server don't start if a remote is unavailable
|
||||||
|
* Do not crash if you pass {name} in name
|
||||||
|
* Fix import/export of dynamips configuration
|
||||||
|
* Simplify conversion process from 1.3 to 2.0
|
||||||
|
* Prevent corruption of VM in VirtualBox when using linked clone
|
||||||
|
* Fix creation of qemu img
|
||||||
|
* Fix rare race condition when stopping ubridge
|
||||||
|
* Prevent renaming of a running VirtualBox linked VM
|
||||||
|
* Avoid crash when you broke your system permissions
|
||||||
|
* Do not crash when you broke permission on your file system during execution
|
||||||
|
* Fix a crash when you broke permission on your file system
|
||||||
|
* Fix a rare race condition when exporting debug informations
|
||||||
|
* Do not try to start the GNS3 VM if the name is none
|
||||||
|
* Fix version check for VPCS
|
||||||
|
* Fix pcap for PPP link with IOU
|
||||||
|
* Correct link are not connected to the correct ethernet switch port after conversion
|
||||||
|
* Fix an error if you don't have permissions on your symbols directory
|
||||||
|
* Fix an error when converting some topologies from 1.3
|
||||||
|
|
||||||
## 2.0.0 beta 3 19/01/2017
|
## 2.0.0 beta 3 19/01/2017
|
||||||
|
|
||||||
* Force the dependency on typing because otherwise it's broke on 3.4
|
* Force the dependency on typing because otherwise it's broke on 3.4
|
||||||
@ -45,7 +78,7 @@
|
|||||||
* Replace JSONDecodeError by ValueError (Python 3.4 compatibility)
|
* Replace JSONDecodeError by ValueError (Python 3.4 compatibility)
|
||||||
* Catch an error when we can't create the IOU directory
|
* Catch an error when we can't create the IOU directory
|
||||||
|
|
||||||
## 1.5.3 12/01/2016
|
## 1.5.3 12/01/2017
|
||||||
|
|
||||||
* Fix sporadically systemd is unable to start gns3-server
|
* Fix sporadically systemd is unable to start gns3-server
|
||||||
|
|
||||||
|
34
Dockerfile
Normal file
34
Dockerfile
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# Dockerfile for GNS3 server development
|
||||||
|
|
||||||
|
FROM ubuntu:16.04
|
||||||
|
|
||||||
|
ENV DEBIAN_FRONTEND noninteractive
|
||||||
|
|
||||||
|
# Set the locale
|
||||||
|
RUN locale-gen en_US.UTF-8
|
||||||
|
ENV LANG en_US.UTF-8
|
||||||
|
ENV LANGUAGE en_US:en
|
||||||
|
ENV LC_ALL en_US.UTF-8
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y software-properties-common
|
||||||
|
RUN add-apt-repository ppa:gns3/ppa
|
||||||
|
RUN apt-get update && apt-get install -y \
|
||||||
|
python3-pip \
|
||||||
|
python3-dev \
|
||||||
|
qemu-system-x86 \
|
||||||
|
qemu-system-arm \
|
||||||
|
qemu-kvm \
|
||||||
|
libvirt-bin \
|
||||||
|
x11vnc
|
||||||
|
|
||||||
|
# Install uninstall to install dependencies
|
||||||
|
RUN apt-get install -y vpcs ubridge
|
||||||
|
|
||||||
|
ADD . /server
|
||||||
|
WORKDIR /server
|
||||||
|
|
||||||
|
RUN pip3 install -r /server/requirements.txt
|
||||||
|
|
||||||
|
EXPOSE 3080
|
||||||
|
|
||||||
|
CMD python3 -m gns3server --local
|
10
README.rst
10
README.rst
@ -71,6 +71,16 @@ To run tests use:
|
|||||||
py.test -v
|
py.test -v
|
||||||
|
|
||||||
|
|
||||||
|
Docker container
|
||||||
|
****************
|
||||||
|
|
||||||
|
For development you can run the GNS3 server in a container
|
||||||
|
|
||||||
|
.. code:: bash
|
||||||
|
|
||||||
|
bash scripts/docker_dev_server.sh
|
||||||
|
|
||||||
|
|
||||||
Run as daemon (Unix only)
|
Run as daemon (Unix only)
|
||||||
**************************
|
**************************
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
-rrequirements.txt
|
-rrequirements.txt
|
||||||
|
|
||||||
sphinx==1.5.2
|
sphinx==1.5.3
|
||||||
pytest==3.0.6
|
pytest==3.0.6
|
||||||
pep8==1.7.0
|
pep8==1.7.0
|
||||||
pytest-catchlog==1.2.2
|
pytest-catchlog==1.2.2
|
||||||
|
@ -23,6 +23,7 @@ A minimal version:
|
|||||||
|
|
||||||
The revision is the version of file format:
|
The revision is the version of file format:
|
||||||
|
|
||||||
|
* 8: GNS3 2.1
|
||||||
* 7: GNS3 2.0
|
* 7: GNS3 2.0
|
||||||
* 6: GNS3 2.0 < beta 3
|
* 6: GNS3 2.0 < beta 3
|
||||||
* 5: GNS3 2.0 < alpha 4
|
* 5: GNS3 2.0 < alpha 4
|
||||||
|
@ -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_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))
|
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")
|
startup_config_content = settings.get("startup_config_content")
|
||||||
if startup_config_path:
|
if startup_config_content:
|
||||||
yield from vm.set_configs(startup_config_path)
|
self._create_config(vm, default_startup_config_path, startup_config_content)
|
||||||
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")
|
|
||||||
private_config_content = settings.get("private_config_content")
|
private_config_content = settings.get("private_config_content")
|
||||||
if private_config_path:
|
if private_config_content:
|
||||||
yield from vm.set_configs(vm.startup_config, private_config_path)
|
self._create_config(vm, default_private_config_path, private_config_content)
|
||||||
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)
|
|
||||||
|
|
||||||
def _create_config(self, vm, path, content=None):
|
def _create_config(self, vm, path, content=None):
|
||||||
"""
|
"""
|
||||||
@ -553,6 +540,11 @@ class Dynamips(BaseManager):
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
raise DynamipsError("Could not create Dynamips configs directory: {}".format(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:
|
try:
|
||||||
with open(path, "wb") as f:
|
with open(path, "wb") as f:
|
||||||
if content:
|
if content:
|
||||||
|
@ -211,7 +211,8 @@ class EthernetSwitch(Device):
|
|||||||
nio = self._nios[port_number]
|
nio = self._nios[port_number]
|
||||||
if isinstance(nio, NIOUDP):
|
if isinstance(nio, NIOUDP):
|
||||||
self.manager.port_manager.release_udp_port(nio.lport, self._project)
|
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,
|
log.info('Ethernet switch "{name}" [{id}]: NIO {nio} removed from port {port}'.format(name=self._name,
|
||||||
id=self._id,
|
id=self._id,
|
||||||
|
@ -24,6 +24,7 @@ import asyncio
|
|||||||
import time
|
import time
|
||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import glob
|
import glob
|
||||||
import shlex
|
import shlex
|
||||||
import base64
|
import base64
|
||||||
@ -78,8 +79,6 @@ class Router(BaseNode):
|
|||||||
self._dynamips_id = dynamips_id
|
self._dynamips_id = dynamips_id
|
||||||
self._platform = platform
|
self._platform = platform
|
||||||
self._image = ""
|
self._image = ""
|
||||||
self._startup_config = ""
|
|
||||||
self._private_config = ""
|
|
||||||
self._ram = 128 # Megabytes
|
self._ram = 128 # Megabytes
|
||||||
self._nvram = 128 # Kilobytes
|
self._nvram = 128 # Kilobytes
|
||||||
self._mmap = True
|
self._mmap = True
|
||||||
@ -102,8 +101,6 @@ class Router(BaseNode):
|
|||||||
self._slots = []
|
self._slots = []
|
||||||
self._ghost_flag = ghost_flag
|
self._ghost_flag = ghost_flag
|
||||||
self._memory_watcher = None
|
self._memory_watcher = None
|
||||||
self._startup_config_content = ""
|
|
||||||
self._private_config_content = ""
|
|
||||||
|
|
||||||
if not ghost_flag:
|
if not ghost_flag:
|
||||||
if not dynamips_id:
|
if not dynamips_id:
|
||||||
@ -152,8 +149,6 @@ class Router(BaseNode):
|
|||||||
"platform": self._platform,
|
"platform": self._platform,
|
||||||
"image": self._image,
|
"image": self._image,
|
||||||
"image_md5sum": md5sum(self._image),
|
"image_md5sum": md5sum(self._image),
|
||||||
"startup_config": self._startup_config,
|
|
||||||
"private_config": self._private_config,
|
|
||||||
"ram": self._ram,
|
"ram": self._ram,
|
||||||
"nvram": self._nvram,
|
"nvram": self._nvram,
|
||||||
"mmap": self._mmap,
|
"mmap": self._mmap,
|
||||||
@ -171,9 +166,7 @@ class Router(BaseNode):
|
|||||||
"console_type": "telnet",
|
"console_type": "telnet",
|
||||||
"aux": self.aux,
|
"aux": self.aux,
|
||||||
"mac_addr": self._mac_addr,
|
"mac_addr": self._mac_addr,
|
||||||
"system_id": self._system_id,
|
"system_id": self._system_id}
|
||||||
"startup_config_content": self._startup_config_content,
|
|
||||||
"private_config_content": self._private_config_content}
|
|
||||||
|
|
||||||
# return the relative path if the IOS image is in the images_path directory
|
# 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)
|
router_info["image"] = self.manager.get_relative_image_path(self._image)
|
||||||
@ -289,6 +282,16 @@ class Router(BaseNode):
|
|||||||
if not self._ghost_flag:
|
if not self._ghost_flag:
|
||||||
self.check_available_ram(self.ram)
|
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))
|
yield from self._hypervisor.send('vm start "{name}"'.format(name=self._name))
|
||||||
self.status = "started"
|
self.status = "started"
|
||||||
log.info('router "{name}" [{id}] has been started'.format(name=self._name, id=self._id))
|
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
|
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
|
@asyncio.coroutine
|
||||||
def set_name(self, new_name):
|
def set_name(self, new_name):
|
||||||
"""
|
"""
|
||||||
@ -1486,89 +1469,34 @@ class Router(BaseNode):
|
|||||||
:param new_name: new name string
|
:param new_name: new name string
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if self._startup_config:
|
# change the hostname in the 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))
|
||||||
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):
|
||||||
if os.path.isfile(startup_config_path):
|
try:
|
||||||
try:
|
with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
||||||
with open(startup_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
old_config = f.read()
|
||||||
old_config = f.read()
|
new_config = re.sub(r"^hostname .+$", "hostname " + new_name, old_config, flags=re.MULTILINE)
|
||||||
new_config = old_config.replace(self.name, new_name)
|
f.seek(0)
|
||||||
f.seek(0)
|
f.write(new_config)
|
||||||
self._startup_config_content = new_config
|
except OSError as e:
|
||||||
f.write(new_config)
|
raise DynamipsError("Could not amend the configuration {}: {}".format(startup_config_path, e))
|
||||||
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
|
||||||
# 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))
|
||||||
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):
|
||||||
if os.path.isfile(private_config_path):
|
try:
|
||||||
try:
|
with open(private_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
||||||
with open(private_config_path, "r+", encoding="utf-8", errors="replace") as f:
|
old_config = f.read()
|
||||||
old_config = f.read()
|
new_config = old_config.replace(self.name, new_name)
|
||||||
new_config = old_config.replace(self.name, new_name)
|
f.seek(0)
|
||||||
f.seek(0)
|
f.write(new_config)
|
||||||
self._private_config_content = new_config
|
except OSError as e:
|
||||||
f.write(new_config)
|
raise DynamipsError("Could not amend the configuration {}: {}".format(private_config_path, e))
|
||||||
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))
|
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))
|
log.info('Router "{name}" [{id}]: renamed to "{new_name}"'.format(name=self._name, id=self._id, new_name=new_name))
|
||||||
self._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
|
@asyncio.coroutine
|
||||||
def extract_config(self):
|
def extract_config(self):
|
||||||
"""
|
"""
|
||||||
@ -1594,41 +1522,35 @@ class Router(BaseNode):
|
|||||||
Saves the startup-config and private-config to files.
|
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:
|
try:
|
||||||
config_path = os.path.join(self._working_directory, "configs")
|
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
|
||||||
os.makedirs(config_path, exist_ok=True)
|
config = "!\n" + config.replace("\r", "")
|
||||||
except OSError as e:
|
config_path = os.path.join(self._working_directory, startup_config)
|
||||||
raise DynamipsError("Could could not create configuration directory {}: {}".format(config_path, e))
|
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 private_config_base64 and base64.b64decode(private_config_base64) != b'\nkerberos password \nend\n':
|
||||||
if startup_config_base64:
|
private_config = os.path.join("configs", "i{}_private-config.cfg".format(self._dynamips_id))
|
||||||
if not self.startup_config:
|
try:
|
||||||
self._startup_config = os.path.join("configs", "i{}_startup-config.cfg".format(self._dynamips_id))
|
config = base64.b64decode(private_config_base64).decode("utf-8", errors="replace")
|
||||||
try:
|
config_path = os.path.join(self._working_directory, private_config)
|
||||||
config = base64.b64decode(startup_config_base64).decode("utf-8", errors="replace")
|
with open(config_path, "wb") as f:
|
||||||
config = "!\n" + config.replace("\r", "")
|
log.info("saving private-config to {}".format(private_config))
|
||||||
config_path = os.path.join(self._working_directory, self.startup_config)
|
f.write(config.encode("utf-8"))
|
||||||
with open(config_path, "wb") as f:
|
except (binascii.Error, OSError) as e:
|
||||||
log.info("saving startup-config to {}".format(self.startup_config))
|
raise DynamipsError("Could not save the private configuration {}: {}".format(config_path, e))
|
||||||
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))
|
|
||||||
|
|
||||||
def delete(self):
|
def delete(self):
|
||||||
"""
|
"""
|
||||||
|
@ -26,8 +26,6 @@ import re
|
|||||||
import asyncio
|
import asyncio
|
||||||
import subprocess
|
import subprocess
|
||||||
import shutil
|
import shutil
|
||||||
import argparse
|
|
||||||
import threading
|
|
||||||
import configparser
|
import configparser
|
||||||
import struct
|
import struct
|
||||||
import hashlib
|
import hashlib
|
||||||
@ -207,10 +205,6 @@ class IOUVM(BaseNode):
|
|||||||
"ram": self._ram,
|
"ram": self._ram,
|
||||||
"nvram": self._nvram,
|
"nvram": self._nvram,
|
||||||
"l1_keepalives": self._l1_keepalives,
|
"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,
|
"use_default_iou_values": self._use_default_iou_values,
|
||||||
"command_line": self.command_line}
|
"command_line": self.command_line}
|
||||||
|
|
||||||
@ -307,7 +301,7 @@ class IOUVM(BaseNode):
|
|||||||
|
|
||||||
if self.startup_config_file:
|
if self.startup_config_file:
|
||||||
content = self.startup_config_content
|
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
|
self.startup_config_content = content
|
||||||
|
|
||||||
super(IOUVM, IOUVM).name.__set__(self, new_name)
|
super(IOUVM, IOUVM).name.__set__(self, new_name)
|
||||||
@ -1167,7 +1161,7 @@ class IOUVM(BaseNode):
|
|||||||
bay=adapter_number,
|
bay=adapter_number,
|
||||||
unit=port_number,
|
unit=port_number,
|
||||||
output_file=output_file,
|
output_file=output_file,
|
||||||
data_link_type=data_link_type))
|
data_link_type=re.sub("^DLT_", "", data_link_type)))
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def stop_capture(self, adapter_number, port_number):
|
def stop_capture(self, adapter_number, port_number):
|
||||||
|
@ -23,12 +23,13 @@ order to run a QEMU VM.
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
|
import math
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
|
||||||
import shlex
|
import shlex
|
||||||
import asyncio
|
import asyncio
|
||||||
import socket
|
import socket
|
||||||
import gns3server
|
import gns3server
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from gns3server.utils import parse_version
|
from gns3server.utils import parse_version
|
||||||
from .qemu_error import QemuError
|
from .qemu_error import QemuError
|
||||||
@ -1446,6 +1447,20 @@ class QemuVM(BaseNode):
|
|||||||
# this is a patched Qemu if version is below 1.1.0
|
# this is a patched Qemu if version is below 1.1.0
|
||||||
patched_qemu = True
|
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):
|
for adapter_number, adapter in enumerate(self._ethernet_adapters):
|
||||||
mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
|
mac = int_to_macaddress(macaddress_to_int(self._mac_address) + adapter_number)
|
||||||
|
|
||||||
@ -1483,8 +1498,14 @@ class QemuVM(BaseNode):
|
|||||||
|
|
||||||
else:
|
else:
|
||||||
# newer QEMU networking syntax
|
# 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:
|
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):
|
if isinstance(nio, NIOUDP):
|
||||||
network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
|
network_options.extend(["-netdev", "socket,id=gns3-{},udp={}:{},localaddr={}:{}".format(adapter_number,
|
||||||
nio.rhost,
|
nio.rhost,
|
||||||
@ -1494,7 +1515,7 @@ class QemuVM(BaseNode):
|
|||||||
elif isinstance(nio, NIOTAP):
|
elif isinstance(nio, NIOTAP):
|
||||||
network_options.extend(["-netdev", "tap,id=gns3-{},ifname={},script=no,downscript=no".format(adapter_number, nio.tap_device)])
|
network_options.extend(["-netdev", "tap,id=gns3-{},ifname={},script=no,downscript=no".format(adapter_number, nio.tap_device)])
|
||||||
else:
|
else:
|
||||||
network_options.extend(["-device", "{},mac={}".format(self._adapter_type, mac)])
|
network_options.extend(["-device", device_string])
|
||||||
|
|
||||||
return network_options
|
return network_options
|
||||||
|
|
||||||
|
@ -19,14 +19,16 @@
|
|||||||
VirtualBox VM instance.
|
VirtualBox VM instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import shlex
|
|
||||||
import re
|
import re
|
||||||
import os
|
import os
|
||||||
import tempfile
|
import sys
|
||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
|
import shlex
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import tempfile
|
||||||
import xml.etree.ElementTree as ET
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from gns3server.utils import parse_version
|
from gns3server.utils import parse_version
|
||||||
@ -209,7 +211,16 @@ class VirtualBoxVM(BaseNode):
|
|||||||
if os.path.exists(self._linked_vbox_file()):
|
if os.path.exists(self._linked_vbox_file()):
|
||||||
tree = ET.parse(self._linked_vbox_file())
|
tree = ET.parse(self._linked_vbox_file())
|
||||||
machine = tree.getroot().find("{http://www.virtualbox.org/}Machine")
|
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 + "}")
|
machine.set("uuid", "{" + self.id + "}")
|
||||||
tree.write(self._linked_vbox_file())
|
tree.write(self._linked_vbox_file())
|
||||||
|
|
||||||
@ -292,6 +303,16 @@ class VirtualBoxVM(BaseNode):
|
|||||||
if self.acpi_shutdown:
|
if self.acpi_shutdown:
|
||||||
# use ACPI to shutdown the VM
|
# use ACPI to shutdown the VM
|
||||||
result = yield from self._control_vm("acpipowerbutton")
|
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"
|
self.status = "stopped"
|
||||||
log.debug("ACPI shutdown result: {}".format(result))
|
log.debug("ACPI shutdown result: {}".format(result))
|
||||||
else:
|
else:
|
||||||
|
@ -104,12 +104,12 @@ class VPCSVM(BaseNode):
|
|||||||
Check if VPCS is available with the correct version.
|
Check if VPCS is available with the correct version.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
path = self.vpcs_path
|
path = self._vpcs_path()
|
||||||
if not path:
|
if not path:
|
||||||
raise VPCSError("No path to a VPCS executable has been set")
|
raise VPCSError("No path to a VPCS executable has been set")
|
||||||
|
|
||||||
# This raise an error if ubridge is not available
|
# This raise an error if ubridge is not available
|
||||||
ubridge_path = self.ubridge_path
|
self.ubridge_path
|
||||||
|
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
raise VPCSError("VPCS program '{}' is not accessible".format(path))
|
raise VPCSError("VPCS program '{}' is not accessible".format(path))
|
||||||
@ -128,8 +128,6 @@ class VPCSVM(BaseNode):
|
|||||||
"console": self._console,
|
"console": self._console,
|
||||||
"console_type": "telnet",
|
"console_type": "telnet",
|
||||||
"project_id": self.project.id,
|
"project_id": self.project.id,
|
||||||
"startup_script": self.startup_script,
|
|
||||||
"startup_script_path": self.relative_startup_script,
|
|
||||||
"command_line": self.command_line}
|
"command_line": self.command_line}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -146,8 +144,7 @@ class VPCSVM(BaseNode):
|
|||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
def _vpcs_path(self):
|
||||||
def vpcs_path(self):
|
|
||||||
"""
|
"""
|
||||||
Returns the VPCS executable path.
|
Returns the VPCS executable path.
|
||||||
|
|
||||||
@ -172,6 +169,7 @@ class VPCSVM(BaseNode):
|
|||||||
if self.script_file:
|
if self.script_file:
|
||||||
content = self.startup_script
|
content = self.startup_script
|
||||||
content = content.replace(self._name, new_name)
|
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
|
self.startup_script = content
|
||||||
|
|
||||||
super(VPCSVM, VPCSVM).name.__set__(self, new_name)
|
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.
|
Checks if the VPCS executable version is >= 0.8b or == 0.6.1.
|
||||||
"""
|
"""
|
||||||
try:
|
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)
|
match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output)
|
||||||
if match:
|
if match:
|
||||||
version = match.group(1)
|
version = match.group(1)
|
||||||
@ -225,7 +223,7 @@ class VPCSVM(BaseNode):
|
|||||||
if self._vpcs_version < parse_version("0.6.1"):
|
if self._vpcs_version < parse_version("0.6.1"):
|
||||||
raise VPCSError("VPCS executable version must be >= 0.6.1 but not a 0.8")
|
raise VPCSError("VPCS executable version must be >= 0.6.1 but not a 0.8")
|
||||||
else:
|
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:
|
except (OSError, subprocess.SubprocessError) as e:
|
||||||
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
|
raise VPCSError("Error while looking for the VPCS version: {}".format(e))
|
||||||
|
|
||||||
@ -270,8 +268,8 @@ class VPCSVM(BaseNode):
|
|||||||
self.status = "started"
|
self.status = "started"
|
||||||
except (OSError, subprocess.SubprocessError) as e:
|
except (OSError, subprocess.SubprocessError) as e:
|
||||||
vpcs_stdout = self.read_vpcs_stdout()
|
vpcs_stdout = self.read_vpcs_stdout()
|
||||||
log.error("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))
|
raise VPCSError("Could not start VPCS {}: {}\n{}".format(self._vpcs_path(), e, vpcs_stdout))
|
||||||
|
|
||||||
def _termination_callback(self, returncode):
|
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(["-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(["-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
|
command.extend(["-i", "1"]) # option to start only one VPC instance
|
||||||
|
26
gns3server/configs/ios_base_startup-config.txt
Normal file
26
gns3server/configs/ios_base_startup-config.txt
Normal file
@ -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
|
181
gns3server/configs/ios_etherswitch_startup-config.txt
Normal file
181
gns3server/configs/ios_etherswitch_startup-config.txt
Normal file
@ -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
|
132
gns3server/configs/iou_l2_base_startup-config.txt
Normal file
132
gns3server/configs/iou_l2_base_startup-config.txt
Normal file
@ -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
|
108
gns3server/configs/iou_l3_base_startup-config.txt
Normal file
108
gns3server/configs/iou_l3_base_startup-config.txt
Normal file
@ -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
gns3server/configs/vpcs_base_config.txt
Normal file
1
gns3server/configs/vpcs_base_config.txt
Normal file
@ -0,0 +1 @@
|
|||||||
|
set pcname %h
|
@ -18,6 +18,7 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
import socket
|
import socket
|
||||||
|
import shutil
|
||||||
import asyncio
|
import asyncio
|
||||||
import aiohttp
|
import aiohttp
|
||||||
|
|
||||||
@ -68,16 +69,22 @@ class Controller:
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def start(self):
|
def start(self):
|
||||||
log.info("Start controller")
|
log.info("Start controller")
|
||||||
yield from self.load()
|
self.load_base_files()
|
||||||
server_config = Config.instance().get_section_config("Server")
|
server_config = Config.instance().get_section_config("Server")
|
||||||
host = server_config.get("host", "localhost")
|
host = server_config.get("host", "localhost")
|
||||||
|
|
||||||
# If console_host is 0.0.0.0 client will use the ip they use
|
# If console_host is 0.0.0.0 client will use the ip they use
|
||||||
# to connect to the controller
|
# to connect to the controller
|
||||||
console_host = host
|
console_host = host
|
||||||
if host == "0.0.0.0":
|
if host == "0.0.0.0":
|
||||||
host = "127.0.0.1"
|
host = "127.0.0.1"
|
||||||
|
|
||||||
|
name = socket.gethostname()
|
||||||
|
if name == "gns3vm":
|
||||||
|
name = "Main server"
|
||||||
|
|
||||||
yield from self.add_compute(compute_id="local",
|
yield from self.add_compute(compute_id="local",
|
||||||
name=socket.gethostname(),
|
name=name,
|
||||||
protocol=server_config.get("protocol", "http"),
|
protocol=server_config.get("protocol", "http"),
|
||||||
host=host,
|
host=host,
|
||||||
console_host=console_host,
|
console_host=console_host,
|
||||||
@ -85,6 +92,7 @@ class Controller:
|
|||||||
user=server_config.get("user", ""),
|
user=server_config.get("user", ""),
|
||||||
password=server_config.get("password", ""),
|
password=server_config.get("password", ""),
|
||||||
force=True)
|
force=True)
|
||||||
|
yield from self._load_controller_settings()
|
||||||
yield from self.load_projects()
|
yield from self.load_projects()
|
||||||
yield from self.gns3vm.auto_start_vm()
|
yield from self.gns3vm.auto_start_vm()
|
||||||
yield from self._project_auto_open()
|
yield from self._project_auto_open()
|
||||||
@ -131,7 +139,7 @@ class Controller:
|
|||||||
json.dump(data, f, indent=4)
|
json.dump(data, f, indent=4)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def load(self):
|
def _load_controller_settings(self):
|
||||||
"""
|
"""
|
||||||
Reload the controller configuration from disk
|
Reload the controller configuration from disk
|
||||||
"""
|
"""
|
||||||
@ -177,6 +185,20 @@ class Controller:
|
|||||||
except OSError as e:
|
except OSError as e:
|
||||||
log.error(str(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):
|
def images_path(self):
|
||||||
"""
|
"""
|
||||||
Get the image storage directory
|
Get the image storage directory
|
||||||
@ -186,6 +208,15 @@ class Controller:
|
|||||||
os.makedirs(images_path, exist_ok=True)
|
os.makedirs(images_path, exist_ok=True)
|
||||||
return images_path
|
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
|
@asyncio.coroutine
|
||||||
def _import_gns3_gui_conf(self):
|
def _import_gns3_gui_conf(self):
|
||||||
"""
|
"""
|
||||||
@ -269,7 +300,7 @@ class Controller:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
for compute in self._computes.values():
|
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))
|
raise aiohttp.web.HTTPConflict(text='Compute name "{}" already exists'.format(name))
|
||||||
|
|
||||||
compute = Compute(compute_id=compute_id, controller=self, name=name, **kwargs)
|
compute = Compute(compute_id=compute_id, controller=self, name=name, **kwargs)
|
||||||
@ -332,7 +363,6 @@ class Controller:
|
|||||||
try:
|
try:
|
||||||
return self._computes[compute_id]
|
return self._computes[compute_id]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
server_config = Config.instance().get_section_config("Server")
|
|
||||||
if compute_id == "vm":
|
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="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))
|
raise aiohttp.web.HTTPNotFound(text="Compute ID {} doesn't exist".format(compute_id))
|
||||||
@ -383,7 +413,8 @@ class Controller:
|
|||||||
return project
|
return project
|
||||||
|
|
||||||
def remove_project(self, project):
|
def remove_project(self, project):
|
||||||
del self._projects[project.id]
|
if project.id in self._projects:
|
||||||
|
del self._projects[project.id]
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def load_project(self, path, load=True):
|
def load_project(self, path, load=True):
|
||||||
@ -394,7 +425,7 @@ class Controller:
|
|||||||
:param load: Load the topology
|
:param load: Load the topology
|
||||||
"""
|
"""
|
||||||
topo_data = load_topology(path)
|
topo_data = load_topology(path)
|
||||||
topology = topo_data.pop("topology")
|
topo_data.pop("topology")
|
||||||
topo_data.pop("version")
|
topo_data.pop("version")
|
||||||
topo_data.pop("revision")
|
topo_data.pop("revision")
|
||||||
topo_data.pop("type")
|
topo_data.pop("type")
|
||||||
|
@ -22,14 +22,12 @@ import socket
|
|||||||
import json
|
import json
|
||||||
import uuid
|
import uuid
|
||||||
import sys
|
import sys
|
||||||
import os
|
|
||||||
import io
|
import io
|
||||||
|
|
||||||
from ..utils import parse_version
|
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 ..utils.asyncio import locked_coroutine
|
||||||
from ..controller.controller_error import ControllerError
|
from ..controller.controller_error import ControllerError
|
||||||
from ..config import Config
|
|
||||||
from ..version import __version__
|
from ..version import __version__
|
||||||
|
|
||||||
|
|
||||||
@ -216,7 +214,10 @@ class Compute:
|
|||||||
"""
|
"""
|
||||||
Return the IP associated to the host
|
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
|
@host.setter
|
||||||
def host(self, host):
|
def host(self, host):
|
||||||
@ -360,6 +361,16 @@ class Compute:
|
|||||||
response = yield from self._run_http_query(method, path, data=data, **kwargs)
|
response = yield from self._run_http_query(method, path, data=data, **kwargs)
|
||||||
return response
|
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
|
@locked_coroutine
|
||||||
def connect(self):
|
def connect(self):
|
||||||
"""
|
"""
|
||||||
@ -374,14 +385,18 @@ class Compute:
|
|||||||
self._connection_failure += 1
|
self._connection_failure += 1
|
||||||
# After 5 failure we close the project using the compute to avoid sync issues
|
# After 5 failure we close the project using the compute to avoid sync issues
|
||||||
if self._connection_failure == 5:
|
if self._connection_failure == 5:
|
||||||
|
log.warning("Can't connect to compute %s", self._id)
|
||||||
yield from self._controller.close_compute_projects(self)
|
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
|
return
|
||||||
except aiohttp.web.HTTPNotFound:
|
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))
|
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:
|
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:
|
if "version" not in response.json:
|
||||||
self._http_session.close()
|
self._http_session.close()
|
||||||
@ -411,7 +426,7 @@ class Compute:
|
|||||||
except aiohttp.errors.WSServerHandshakeError:
|
except aiohttp.errors.WSServerHandshakeError:
|
||||||
self._ws = None
|
self._ws = None
|
||||||
break
|
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
|
self._connected = False
|
||||||
break
|
break
|
||||||
msg = json.loads(response.data)
|
msg = json.loads(response.data)
|
||||||
|
@ -43,6 +43,7 @@ class Drawing:
|
|||||||
self._id = str(uuid.uuid4())
|
self._id = str(uuid.uuid4())
|
||||||
else:
|
else:
|
||||||
self._id = drawing_id
|
self._id = drawing_id
|
||||||
|
self._svg = "<svg></svg>"
|
||||||
self.svg = svg
|
self.svg = svg
|
||||||
self._x = x
|
self._x = x
|
||||||
self._y = y
|
self._y = y
|
||||||
|
@ -47,6 +47,9 @@ def export_project(project, temporary_dir, include_images=False, keep_compute_id
|
|||||||
if project.is_running():
|
if project.is_running():
|
||||||
raise aiohttp.web.HTTPConflict(text="Running topology could not be exported")
|
raise aiohttp.web.HTTPConflict(text="Running topology could not be exported")
|
||||||
|
|
||||||
|
# Make sure we save the project
|
||||||
|
project.dump()
|
||||||
|
|
||||||
z = zipstream.ZipFile(allowZip64=True)
|
z = zipstream.ZipFile(allowZip64=True)
|
||||||
|
|
||||||
if not os.path.exists(project._path):
|
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 "topology" in topology:
|
||||||
if "nodes" in topology["topology"]:
|
if "nodes" in topology["topology"]:
|
||||||
for node in topology["topology"]["nodes"]:
|
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"]:
|
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"]))
|
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)
|
engine = self._get_engine(engine)
|
||||||
vms = []
|
vms = []
|
||||||
for vm in (yield from engine.list()):
|
try:
|
||||||
vms.append({"vmname": vm["vmname"]})
|
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
|
return vms
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
@ -267,6 +273,7 @@ class GNS3VM:
|
|||||||
engine.vmname = self._settings["vmname"]
|
engine.vmname = self._settings["vmname"]
|
||||||
engine.ram = self._settings["ram"]
|
engine.ram = self._settings["ram"]
|
||||||
engine.vpcus = self._settings["vcpus"]
|
engine.vpcus = self._settings["vcpus"]
|
||||||
|
engine.headless = self._settings["headless"]
|
||||||
compute = yield from self._controller.add_compute(compute_id="vm",
|
compute = yield from self._controller.add_compute(compute_id="vm",
|
||||||
name="GNS3 VM is starting ({})".format(engine.vmname),
|
name="GNS3 VM is starting ({})".format(engine.vmname),
|
||||||
host=None,
|
host=None,
|
||||||
@ -277,6 +284,7 @@ class GNS3VM:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
yield from self._controller.delete_compute("vm")
|
yield from self._controller.delete_compute("vm")
|
||||||
log.error("Can't start the GNS3 VM: {}", str(e))
|
log.error("Can't start the GNS3 VM: {}", str(e))
|
||||||
|
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname))
|
||||||
raise e
|
raise e
|
||||||
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname),
|
yield from compute.update(name="GNS3 VM ({})".format(engine.vmname),
|
||||||
protocol=self.protocol,
|
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
|
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.
|
their IP and after that match it with VirtualBox host only.
|
||||||
"""
|
"""
|
||||||
remaining_try = 240
|
remaining_try = 300
|
||||||
while remaining_try > 0:
|
while remaining_try > 0:
|
||||||
json_data = None
|
json_data = None
|
||||||
session = aiohttp.ClientSession()
|
session = aiohttp.ClientSession()
|
||||||
|
@ -52,9 +52,11 @@ class Link:
|
|||||||
return self._created
|
return self._created
|
||||||
|
|
||||||
@asyncio.coroutine
|
@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
|
Add a node to the link
|
||||||
|
|
||||||
|
:param dump: Dump project on disk
|
||||||
"""
|
"""
|
||||||
|
|
||||||
port = node.get_port(adapter_number, port_number)
|
port = node.get_port(adapter_number, port_number)
|
||||||
@ -101,7 +103,8 @@ class Link:
|
|||||||
self._created = True
|
self._created = True
|
||||||
self._project.controller.notification.emit("link.created", self.__json__())
|
self._project.controller.notification.emit("link.created", self.__json__())
|
||||||
|
|
||||||
self._project.dump()
|
if dump:
|
||||||
|
self._project.dump()
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def update_nodes(self, nodes):
|
def update_nodes(self, nodes):
|
||||||
|
@ -146,6 +146,15 @@ class Node:
|
|||||||
def properties(self, val):
|
def properties(self, val):
|
||||||
self._properties = 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
|
@property
|
||||||
def project(self):
|
def project(self):
|
||||||
return self._project
|
return self._project
|
||||||
@ -366,8 +375,12 @@ class Node:
|
|||||||
self._console_type = value
|
self._console_type = value
|
||||||
elif key == "name":
|
elif key == "name":
|
||||||
self.name = value
|
self.name = value
|
||||||
elif key in ["node_id", "project_id", "console_host"]:
|
elif key in ["node_id", "project_id", "console_host",
|
||||||
pass
|
"startup_config_content",
|
||||||
|
"private_config_content",
|
||||||
|
"startup_script"]:
|
||||||
|
if key in self._properties:
|
||||||
|
del self._properties[key]
|
||||||
else:
|
else:
|
||||||
self._properties[key] = value
|
self._properties[key] = value
|
||||||
self._list_ports()
|
self._list_ports()
|
||||||
@ -384,6 +397,17 @@ class Node:
|
|||||||
data = copy.copy(properties)
|
data = copy.copy(properties)
|
||||||
else:
|
else:
|
||||||
data = copy.copy(self._properties)
|
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
|
data["name"] = self._name
|
||||||
if self._console:
|
if self._console:
|
||||||
# console is optional for builtin nodes
|
# console is optional for builtin nodes
|
||||||
@ -585,17 +609,6 @@ class Node:
|
|||||||
return False
|
return False
|
||||||
return self.id == other.id and other.project.id == self.project.id
|
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):
|
def __json__(self, topology_dump=False):
|
||||||
"""
|
"""
|
||||||
:param topology_dump: Filter to keep only properties require for saving on disk
|
:param topology_dump: Filter to keep only properties require for saving on disk
|
||||||
@ -608,7 +621,7 @@ class Node:
|
|||||||
"name": self._name,
|
"name": self._name,
|
||||||
"console": self._console,
|
"console": self._console,
|
||||||
"console_type": self._console_type,
|
"console_type": self._console_type,
|
||||||
"properties": self._filter_properties(),
|
"properties": self._properties,
|
||||||
"label": self._label,
|
"label": self._label,
|
||||||
"x": self._x,
|
"x": self._x,
|
||||||
"y": self._y,
|
"y": self._y,
|
||||||
@ -631,7 +644,7 @@ class Node:
|
|||||||
"console_host": str(self._compute.console_host),
|
"console_host": str(self._compute.console_host),
|
||||||
"console_type": self._console_type,
|
"console_type": self._console_type,
|
||||||
"command_line": self._command_line,
|
"command_line": self._command_line,
|
||||||
"properties": self._filter_properties(),
|
"properties": self._properties,
|
||||||
"status": self._status,
|
"status": self._status,
|
||||||
"label": self._label,
|
"label": self._label,
|
||||||
"x": self._x,
|
"x": self._x,
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
# You should have received a copy of the GNU General Public License
|
# You should have received a copy of the GNU General Public License
|
||||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import aiohttp
|
||||||
|
|
||||||
from .atm_port import ATMPort
|
from .atm_port import ATMPort
|
||||||
from .frame_relay_port import FrameRelayPort
|
from .frame_relay_port import FrameRelayPort
|
||||||
from .gigabitethernet_port import GigabitEthernetPort
|
from .gigabitethernet_port import GigabitEthernetPort
|
||||||
@ -64,11 +66,14 @@ class StandardPortFactory:
|
|||||||
port_name = first_port_name
|
port_name = first_port_name
|
||||||
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
||||||
else:
|
else:
|
||||||
port_name = port_name_format.format(
|
try:
|
||||||
interface_number,
|
port_name = port_name_format.format(
|
||||||
segment_number,
|
interface_number,
|
||||||
adapter=adapter_number,
|
segment_number,
|
||||||
**cls._generate_replacement(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")
|
port = PortFactory(port_name, segment_number, adapter_number, port_number, "ethernet")
|
||||||
interface_number += 1
|
interface_number += 1
|
||||||
if port_segment_size:
|
if port_segment_size:
|
||||||
|
@ -289,7 +289,12 @@ class Project:
|
|||||||
if '{0}' in base_name or '{id}' in base_name:
|
if '{0}' in base_name or '{id}' in base_name:
|
||||||
# base name is a template, replace {0} or {id} by an unique identifier
|
# base name is a template, replace {0} or {id} by an unique identifier
|
||||||
for number in range(1, 1000000):
|
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:
|
if name not in self._allocated_node_names:
|
||||||
self._allocated_node_names.add(name)
|
self._allocated_node_names.add(name)
|
||||||
return name
|
return name
|
||||||
@ -314,10 +319,11 @@ class Project:
|
|||||||
|
|
||||||
@open_required
|
@open_required
|
||||||
@asyncio.coroutine
|
@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
|
Create a node or return an existing node
|
||||||
|
|
||||||
|
:param dump: Dump topology to disk
|
||||||
:param kwargs: See the documentation of node
|
:param kwargs: See the documentation of node
|
||||||
"""
|
"""
|
||||||
if node_id in self._nodes:
|
if node_id in self._nodes:
|
||||||
@ -349,7 +355,8 @@ class Project:
|
|||||||
yield from node.create()
|
yield from node.create()
|
||||||
self._nodes[node.id] = node
|
self._nodes[node.id] = node
|
||||||
self.controller.notification.emit("node.created", node.__json__())
|
self.controller.notification.emit("node.created", node.__json__())
|
||||||
self.dump()
|
if dump:
|
||||||
|
self.dump()
|
||||||
return node
|
return node
|
||||||
|
|
||||||
@locked_coroutine
|
@locked_coroutine
|
||||||
@ -401,17 +408,19 @@ class Project:
|
|||||||
|
|
||||||
@open_required
|
@open_required
|
||||||
@asyncio.coroutine
|
@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
|
Create an drawing or return an existing drawing
|
||||||
|
|
||||||
|
:param dump: Dump the topology to disk
|
||||||
:param kwargs: See the documentation of drawing
|
:param kwargs: See the documentation of drawing
|
||||||
"""
|
"""
|
||||||
if drawing_id not in self._drawings:
|
if drawing_id not in self._drawings:
|
||||||
drawing = Drawing(self, drawing_id=drawing_id, **kwargs)
|
drawing = Drawing(self, drawing_id=drawing_id, **kwargs)
|
||||||
self._drawings[drawing.id] = drawing
|
self._drawings[drawing.id] = drawing
|
||||||
self.controller.notification.emit("drawing.created", drawing.__json__())
|
self.controller.notification.emit("drawing.created", drawing.__json__())
|
||||||
self.dump()
|
if dump:
|
||||||
|
self.dump()
|
||||||
return drawing
|
return drawing
|
||||||
return self._drawings[drawing_id]
|
return self._drawings[drawing_id]
|
||||||
|
|
||||||
@ -435,15 +444,18 @@ class Project:
|
|||||||
|
|
||||||
@open_required
|
@open_required
|
||||||
@asyncio.coroutine
|
@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
|
Create a link. By default the link is empty
|
||||||
|
|
||||||
|
:param dump: Dump topology to disk
|
||||||
"""
|
"""
|
||||||
if link_id and link_id in self._links:
|
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)
|
link = UDPLink(self, link_id=link_id)
|
||||||
self._links[link.id] = link
|
self._links[link.id] = link
|
||||||
self.dump()
|
if dump:
|
||||||
|
self.dump()
|
||||||
return link
|
return link
|
||||||
|
|
||||||
@open_required
|
@open_required
|
||||||
@ -526,7 +538,7 @@ class Project:
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def close(self, ignore_notification=False):
|
def close(self, ignore_notification=False):
|
||||||
yield from self.stop_all()
|
yield from self.stop_all()
|
||||||
for compute in self._project_created_on_compute:
|
for compute in list(self._project_created_on_compute):
|
||||||
try:
|
try:
|
||||||
yield from compute.post("/projects/{}/close".format(self._id), dont_connect=True)
|
yield from compute.post("/projects/{}/close".format(self._id), dont_connect=True)
|
||||||
# We don't care if a compute is down at this step
|
# 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"))
|
compute = self.controller.get_compute(node.pop("compute_id"))
|
||||||
name = node.pop("name")
|
name = node.pop("name")
|
||||||
node_id = node.pop("node_id")
|
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", []):
|
for link_data in topology.get("links", []):
|
||||||
link = yield from self.add_link(link_id=link_data["link_id"])
|
link = yield from self.add_link(link_id=link_data["link_id"])
|
||||||
for node_link in link_data["nodes"]:
|
for node_link in link_data["nodes"]:
|
||||||
node = self.get_node(node_link["node_id"])
|
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", []):
|
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
|
# We catch all error to be able to rollback the .gns3 to the previous state
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
for compute in self._project_created_on_compute:
|
for compute in self._project_created_on_compute:
|
||||||
|
@ -20,6 +20,7 @@ import os
|
|||||||
import uuid
|
import uuid
|
||||||
import shutil
|
import shutil
|
||||||
import asyncio
|
import asyncio
|
||||||
|
import aiohttp.web
|
||||||
from datetime import datetime, timezone
|
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
|
# We don't send close notif to clients because the close / open dance is purely internal
|
||||||
yield from self._project.close(ignore_notification=True)
|
yield from self._project.close(ignore_notification=True)
|
||||||
self._project.controller.notification.emit("snapshot.restored", self.__json__())
|
self._project.controller.notification.emit("snapshot.restored", self.__json__())
|
||||||
if os.path.exists(os.path.join(self._project.path, "project-files")):
|
try:
|
||||||
shutil.rmtree(os.path.join(self._project.path, "project-files"))
|
if os.path.exists(os.path.join(self._project.path, "project-files")):
|
||||||
with open(self._path, "rb") as f:
|
shutil.rmtree(os.path.join(self._project.path, "project-files"))
|
||||||
project = yield from import_project(self._project.controller, self._project.id, f, location=self._project.path)
|
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()
|
yield from project.open()
|
||||||
return project
|
return project
|
||||||
|
|
||||||
|
@ -39,16 +39,17 @@ class Symbols:
|
|||||||
def list(self):
|
def list(self):
|
||||||
self._symbols_path = {}
|
self._symbols_path = {}
|
||||||
symbols = []
|
symbols = []
|
||||||
for file in os.listdir(get_resource("symbols")):
|
if get_resource("symbols"):
|
||||||
if file.startswith('.'):
|
for file in os.listdir(get_resource("symbols")):
|
||||||
continue
|
if file.startswith('.'):
|
||||||
symbol_id = ':/symbols/' + file
|
continue
|
||||||
symbols.append({
|
symbol_id = ':/symbols/' + file
|
||||||
'symbol_id': symbol_id,
|
symbols.append({
|
||||||
'filename': file,
|
'symbol_id': symbol_id,
|
||||||
'builtin': True,
|
'filename': file,
|
||||||
})
|
'builtin': True,
|
||||||
self._symbols_path[symbol_id] = os.path.join(get_resource("symbols"), file)
|
})
|
||||||
|
self._symbols_path[symbol_id] = os.path.join(get_resource("symbols"), file)
|
||||||
directory = self.symbols_path()
|
directory = self.symbols_path()
|
||||||
if directory:
|
if directory:
|
||||||
for file in os.listdir(directory):
|
for file in os.listdir(directory):
|
||||||
|
@ -23,7 +23,6 @@ import glob
|
|||||||
import shutil
|
import shutil
|
||||||
import zipfile
|
import zipfile
|
||||||
import aiohttp
|
import aiohttp
|
||||||
import platform
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
|
|
||||||
@ -37,7 +36,7 @@ import logging
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
GNS3_FILE_FORMAT_REVISION = 7
|
GNS3_FILE_FORMAT_REVISION = 8
|
||||||
|
|
||||||
|
|
||||||
def _check_topology_schema(topo):
|
def _check_topology_schema(topo):
|
||||||
@ -117,34 +116,65 @@ def load_topology(path):
|
|||||||
topo = json.load(f)
|
topo = json.load(f)
|
||||||
except (OSError, UnicodeDecodeError, ValueError) as e:
|
except (OSError, UnicodeDecodeError, ValueError) as e:
|
||||||
raise aiohttp.web.HTTPConflict(text="Could not load topology {}: {}".format(path, str(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
|
# If it's an old GNS3 file we need to convert it
|
||||||
# first we backup the file
|
# first we backup the file
|
||||||
shutil.copy(path, path + ".backup{}".format(topo.get("revision", 0)))
|
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)
|
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
|
# Version before GNS3 2.0 alpha 4
|
||||||
if topo["revision"] < 6:
|
if topo["revision"] < 6:
|
||||||
shutil.copy(path, path + ".backup{}".format(topo.get("revision", 0)))
|
|
||||||
topo = _convert_2_0_0_alpha(topo, path)
|
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
|
# Version before GNS3 2.0 beta 3
|
||||||
if topo["revision"] < 7:
|
if topo["revision"] < 7:
|
||||||
shutil.copy(path, path + ".backup{}".format(topo.get("revision", 0)))
|
|
||||||
topo = _convert_2_0_0_beta_2(topo, path)
|
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:
|
with open(path, "w+", encoding="utf-8") as f:
|
||||||
json.dump(topo, f, indent=4, sort_keys=True)
|
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"]))
|
def _convert_2_0_0(topo, topo_path):
|
||||||
_check_topology_schema(topo)
|
"""
|
||||||
|
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
|
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")
|
dynamips_dir = os.path.join(topo_dir, "project-files", "dynamips")
|
||||||
node_dir = os.path.join(dynamips_dir, node_id)
|
node_dir = os.path.join(dynamips_dir, node_id)
|
||||||
os.makedirs(os.path.join(node_dir, "configs"), exist_ok=True)
|
try:
|
||||||
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))):
|
os.makedirs(os.path.join(node_dir, "configs"), exist_ok=True)
|
||||||
shutil.move(path, os.path.join(node_dir, os.path.basename(path)))
|
for path in glob.glob(os.path.join(glob.escape(dynamips_dir), "*_i{}_*".format(dynamips_id))):
|
||||||
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, os.path.basename(path)))
|
||||||
shutil.move(path, os.path.join(node_dir, "configs", 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
|
return topo
|
||||||
|
|
||||||
|
|
||||||
@ -320,14 +353,24 @@ def _convert_1_3_later(topo, topo_path):
|
|||||||
node["properties"]["ram"] = PLATFORMS_DEFAULT_RAM[old_node["type"].lower()]
|
node["properties"]["ram"] = PLATFORMS_DEFAULT_RAM[old_node["type"].lower()]
|
||||||
elif old_node["type"] == "VMwareVM":
|
elif old_node["type"] == "VMwareVM":
|
||||||
node["node_type"] = "vmware"
|
node["node_type"] = "vmware"
|
||||||
|
node["properties"]["linked_clone"] = old_node.get("linked_clone", False)
|
||||||
if node["symbol"] is None:
|
if node["symbol"] is None:
|
||||||
node["symbol"] = ":/symbols/vmware_guest.svg"
|
node["symbol"] = ":/symbols/vmware_guest.svg"
|
||||||
elif old_node["type"] == "VirtualBoxVM":
|
elif old_node["type"] == "VirtualBoxVM":
|
||||||
node["node_type"] = "virtualbox"
|
node["node_type"] = "virtualbox"
|
||||||
|
node["properties"]["linked_clone"] = old_node.get("linked_clone", False)
|
||||||
if node["symbol"] is None:
|
if node["symbol"] is None:
|
||||||
node["symbol"] = ":/symbols/vbox_guest.svg"
|
node["symbol"] = ":/symbols/vbox_guest.svg"
|
||||||
elif old_node["type"] == "IOUDevice":
|
elif old_node["type"] == "IOUDevice":
|
||||||
node["node_type"] = "iou"
|
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":
|
elif old_node["type"] == "Cloud":
|
||||||
old_node["ports"] = _create_cloud(node, old_node, ":/symbols/cloud.svg")
|
old_node["ports"] = _create_cloud(node, old_node, ":/symbols/cloud.svg")
|
||||||
elif old_node["type"] == "Host":
|
elif old_node["type"] == "Host":
|
||||||
|
@ -18,6 +18,7 @@
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import struct
|
import struct
|
||||||
|
import aiohttp
|
||||||
import platform
|
import platform
|
||||||
|
|
||||||
|
|
||||||
@ -53,7 +54,7 @@ class CrashReport:
|
|||||||
Report crash to a third party service
|
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"):
|
if hasattr(sys, "frozen"):
|
||||||
cacert = get_resource("cacert.pem")
|
cacert = get_resource("cacert.pem")
|
||||||
if cacert is not None and os.path.isfile(cacert):
|
if cacert is not None and os.path.isfile(cacert):
|
||||||
@ -94,6 +95,7 @@ class CrashReport:
|
|||||||
"os:win_32": " ".join(platform.win32_ver()),
|
"os:win_32": " ".join(platform.win32_ver()),
|
||||||
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
|
"os:mac": "{} {}".format(platform.mac_ver()[0], platform.mac_ver()[2]),
|
||||||
"os:linux": " ".join(platform.linux_distribution()),
|
"os:linux": " ".join(platform.linux_distribution()),
|
||||||
|
"aiohttp:version": aiohttp.__version__,
|
||||||
"python:version": "{}.{}.{}".format(sys.version_info[0],
|
"python:version": "{}.{}.{}".format(sys.version_info[0],
|
||||||
sys.version_info[1],
|
sys.version_info[1],
|
||||||
sys.version_info[2]),
|
sys.version_info[2]),
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import base64
|
|
||||||
|
|
||||||
from gns3server.web.route import Route
|
from gns3server.web.route import Route
|
||||||
from gns3server.schemas.nio import NIO_SCHEMA
|
from gns3server.schemas.nio import NIO_SCHEMA
|
||||||
@ -78,7 +77,6 @@ class DynamipsVMHandler:
|
|||||||
aux=request.json.get("aux"),
|
aux=request.json.get("aux"),
|
||||||
chassis=request.json.pop("chassis", default_chassis),
|
chassis=request.json.pop("chassis", default_chassis),
|
||||||
node_type="dynamips")
|
node_type="dynamips")
|
||||||
|
|
||||||
yield from dynamips_manager.update_vm_settings(vm, request.json)
|
yield from dynamips_manager.update_vm_settings(vm, request.json)
|
||||||
response.set_status(201)
|
response.set_status(201)
|
||||||
response.json(vm)
|
response.json(vm)
|
||||||
|
@ -30,8 +30,7 @@ from gns3server.schemas.node import (
|
|||||||
from gns3server.schemas.iou import (
|
from gns3server.schemas.iou import (
|
||||||
IOU_CREATE_SCHEMA,
|
IOU_CREATE_SCHEMA,
|
||||||
IOU_START_SCHEMA,
|
IOU_START_SCHEMA,
|
||||||
IOU_OBJECT_SCHEMA,
|
IOU_OBJECT_SCHEMA
|
||||||
IOU_CONFIGS_SCHEMA,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -344,10 +344,7 @@ class NodeHandler:
|
|||||||
raise aiohttp.web.HTTPForbidden
|
raise aiohttp.web.HTTPForbidden
|
||||||
|
|
||||||
node_type = node.node_type
|
node_type = node.node_type
|
||||||
if node_type == "dynamips":
|
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||||
path = "/project-files/{}/{}".format(node_type, path)
|
|
||||||
else:
|
|
||||||
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)
|
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)
|
response.set_status(200)
|
||||||
@ -384,12 +381,9 @@ class NodeHandler:
|
|||||||
raise aiohttp.web.HTTPForbidden
|
raise aiohttp.web.HTTPForbidden
|
||||||
|
|
||||||
node_type = node.node_type
|
node_type = node.node_type
|
||||||
if node_type == "dynamips":
|
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
||||||
path = "/project-files/{}/{}".format(node_type, path)
|
|
||||||
else:
|
|
||||||
path = "/project-files/{}/{}/{}".format(node_type, node.id, path)
|
|
||||||
|
|
||||||
data = yield from request.content.read()
|
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)
|
response.set_status(201)
|
||||||
|
@ -114,7 +114,10 @@ class ServerHandler:
|
|||||||
def write_settings(request, response):
|
def write_settings(request, response):
|
||||||
controller = Controller.instance()
|
controller = Controller.instance()
|
||||||
controller.settings = request.json
|
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.json(controller.settings)
|
||||||
response.set_status(201)
|
response.set_status(201)
|
||||||
|
|
||||||
|
@ -62,18 +62,10 @@ VM_CREATE_SCHEMA = {
|
|||||||
"type": ["string", "null"],
|
"type": ["string", "null"],
|
||||||
"minLength": 1,
|
"minLength": 1,
|
||||||
},
|
},
|
||||||
"startup_config": {
|
|
||||||
"description": "Path to the IOS startup configuration file",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"startup_config_content": {
|
"startup_config_content": {
|
||||||
"description": "Content of IOS startup configuration file",
|
"description": "Content of IOS startup configuration file",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"private_config": {
|
|
||||||
"description": "Path to the IOS private configuration file",
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
"private_config_content": {
|
"private_config_content": {
|
||||||
"description": "Content of IOS private configuration file",
|
"description": "Content of IOS private configuration file",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -296,22 +288,6 @@ VM_UPDATE_SCHEMA = {
|
|||||||
"description": "Dynamips ID",
|
"description": "Dynamips ID",
|
||||||
"type": "integer"
|
"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": {
|
"ram": {
|
||||||
"description": "Amount of RAM in MB",
|
"description": "Amount of RAM in MB",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -552,14 +528,6 @@ VM_OBJECT_SCHEMA = {
|
|||||||
"type": ["string", "null"],
|
"type": ["string", "null"],
|
||||||
"minLength": 1,
|
"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": {
|
"ram": {
|
||||||
"description": "Amount of RAM in MB",
|
"description": "Amount of RAM in MB",
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -706,14 +674,6 @@ VM_OBJECT_SCHEMA = {
|
|||||||
{"type": "null"}
|
{"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
|
# C7200 properties
|
||||||
"npe": {
|
"npe": {
|
||||||
"description": "NPE model",
|
"description": "NPE model",
|
||||||
|
@ -78,14 +78,6 @@ IOU_CREATE_SCHEMA = {
|
|||||||
"description": "Use default IOU values",
|
"description": "Use default IOU values",
|
||||||
"type": ["boolean", "null"]
|
"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": {
|
"startup_config_content": {
|
||||||
"description": "Startup-config of IOU",
|
"description": "Startup-config of IOU",
|
||||||
"type": ["string", "null"]
|
"type": ["string", "null"]
|
||||||
@ -94,10 +86,6 @@ IOU_CREATE_SCHEMA = {
|
|||||||
"description": "Private-config of IOU",
|
"description": "Private-config of IOU",
|
||||||
"type": ["string", "null"]
|
"type": ["string", "null"]
|
||||||
},
|
},
|
||||||
"iourc_content": {
|
|
||||||
"description": "Content of the iourc file. Ignored if Null",
|
|
||||||
"type": ["string", "null"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
"required": ["name", "path"]
|
"required": ["name", "path"]
|
||||||
@ -187,30 +175,10 @@ IOU_OBJECT_SCHEMA = {
|
|||||||
"description": "Always up ethernet interface",
|
"description": "Always up ethernet interface",
|
||||||
"type": "boolean"
|
"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": {
|
"use_default_iou_values": {
|
||||||
"description": "Use default IOU values",
|
"description": "Use default IOU values",
|
||||||
"type": ["boolean", "null"]
|
"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": {
|
"command_line": {
|
||||||
"description": "Last command line used by GNS3 to start QEMU",
|
"description": "Last command line used by GNS3 to start QEMU",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
@ -218,23 +186,3 @@ IOU_OBJECT_SCHEMA = {
|
|||||||
},
|
},
|
||||||
"additionalProperties": False
|
"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",
|
"description": "Number of adapters",
|
||||||
"type": ["integer", "null"],
|
"type": ["integer", "null"],
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"maximum": 32,
|
"maximum": 275,
|
||||||
},
|
},
|
||||||
"adapter_type": {
|
"adapter_type": {
|
||||||
"description": "QEMU adapter type",
|
"description": "QEMU adapter type",
|
||||||
@ -332,7 +332,7 @@ QEMU_UPDATE_SCHEMA = {
|
|||||||
"description": "Number of adapters",
|
"description": "Number of adapters",
|
||||||
"type": ["integer", "null"],
|
"type": ["integer", "null"],
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"maximum": 32,
|
"maximum": 275,
|
||||||
},
|
},
|
||||||
"adapter_type": {
|
"adapter_type": {
|
||||||
"description": "QEMU adapter type",
|
"description": "QEMU adapter type",
|
||||||
@ -520,7 +520,7 @@ QEMU_OBJECT_SCHEMA = {
|
|||||||
"description": "Number of adapters",
|
"description": "Number of adapters",
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"maximum": 32,
|
"maximum": 275,
|
||||||
},
|
},
|
||||||
"adapter_type": {
|
"adapter_type": {
|
||||||
"description": "QEMU adapter type",
|
"description": "QEMU adapter type",
|
||||||
|
@ -50,10 +50,6 @@ VPCS_CREATE_SCHEMA = {
|
|||||||
"description": "Content of the VPCS startup script",
|
"description": "Content of the VPCS startup script",
|
||||||
"type": ["string", "null"]
|
"type": ["string", "null"]
|
||||||
},
|
},
|
||||||
"startup_script_path": {
|
|
||||||
"description": "Path of the VPCS startup script relative to project directory (IGNORED)",
|
|
||||||
"type": ["string", "null"]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"additionalProperties": False,
|
||||||
"required": ["name"]
|
"required": ["name"]
|
||||||
@ -79,14 +75,6 @@ VPCS_UPDATE_SCHEMA = {
|
|||||||
"description": "Console type",
|
"description": "Console type",
|
||||||
"enum": ["telnet"]
|
"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,
|
"additionalProperties": False,
|
||||||
}
|
}
|
||||||
@ -133,19 +121,11 @@ VPCS_OBJECT_SCHEMA = {
|
|||||||
"maxLength": 36,
|
"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}$"
|
"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": {
|
"command_line": {
|
||||||
"description": "Last command line used by GNS3 to start QEMU",
|
"description": "Last command line used by GNS3 to start QEMU",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"additionalProperties": False,
|
"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):
|
def __init__(self):
|
||||||
self._output = asyncio.StreamReader()
|
self._output = asyncio.StreamReader()
|
||||||
|
self._closed = False
|
||||||
self.transport = None
|
self.transport = None
|
||||||
|
|
||||||
def read(self, n=-1):
|
def read(self, n=-1):
|
||||||
@ -54,9 +55,11 @@ class SerialReaderWriterProtocol(asyncio.Protocol):
|
|||||||
self.transport = transport
|
self.transport = transport
|
||||||
|
|
||||||
def data_received(self, data):
|
def data_received(self, data):
|
||||||
self._output.feed_data(data)
|
if not self._closed:
|
||||||
|
self._output.feed_data(data)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
self._closed = True
|
||||||
self._output.feed_eof()
|
self._output.feed_eof()
|
||||||
|
|
||||||
|
|
||||||
@ -122,7 +125,10 @@ def _asyncio_open_serial_unix(path):
|
|||||||
raise NodeError('Pipe file "{}" is missing'.format(path))
|
raise NodeError('Pipe file "{}" is missing'.format(path))
|
||||||
|
|
||||||
output = SerialReaderWriterProtocol()
|
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
|
return output
|
||||||
|
|
||||||
|
|
||||||
|
@ -120,7 +120,8 @@ def _svg_convert_size(size):
|
|||||||
"pc": 15,
|
"pc": 15,
|
||||||
"mm": 3.543307,
|
"mm": 3.543307,
|
||||||
"cm": 35.43307,
|
"cm": 35.43307,
|
||||||
"in": 90
|
"in": 90,
|
||||||
|
"px": 1
|
||||||
}
|
}
|
||||||
if len(size) > 3:
|
if len(size) > 3:
|
||||||
if size[-2:] in conversion_table:
|
if size[-2:] in conversion_table:
|
||||||
|
@ -25,3 +25,14 @@
|
|||||||
|
|
||||||
__version__ = "2.1.0dev1"
|
__version__ = "2.1.0dev1"
|
||||||
__version_info__ = (2, 1, 0, -99)
|
__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._route = route
|
||||||
self._output_schema = output_schema
|
self._output_schema = output_schema
|
||||||
self._request = request
|
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['X-Route'] = self._route
|
||||||
headers['Server'] = "Python/{0[0]}.{0[1]} GNS3/{1}".format(sys.version_info, __version__)
|
headers['Server'] = "Python/{0[0]}.{0[1]} GNS3/{1}".format(sys.version_info, __version__)
|
||||||
super().__init__(headers=headers, **kwargs)
|
super().__init__(headers=headers, **kwargs)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
jsonschema>=2.4.0
|
jsonschema>=2.4.0
|
||||||
aiohttp>=1.2.0
|
aiohttp>=1.3.0,<=1.4.0
|
||||||
aiohttp_cors>=0.4.0
|
aiohttp_cors>=0.4.0
|
||||||
yarl>=0.8.1
|
yarl>=0.9.8
|
||||||
typing>=3.5.3.0 # Otherwise yarl fail with python 3.4
|
typing>=3.5.3.0 # Otherwise yarl fail with python 3.4
|
||||||
Jinja2>=2.7.3
|
Jinja2>=2.7.3
|
||||||
raven>=5.23.0
|
raven>=5.23.0
|
||||||
|
23
scripts/docker_dev_server.sh
Normal file
23
scripts/docker_dev_server.sh
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# Copyright (C) 2016 GNS3 Technologies Inc.
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
# A docker server use for localy test a remote GNS3 server
|
||||||
|
|
||||||
|
docker build -t gns3-server .
|
||||||
|
docker run -i -h gns3vm -p 8001:8001/tcp -t gns3-server python3 -m gns3server --local --port 8001
|
||||||
|
|
||||||
|
|
@ -298,6 +298,14 @@ def test_change_name(vm, tmpdir):
|
|||||||
assert vm.name == "hello"
|
assert vm.name == "hello"
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
assert f.read() == "hostname hello"
|
assert f.read() == "hostname hello"
|
||||||
|
# support hostname not sync
|
||||||
|
vm.name = "alpha"
|
||||||
|
with open(path, 'w+') as f:
|
||||||
|
f.write("no service password-encryption\nhostname beta\nno ip icmp rate-limit unreachable")
|
||||||
|
vm.name = "charlie"
|
||||||
|
assert vm.name == "charlie"
|
||||||
|
with open(path) as f:
|
||||||
|
assert f.read() == "no service password-encryption\nhostname charlie\nno ip icmp rate-limit unreachable"
|
||||||
|
|
||||||
|
|
||||||
def test_library_check(loop, vm):
|
def test_library_check(loop, vm):
|
||||||
|
@ -588,7 +588,7 @@ def test_build_command_two_adapters_mac_address(vm, loop, fake_qemu_binary, port
|
|||||||
vm.adapters = 2
|
vm.adapters = 2
|
||||||
vm.mac_address = "00:00:ab:0e:0f:09"
|
vm.mac_address = "00:00:ab:0e:0f:09"
|
||||||
mac_0 = vm._mac_address
|
mac_0 = vm._mac_address
|
||||||
mac_1 = int_to_macaddress(macaddress_to_int(vm._mac_address))
|
mac_1 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 1)
|
||||||
assert mac_0[:8] == "00:00:ab"
|
assert mac_0[:8] == "00:00:ab"
|
||||||
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
||||||
cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
|
cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
|
||||||
@ -605,7 +605,49 @@ def test_build_command_two_adapters_mac_address(vm, loop, fake_qemu_binary, port
|
|||||||
assert "e1000,mac={}".format(mac_1) in cmd
|
assert "e1000,mac={}".format(mac_1) in cmd
|
||||||
|
|
||||||
|
|
||||||
|
def test_build_command_large_number_of_adapters(vm, loop, fake_qemu_binary, port_manager):
|
||||||
|
"""
|
||||||
|
When we have more than 28 interface we need to add a pci bridge for
|
||||||
|
additionnal interfaces
|
||||||
|
"""
|
||||||
|
|
||||||
|
# It's supported only with Qemu 2.4 and later
|
||||||
|
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.4.0")
|
||||||
|
|
||||||
|
vm.adapters = 100
|
||||||
|
vm.mac_address = "00:00:ab:0e:0f:09"
|
||||||
|
mac_0 = vm._mac_address
|
||||||
|
mac_1 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 1)
|
||||||
|
assert mac_0[:8] == "00:00:ab"
|
||||||
|
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
||||||
|
cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
|
||||||
|
|
||||||
|
assert "e1000,mac={}".format(mac_0) in cmd
|
||||||
|
assert "e1000,mac={}".format(mac_1) in cmd
|
||||||
|
assert "pci-bridge,id=pci-bridge0,bus=dmi_pci_bridge0,chassis_nr=0x1,addr=0x0,shpc=off" not in cmd
|
||||||
|
assert "pci-bridge,id=pci-bridge1,bus=dmi_pci_bridge1,chassis_nr=0x1,addr=0x1,shpc=off" in cmd
|
||||||
|
assert "pci-bridge,id=pci-bridge2,bus=dmi_pci_bridge2,chassis_nr=0x1,addr=0x2,shpc=off" in cmd
|
||||||
|
assert "i82801b11-bridge,id=dmi_pci_bridge1" in cmd
|
||||||
|
|
||||||
|
mac_29 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 29)
|
||||||
|
assert "e1000,mac={},bus=pci-bridge1,addr=0x04".format(mac_29) in cmd
|
||||||
|
mac_30 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 30)
|
||||||
|
assert "e1000,mac={},bus=pci-bridge1,addr=0x05".format(mac_30) in cmd
|
||||||
|
mac_74 = int_to_macaddress(macaddress_to_int(vm._mac_address) + 74)
|
||||||
|
assert "e1000,mac={},bus=pci-bridge2,addr=0x11".format(mac_74) in cmd
|
||||||
|
|
||||||
|
# Qemu < 2.4 doesn't support large number of adapters
|
||||||
|
vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.0.0")
|
||||||
|
with pytest.raises(QemuError):
|
||||||
|
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
||||||
|
cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
|
||||||
|
vm.adapters = 5
|
||||||
|
with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()) as process:
|
||||||
|
cmd = loop.run_until_complete(asyncio.async(vm._build_command()))
|
||||||
|
|
||||||
# Windows accept this kind of mistake
|
# Windows accept this kind of mistake
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
|
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
|
||||||
def test_build_command_with_invalid_options(vm, loop, fake_qemu_binary):
|
def test_build_command_with_invalid_options(vm, loop, fake_qemu_binary):
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ def test_vm_invalid_vpcs_version(loop, manager, vm):
|
|||||||
|
|
||||||
|
|
||||||
def test_vm_invalid_vpcs_path(vm, manager, loop):
|
def test_vm_invalid_vpcs_path(vm, manager, loop):
|
||||||
with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.vpcs_path", return_value="/tmp/fake/path/vpcs"):
|
with patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM._vpcs_path", return_value="/tmp/fake/path/vpcs"):
|
||||||
with pytest.raises(VPCSError):
|
with pytest.raises(VPCSError):
|
||||||
nio = manager.create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
|
nio = manager.create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
|
||||||
vm.port_add_nio_binding(0, nio)
|
vm.port_add_nio_binding(0, nio)
|
||||||
@ -97,7 +97,7 @@ def test_start(loop, vm, async_run):
|
|||||||
nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
|
nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
|
||||||
async_run(vm.port_add_nio_binding(0, nio))
|
async_run(vm.port_add_nio_binding(0, nio))
|
||||||
loop.run_until_complete(asyncio.async(vm.start()))
|
loop.run_until_complete(asyncio.async(vm.start()))
|
||||||
assert mock_exec.call_args[0] == (vm.vpcs_path,
|
assert mock_exec.call_args[0] == (vm._vpcs_path(),
|
||||||
'-p',
|
'-p',
|
||||||
str(vm._internal_console_port),
|
str(vm._internal_console_port),
|
||||||
'-m', '1',
|
'-m', '1',
|
||||||
@ -133,7 +133,7 @@ def test_start_0_6_1(loop, vm, async_run):
|
|||||||
nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
|
nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"})
|
||||||
async_run(vm.port_add_nio_binding(0, nio))
|
async_run(vm.port_add_nio_binding(0, nio))
|
||||||
async_run(vm.start())
|
async_run(vm.start())
|
||||||
assert mock_exec.call_args[0] == (vm.vpcs_path,
|
assert mock_exec.call_args[0] == (vm._vpcs_path(),
|
||||||
'-p',
|
'-p',
|
||||||
str(vm._internal_console_port),
|
str(vm._internal_console_port),
|
||||||
'-m', '1',
|
'-m', '1',
|
||||||
@ -243,12 +243,12 @@ def test_update_startup_script(vm):
|
|||||||
|
|
||||||
|
|
||||||
def test_update_startup_script_h(vm):
|
def test_update_startup_script_h(vm):
|
||||||
content = "setname %h\n"
|
content = "set pcname %h\n"
|
||||||
vm.name = "pc1"
|
vm.name = "pc1"
|
||||||
vm.startup_script = content
|
vm.startup_script = content
|
||||||
assert os.path.exists(vm.script_file)
|
assert os.path.exists(vm.script_file)
|
||||||
with open(vm.script_file) as f:
|
with open(vm.script_file) as f:
|
||||||
assert f.read() == "setname pc1\n"
|
assert f.read() == "set pcname pc1\n"
|
||||||
|
|
||||||
|
|
||||||
def test_get_startup_script(vm):
|
def test_get_startup_script(vm):
|
||||||
@ -275,11 +275,18 @@ def test_change_name(vm, tmpdir):
|
|||||||
path = os.path.join(vm.working_dir, 'startup.vpc')
|
path = os.path.join(vm.working_dir, 'startup.vpc')
|
||||||
vm.name = "world"
|
vm.name = "world"
|
||||||
with open(path, 'w+') as f:
|
with open(path, 'w+') as f:
|
||||||
f.write("name world")
|
f.write("set pcname world")
|
||||||
vm.name = "hello"
|
vm.name = "hello"
|
||||||
assert vm.name == "hello"
|
assert vm.name == "hello"
|
||||||
with open(path) as f:
|
with open(path) as f:
|
||||||
assert f.read() == "name hello"
|
assert f.read() == "set pcname hello"
|
||||||
|
# Support when the name is not sync with config
|
||||||
|
with open(path, 'w+') as f:
|
||||||
|
f.write("set pcname alpha")
|
||||||
|
vm.name = "beta"
|
||||||
|
assert vm.name == "beta"
|
||||||
|
with open(path) as f:
|
||||||
|
assert f.read() == "set pcname beta"
|
||||||
|
|
||||||
|
|
||||||
def test_close(vm, port_manager, loop):
|
def test_close(vm, port_manager, loop):
|
||||||
|
@ -39,7 +39,7 @@ def test_save(controller, controller_config_path):
|
|||||||
assert data["gns3vm"] == controller.gns3vm.__json__()
|
assert data["gns3vm"] == controller.gns3vm.__json__()
|
||||||
|
|
||||||
|
|
||||||
def test_load(controller, controller_config_path, async_run):
|
def test_load_controller_settings(controller, controller_config_path, async_run):
|
||||||
controller.save()
|
controller.save()
|
||||||
with open(controller_config_path) as f:
|
with open(controller_config_path) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
@ -57,7 +57,7 @@ def test_load(controller, controller_config_path, async_run):
|
|||||||
data["gns3vm"] = {"vmname": "Test VM"}
|
data["gns3vm"] = {"vmname": "Test VM"}
|
||||||
with open(controller_config_path, "w+") as f:
|
with open(controller_config_path, "w+") as f:
|
||||||
json.dump(data, f)
|
json.dump(data, f)
|
||||||
async_run(controller.load())
|
async_run(controller._load_controller_settings())
|
||||||
assert controller.settings["IOU"]
|
assert controller.settings["IOU"]
|
||||||
assert controller.computes["test1"].__json__() == {
|
assert controller.computes["test1"].__json__() == {
|
||||||
"compute_id": "test1",
|
"compute_id": "test1",
|
||||||
@ -101,7 +101,7 @@ def test_import_computes_1_x(controller, controller_config_path, async_run):
|
|||||||
with open(os.path.join(config_dir, "gns3_gui.conf"), "w+") as f:
|
with open(os.path.join(config_dir, "gns3_gui.conf"), "w+") as f:
|
||||||
json.dump(gns3_gui_conf, f)
|
json.dump(gns3_gui_conf, f)
|
||||||
|
|
||||||
async_run(controller.load())
|
async_run(controller._load_controller_settings())
|
||||||
for compute in controller.computes.values():
|
for compute in controller.computes.values():
|
||||||
if compute.id != "local":
|
if compute.id != "local":
|
||||||
assert len(compute.id) == 36
|
assert len(compute.id) == 36
|
||||||
@ -143,7 +143,7 @@ def test_import_gns3vm_1_x(controller, controller_config_path, async_run):
|
|||||||
json.dump(gns3_gui_conf, f)
|
json.dump(gns3_gui_conf, f)
|
||||||
|
|
||||||
controller.gns3vm.settings["engine"] = None
|
controller.gns3vm.settings["engine"] = None
|
||||||
async_run(controller.load())
|
async_run(controller._load_controller_settings())
|
||||||
assert controller.gns3vm.settings["engine"] == "vmware"
|
assert controller.gns3vm.settings["engine"] == "vmware"
|
||||||
assert controller.gns3vm.settings["enable"]
|
assert controller.gns3vm.settings["enable"]
|
||||||
assert controller.gns3vm.settings["headless"]
|
assert controller.gns3vm.settings["headless"]
|
||||||
@ -199,7 +199,7 @@ def test_import_remote_gns3vm_1_x(controller, controller_config_path, async_run)
|
|||||||
json.dump(gns3_gui_conf, f)
|
json.dump(gns3_gui_conf, f)
|
||||||
|
|
||||||
with asyncio_patch("gns3server.controller.compute.Compute.connect"):
|
with asyncio_patch("gns3server.controller.compute.Compute.connect"):
|
||||||
async_run(controller.load())
|
async_run(controller._load_controller_settings())
|
||||||
assert controller.gns3vm.settings["engine"] == "remote"
|
assert controller.gns3vm.settings["engine"] == "remote"
|
||||||
assert controller.gns3vm.settings["vmname"] == "http://127.0.0.1:3081"
|
assert controller.gns3vm.settings["vmname"] == "http://127.0.0.1:3081"
|
||||||
|
|
||||||
@ -466,3 +466,17 @@ def test_get_free_project_name(controller, async_run):
|
|||||||
|
|
||||||
def test_appliance_templates(controller):
|
def test_appliance_templates(controller):
|
||||||
assert len(controller.appliance_templates) > 0
|
assert len(controller.appliance_templates) > 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_load_base_files(controller, config, tmpdir):
|
||||||
|
config.set_section_config("Server", {"configs_path": str(tmpdir)})
|
||||||
|
|
||||||
|
with open(str(tmpdir / 'iou_l2_base_startup-config.txt'), 'w+') as f:
|
||||||
|
f.write('test')
|
||||||
|
|
||||||
|
controller.load_base_files()
|
||||||
|
assert os.path.exists(str(tmpdir / 'iou_l3_base_startup-config.txt'))
|
||||||
|
|
||||||
|
# Check is the file has not been overwrite
|
||||||
|
with open(str(tmpdir / 'iou_l2_base_startup-config.txt')) as f:
|
||||||
|
assert f.read() == 'test'
|
||||||
|
@ -33,7 +33,9 @@ from gns3server.controller.export_project import export_project, _filter_files
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def project(controller):
|
def project(controller):
|
||||||
return Project(controller=controller, name="Test")
|
p = Project(controller=controller, name="Test")
|
||||||
|
p.dump = MagicMock()
|
||||||
|
return p
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@ -190,9 +192,9 @@ def test_export_disallow_some_type(tmpdir, project, async_run):
|
|||||||
topology = {
|
topology = {
|
||||||
"topology": {
|
"topology": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
{
|
||||||
"node_type": "virtualbox"
|
"node_type": "cloud"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,6 +204,24 @@ def test_export_disallow_some_type(tmpdir, project, async_run):
|
|||||||
|
|
||||||
with pytest.raises(aiohttp.web.HTTPConflict):
|
with pytest.raises(aiohttp.web.HTTPConflict):
|
||||||
z = async_run(export_project(project, str(tmpdir)))
|
z = async_run(export_project(project, str(tmpdir)))
|
||||||
|
z = async_run(export_project(project, str(tmpdir), allow_all_nodes=True))
|
||||||
|
|
||||||
|
# VirtualBox is always disallowed
|
||||||
|
topology = {
|
||||||
|
"topology": {
|
||||||
|
"nodes": [
|
||||||
|
{
|
||||||
|
"node_type": "virtualbox",
|
||||||
|
"properties": {
|
||||||
|
"linked_clone": True
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
with open(os.path.join(path, "test.gns3"), 'w+') as f:
|
||||||
|
json.dump(topology, f)
|
||||||
|
with pytest.raises(aiohttp.web.HTTPConflict):
|
||||||
z = async_run(export_project(project, str(tmpdir), allow_all_nodes=True))
|
z = async_run(export_project(project, str(tmpdir), allow_all_nodes=True))
|
||||||
|
|
||||||
|
|
||||||
@ -215,18 +235,18 @@ def test_export_fix_path(tmpdir, project, async_run):
|
|||||||
topology = {
|
topology = {
|
||||||
"topology": {
|
"topology": {
|
||||||
"nodes": [
|
"nodes": [
|
||||||
{
|
|
||||||
"properties": {
|
|
||||||
"image": "/tmp/c3725-adventerprisek9-mz.124-25d.image"
|
|
||||||
},
|
|
||||||
"node_type": "dynamips"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"properties": {
|
"properties": {
|
||||||
"image": "gns3/webterm:lastest"
|
"image": "/tmp/c3725-adventerprisek9-mz.124-25d.image"
|
||||||
},
|
},
|
||||||
"node_type": "docker"
|
"node_type": "dynamips"
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"image": "gns3/webterm:lastest"
|
||||||
|
},
|
||||||
|
"node_type": "docker"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,19 +88,6 @@ def test_eq(compute, project, node, controller):
|
|||||||
assert node != Node(Project(str(uuid.uuid4()), controller=controller), compute, "demo3", node_id=node.id, node_type="qemu")
|
assert node != Node(Project(str(uuid.uuid4()), controller=controller), compute, "demo3", node_id=node.id, node_type="qemu")
|
||||||
|
|
||||||
|
|
||||||
def test_properties_filter(project, compute):
|
|
||||||
"""
|
|
||||||
Some properties are private and should not be exposed
|
|
||||||
"""
|
|
||||||
node = Node(project, compute, "demo",
|
|
||||||
node_id=str(uuid.uuid4()),
|
|
||||||
node_type="vpcs",
|
|
||||||
console_type="vnc",
|
|
||||||
properties={"startup_script": "echo test", "iourc_content": "test"})
|
|
||||||
assert node._properties == {"startup_script": "echo test", "iourc_content": "test"}
|
|
||||||
assert node._filter_properties() == {"startup_script": "echo test"}
|
|
||||||
|
|
||||||
|
|
||||||
def test_json(node, compute):
|
def test_json(node, compute):
|
||||||
assert node.__json__() == {
|
assert node.__json__() == {
|
||||||
"compute_id": str(compute.id),
|
"compute_id": str(compute.id),
|
||||||
@ -207,6 +194,30 @@ def test_create_image_missing(node, compute, project, async_run):
|
|||||||
node._upload_missing_image.called is True
|
node._upload_missing_image.called is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_base_script(node, config, compute, tmpdir, async_run):
|
||||||
|
config.set_section_config("Server", {"configs_path": str(tmpdir)})
|
||||||
|
|
||||||
|
with open(str(tmpdir / 'test.txt'), 'w+') as f:
|
||||||
|
f.write('hostname test')
|
||||||
|
|
||||||
|
node._properties = {"base_script_file": "test.txt"}
|
||||||
|
node._console = 2048
|
||||||
|
|
||||||
|
response = MagicMock()
|
||||||
|
response.json = {"console": 2048}
|
||||||
|
compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
|
||||||
|
assert async_run(node.create()) is True
|
||||||
|
data = {
|
||||||
|
"console": 2048,
|
||||||
|
"console_type": "vnc",
|
||||||
|
"node_id": node.id,
|
||||||
|
"startup_script": "hostname test",
|
||||||
|
"name": "demo"
|
||||||
|
}
|
||||||
|
compute.post.assert_called_with("/projects/{}/vpcs/nodes".format(node.project.id), data=data, timeout=120)
|
||||||
|
|
||||||
|
|
||||||
def test_symbol(node, symbols_dir):
|
def test_symbol(node, symbols_dir):
|
||||||
"""
|
"""
|
||||||
Change symbol should change the node size
|
Change symbol should change the node size
|
||||||
|
@ -137,7 +137,7 @@ def test_add_node_local(async_run, controller):
|
|||||||
response.json = {"console": 2048}
|
response.json = {"console": 2048}
|
||||||
compute.post = AsyncioMagicMock(return_value=response)
|
compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
|
||||||
node = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
node = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_script": "test.cfg"}))
|
||||||
assert node.id in project._nodes
|
assert node.id in project._nodes
|
||||||
|
|
||||||
compute.post.assert_any_call('/projects', data={
|
compute.post.assert_any_call('/projects', data={
|
||||||
@ -147,7 +147,7 @@ def test_add_node_local(async_run, controller):
|
|||||||
})
|
})
|
||||||
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
|
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
|
||||||
data={'node_id': node.id,
|
data={'node_id': node.id,
|
||||||
'startup_config': 'test.cfg',
|
'startup_script': 'test.cfg',
|
||||||
'name': 'test'},
|
'name': 'test'},
|
||||||
timeout=120)
|
timeout=120)
|
||||||
assert compute in project._project_created_on_compute
|
assert compute in project._project_created_on_compute
|
||||||
@ -167,7 +167,7 @@ def test_add_node_non_local(async_run, controller):
|
|||||||
response.json = {"console": 2048}
|
response.json = {"console": 2048}
|
||||||
compute.post = AsyncioMagicMock(return_value=response)
|
compute.post = AsyncioMagicMock(return_value=response)
|
||||||
|
|
||||||
node = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
node = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_script": "test.cfg"}))
|
||||||
|
|
||||||
compute.post.assert_any_call('/projects', data={
|
compute.post.assert_any_call('/projects', data={
|
||||||
"name": project._name,
|
"name": project._name,
|
||||||
@ -175,7 +175,7 @@ def test_add_node_non_local(async_run, controller):
|
|||||||
})
|
})
|
||||||
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
|
compute.post.assert_any_call('/projects/{}/vpcs/nodes'.format(project.id),
|
||||||
data={'node_id': node.id,
|
data={'node_id': node.id,
|
||||||
'startup_config': 'test.cfg',
|
'startup_script': 'test.cfg',
|
||||||
'name': 'test'},
|
'name': 'test'},
|
||||||
timeout=120)
|
timeout=120)
|
||||||
assert compute in project._project_created_on_compute
|
assert compute in project._project_created_on_compute
|
||||||
@ -427,7 +427,7 @@ def test_duplicate(project, async_run, controller):
|
|||||||
remote_vpcs = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
remote_vpcs = async_run(project.add_node(compute, "test", None, node_type="vpcs", properties={"startup_config": "test.cfg"}))
|
||||||
|
|
||||||
# We allow node not allowed for standard import / export
|
# We allow node not allowed for standard import / export
|
||||||
remote_virtualbox = async_run(project.add_node(compute, "test", None, node_type="virtualbox", properties={"startup_config": "test.cfg"}))
|
remote_virtualbox = async_run(project.add_node(compute, "test", None, node_type="vmware", properties={"startup_config": "test.cfg"}))
|
||||||
|
|
||||||
new_project = async_run(project.duplicate(name="Hello"))
|
new_project = async_run(project.duplicate(name="Hello"))
|
||||||
assert new_project.id != project.id
|
assert new_project.id != project.id
|
||||||
|
@ -106,7 +106,6 @@ def demo_topology():
|
|||||||
"node_type": "vpcs",
|
"node_type": "vpcs",
|
||||||
"properties": {
|
"properties": {
|
||||||
"startup_script": "",
|
"startup_script": "",
|
||||||
"startup_script_path": "startup.vpc"
|
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/computer.svg",
|
"symbol": ":/symbols/computer.svg",
|
||||||
"width": 65,
|
"width": 65,
|
||||||
@ -131,7 +130,6 @@ def demo_topology():
|
|||||||
"node_type": "vpcs",
|
"node_type": "vpcs",
|
||||||
"properties": {
|
"properties": {
|
||||||
"startup_script": "",
|
"startup_script": "",
|
||||||
"startup_script_path": "startup.vpc"
|
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/computer.svg",
|
"symbol": ":/symbols/computer.svg",
|
||||||
"width": 65,
|
"width": 65,
|
||||||
|
@ -80,7 +80,6 @@ def test_iou_create_with_params(http_compute, project, base_params):
|
|||||||
params["l1_keepalives"] = True
|
params["l1_keepalives"] = True
|
||||||
params["startup_config_content"] = "hostname test"
|
params["startup_config_content"] = "hostname test"
|
||||||
params["use_default_iou_values"] = True
|
params["use_default_iou_values"] = True
|
||||||
params["iourc_content"] = "test"
|
|
||||||
|
|
||||||
response = http_compute.post("/projects/{project_id}/iou/nodes".format(project_id=project.id), params, example=True)
|
response = http_compute.post("/projects/{project_id}/iou/nodes".format(project_id=project.id), params, example=True)
|
||||||
assert response.status == 201
|
assert response.status == 201
|
||||||
@ -94,7 +93,6 @@ def test_iou_create_with_params(http_compute, project, base_params):
|
|||||||
assert response.json["l1_keepalives"] is True
|
assert response.json["l1_keepalives"] is True
|
||||||
assert response.json["use_default_iou_values"] is True
|
assert response.json["use_default_iou_values"] is True
|
||||||
|
|
||||||
assert "startup-config.cfg" in response.json["startup_config"]
|
|
||||||
with open(startup_config_file(project, response.json)) as f:
|
with open(startup_config_file(project, response.json)) as f:
|
||||||
assert f.read() == "hostname test"
|
assert f.read() == "hostname test"
|
||||||
|
|
||||||
@ -115,7 +113,6 @@ def test_iou_create_startup_config_already_exist(http_compute, project, base_par
|
|||||||
assert response.status == 201
|
assert response.status == 201
|
||||||
assert response.route == "/projects/{project_id}/iou/nodes"
|
assert response.route == "/projects/{project_id}/iou/nodes"
|
||||||
|
|
||||||
assert "startup-config.cfg" in response.json["startup_config"]
|
|
||||||
with open(startup_config_file(project, response.json)) as f:
|
with open(startup_config_file(project, response.json)) as f:
|
||||||
assert f.read() == "echo hello"
|
assert f.read() == "echo hello"
|
||||||
|
|
||||||
@ -183,9 +180,7 @@ def test_iou_update(http_compute, vm, tmpdir, free_console_port, project):
|
|||||||
"ethernet_adapters": 4,
|
"ethernet_adapters": 4,
|
||||||
"serial_adapters": 0,
|
"serial_adapters": 0,
|
||||||
"l1_keepalives": True,
|
"l1_keepalives": True,
|
||||||
"startup_config_content": "hostname test",
|
|
||||||
"use_default_iou_values": True,
|
"use_default_iou_values": True,
|
||||||
"iourc_content": "test"
|
|
||||||
}
|
}
|
||||||
response = http_compute.put("/projects/{project_id}/iou/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params, example=True)
|
response = http_compute.put("/projects/{project_id}/iou/nodes/{node_id}".format(project_id=vm["project_id"], node_id=vm["node_id"]), params, example=True)
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
@ -197,9 +192,6 @@ def test_iou_update(http_compute, vm, tmpdir, free_console_port, project):
|
|||||||
assert response.json["nvram"] == 2048
|
assert response.json["nvram"] == 2048
|
||||||
assert response.json["l1_keepalives"] is True
|
assert response.json["l1_keepalives"] is True
|
||||||
assert response.json["use_default_iou_values"] is True
|
assert response.json["use_default_iou_values"] is True
|
||||||
assert "startup-config.cfg" in response.json["startup_config"]
|
|
||||||
with open(startup_config_file(project, response.json)) as f:
|
|
||||||
assert f.read() == "hostname test"
|
|
||||||
|
|
||||||
|
|
||||||
def test_iou_nio_create_udp(http_compute, vm):
|
def test_iou_nio_create_udp(http_compute, vm):
|
||||||
|
@ -43,7 +43,6 @@ def test_vpcs_get(http_compute, project, vm):
|
|||||||
assert response.route == "/projects/{project_id}/vpcs/nodes/{node_id}"
|
assert response.route == "/projects/{project_id}/vpcs/nodes/{node_id}"
|
||||||
assert response.json["name"] == "PC TEST 1"
|
assert response.json["name"] == "PC TEST 1"
|
||||||
assert response.json["project_id"] == project.id
|
assert response.json["project_id"] == project.id
|
||||||
assert response.json["startup_script_path"] is None
|
|
||||||
assert response.json["status"] == "stopped"
|
assert response.json["status"] == "stopped"
|
||||||
|
|
||||||
|
|
||||||
@ -53,8 +52,6 @@ def test_vpcs_create_startup_script(http_compute, project):
|
|||||||
assert response.route == "/projects/{project_id}/vpcs/nodes"
|
assert response.route == "/projects/{project_id}/vpcs/nodes"
|
||||||
assert response.json["name"] == "PC TEST 1"
|
assert response.json["name"] == "PC TEST 1"
|
||||||
assert response.json["project_id"] == project.id
|
assert response.json["project_id"] == project.id
|
||||||
assert response.json["startup_script"] == os.linesep.join(["ip 192.168.1.2", "echo TEST"])
|
|
||||||
assert response.json["startup_script_path"] == "startup.vpc"
|
|
||||||
|
|
||||||
|
|
||||||
def test_vpcs_create_port(http_compute, project, free_console_port):
|
def test_vpcs_create_port(http_compute, project, free_console_port):
|
||||||
|
420
tests/resources/firefox.svg
Normal file
420
tests/resources/firefox.svg
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="66px" height="70px" viewBox="0 0 66 70" enable-background="new 0 0 66 70" xml:space="preserve"> <image id="image0" width="66" height="70" x="0" y="0"
|
||||||
|
xlink:href="data:image/png;base64,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
|
||||||
|
vzR9xUPv+uoJz714H43nyVyvMOd4D4+ce/cx/IaHYQ89Dxnykr3QIQ9fv4/BmPuYf9Czvffi/37W
|
||||||
|
EPY8qetdX/92/fn/hVzd77n/6gv+Q8v1foBX+nb+Ri/G5Xn1C28Bf+z699HvzW58ngsN/IOQ/F5M
|
||||||
|
9HlJlL+O93lv12ufV1G1vBFje7mL5YUc8TpUbnvoFd7+jrHquXz1BKNehc9090773rP/Dh1/Z+9e
|
||||||
|
3cXxkkHHe0gYjr+Pob9DizdLC3uFPufvgp43QExZ734O/ev7+u9v/92C+GfNGzl48+q/a+nmH/4b
|
||||||
|
z5X/7WHEc5mWFystzwPZXqXUKu/ZLTzbE5TlPf+3vvWu4+Us4o1wvFmPtzQhfOKZ3qzpb0H/SwV3
|
||||||
|
todeUuh61zdDPfv9f22byH+4Xinve/78H+j7/7/tvztk/E81b2Tjzcvp6+HD3s9u7dmeC6fsP3z+
|
||||||
|
oofeyFXP8+B5JDyh8ZuHef/TP/i/qv3/AKaOqd0T2CyuAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE1
|
||||||
|
LTEwLTMwVDEzOjUxOjI2KzAxOjAwIBTXKwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNS0xMC0zMFQx
|
||||||
|
Mzo1MToyMCswMTowMDKZWq0AAABJdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL1VzZXJzL25vcGxh
|
||||||
|
eS9Eb3dubG9hZHMvTW96aWxsYV9GaXJlZm94X2xvZ29fMjAxMy5zdmdpkgjUAAAAAElFTkSuQmCC" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 32 KiB |
@ -63,7 +63,6 @@
|
|||||||
],
|
],
|
||||||
"slot0": "C7200-IO-FE",
|
"slot0": "C7200-IO-FE",
|
||||||
"sparsemem": true,
|
"sparsemem": true,
|
||||||
"startup_config": "configs/i1_startup-config.cfg",
|
|
||||||
"system_id": "FTX0945W0MY"
|
"system_id": "FTX0945W0MY"
|
||||||
},
|
},
|
||||||
"x": -112,
|
"x": -112,
|
||||||
|
@ -27,7 +27,6 @@
|
|||||||
"slot0": "Leopard-2FE",
|
"slot0": "Leopard-2FE",
|
||||||
"idlepc": "0x6057efc8",
|
"idlepc": "0x6057efc8",
|
||||||
"chassis": "3660",
|
"chassis": "3660",
|
||||||
"startup_config": "configs/i1_startup-config.cfg",
|
|
||||||
"image": "c3660-a3jk9s-mz.124-25c.bin",
|
"image": "c3660-a3jk9s-mz.124-25c.bin",
|
||||||
"mac_addr": "cc01.20b8.0000",
|
"mac_addr": "cc01.20b8.0000",
|
||||||
"aux": 2103,
|
"aux": 2103,
|
||||||
|
@ -53,7 +53,6 @@
|
|||||||
"ram": 256,
|
"ram": 256,
|
||||||
"slot0": "GT96100-FE",
|
"slot0": "GT96100-FE",
|
||||||
"sparsemem": true,
|
"sparsemem": true,
|
||||||
"startup_config": "configs/i1_startup-config.cfg",
|
|
||||||
"system_id": "FTX0945W0MY"
|
"system_id": "FTX0945W0MY"
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/router.svg",
|
"symbol": ":/symbols/router.svg",
|
||||||
@ -100,7 +99,6 @@
|
|||||||
"slot0": "Leopard-2FE",
|
"slot0": "Leopard-2FE",
|
||||||
"slot1": "NM-16ESW",
|
"slot1": "NM-16ESW",
|
||||||
"sparsemem": true,
|
"sparsemem": true,
|
||||||
"startup_config": "configs/i2_startup-config.cfg",
|
|
||||||
"system_id": "FTX0945W0MY"
|
"system_id": "FTX0945W0MY"
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/multilayer_switch.svg",
|
"symbol": ":/symbols/multilayer_switch.svg",
|
||||||
|
@ -76,7 +76,6 @@
|
|||||||
"port_segment_size": 0,
|
"port_segment_size": 0,
|
||||||
"first_port_name": null,
|
"first_port_name": null,
|
||||||
"properties": {
|
"properties": {
|
||||||
"startup_script_path": "startup.vpc"
|
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/vpcs_guest.svg",
|
"symbol": ":/symbols/vpcs_guest.svg",
|
||||||
"x": -29,
|
"x": -29,
|
||||||
|
@ -30,8 +30,8 @@
|
|||||||
"name": "IOU1",
|
"name": "IOU1",
|
||||||
"node_id": "aaeb2288-a7d8-42a9-b9d8-c42ab464a390",
|
"node_id": "aaeb2288-a7d8-42a9-b9d8-c42ab464a390",
|
||||||
"node_type": "iou",
|
"node_type": "iou",
|
||||||
"port_name_format": "Ethernet{0}",
|
"port_name_format": "Ethernet{segment0}/{port0}",
|
||||||
"port_segment_size": 0,
|
"port_segment_size": 4,
|
||||||
"first_port_name": null,
|
"first_port_name": null,
|
||||||
"properties": {
|
"properties": {
|
||||||
"ethernet_adapters": 2,
|
"ethernet_adapters": 2,
|
||||||
@ -41,7 +41,6 @@
|
|||||||
"path": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin",
|
"path": "i86bi-linux-l3-adventerprisek9-15.4.1T.bin",
|
||||||
"ram": 256,
|
"ram": 256,
|
||||||
"serial_adapters": 2,
|
"serial_adapters": 2,
|
||||||
"startup_config": "startup-config.cfg",
|
|
||||||
"use_default_iou_values": true
|
"use_default_iou_values": true
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/router.svg",
|
"symbol": ":/symbols/router.svg",
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
"port_segment_size": 0,
|
"port_segment_size": 0,
|
||||||
"first_port_name": null,
|
"first_port_name": null,
|
||||||
"properties" : {
|
"properties" : {
|
||||||
"startup_script_path" : "startup.vpc"
|
|
||||||
},
|
},
|
||||||
"label" : {
|
"label" : {
|
||||||
"y" : -25,
|
"y" : -25,
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"port_segment_size": 0,
|
"port_segment_size": 0,
|
||||||
"first_port_name": null,
|
"first_port_name": null,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"linked_clone": false,
|
||||||
"acpi_shutdown": false,
|
"acpi_shutdown": false,
|
||||||
"adapter_type": "Intel PRO/1000 MT Desktop (82540EM)",
|
"adapter_type": "Intel PRO/1000 MT Desktop (82540EM)",
|
||||||
"adapters": 1,
|
"adapters": 1,
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"port_segment_size": 0,
|
"port_segment_size": 0,
|
||||||
"first_port_name": null,
|
"first_port_name": null,
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"linked_clone": false,
|
||||||
"acpi_shutdown": false,
|
"acpi_shutdown": false,
|
||||||
"adapter_type": "e1000",
|
"adapter_type": "e1000",
|
||||||
"adapters": 1,
|
"adapters": 1,
|
||||||
|
@ -50,7 +50,6 @@
|
|||||||
"port_segment_size": 0,
|
"port_segment_size": 0,
|
||||||
"first_port_name": null,
|
"first_port_name": null,
|
||||||
"properties": {
|
"properties": {
|
||||||
"startup_script_path": "startup.vpc"
|
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/vpcs_guest.svg",
|
"symbol": ":/symbols/vpcs_guest.svg",
|
||||||
"x": -87,
|
"x": -87,
|
||||||
@ -75,7 +74,6 @@
|
|||||||
"port_segment_size": 0,
|
"port_segment_size": 0,
|
||||||
"first_port_name": null,
|
"first_port_name": null,
|
||||||
"properties": {
|
"properties": {
|
||||||
"startup_script_path": "startup.vpc"
|
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/vpcs_guest.svg",
|
"symbol": ":/symbols/vpcs_guest.svg",
|
||||||
"x": 123,
|
"x": 123,
|
||||||
|
@ -61,8 +61,6 @@
|
|||||||
1,
|
1,
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
"private_config": "",
|
|
||||||
"private_config_content": "",
|
|
||||||
"ram": 512,
|
"ram": 512,
|
||||||
"sensors": [
|
"sensors": [
|
||||||
22,
|
22,
|
||||||
@ -78,8 +76,6 @@
|
|||||||
"slot5": null,
|
"slot5": null,
|
||||||
"slot6": null,
|
"slot6": null,
|
||||||
"sparsemem": true,
|
"sparsemem": true,
|
||||||
"startup_config": "configs/i1_startup-config.cfg",
|
|
||||||
"startup_config_content": "!\n!\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno service password-encryption\n!\nhostname R1\n!\nip cef\nno ip domain-lookup\nno ip icmp rate-limit unreachable\nip tcp synwait 5\nno cdp log mismatch duplex\n!\nline con 0\n exec-timeout 0 0\n logging synchronous\n privilege level 15\n no login\nline aux 0\n exec-timeout 0 0\n logging synchronous\n privilege level 15\n no login\n!\n!\nend\n",
|
|
||||||
"system_id": "FTX0945W0MY"
|
"system_id": "FTX0945W0MY"
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/router.svg",
|
"symbol": ":/symbols/router.svg",
|
||||||
@ -129,8 +125,6 @@
|
|||||||
1,
|
1,
|
||||||
1
|
1
|
||||||
],
|
],
|
||||||
"private_config": "",
|
|
||||||
"private_config_content": "",
|
|
||||||
"ram": 512,
|
"ram": 512,
|
||||||
"sensors": [
|
"sensors": [
|
||||||
22,
|
22,
|
||||||
@ -146,8 +140,6 @@
|
|||||||
"slot5": null,
|
"slot5": null,
|
||||||
"slot6": null,
|
"slot6": null,
|
||||||
"sparsemem": true,
|
"sparsemem": true,
|
||||||
"startup_config": "configs/i2_startup-config.cfg",
|
|
||||||
"startup_config_content": "!\n!\nservice timestamps debug datetime msec\nservice timestamps log datetime msec\nno service password-encryption\n!\nhostname R2\n!\nip cef\nno ip domain-lookup\nno ip icmp rate-limit unreachable\nip tcp synwait 5\nno cdp log mismatch duplex\n!\nline con 0\n exec-timeout 0 0\n logging synchronous\n privilege level 15\n no login\nline aux 0\n exec-timeout 0 0\n logging synchronous\n privilege level 15\n no login\n!\n!\nend\n",
|
|
||||||
"system_id": "FTX0945W0MY"
|
"system_id": "FTX0945W0MY"
|
||||||
},
|
},
|
||||||
"symbol": ":/symbols/router.svg",
|
"symbol": ":/symbols/router.svg",
|
||||||
|
@ -39,3 +39,7 @@ def test_get_size():
|
|||||||
with open("gns3server/symbols/cloud.svg", "rb") as f:
|
with open("gns3server/symbols/cloud.svg", "rb") as f:
|
||||||
res = get_size(f.read())
|
res = get_size(f.read())
|
||||||
assert res == (159, 71, "svg")
|
assert res == (159, 71, "svg")
|
||||||
|
# Size with px
|
||||||
|
with open("tests/resources/firefox.svg", "rb") as f:
|
||||||
|
res = get_size(f.read())
|
||||||
|
assert res == (66, 70, "svg")
|
||||||
|
Loading…
Reference in New Issue
Block a user