Merge pull request #2452 from GNS3/release/v2.2.52

release/v2.2.52
This commit is contained in:
Jeremy Grossmann 2024-12-02 11:36:09 +10:00 committed by GitHub
commit e94b55e3bf
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
25 changed files with 373 additions and 104 deletions

View File

@ -1,5 +1,14 @@
# Change Log # Change Log
## 2.2.52 02/12/2024
* Bundle web-ui v2.2.52
* Sync appliances
* Remove restrictions based on file extension when listing images and fix ELF header checks
* Fix use project name instead of ID for fast duplication when running local server. Fixes #2446
* Overwrite user resources when the originals have changed.
* Relax setuptools requirement to allow for easier Debian packaging on Ubuntu Focal & Jammy
## 2.2.51 07/11/2024 ## 2.2.51 07/11/2024
* Catch error when cannot resize Docker container TTY. * Catch error when cannot resize Docker container TTY.

View File

@ -33,11 +33,23 @@
"md5sum": "cbbbea66a253f1dac0fcf81274dc778d", "md5sum": "cbbbea66a253f1dac0fcf81274dc778d",
"filesize": 87756936 "filesize": 87756936
}, },
{
"filename": "c7200-adventerprisek9-mz.152-4.M11.image",
"version": "152-4.M11",
"md5sum": "9a2005ad09ce1ec6fe7cf9af1e9b099e",
"filesize": 128487680
},
{ {
"filename": "c7200-adventerprisek9-mz.124-24.T5.image", "filename": "c7200-adventerprisek9-mz.124-24.T5.image",
"version": "124-24.T5", "version": "124-24.T5",
"md5sum": "6b89d0d804e1f2bb5b8bda66b5692047", "md5sum": "6b89d0d804e1f2bb5b8bda66b5692047",
"filesize": 102345240 "filesize": 102345240
},
{
"filename": "c7200-a3jk9s-mz.124-25g.image",
"version": "124-25G",
"md5sum": "9c7cc9b3f3b3571411a7f62faaa2c036",
"filesize": 71528984
} }
], ],
"versions": [ "versions": [
@ -55,12 +67,26 @@
"image": "c7200-advipservicesk9-mz.152-4.S5.image" "image": "c7200-advipservicesk9-mz.152-4.S5.image"
} }
}, },
{
"name": "152-4.M11",
"idlepc": "0x6062e5c0",
"images": {
"image": "c7200-adventerprisek9-mz.152-4.M11.image"
}
},
{ {
"name": "124-24.T5", "name": "124-24.T5",
"idlepc": "0x606df838", "idlepc": "0x606df838",
"images": { "images": {
"image": "c7200-adventerprisek9-mz.124-24.T5.image" "image": "c7200-adventerprisek9-mz.124-24.T5.image"
} }
},
{
"name": "124-25G",
"idlepc": "0x6066a998",
"images": {
"image": "c7200-a3jk9s-mz.124-25g.image"
}
} }
] ]
} }

View File

