mirror of
https://github.com/GNS3/gns3-registry.git
synced 2024-12-18 12:36:25 +00:00
Generate a website with devices
This commit is contained in:
parent
73231d36a9
commit
3ef5cf875c
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
||||
__pycache__
|
||||
*.pyc
|
||||
.venv
|
||||
build
|
||||
|
35
README.rst
35
README.rst
@ -4,35 +4,20 @@ GNS3-registry
|
||||
|
||||
This is the GNS3 devices registry.
|
||||
|
||||
You need python 3.4 installed
|
||||
|
||||
Add an image
|
||||
************
|
||||
Build website
|
||||
#############
|
||||
|
||||
.. code:: bash
|
||||
|
||||
bin/gns3-get --add ~/Downloads/linux-microcore-3.4.1.img
|
||||
python build.py
|
||||
|
||||
Search an image
|
||||
****************
|
||||
|
||||
Run website
|
||||
#############
|
||||
|
||||
.. code:: bash
|
||||
|
||||
bin/gns3-get --search core
|
||||
|
||||
Micro Core Linux:
|
||||
* hda_disk_image:
|
||||
* 3.4.1 linux-microcore-3.4.1.img: fa2ec4b1fffad67d8103c3391bbf9df2
|
||||
* 4.0.2 linux-microcore-4.0.2-clean.img: e13d0d1c0b3999ae2386bba70417930c
|
||||
|
||||
|
||||
Install a remote image
|
||||
**************************
|
||||
|
||||
If the image is available from the internet you can download and install it:
|
||||
|
||||
.. code:: bash
|
||||
|
||||
bin/gns3-get --install e13d0d1c0b3999ae2386bba70417930c
|
||||
|
||||
Download http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-4.0.2-clean.img to /Users/noplay/GNS3/images/linux-microcore-4.0.2-clean.img
|
||||
python server.py
|
||||
|
||||
|
||||
|
||||
|
44
bin/gns3-get
44
bin/gns3-get
@ -1,44 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
SCRIPT_PATH="${BASH_SOURCE[0]}";
|
||||
GNS3_REPOSITORY_DIR=`dirname ${SCRIPT_PATH}`/..
|
||||
|
||||
# Detect Python 3.4 installation
|
||||
|
||||
PY34_TEST="import sys; sys.exit(1) if sys.version_info[0] != 3 or sys.version_info[1] < 4 else sys.exit(0)"
|
||||
|
||||
if python -c "$PY34_TEST" 2> /dev/null
|
||||
then
|
||||
PYTHON="python"
|
||||
elif python3 -c "$PY34_TEST" 2> /dev/null
|
||||
then
|
||||
PYTHON="python3"
|
||||
elif python3.4 -c "$PY34_TEST" 2> /dev/null
|
||||
then
|
||||
PYTHON="python3.4"
|
||||
elif python34 -c "$PY34_TEST" 2> /dev/null
|
||||
then
|
||||
PYTHON="python34"
|
||||
else
|
||||
echo "No Python 3.4 installation"
|
||||
|
||||
echo "On debian based distribution":
|
||||
echo "apt-get install python3"
|
||||
|
||||
echo "On MacOSX:"
|
||||
echo "brew install python3"
|
||||
echo "port install python34"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
$PYTHON -m venv $GNS3_REPOSITORY_DIR/.venv
|
||||
source $GNS3_REPOSITORY_DIR/.venv/bin/activate
|
||||
|
||||
$PYTHON $GNS3_REPOSITORY_DIR/gns3registry/main.py --test
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
$PYTHON -m pip install -r $GNS3_REPOSITORY_DIR/requirements.txt
|
||||
fi
|
||||
|
||||
$PYTHON $GNS3_REPOSITORY_DIR/gns3registry/main.py $*
|
||||
|
66
build.py
Normal file
66
build.py
Normal file
@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 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/>.
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import shutil
|
||||
|
||||
from jinja2 import Environment, FileSystemLoader
|
||||
|
||||
import logging
|
||||
log = logging.getLogger(__name__)
|
||||
log.setLevel(logging.DEBUG)
|
||||
|
||||
handler = logging.StreamHandler(sys.stdout)
|
||||
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
|
||||
handler.setFormatter(formatter)
|
||||
log.addHandler(handler)
|
||||
|
||||
if os.path.exists('build'):
|
||||
for file in os.listdir('build'):
|
||||
if os.path.isdir(os.path.join('build', file)):
|
||||
shutil.rmtree(os.path.join('build', file))
|
||||
else:
|
||||
os.remove(os.path.join('build', file))
|
||||
else:
|
||||
os.mkdir('build')
|
||||
os.mkdir(os.path.join('build', 'devices'))
|
||||
|
||||
|
||||
def render(template_file, out, **kwargs):
|
||||
log.info('Build %s', out)
|
||||
env = Environment(loader=FileSystemLoader('templates'))
|
||||
env.filters['jsonify'] = json.dumps
|
||||
template = env.get_template(template_file)
|
||||
template.stream(**kwargs).dump(os.path.join('build', out))
|
||||
|
||||
|
||||
render('index.html', 'index.html')
|
||||
|
||||
|
||||
devices = []
|
||||
for file in os.listdir('devices'):
|
||||
filename = file[:-5]
|
||||
with open(os.path.join('devices', file)) as f:
|
||||
device = json.load(f)
|
||||
device['id'] = filename
|
||||
render('device.html', os.path.join('devices', filename + '.html'), device=device)
|
||||
devices.append(device)
|
||||
|
||||
|
||||
render('devices.html', os.path.join('devices', 'index.html'), devices=devices)
|
@ -1,218 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 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/>.
|
||||
|
||||
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from gns3registry.image import Image
|
||||
|
||||
|
||||
class ConfigException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
GNS3 config file
|
||||
"""
|
||||
|
||||
def __init__(self, path=None):
|
||||
"""
|
||||
:params path: Path of the configuration file otherwise detect it on the system
|
||||
"""
|
||||
|
||||
#TODO: Manage errors
|
||||
self.path = path
|
||||
if self.path is None:
|
||||
self.path = self._get_standard_config_file_path()
|
||||
|
||||
with open(self.path) as f:
|
||||
self._config = json.load(f)
|
||||
|
||||
@property
|
||||
def images_dir(self):
|
||||
"""
|
||||
:returns: Localion of the images directory on the server
|
||||
"""
|
||||
return self._config["LocalServer"]["images_path"]
|
||||
|
||||
def _get_standard_config_file_path(self):
|
||||
if sys.platform.startswith("win"):
|
||||
filename = "gns3_gui.ini"
|
||||
else:
|
||||
filename = "gns3_gui.conf"
|
||||
|
||||
appname = "GNS3"
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
appdata = os.path.expandvars("%APPDATA%")
|
||||
return os.path.join(appdata, appname, filename)
|
||||
else:
|
||||
home = os.path.expanduser("~")
|
||||
return os.path.join(home, ".config", appname, filename)
|
||||
|
||||
def add_images(self, device_config):
|
||||
"""
|
||||
Add images to the user configuration
|
||||
"""
|
||||
new_config = {
|
||||
"server": "local",
|
||||
"name": device_config["name"]
|
||||
}
|
||||
if device_config["category"] == "guest":
|
||||
new_config["category"] = 2
|
||||
elif device_config["category"] == "router":
|
||||
new_config["category"] = 0
|
||||
|
||||
if "qemu" in device_config:
|
||||
self._add_qemu_config(new_config, device_config)
|
||||
|
||||
def _add_qemu_config(self, new_config, device_config):
|
||||
|
||||
new_config["adapter_type"] = device_config["qemu"]["adapter_type"]
|
||||
new_config["adapters"] = device_config["qemu"]["adapters"]
|
||||
new_config["cpu_throttling"] = 0
|
||||
new_config["ram"] = device_config["qemu"]["ram"]
|
||||
new_config["legacy_networking"] = False
|
||||
new_config["process_priority"] = "normal"
|
||||
|
||||
new_config["initrd"] = ""
|
||||
new_config["kernel_command_line"] = ""
|
||||
new_config["kernel_image"] = ""
|
||||
|
||||
if device_config["qemu"].get("graphic", False):
|
||||
options = ""
|
||||
else:
|
||||
options = "-nographic "
|
||||
options += device_config["qemu"].get("options", "")
|
||||
|
||||
new_config["options"] = options.strip()
|
||||
|
||||
new_config["hda_disk_image"] = device_config["qemu"].get("hda_disk_image", "")
|
||||
new_config["hdb_disk_image"] = device_config["qemu"].get("hdb_disk_image", "")
|
||||
new_config["hdc_disk_image"] = device_config["qemu"].get("hdc_disk_image", "")
|
||||
new_config["hdd_disk_image"] = device_config["qemu"].get("hdd_disk_image", "")
|
||||
|
||||
new_config["qemu_path"] = self._get_qemu_binary(device_config)
|
||||
|
||||
if device_config["category"] == "guest":
|
||||
new_config["default_symbol"] = ":/symbols/qemu_guest.normal.svg"
|
||||
new_config["hover_symbol"] = ":/symbols/qemu_guest.selected.svg"
|
||||
elif device_config["category"] == "router":
|
||||
new_config["default_symbol"] = ":/symbols/router.normal.svg"
|
||||
new_config["hover_symbol"] = ":/symbols/router.selected.svg"
|
||||
|
||||
disks = ["hda_disk_image", "hdb_disk_image", "hdc_disk_image", "hdd_disk_image", "cdrom"]
|
||||
for disk in disks:
|
||||
if disk in device_config["images"]:
|
||||
if isinstance(device_config["images"][disk], list):
|
||||
require_images = ""
|
||||
for image in device_config["images"][disk]:
|
||||
require_images += "* {}\n".format(image["filename"])
|
||||
raise ConfigException("Missing image for {} you should provide one of the following images:\n{}".format(disk, require_images))
|
||||
else:
|
||||
new_config["name"] += " {}".format(device_config["images"][disk].version)
|
||||
new_config[disk] = self._relative_image_path(device_config["images"][disk].path)
|
||||
|
||||
|
||||
if device_config["qemu"].get("install_cdrom_to_hda", False):
|
||||
new_config["hda_disk_image"] = self._create_qemu_img(device_config, new_config)
|
||||
if "cdrom" in new_config:
|
||||
self._install_qemu_cdrom(device_config, new_config)
|
||||
del new_config["cdrom"]
|
||||
|
||||
# Remove VM with the same Name
|
||||
self._config["Qemu"]["vms"] = [item for item in self._config["Qemu"]["vms"] if item["name"] != new_config["name"]]
|
||||
|
||||
self._config["Qemu"]["vms"].append(new_config)
|
||||
|
||||
def _relative_image_path(self, path):
|
||||
"""
|
||||
:returns: Path relative to image directory if image is inside or full path
|
||||
"""
|
||||
print(os.path.dirname(path))
|
||||
print(self.images_dir)
|
||||
if os.path.dirname(path) == self.images_dir:
|
||||
return os.path.basename(path)
|
||||
return path
|
||||
|
||||
def _get_qemu_binary(self, device_config):
|
||||
"""
|
||||
Create a blank hda disk image
|
||||
|
||||
:param device_config: The require device configuration
|
||||
"""
|
||||
#TODO: Manage Windows
|
||||
if device_config["qemu"]["processor"] == "i386":
|
||||
return "qemu-system-i386"
|
||||
elif device_config["qemu"]["processor"] == "x64":
|
||||
return "qemu-system-x86_64"
|
||||
|
||||
def _create_qemu_img(self, device_config, new_config):
|
||||
"""
|
||||
Create a blank hda disk image
|
||||
|
||||
:param device_config: The require device configuration
|
||||
:param new_config: The GNS3 device configuration
|
||||
:returns: Return new disk path
|
||||
"""
|
||||
#TODO: Manage error
|
||||
image_path = os.path.join(self.images_dir, "QEMU", device_config["qemu"]["hda_disk_image"])
|
||||
#TODO: raise an error if size is missing
|
||||
cmd = ["qemu-img", "create", "-f", "qcow2", image_path, device_config["qemu"]["hda_disk_size"]]
|
||||
print(" ".join(cmd))
|
||||
subprocess.call(cmd)
|
||||
return image_path
|
||||
|
||||
def _install_qemu_cdrom(self, device_config, new_config):
|
||||
"""
|
||||
Install the cdrom to disk
|
||||
|
||||
:param device_config: The require device configuration
|
||||
:param new_config: The GNS3 device configuration
|
||||
"""
|
||||
|
||||
print("Starting cdrom installation. Please follow the instructions in the qemu Windows and close qemu when install is finish in order to finish the process.")
|
||||
print("\nInstall instructions:")
|
||||
print(device_config["qemu"]["install_instructions"])
|
||||
cmd = "{options} -cdrom {cdrom} -m {ram} {hda}".format(
|
||||
options=device_config.get("options", ""),
|
||||
cdrom=device_config["images"]["cdrom"].path,
|
||||
ram=device_config["qemu"]["ram"],
|
||||
hda=new_config["hda_disk_image"])
|
||||
self._qemu_run(device_config, cmd.strip())
|
||||
|
||||
def _qemu_run(self, device_config, cmd):
|
||||
"""
|
||||
Run the qemu command
|
||||
"""
|
||||
cmd = shlex.split(cmd)
|
||||
cmd.insert(0, self._get_qemu_binary(device_config))
|
||||
print(" ".join(cmd))
|
||||
subprocess.call(cmd)
|
||||
|
||||
def save(self):
|
||||
"""
|
||||
Save the configuration file
|
||||
"""
|
||||
with open(self.path, "w+") as f:
|
||||
json.dump(self._config, f, indent=4)
|
||||
|
@ -1,65 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 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/>.
|
||||
|
||||
import hashlib
|
||||
|
||||
|
||||
class Image:
|
||||
"""
|
||||
A disk image
|
||||
"""
|
||||
|
||||
def __init__(self, path):
|
||||
"""
|
||||
:params: path of the image
|
||||
"""
|
||||
self.path = path
|
||||
self._md5sum = None
|
||||
self._version = None
|
||||
|
||||
@property
|
||||
def version(self):
|
||||
"""
|
||||
:returns: Get the file version / release
|
||||
"""
|
||||
return self._version
|
||||
|
||||
@version.setter
|
||||
def version(self, version):
|
||||
"""
|
||||
:returns: Set the file version / release
|
||||
"""
|
||||
self._version = version
|
||||
|
||||
@property
|
||||
def md5sum(self):
|
||||
"""
|
||||
Compute a md5 hash for file
|
||||
|
||||
:returns: hexadecimal md5
|
||||
"""
|
||||
|
||||
if self._md5sum is None:
|
||||
m = hashlib.md5()
|
||||
with open(self.path, "rb") as f:
|
||||
while True:
|
||||
buf = f.read(4096)
|
||||
if not buf:
|
||||
break
|
||||
m.update(buf)
|
||||
self._md5sum = m.hexdigest()
|
||||
return self._md5sum
|
@ -1,107 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 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/>.
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import os
|
||||
import math
|
||||
from distutils.util import strtobool
|
||||
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
|
||||
from gns3registry.registry import Registry
|
||||
from gns3registry.config import Config, ConfigException
|
||||
|
||||
|
||||
def yes_no(message):
|
||||
while True:
|
||||
try:
|
||||
return strtobool(input("{} (y/n) ".format(message)).lower())
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
def download_progress_callback(count, blockSize, totalSize):
|
||||
"""
|
||||
Callback called when a file is downloading
|
||||
"""
|
||||
|
||||
if totalSize == -1:
|
||||
sys.stdout.write("Unknow size downloading...\n")
|
||||
|
||||
percent = int(count * blockSize * 100/totalSize)
|
||||
sys.stdout.write("\r[{}{}] {} %".format("#" * math.floor(percent / 2), " " * math.ceil(100 / 2 - percent / 2), percent))
|
||||
if count * blockSize == totalSize:
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
registry = Registry(download_progress_callback=download_progress_callback)
|
||||
config = Config()
|
||||
|
||||
|
||||
def add_images(images):
|
||||
print("WARNING WARNING WARNING")
|
||||
print("It's experimental")
|
||||
print("")
|
||||
|
||||
confs = registry.detect_images(images)
|
||||
if len(confs) > 0:
|
||||
print("Found: {} devices configuration".format(len(confs)))
|
||||
for conf in confs:
|
||||
if yes_no("Add {}?".format(conf["name"])):
|
||||
try:
|
||||
config.add_images(conf)
|
||||
except ConfigException as e:
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
config.save()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Manage GNS3 registry")
|
||||
parser.add_argument("--add", dest="add_images", action="store", nargs='+',
|
||||
help="Add images to GNS3")
|
||||
parser.add_argument("--search", dest="search", action="store",
|
||||
help="Search an image for GNS3")
|
||||
parser.add_argument("--install", dest="install", action="store",
|
||||
help="Download and install an image for GNS3")
|
||||
parser.add_argument("--test", dest="test", action="store_true",
|
||||
help="Test if installation of gns3 registry is OK")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.test:
|
||||
sys.exit(0)
|
||||
|
||||
if args.add_images:
|
||||
add_images(args.add_images)
|
||||
elif args.search:
|
||||
print("Available images\n")
|
||||
for res in registry.search_device(args.search):
|
||||
print("{}: ".format(res["name"]))
|
||||
for image_type in res["images"]:
|
||||
print(" * {}:".format(image_type))
|
||||
for file in res["images"][image_type]:
|
||||
print(" * {} {}: {}".format(file["version"], file["filename"], file["md5sum"]))
|
||||
elif args.install:
|
||||
image = registry.download_image(args.install, config.images_dir)
|
||||
if image:
|
||||
add_images([image])
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
@ -1,121 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 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/>.
|
||||
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import urllib.request
|
||||
|
||||
from gns3registry.image import Image
|
||||
|
||||
|
||||
class Registry:
|
||||
def __init__(self, download_progress_callback=None):
|
||||
"""
|
||||
:param download_progress_callback: Callback called when a file is downloaded
|
||||
"""
|
||||
self._download_progress_callback = download_progress_callback
|
||||
|
||||
def detect_images(self, images_path):
|
||||
"""
|
||||
:param images_path: Array of path to images
|
||||
:returns: Array of configuration corresponding to the image
|
||||
"""
|
||||
|
||||
images = []
|
||||
|
||||
for path in images_path:
|
||||
images.append(Image(path))
|
||||
|
||||
configurations = []
|
||||
|
||||
#TODO: Manage open error
|
||||
for config in self._all_configs():
|
||||
|
||||
matched = False
|
||||
for image in images:
|
||||
if self._image_match(image, config):
|
||||
matched = True
|
||||
if matched:
|
||||
configurations.append(config)
|
||||
|
||||
return configurations
|
||||
|
||||
def download_image(self, md5sum, images_dir):
|
||||
for config in self._all_configs():
|
||||
for image_type in config.get("images", {}):
|
||||
for file in config["images"].get(image_type, []):
|
||||
if file["md5sum"] == md5sum:
|
||||
path = os.path.join(images_dir, "QEMU", file["filename"])
|
||||
|
||||
if "direct_download_url" in file:
|
||||
print("Download {} to {}".format(file["direct_download_url"], path))
|
||||
#TODO: Skip download if file already exist with same sha1
|
||||
urllib.request.urlretrieve(file["direct_download_url"], path, reporthook=self._download_progress_callback)
|
||||
return path
|
||||
else:
|
||||
print("You need to manually download the image {filename} from:\n{download_url}\n\nAnd run: ./bin/gns3-get --add {filename}".format(filename=file["filename"], download_url=file["download_url"]))
|
||||
return None
|
||||
|
||||
def search_device(self, query):
|
||||
results = []
|
||||
for config in self._all_configs():
|
||||
if re.match(r".*{}.*".format(query), config["name"], flags=re.IGNORECASE):
|
||||
results.append(config)
|
||||
return results
|
||||
|
||||
def _all_configs(self):
|
||||
"""
|
||||
Iterate on all configs available on devices
|
||||
"""
|
||||
devices_path = self._get_devices_path()
|
||||
for (dirpath, dirnames, filenames) in os.walk(devices_path):
|
||||
for filename in filenames:
|
||||
file = os.path.join(dirpath, filename)
|
||||
if file.endswith(".json"):
|
||||
with open(os.path.join(devices_path, file)) as f:
|
||||
config = json.load(f)
|
||||
yield config
|
||||
|
||||
def _image_match(self, image, config):
|
||||
"""
|
||||
:returns: True if image is present in configuration
|
||||
"""
|
||||
matched = False
|
||||
|
||||
for image_type in config.get("images", {}):
|
||||
images = config["images"].get(image_type, [])
|
||||
# If it's not a list it's mean we have already detect the image
|
||||
if not isinstance(images, list):
|
||||
continue
|
||||
for file in images:
|
||||
if isinstance(file, dict):
|
||||
if file.get("md5sum", None) == image.md5sum:
|
||||
image.version = file["version"]
|
||||
config["images"][image_type] = image
|
||||
matched = True
|
||||
return matched
|
||||
|
||||
def _get_devices_path(self):
|
||||
"""
|
||||
Get the path where the registry files are located
|
||||
"""
|
||||
path = os.path.abspath(os.path.dirname(__file__))
|
||||
return os.path.join(path, "..", "devices")
|
@ -1 +1 @@
|
||||
jsonschema==2.4.0
|
||||
Jinja2==2.7.3
|
||||
|
@ -1,4 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (C) 2015 GNS3 Technologies Inc.
|
||||
#
|
||||
@ -15,24 +15,15 @@
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import pytest
|
||||
import urllib.request
|
||||
import tempfile
|
||||
import os
|
||||
import http.server
|
||||
import socketserver
|
||||
|
||||
@pytest.fixture
|
||||
def linux_microcore_img():
|
||||
PORT = 8001
|
||||
|
||||
path = os.path.join(tempfile.tempdir, "linux-microcore-3.4.1.img")
|
||||
if not os.path.exists(path):
|
||||
urllib.request.urlretrieve("http://downloads.sourceforge.net/project/gns-3/Qemu%20Appliances/linux-microcore-3.4.1.img?r=&ts=1432209459&use_mirror=heanet", path)
|
||||
return path
|
||||
os.chdir('build')
|
||||
Handler = http.server.SimpleHTTPRequestHandler
|
||||
httpd = socketserver.TCPServer(('', PORT), Handler)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def empty_file(tmpdir):
|
||||
|
||||
path = str(tmpdir / "a")
|
||||
open(path, "w+").close()
|
||||
return path
|
||||
print('Serving at port', PORT)
|
||||
httpd.serve_forever()
|
20
templates/device.html
Normal file
20
templates/device.html
Normal file
@ -0,0 +1,20 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block body %}
|
||||
<div class="jumbotron">
|
||||
<h1>{{ device["name"] }}</h1>
|
||||
Vendor: <a href="{{ device["vendor_url"] }}">{{ device["vendor_name"] }}</a>
|
||||
<br />
|
||||
Documentation: <a href="{{ device["documentation_url"] }}">{{ device["documentation_url"] }}</a>
|
||||
</div>
|
||||
{% for image_type in device["images"] %}
|
||||
<h2>Images for {{image_type}}</h2>
|
||||
<ul>
|
||||
{% for image in device["images"][image_type] %}
|
||||
<h3>{{image["filename"]}}</h3>
|
||||
Version: {{image["version"]}}<br />
|
||||
Checksum: {{image["md5sum"]}}<br />
|
||||
<button class="btn btn-primary btn-lg" type="button" onclick='gns3.install({{device|jsonify}}, "{{image["md5sum"]}}")'>Install</button>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
8
templates/devices.html
Normal file
8
templates/devices.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block body %}
|
||||
<ul>
|
||||
{% for device in devices %}
|
||||
<li><a href="/devices/{{device["id"]}}.html">{{device["name"]}}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endblock %}
|
7
templates/index.html
Normal file
7
templates/index.html
Normal file
@ -0,0 +1,7 @@
|
||||
{% extends "layout/default.html" %}
|
||||
{% block body %}
|
||||
<div class="jumbotron">
|
||||
<h1>Hello Networker!</h1>
|
||||
<p><a class="btn btn-primary btn-lg" href="/devices" role="button">Show devices</a></p>
|
||||
</div>
|
||||
{% endblock %}
|
41
templates/layout/default.html
Normal file
41
templates/layout/default.html
Normal file
@ -0,0 +1,41 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title></title>
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
|
||||
|
||||
<!-- Latest compiled and minified JavaScript -->
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-default">
|
||||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="/">GNS3</a>
|
||||
</div>
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/devices">Devices</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="container-fluid">
|
||||
{% block body %}{% endblock %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -1,253 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 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/>.
|
||||
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
from gns3registry.config import Config, ConfigException
|
||||
from gns3registry.image import Image
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def empty_config(tmpdir):
|
||||
config = {
|
||||
"LocalServer": {
|
||||
"allow_console_from_anywhere": False,
|
||||
"auto_start": False,
|
||||
"console_end_port_range": 5000,
|
||||
"console_start_port_range": 2001,
|
||||
"host": "127.0.0.1",
|
||||
"images_path": str(tmpdir),
|
||||
"path": "",
|
||||
"port": 8000,
|
||||
"projects_path": str(tmpdir),
|
||||
"report_errors": False,
|
||||
"udp_end_port_range": 20000,
|
||||
"udp_start_port_range": 10000
|
||||
},
|
||||
"Dynamips": {
|
||||
"allocate_aux_console_ports": False,
|
||||
"dynamips_path": "/Applications/GNS3.app/Contents/Resources/dynamips",
|
||||
"ghost_ios_support": True,
|
||||
"mmap_support": True,
|
||||
"routers": [
|
||||
{
|
||||
}
|
||||
],
|
||||
"sparse_memory_support": True,
|
||||
"use_local_server": True
|
||||
},
|
||||
"IOU": {
|
||||
"devices": [
|
||||
{
|
||||
}
|
||||
],
|
||||
"iourc_path": "/Users/noplay/code/gns3/gns3-vagrant/images/iou/iourc.txt",
|
||||
"iouyap_path": "",
|
||||
"license_check": True,
|
||||
"use_local_server": False
|
||||
},
|
||||
"Qemu": {
|
||||
"use_local_server": True,
|
||||
"vms": [
|
||||
]
|
||||
}
|
||||
}
|
||||
path = str(tmpdir / "config")
|
||||
with open(path, "w+") as f:
|
||||
json.dump(config, f)
|
||||
return Config(path)
|
||||
|
||||
|
||||
def test_add_images_guest(empty_config, linux_microcore_img):
|
||||
with open("devices/microcore-linux.json") as f:
|
||||
config = json.load(f)
|
||||
image = Image(linux_microcore_img)
|
||||
image.version = "3.4.1"
|
||||
config["images"]["hda_disk_image"] = image
|
||||
empty_config.add_images(config)
|
||||
assert empty_config._config["Qemu"]["vms"][0] == {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 1,
|
||||
"category": 2,
|
||||
"cpu_throttling": 0,
|
||||
"default_symbol": ":/symbols/qemu_guest.normal.svg",
|
||||
"hda_disk_image": image.path,
|
||||
"hdb_disk_image": "",
|
||||
"hdc_disk_image": "",
|
||||
"hdd_disk_image": "",
|
||||
"hover_symbol": ":/symbols/qemu_guest.selected.svg",
|
||||
"initrd": "",
|
||||
"kernel_command_line": "",
|
||||
"kernel_image": "",
|
||||
"legacy_networking": False,
|
||||
"name": "Micro Core Linux 3.4.1",
|
||||
"options": "-nographic",
|
||||
"process_priority": "normal",
|
||||
"qemu_path": "qemu-system-i386",
|
||||
"ram": 32,
|
||||
"server": "local"
|
||||
}
|
||||
|
||||
|
||||
def test_add_images_cdrom(empty_config, linux_microcore_img):
|
||||
with open("devices/hp-vsr1001.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
hda = os.path.join(empty_config.images_dir, "QEMU", "vsr1000-hp.img")
|
||||
|
||||
image = Image(linux_microcore_img)
|
||||
image.version = "7.10.R0204P01"
|
||||
config["images"]["cdrom"] = image
|
||||
|
||||
with patch("subprocess.call") as mock_qemu_img:
|
||||
with patch("gns3registry.config.Config._qemu_run") as mock_qemu:
|
||||
mock_qemu.return_value = hda
|
||||
empty_config.add_images(config)
|
||||
assert mock_qemu_img.called
|
||||
args, kwargs = mock_qemu_img.call_args
|
||||
assert args[0] == ["qemu-img", "create", "-f", "qcow2", hda, "8G"]
|
||||
|
||||
assert mock_qemu.called
|
||||
args, kwargs = mock_qemu.call_args
|
||||
assert args[1] == "-cdrom {} -m 1024 {}".format(image.path, hda)
|
||||
|
||||
assert empty_config._config["Qemu"]["vms"][0] == {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 16,
|
||||
"category": 0,
|
||||
"cpu_throttling": 0,
|
||||
"default_symbol": ":/symbols/router.normal.svg",
|
||||
"hda_disk_image": hda,
|
||||
"hdb_disk_image": "",
|
||||
"hdc_disk_image": "",
|
||||
"hdd_disk_image": "",
|
||||
"hover_symbol": ":/symbols/router.selected.svg",
|
||||
"initrd": "",
|
||||
"kernel_command_line": "",
|
||||
"kernel_image": "",
|
||||
"legacy_networking": False,
|
||||
"name": "HP VSR1001 7.10.R0204P01",
|
||||
"options": "",
|
||||
"process_priority": "normal",
|
||||
"qemu_path": "qemu-system-x86_64",
|
||||
"ram": 1024,
|
||||
"server": "local"
|
||||
}
|
||||
|
||||
|
||||
def test_add_images_router_two_disk(empty_config):
|
||||
with open("devices/arista-veos.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
image = MagicMock()
|
||||
image.version = "2.1.0"
|
||||
image.md5sum = "ea9dc1989764fc6db1d388b061340743016214a7"
|
||||
image.path = "/a"
|
||||
config["images"]["hda_disk_image"] = image
|
||||
|
||||
image = MagicMock()
|
||||
image.version = "4.13.8M"
|
||||
image.md5sum = "ff50656fe817c420e9f7fbb0c0ee41f1ca52fee2"
|
||||
image.path = "/b"
|
||||
config["images"]["hdb_disk_image"] = image
|
||||
|
||||
empty_config.add_images(config)
|
||||
assert empty_config._config["Qemu"]["vms"][0]["name"] == "Arista vEOS 2.1.0 4.13.8M"
|
||||
|
||||
assert empty_config._config["Qemu"]["vms"][0] == {
|
||||
"adapter_type": "e1000",
|
||||
"adapters": 8,
|
||||
"category": 0,
|
||||
"cpu_throttling": 0,
|
||||
"default_symbol": ":/symbols/router.normal.svg",
|
||||
"hda_disk_image": "/a",
|
||||
"hdb_disk_image": "/b",
|
||||
"hdc_disk_image": "",
|
||||
"hdd_disk_image": "",
|
||||
"hover_symbol": ":/symbols/router.selected.svg",
|
||||
"initrd": "",
|
||||
"kernel_command_line": "",
|
||||
"kernel_image": "",
|
||||
"legacy_networking": False,
|
||||
"name": "Arista vEOS 2.1.0 4.13.8M",
|
||||
"options": "-nographic",
|
||||
"process_priority": "normal",
|
||||
"qemu_path": "qemu-system-x86_64",
|
||||
"ram": 2048,
|
||||
"server": "local"
|
||||
}
|
||||
|
||||
|
||||
def test_add_images_uniq(empty_config, linux_microcore_img):
|
||||
with open("devices/microcore-linux.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
image = Image(linux_microcore_img)
|
||||
image.version = "3.4.1"
|
||||
config["images"]["hda_disk_image"] = image
|
||||
|
||||
empty_config.add_images(config)
|
||||
config["qemu"]["adapters"] = 2
|
||||
empty_config.add_images(config)
|
||||
assert len(empty_config._config["Qemu"]["vms"]) == 1
|
||||
assert empty_config._config["Qemu"]["vms"][0]["adapters"] == 2
|
||||
|
||||
|
||||
def test_add_images_two_disk_one_missing(empty_config):
|
||||
with open("devices/arista-veos.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
image = MagicMock()
|
||||
image.version = "2.1.0"
|
||||
image.md5sum = "ea9dc1989764fc6db1d388b061340743016214a7"
|
||||
config["images"]["hda_disk_image"] = image
|
||||
|
||||
with pytest.raises(ConfigException):
|
||||
empty_config.add_images(config)
|
||||
assert len(empty_config._config["Qemu"]["vms"]) == 0
|
||||
|
||||
|
||||
def test_add_image_path_relative_to_images_dir(empty_config, tmpdir, linux_microcore_img):
|
||||
with open("devices/microcore-linux.json") as f:
|
||||
config = json.load(f)
|
||||
image = Image(linux_microcore_img)
|
||||
image.version = "3.4.1"
|
||||
image.path = str(tmpdir / "linux-microcore-3.4.1.img")
|
||||
|
||||
config["images"]["hda_disk_image"] = image
|
||||
empty_config.add_images(config)
|
||||
assert empty_config._config["Qemu"]["vms"][0]["hda_disk_image"] == "linux-microcore-3.4.1.img"
|
||||
|
||||
|
||||
def test_save(empty_config, linux_microcore_img):
|
||||
|
||||
with open("devices/microcore-linux.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
image = Image(linux_microcore_img)
|
||||
image.version = "3.4.1"
|
||||
config["images"]["hda_disk_image"] = image
|
||||
|
||||
empty_config.add_images(config)
|
||||
empty_config.save()
|
||||
with open(empty_config.path) as f:
|
||||
assert "Micro Core" in f.read()
|
@ -1,56 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (C) 2015 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/>.
|
||||
|
||||
|
||||
import pytest
|
||||
import json
|
||||
|
||||
from gns3registry.registry import Registry
|
||||
|
||||
|
||||
def test_detect_image(linux_microcore_img):
|
||||
|
||||
with open("devices/microcore-linux.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
registry = Registry()
|
||||
detected = registry.detect_images([linux_microcore_img])
|
||||
assert detected[0]["name"] == "Micro Core Linux"
|
||||
assert detected[0]["images"]["hda_disk_image"].version == "3.4.1"
|
||||
|
||||
|
||||
def test_detect_two_image(linux_microcore_img):
|
||||
|
||||
with open("devices/microcore-linux.json") as f:
|
||||
config = json.load(f)
|
||||
|
||||
registry = Registry()
|
||||
detected = registry.detect_images([linux_microcore_img])
|
||||
assert detected[0]["name"] == "Micro Core Linux"
|
||||
assert detected[0]["images"]["hda_disk_image"].version == "3.4.1"
|
||||
|
||||
|
||||
def test_detect_unknow_image(empty_file):
|
||||
registry = Registry()
|
||||
assert registry.detect_images([empty_file]) == []
|
||||
|
||||
|
||||
def test_search_device():
|
||||
registry = Registry()
|
||||
results = registry.search_device("Micro Core Linux")
|
||||
assert len(results) == 1
|
Loading…
Reference in New Issue
Block a user