@ -12,7 +12,7 @@
"status": "stable", "status": "stable",
"maintainer": "GNS3 Team", "maintainer": "GNS3 Team",
"maintainer_email": "developers@gns3.net", "maintainer_email": "developers@gns3.net",
"usage": "There is no default password and enable password. A default configuration is present. ASAv goes through a double-boot before becoming active. This is normal and expected.", "usage": "There is no default password and enable password. A default configuration is present. ASAv goes through a double-boot before becoming active. This is normal and expected. Switch to the Telnet console type after the first boot.",
"symbol": ":/symbols/asa.svg", "symbol": ":/symbols/asa.svg",
"first_port_name": "Management0/0", "first_port_name": "Management0/0",
"port_name_format": "Gi0/{0}", "port_name_format": "Gi0/{0}",
@ -26,6 +26,13 @@
"kvm": "require" "kvm": "require"
}, },
"images": [ "images": [
{
"filename": "asav9-22-1-1.qcow2",
"version": "9.22.1.1 CML",
"md5sum": "250a924cdc2370208eaac9d1dc8dc9e3",
"filesize": 379518976,
"download_url": "https://learningnetworkstore.cisco.com/cisco-modeling-labs-personal/cisco-modeling-labs-personal/CML-PERSONAL.html"
},
{ {
"filename": "asav9-18-2.qcow2", "filename": "asav9-18-2.qcow2",
"version": "9.18.2 CML", "version": "9.18.2 CML",
@ -126,6 +133,12 @@
} }
], ],
"versions": [ "versions": [
{
"name": "9.22.1.1 CML",
"images": {
"hda_disk_image": "asav9-22-1-1.qcow2"
}
},
{ {
"name": "9.18.2 CML", "name": "9.18.2 CML",
"images": { "images": {

View File

@ -24,12 +24,26 @@
"kvm": "require" "kvm": "require"
}, },
"images": [ "images": [
{
"filename": "csr1000v-universalk9.17.03.08a-serial.qcow2",
"version": "17.03.08a",
"md5sum": "6abece87d6db99d9fd6917203e253f91",
"filesize": 1421410304,
"download_url": "https://software.cisco.com/download/home/286323714/type/282046477/release/Amsterdam-17.3.8a"
},
{ {
"filename": "csr1000v-universalk9.17.03.06-serial.qcow2", "filename": "csr1000v-universalk9.17.03.06-serial.qcow2",
"version": "17.03.06", "version": "17.03.06",
"md5sum": "086ab9bef6e66de847af0da3910c60e8", "md5sum": "086ab9bef6e66de847af0da3910c60e8",
"filesize": 1422000128, "filesize": 1422000128,
"download_url": "https://software.cisco.com/download/home/284364978/type/282046477/release/Gibraltar-16.12.3" "download_url": "https://software.cisco.com/download/home/286323714/type/282046477/release/Amsterdam-17.3.6"
},
{
"filename": "csr1000v-ucmk9.16.12.5-serial.qcow2",
"version": "16.12.05",
"md5sum": "5c0cc217f0f0648407b34b11a1dd5b8e",
"filesize": 844103680,
"download_url": "https://software.cisco.com/download/home/286323714/type/286321980/release/16.12.5"
}, },
{ {
"filename": "csr1000v-universalk9.16.12.03-serial.qcow2", "filename": "csr1000v-universalk9.16.12.03-serial.qcow2",
@ -166,11 +180,23 @@
} }
], ],
"versions": [ "versions": [
{
"name": "17.03.08a",
"images": {
"hda_disk_image": "csr1000v-universalk9.17.03.08a-serial.qcow2"
}
},
{ {
"name": "17.03.06", "name": "17.03.06",
"images": { "images": {
"hda_disk_image": "csr1000v-universalk9.17.03.06-serial.qcow2" "hda_disk_image": "csr1000v-universalk9.17.03.06-serial.qcow2"
} }
},
{
"name": "16.12.05",
"images": {
"hda_disk_image": "csr1000v-ucmk9.16.12.5-serial.qcow2"
}
}, },
{ {
"name": "16.12.3", "name": "16.12.3",

View File

@ -13,11 +13,17 @@
"iou": { "iou": {
"ethernet_adapters": 4, "ethernet_adapters": 4,
"serial_adapters": 0, "serial_adapters": 0,
"nvram": 128, "nvram": 512,
"ram": 256, "ram": 512,
"startup_config": "iou_l2_base_startup-config.txt" "startup_config": "iou_l2_base_startup-config.txt"
}, },
"images": [ "images": [
{
"filename": "x86_64_crb_linux_l2-adventerprisek9-ms.iol",
"version": "17.15.1",
"md5sum": "6c587cdfd5056078e70b3f6c26800d66",
"filesize": 243251976
},
{ {
"filename": "x86_64_crb_linux_l2-adventerprisek9-ms.bin", "filename": "x86_64_crb_linux_l2-adventerprisek9-ms.bin",
"version": "17.12.1", "version": "17.12.1",
@ -44,6 +50,12 @@
} }
], ],
"versions": [ "versions": [
{
"name": "17.15.1",
"images": {
"image": "x86_64_crb_linux_l2-adventerprisek9-ms.iol"
}
},
{ {
"name": "17.12.1", "name": "17.12.1",
"images": { "images": {

View File

@ -13,11 +13,17 @@
"iou": { "iou": {
"ethernet_adapters": 2, "ethernet_adapters": 2,
"serial_adapters": 2, "serial_adapters": 2,
"nvram": 128, "nvram": 512,
"ram": 256, "ram": 512,
"startup_config": "iou_l3_base_startup-config.txt" "startup_config": "iou_l3_base_startup-config.txt"
}, },
"images": [ "images": [
{
"filename": "x86_64_crb_linux-adventerprisek9-ms.iol",
"version": "17.15.1",
"md5sum": "5d584f6cfbeaadc87d55f613da1049ed",
"filesize": 292001512
},
{ {
"filename": "x86_64_crb_linux-adventerprisek9-ms.bin", "filename": "x86_64_crb_linux-adventerprisek9-ms.bin",
"version": "17.12.1", "version": "17.12.1",
@ -44,6 +50,12 @@
} }
], ],
"versions": [ "versions": [
{
"name": "17.15.1",
"images": {
"image": "x86_64_crb_linux-adventerprisek9-ms.iol"
}
},
{ {
"name": "17.12.1", "name": "17.12.1",
"images": { "images": {

View File

@ -29,6 +29,20 @@
}, },
"images": [ "images": [
{ {
"filename": "MFG_CTVM_8_10_196_0.iso",
"version": "8.10.196.0",
"md5sum": "6093aca44dcf45c999f83e62dc9aeea2",
"filesize": 650809344,
"download_url": "https://software.cisco.com/download/release.html?mdfid=284464214&flowid=&softwareid=280926587&release=8.10.196.0"
},
{
"filename": "MFG_CTVM_8_5_182_0.iso",
"version": "8.5.182.0",
"md5sum": "1cf3c57c2b123e739ab4662ea0abbc34",
"filesize": 388579328,
"download_url": "https://software.cisco.com/download/home/284464214/type/280926587/release/8.5.182.0"
},
{
"filename": "MFG_CTVM_8_3_102_0.iso", "filename": "MFG_CTVM_8_3_102_0.iso",
"version": "8.3.102.0", "version": "8.3.102.0",
"md5sum": "7f6b7968b5bed04b5ecc119b6ba4e41c", "md5sum": "7f6b7968b5bed04b5ecc119b6ba4e41c",
@ -73,6 +87,20 @@
} }
], ],
"versions": [ "versions": [
{
"name": "8.10.196.0",
"images": {
"hda_disk_image": "empty8G.qcow2",
"cdrom_image": "MFG_CTVM_8_10_196_0.iso"
}
},
{
"name": "8.5.182.0",
"images": {
"hda_disk_image": "empty8G.qcow2",
"cdrom_image": "MFG_CTVM_8_5_182_0.iso"
}
},
{ {
"name": "8.3.102.0", "name": "8.3.102.0",
"images": { "images": {

View File

@ -10,8 +10,8 @@
"product_url": "http://www.cisco.com/", "product_url": "http://www.cisco.com/",
"registry_version": 4, "registry_version": 4,
"status": "experimental", "status": "experimental",
"maintainer": "Laurent LEVIER", "maintainer": "Christopher Uhrig",
"maintainer_email": "laurent.levier@orange.com", "maintainer_email": "christopher.uhrig@telekom.de",
"usage": "Initial username is admin, password is admin as well.", "usage": "Initial username is admin, password is admin as well.",
"first_port_name": "Management0/0", "first_port_name": "Management0/0",
"port_name_format": "Ge0/{0}", "port_name_format": "Ge0/{0}",
@ -39,6 +39,20 @@
"md5sum": "4aa487101d4cdc390f53a6e8b6f45ca7", "md5sum": "4aa487101d4cdc390f53a6e8b6f45ca7",
"filesize": 328400896, "filesize": 328400896,
"download_url": "http://www.cisco.com/" "download_url": "http://www.cisco.com/"
},
{
"filename": "viptela-edge-20.9.5.1-genericx86-64.qcow2",
"version": "20.9.5.1",
"md5sum": "41d9e981908fd83695de78d6ca5794bd",
"filesize": 409468928,
"download_url": "https://software.cisco.com/download/home/286320995/type/286321047/release/20.9.5.1"
},
{
"filename": "viptela-edge-20.12.4-genericx86-64.qcow2",
"version": "20.12.4",
"md5sum": "9f1aedada5e632c7bc29a51c004f4486",
"filesize": 411762688,
"download_url": "https://software.cisco.com/download/home/286320995/type/286321047/release/20.12.4"
} }
], ],
"versions": [ "versions": [
@ -53,6 +67,18 @@
"images": { "images": {
"hda_disk_image": "viptela-edge-genericx86-64.qcow2" "hda_disk_image": "viptela-edge-genericx86-64.qcow2"
} }
},
{
"name": "20.9.5.1",
"images": {
"hda_disk_image": "viptela-edge-20.9.5.1-genericx86-64.qcow2"
}
},
{
"name": "20.12.4",
"images": {
"hda_disk_image": "viptela-edge-20.12.4-genericx86-64.qcow2"
}
} }
] ]
} }

View File

@ -24,6 +24,20 @@
"options": "-smp 2,maxcpus=2 -cpu host" "options": "-smp 2,maxcpus=2 -cpu host"
}, },
"images": [ "images": [
{
"filename": "viptela-smart-20.12.4-genericx86-64.qcow2",
"version": "20.12.4",
"md5sum": "0e7b6468498a89195ab815260bc4cfb6",
"filesize": 411762688,
"download_url": "https://software.cisco.com/download/home/286320995/type/286321043/release/20.12.4"
},
{
"filename": "viptela-smart-20.9.5.1-genericx86-64.qcow2",
"version": "20.9.5.1",
"md5sum": "08e105778bb68f8f24f323dfd263a91a",
"filesize": 409468928,
"download_url": "https://software.cisco.com/download/home/286320995/type/286321043/release/20.9.5.1"
},
{ {
"filename": "viptela-smart-19.2.0-genericx86-64.qcow2", "filename": "viptela-smart-19.2.0-genericx86-64.qcow2",
"version": "19.2.0", "version": "19.2.0",
@ -51,6 +65,18 @@
"images": { "images": {
"hda_disk_image": "viptela-smart-19.2.0-genericx86-64.qcow2" "hda_disk_image": "viptela-smart-19.2.0-genericx86-64.qcow2"
} }
},
{
"name": "20.9.5.1",
"images": {
"hda_disk_image": "viptela-smart-20.9.5.1-genericx86-64.qcow2"
}
},
{
"name": "20.12.4",
"images": {
"hda_disk_image": "viptela-smart-20.12.4-genericx86-64.qcow2"
}
} }
] ]
} }

View File

@ -10,8 +10,8 @@
"product_url": "http://www.cisco.com/", "product_url": "http://www.cisco.com/",
"registry_version": 4, "registry_version": 4,
"status": "experimental", "status": "experimental",
"maintainer": "Laurent LEVIER", "maintainer": "Christopher Uhrig",
"maintainer_email": "laurent.levier@orange.com", "maintainer_email": "christopher.uhrig@telekom.de",
"usage": "Initial username is admin, password is admin as well.", "usage": "Initial username is admin, password is admin as well.",
"qemu": { "qemu": {
"adapter_type": "vmxnet3", "adapter_type": "vmxnet3",
@ -26,6 +26,20 @@
}, },
"images": [ "images": [
{ {
"filename": "viptela-vmanage-20.12.4-genericx86-64.qcow2",
"version": "20.12.4",
"md5sum": "4e0d4c379623c495a0bb671a76a12f9f",
"filesize": 4951375872,
"download_url": "https://software.cisco.com/download/home/286320995/type/286321039/release/20.12.4"
},
{
"filename": "viptela-vmanage-20.9.5.2-genericx86-64.qcow2",
"version": "20.9.5.2",
"md5sum": "a8a4ebee0274541200f4fe94a739d454",
"filesize": 3133865984,
"download_url": "https://software.cisco.com/download/home/286320995/type/286321039/release/20.9.5.2"
},
{
"filename": "viptela-vmanage-19.2.0-genericx86-64.qcow2", "filename": "viptela-vmanage-19.2.0-genericx86-64.qcow2",
"version": "19.2.0", "version": "19.2.0",
"md5sum": "27ef126f178c6c929a36ad2cf6ed8db7", "md5sum": "27ef126f178c6c929a36ad2cf6ed8db7",
@ -55,6 +69,20 @@
"hda_disk_image": "viptela-vmanage-19.2.0-genericx86-64.qcow2", "hda_disk_image": "viptela-vmanage-19.2.0-genericx86-64.qcow2",
"hdb_disk_image": "empty30G.qcow2" "hdb_disk_image": "empty30G.qcow2"
} }
},
{
"name": "20.12.4",
"images": {
"hda_disk_image": "viptela-vmanage-20.12.4-genericx86-64.qcow2",
"hdb_disk_image": "empty30G.qcow2"
}
},
{
"name": "20.9.5.2",
"images": {
"hda_disk_image": "viptela-vmanage-20.9.5.2-genericx86-64.qcow2",
"hdb_disk_image": "empty30G.qcow2"
}
}, },
{ {
"name": "18.3.7", "name": "18.3.7",

View File

@ -29,7 +29,7 @@ except ImportError:
from importlib import resources as importlib_resources from importlib import resources as importlib_resources
from ..config import Config from ..config import Config
from ..utils import parse_version from ..utils import parse_version, md5sum
from ..utils.images import default_images_directory from ..utils.images import default_images_directory
from .project import Project from .project import Project
@ -289,12 +289,21 @@ class Controller:
except OSError as e: except OSError as e:
log.error(str(e)) log.error(str(e))
@staticmethod @staticmethod
def install_resource_files(dst_path, resource_name): def install_resource_files(dst_path, resource_name, upgrade_resources=True):
""" """
Install files from resources to user's file system Install files from resources to user's file system
""" """
def should_copy(src, dst, upgrade_resources):
if not os.path.exists(dst):
return True
if upgrade_resources is False:
return False
# copy the resource if it is different
return md5sum(src) != md5sum(dst)
if hasattr(sys, "frozen") and sys.platform.startswith("win"): if hasattr(sys, "frozen") and sys.platform.startswith("win"):
resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name)) resource_path = os.path.normpath(os.path.join(os.path.dirname(sys.executable), resource_name))
for filename in os.listdir(resource_path): for filename in os.listdir(resource_path):
@ -303,7 +312,7 @@ class Controller:
else: else:
for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir(): for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir():
full_path = os.path.join(dst_path, entry.name) full_path = os.path.join(dst_path, entry.name)
if entry.is_file() and not os.path.exists(full_path): if entry.is_file() and should_copy(str(entry), full_path, upgrade_resources):
log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"') log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
shutil.copy(str(entry), os.path.join(dst_path, entry.name)) shutil.copy(str(entry), os.path.join(dst_path, entry.name))
elif entry.is_dir(): elif entry.is_dir():
@ -319,7 +328,7 @@ class Controller:
dst_path = self.configs_path() dst_path = self.configs_path()
log.info(f"Installing base configs in '{dst_path}'") log.info(f"Installing base configs in '{dst_path}'")
try: try:
Controller.install_resource_files(dst_path, "configs") Controller.install_resource_files(dst_path, "configs", upgrade_resources=False)
except OSError as e: except OSError as e:
log.error(f"Could not install base config files to {dst_path}: {e}") log.error(f"Could not install base config files to {dst_path}: {e}")
@ -332,7 +341,7 @@ class Controller:
dst_path = self.disks_path() dst_path = self.disks_path()
log.info(f"Installing built-in disks in '{dst_path}'") log.info(f"Installing built-in disks in '{dst_path}'")
try: try:
Controller.install_resource_files(dst_path, "disks") Controller.install_resource_files(dst_path, "disks", upgrade_resources=False)
except OSError as e: except OSError as e:
log.error(f"Could not install disk files to {dst_path}: {e}") log.error(f"Could not install disk files to {dst_path}: {e}")

View File

@ -82,7 +82,7 @@ class ApplianceManager:
os.makedirs(appliances_path, exist_ok=True) os.makedirs(appliances_path, exist_ok=True)
return appliances_path return appliances_path
def builtin_appliances_path(self, delete_first=False): def builtin_appliances_path(self):
""" """
Get the built-in appliance storage directory Get the built-in appliance storage directory
""" """
@ -91,8 +91,6 @@ class ApplianceManager:
appname = vendor = "GNS3" appname = vendor = "GNS3"
resources_path = os.path.expanduser(server_config.get("resources_path", platformdirs.user_data_dir(appname, vendor, roaming=True))) resources_path = os.path.expanduser(server_config.get("resources_path", platformdirs.user_data_dir(appname, vendor, roaming=True)))
appliances_dir = os.path.join(resources_path, "appliances") appliances_dir = os.path.join(resources_path, "appliances")
if delete_first:
shutil.rmtree(appliances_dir, ignore_errors=True)
os.makedirs(appliances_dir, exist_ok=True) os.makedirs(appliances_dir, exist_ok=True)
return appliances_dir return appliances_dir
@ -101,7 +99,7 @@ class ApplianceManager:
At startup we copy the built-in appliances files. At startup we copy the built-in appliances files.
""" """
dst_path = self.builtin_appliances_path(delete_first=True) dst_path = self.builtin_appliances_path()
log.info(f"Installing built-in appliances in '{dst_path}'") log.info(f"Installing built-in appliances in '{dst_path}'")
from . import Controller from . import Controller
try: try:

View File

@ -588,7 +588,7 @@ class Project:
if node_type == "iou": if node_type == "iou":
async with self._iou_id_lock: async with self._iou_id_lock:
# wait for a IOU node to be completely created before adding a new one # wait for an IOU node to be completely created before adding a new one
# this is important otherwise we allocate the same application ID (used # this is important otherwise we allocate the same application ID (used
# to generate MAC addresses) when creating multiple IOU node at the same time # to generate MAC addresses) when creating multiple IOU node at the same time
if "properties" in kwargs.keys(): if "properties" in kwargs.keys():
@ -1275,6 +1275,9 @@ class Project:
p_work = pathlib.Path(location or self.path).parent.absolute() p_work = pathlib.Path(location or self.path).parent.absolute()
t0 = time.time() t0 = time.time()
new_project_id = str(uuid.uuid4()) new_project_id = str(uuid.uuid4())
if location:
new_project_path = p_work.joinpath(location)
else:
new_project_path = p_work.joinpath(new_project_id) new_project_path = p_work.joinpath(new_project_id)
# copy dir # copy dir
await wait_run_in_executor(shutil.copytree, self.path, new_project_path.as_posix(), symlinks=True, ignore_dangling_symlinks=True) await wait_run_in_executor(shutil.copytree, self.path, new_project_path.as_posix(), symlinks=True, ignore_dangling_symlinks=True)

View File

@ -57,7 +57,7 @@ class CrashReport:
Report crash to a third party service Report crash to a third party service
""" """
DSN = "https://088679fcf3aa35f775356982a80fe37c@o19455.ingest.us.sentry.io/38482" DSN = "https://b2ea85d65646be3f56aebf2458337918@o19455.ingest.us.sentry.io/38482"
_instance = None _instance = None
def __init__(self): def __init__(self):

View File

@ -46,6 +46,6 @@
gtag('config', 'G-0BT7QQV1W1'); gtag('config', 'G-0BT7QQV1W1');
</script> </script>
<script src="runtime.415291667f70565cd8ef.js" defer></script><script src="polyfills-es5.865074f5cd9a121111a2.js" nomodule defer></script><script src="polyfills.2f91a039d848e57ff02e.js" defer></script><script src="main.df8c319a3da6fb0e3629.js" defer></script> <script src="runtime.415291667f70565cd8ef.js" defer></script><script src="polyfills-es5.865074f5cd9a121111a2.js" nomodule defer></script><script src="polyfills.2f91a039d848e57ff02e.js" defer></script><script src="main.9297104511b6616fc55c.js" defer></script>
</body></html> </body></html>

View File

@ -23,6 +23,7 @@ import textwrap
import posixpath import posixpath
import socket import socket
import errno import errno
import hashlib
def force_unix_path(path): def force_unix_path(path):
@ -120,3 +121,14 @@ def is_ipv6_enabled() -> bool:
if e.errno == errno.EADDRINUSE: if e.errno == errno.EADDRINUSE:
return True return True
raise raise
def md5sum(filename):
"""
Calculate the MD5 checksum of a file.
"""
hash_md5 = hashlib.md5()
with open(filename, "rb") as f:
for chunk in iter(lambda: f.read(4096), b""):
hash_md5.update(chunk)
return hash_md5.hexdigest()

View File

@ -45,7 +45,7 @@ def list_images(emulator_type):
# We limit recursion to path outside the default images directory # We limit recursion to path outside the default images directory
# the reason is in the default directory manage file organization and # the reason is in the default directory manage file organization and
# it should be flatten to keep things simple # it should be flat to keep things simple
recurse = True recurse = True
if os.path.commonprefix([directory, general_images_directory]) == general_images_directory: if os.path.commonprefix([directory, general_images_directory]) == general_images_directory:
recurse = False recurse = False
@ -53,35 +53,51 @@ def list_images(emulator_type):
directory = os.path.normpath(directory) directory = os.path.normpath(directory)
for root, _, filenames in _os_walk(directory, recurse=recurse): for root, _, filenames in _os_walk(directory, recurse=recurse):
for filename in filenames: for filename in filenames:
if filename not in files: if filename in files:
log.debug("File {} has already been found, skipping...".format(filename))
continue
if filename.endswith(".md5sum") or filename.startswith("."): if filename.endswith(".md5sum") or filename.startswith("."):
continue continue
elif ((filename.endswith(".image") or filename.endswith(".bin")) and emulator_type == "dynamips") \
or ((filename.endswith(".bin") or filename.startswith("i86bi")) and emulator_type == "iou") \
or (not filename.endswith(".bin") and not filename.endswith(".image") and emulator_type == "qemu"):
files.add(filename) files.add(filename)
filesize = os.stat(os.path.join(root, filename)).st_size
if filesize < 7:
log.debug("File {} is too small to be an image, skipping...".format(filename))
continue
try:
with open(os.path.join(root, filename), "rb") as f:
# read the first 7 bytes of the file.
elf_header_start = f.read(7)
if emulator_type == "dynamips" and elf_header_start != b'\x7fELF\x01\x02\x01':
# IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1
log.warning("IOS image {} does not start with a valid ELF magic number, skipping...".format(filename))
continue
elif emulator_type == "iou" and elf_header_start != b'\x7fELF\x02\x01\x01' and elf_header_start != b'\x7fELF\x01\x01\x01':
# IOU images must start with the ELF magic number, be 32-bit or 64-bit, little endian and have an ELF version of 1
log.warning("IOU image {} does not start with a valid ELF magic number, skipping...".format(filename))
continue
elif emulator_type == "qemu" and elf_header_start[:4] == b'\x7fELF':
# QEMU images should not start with an ELF magic number
log.warning("QEMU image {} starts with an ELF magic number, skipping...".format(filename))
continue
# It the image is located in the standard directory the path is relative # It the image is located in the standard directory the path is relative
if os.path.commonprefix([root, default_directory]) != default_directory: if os.path.commonprefix([root, default_directory]) != default_directory:
path = os.path.join(root, filename) path = os.path.join(root, filename)
else: else:
path = os.path.relpath(os.path.join(root, filename), default_directory) path = os.path.relpath(os.path.join(root, filename), default_directory)
try: images.append(
if emulator_type in ["dynamips", "iou"]: {
with open(os.path.join(root, filename), "rb") as f:
# read the first 7 bytes of the file.
elf_header_start = f.read(7)
# valid IOU or IOS images must start with the ELF magic number, be 32-bit or 64-bit,
# little endian and have an ELF version of 1
if elf_header_start != b'\x7fELF\x02\x01\x01' and elf_header_start != b'\x7fELF\x01\x01\x01':
continue
images.append({
"filename": filename, "filename": filename,
"path": force_unix_path(path), "path": force_unix_path(path),
"md5sum": md5sum(os.path.join(root, filename)), "md5sum": md5sum(os.path.join(root, filename)),
"filesize": os.stat(os.path.join(root, filename)).st_size}) "filesize": filesize
}
)
except OSError as e: except OSError as e:
log.warning("Can't add image {}: {}".format(path, str(e))) log.warning("Can't add image {}: {}".format(path, str(e)))
return images return images

View File

@ -23,8 +23,8 @@
# or negative for a release candidate or beta (after the base version # or negative for a release candidate or beta (after the base version
# number has been incremented) # number has been incremented)
__version__ = "2.2.51" __version__ = "2.2.52"
__version_info__ = (2, 2, 51, 0) __version_info__ = (2, 2, 52, 0)
if "dev" in __version__: if "dev" in __version__:
try: try:

View File

@ -8,6 +8,6 @@ psutil>=6.1.0
async-timeout>=4.0.3,<4.1 async-timeout>=4.0.3,<4.1
distro>=1.9.0 distro>=1.9.0
py-cpuinfo>=9.0.0,<10.0 py-cpuinfo>=9.0.0,<10.0
platformdirs>=2.4.0 platformdirs>=2.4.0,<3 # platformdirs >=3 conflicts when building Debian packages
importlib-resources>=1.3; python_version < '3.9' importlib-resources>=1.3; python_version < '3.9'
truststore>=0.10.0; python_version >= '3.10' truststore>=0.10.0; python_version >= '3.10'

View File

@ -65,7 +65,7 @@ setup(
zip_safe=False, zip_safe=False,
platforms="any", platforms="any",
python_requires=">=3.8", python_requires=">=3.8",
setup_requires=["setuptools>=61.0"], setup_requires=["setuptools>=45.2"],
classifiers=[ classifiers=[
"Development Status :: 5 - Production/Stable", "Development Status :: 5 - Production/Stable",
"Environment :: Console", "Environment :: Console",

View File

@ -239,12 +239,12 @@ async def test_list_images(qemu, tmpdir):
os.makedirs(tmp_images_dir, exist_ok=True) os.makedirs(tmp_images_dir, exist_ok=True)
for image in fake_images: for image in fake_images:
with open(os.path.join(tmp_images_dir, image), "w+") as f: with open(os.path.join(tmp_images_dir, image), "w+") as f:
f.write("1") f.write("1234567")
with patch("gns3server.utils.images.default_images_directory", return_value=str(tmp_images_dir)): with patch("gns3server.utils.images.default_images_directory", return_value=str(tmp_images_dir)):
assert sorted(await qemu.list_images(), key=lambda k: k['filename']) == [ assert sorted(await qemu.list_images(), key=lambda k: k['filename']) == [
{"filename": "a.qcow2", "path": "a.qcow2", "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1}, {"filename": "a.qcow2", "path": "a.qcow2", "md5sum": "fcea920f7412b5da7be0cf42b8c93759", "filesize": 7},
{"filename": "b.qcow2", "path": "b.qcow2", "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1} {"filename": "b.qcow2", "path": "b.qcow2", "md5sum": "fcea920f7412b5da7be0cf42b8c93759", "filesize": 7}
] ]
@ -255,19 +255,19 @@ async def test_list_images_recursives(qemu, tmpdir):
fake_images = ["a.qcow2", "b.qcow2", ".blu.qcow2", "a.qcow2.md5sum"] fake_images = ["a.qcow2", "b.qcow2", ".blu.qcow2", "a.qcow2.md5sum"]
for image in fake_images: for image in fake_images:
with open(os.path.join(tmp_images_dir, image), "w+") as f: with open(os.path.join(tmp_images_dir, image), "w+") as f:
f.write("1") f.write("1234567")
os.makedirs(os.path.join(tmp_images_dir, "c")) os.makedirs(os.path.join(tmp_images_dir, "c"))
fake_images = ["c.qcow2", "c.qcow2.md5sum"] fake_images = ["c.qcow2", "c.qcow2.md5sum"]
for image in fake_images: for image in fake_images:
with open(os.path.join(tmp_images_dir, "c", image), "w+") as f: with open(os.path.join(tmp_images_dir, "c", image), "w+") as f:
f.write("1") f.write("1234567")
with patch("gns3server.utils.images.default_images_directory", return_value=str(tmp_images_dir)): with patch("gns3server.utils.images.default_images_directory", return_value=str(tmp_images_dir)):
assert sorted(await qemu.list_images(), key=lambda k: k['filename']) == [ assert sorted(await qemu.list_images(), key=lambda k: k['filename']) == [
{"filename": "a.qcow2", "path": "a.qcow2", "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1}, {"filename": "a.qcow2", "path": "a.qcow2", "md5sum": "fcea920f7412b5da7be0cf42b8c93759", "filesize": 7},
{"filename": "b.qcow2", "path": "b.qcow2", "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1}, {"filename": "b.qcow2", "path": "b.qcow2", "md5sum": "fcea920f7412b5da7be0cf42b8c93759", "filesize": 7},
{"filename": "c.qcow2", "path": force_unix_path(os.path.sep.join(["c", "c.qcow2"])), "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1} {"filename": "c.qcow2", "path": force_unix_path(os.path.sep.join(["c", "c.qcow2"])), "md5sum": "fcea920f7412b5da7be0cf42b8c93759", "filesize": 7}
] ]

View File

@ -144,7 +144,7 @@ def fake_image(tmpdir):
path = str(tmpdir / "7200.bin") path = str(tmpdir / "7200.bin")
with open(path, "wb+") as f: with open(path, "wb+") as f:
f.write(b'\x7fELF\x01\x01\x01') f.write(b'\x7fELF\x01\x02\x01')
os.chmod(path, stat.S_IREAD) os.chmod(path, stat.S_IREAD)
return path return path
@ -168,7 +168,7 @@ async def test_images(compute_api, tmpdir, fake_image, fake_file):
assert response.json == [{"filename": "7200.bin", assert response.json == [{"filename": "7200.bin",
"path": "7200.bin", "path": "7200.bin",
"filesize": 7, "filesize": 7,
"md5sum": "e573e8f5c93c6c00783f20c7a170aa6c" "md5sum": "b0d5aa897d937aced5a6b1046e8f7e2e"
}] }]

View File

@ -45,7 +45,7 @@ def fake_qemu_vm(images_dir):
img_dir = os.path.join(images_dir, "QEMU") img_dir = os.path.join(images_dir, "QEMU")
bin_path = os.path.join(img_dir, "linux载.img") bin_path = os.path.join(img_dir, "linux载.img")
with open(bin_path, "w+") as f: with open(bin_path, "w+") as f:
f.write("1") f.write("1234567")
os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR) os.chmod(bin_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
return bin_path return bin_path
@ -101,7 +101,7 @@ async def test_qemu_create_with_params(compute_api, compute_project, base_params
assert response.json["project_id"] == compute_project.id assert response.json["project_id"] == compute_project.id
assert response.json["ram"] == 1024 assert response.json["ram"] == 1024
assert response.json["hda_disk_image"] == "linux载.img" assert response.json["hda_disk_image"] == "linux载.img"
assert response.json["hda_disk_image_md5sum"] == "c4ca4238a0b923820dcc509a6f75849b" assert response.json["hda_disk_image_md5sum"] == "fcea920f7412b5da7be0cf42b8c93759"
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows") @pytest.mark.skipif(sys.platform.startswith("win"), reason="Not supported on Windows")
@ -279,7 +279,7 @@ async def test_images(compute_api, fake_qemu_vm):
response = await compute_api.get("/qemu/images") response = await compute_api.get("/qemu/images")
assert response.status == 200 assert response.status == 200
assert {"filename": "linux载.img", "path": "linux载.img", "md5sum": "c4ca4238a0b923820dcc509a6f75849b", "filesize": 1} in response.json assert {"filename": "linux载.img", "path": "linux载.img", "md5sum": "fcea920f7412b5da7be0cf42b8c93759", "filesize": 7} in response.json
@pytest.mark.skipif(sys.platform.startswith("win"), reason="Does not work on Windows") @pytest.mark.skipif(sys.platform.startswith("win"), reason="Does not work on Windows")

View File

@ -114,30 +114,49 @@ def test_remove_checksum(tmpdir):
def test_list_images(tmpdir): def test_list_images(tmpdir):
path1 = tmpdir / "images1" / "IOS" / "test1.image" # IOS image in the images directory
path1.write(b'\x7fELF\x01\x01\x01', ensure=True) ios_image_1 = tmpdir / "images1" / "IOS" / "ios_image_1.image"
path1 = force_unix_path(str(path1)) ios_image_1.write(b'\x7fELF\x01\x02\x01', ensure=True)
ios_image_1 = force_unix_path(str(ios_image_1))
path2 = tmpdir / "images2" / "test2.image" # IOS image in an additional images path
path2.write(b'\x7fELF\x01\x01\x01', ensure=True) ios_image_2 = tmpdir / "images2" / "ios_image_2.image"
path2 = force_unix_path(str(path2)) ios_image_2.write(b'\x7fELF\x01\x02\x01', ensure=True)
ios_image_2 = force_unix_path(str(ios_image_2))
# Invalid image because not a valid elf file # Not a valid elf file
path = tmpdir / "images2" / "test_invalid.image" not_elf_file = tmpdir / "images1" / "IOS" / "not_elf.image"
path.write(b'NOTANELF', ensure=True) not_elf_file.write(b'NOTANELF', ensure=True)
not_elf_file = force_unix_path(str(not_elf_file))
# Invalid image because it is very small
small_file = tmpdir / "images1" / "too_small.image"
small_file.write(b'1', ensure=True)
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
path3 = tmpdir / "images1" / "IOU" / "test3.bin" # 64-bit IOU image
path3.write(b'\x7fELF\x02\x01\x01', ensure=True) iou_image_1 = tmpdir / "images1" / "IOU" / "iou64.bin"
path3 = force_unix_path(str(path3)) iou_image_1.write(b'\x7fELF\x02\x01\x01', ensure=True)
iou_image_1 = force_unix_path(str(iou_image_1))
# 32-bit IOU image
iou_image_2 = tmpdir / "images1" / "IOU" / "iou32.bin"
iou_image_2.write(b'\x7fELF\x01\x01\x01', ensure=True) # 32-bit IOU image
iou_image_2 = force_unix_path(str(iou_image_2))
path4 = tmpdir / "images1" / "QEMU" / "test4.qcow2"
path4.write("1", ensure=True)
path4 = force_unix_path(str(path4))
path5 = tmpdir / "images1" / "QEMU" / "test4.qcow2.md5sum" # Qemu image
path5.write("1", ensure=True) qemu_image_1 = tmpdir / "images1" / "QEMU" / "qemu_image.qcow2"
path5 = force_unix_path(str(path5)) qemu_image_1.write("1234567", ensure=True)
qemu_image_1 = force_unix_path(str(qemu_image_1))
# ELF file inside the Qemu
elf_file = tmpdir / "images1" / "QEMU" / "elf_file.bin"
elf_file.write(b'\x7fELF\x02\x01\x01', ensure=True) # ELF file
elf_file = force_unix_path(str(elf_file))
md5sum_file = tmpdir / "images1" / "QEMU" / "image.qcow2.md5sum"
md5sum_file.write("1", ensure=True)
md5sum_file = force_unix_path(str(md5sum_file))
with patch("gns3server.config.Config.get_section_config", return_value={ with patch("gns3server.config.Config.get_section_config", return_value={
"images_path": str(tmpdir / "images1"), "images_path": str(tmpdir / "images1"),
@ -146,34 +165,40 @@ def test_list_images(tmpdir):
assert list_images("dynamips") == [ assert list_images("dynamips") == [
{ {
'filename': 'test1.image', 'filename': 'ios_image_1.image',
'filesize': 7, 'filesize': 7,
'md5sum': 'e573e8f5c93c6c00783f20c7a170aa6c', 'md5sum': 'b0d5aa897d937aced5a6b1046e8f7e2e',
'path': 'test1.image' 'path': 'ios_image_1.image'
}, },
{ {
'filename': 'test2.image', 'filename': 'ios_image_2.image',
'filesize': 7, 'filesize': 7,
'md5sum': 'e573e8f5c93c6c00783f20c7a170aa6c', 'md5sum': 'b0d5aa897d937aced5a6b1046e8f7e2e',
'path': str(path2) 'path': str(ios_image_2)
} }
] ]
if sys.platform.startswith("linux"): if sys.platform.startswith("linux"):
assert list_images("iou") == [ assert list_images("iou") == [
{ {
'filename': 'test3.bin', 'filename': 'iou64.bin',
'filesize': 7, 'filesize': 7,
'md5sum': 'c73626d23469519894d58bc98bee9655', 'md5sum': 'c73626d23469519894d58bc98bee9655',
'path': 'test3.bin' 'path': 'iou64.bin'
},
{
'filename': 'iou32.bin',
'filesize': 7,
'md5sum': 'e573e8f5c93c6c00783f20c7a170aa6c',
'path': 'iou32.bin'
} }
] ]
assert list_images("qemu") == [ assert list_images("qemu") == [
{ {
'filename': 'test4.qcow2', 'filename': 'qemu_image.qcow2',
'filesize': 1, 'filesize': 7,
'md5sum': 'c4ca4238a0b923820dcc509a6f75849b', 'md5sum': 'fcea920f7412b5da7be0cf42b8c93759',
'path': 'test4.qcow2' 'path': 'qemu_image.qcow2'
} }
] ]