gns3-server 3.0.0rc2__py3-none-any.whl → 3.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of gns3-server might be problematic. Click here for more details.
- {gns3_server-3.0.0rc2.dist-info → gns3_server-3.0.2.dist-info}/METADATA +19 -18
- {gns3_server-3.0.0rc2.dist-info → gns3_server-3.0.2.dist-info}/RECORD +48 -42
- {gns3_server-3.0.0rc2.dist-info → gns3_server-3.0.2.dist-info}/WHEEL +1 -1
- gns3server/api/routes/controller/images.py +58 -21
- gns3server/api/routes/controller/projects.py +20 -4
- gns3server/api/routes/controller/templates.py +23 -2
- gns3server/api/routes/index.py +3 -3
- gns3server/api/server.py +38 -3
- gns3server/appliances/arista-veos.gns3a +20 -514
- gns3server/appliances/cisco-7200.gns3a +26 -0
- gns3server/appliances/cisco-asav.gns3a +14 -1
- gns3server/appliances/cisco-iou-l2.gns3a +16 -4
- gns3server/appliances/cisco-iou-l3.gns3a +16 -4
- gns3server/appliances/innovaphone-app.gns3a +50 -0
- gns3server/appliances/innovaphone-ipva.gns3a +78 -0
- gns3server/appliances/pfsense.gns3a +14 -0
- gns3server/compute/docker/__init__.py +1 -1
- gns3server/compute/iou/iou_vm.py +22 -12
- gns3server/controller/__init__.py +48 -38
- gns3server/controller/appliance_manager.py +2 -2
- gns3server/controller/compute.py +8 -3
- gns3server/controller/node.py +2 -6
- gns3server/controller/project.py +1 -2
- gns3server/crash_report.py +1 -1
- gns3server/db/repositories/images.py +22 -3
- gns3server/db/repositories/pools.py +1 -1
- gns3server/db/repositories/templates.py +11 -0
- gns3server/db/tasks.py +120 -79
- gns3server/disks/empty100G.qcow2 +0 -0
- gns3server/disks/empty200G.qcow2 +0 -0
- gns3server/disks/empty30G.qcow2 +0 -0
- gns3server/disks/empty8G.qcow2 +0 -0
- gns3server/schemas/config.py +1 -1
- gns3server/server.py +3 -3
- gns3server/services/authentication.py +9 -6
- gns3server/static/favicon.ico +0 -0
- gns3server/static/redoc.standalone.js +1782 -0
- gns3server/static/swagger-ui-bundle.js +2 -0
- gns3server/static/swagger-ui.css +3 -0
- gns3server/static/web-ui/index.html +1 -1
- gns3server/static/web-ui/main.62c99707e4709a56.js +1 -0
- gns3server/utils/asyncio/__init__.py +4 -12
- gns3server/utils/asyncio/pool.py +1 -4
- gns3server/utils/images.py +62 -39
- gns3server/version.py +2 -2
- gns3server/static/web-ui/main.ed82697b58d803e7.js +0 -1
- {gns3_server-3.0.0rc2.dist-info → gns3_server-3.0.2.dist-info}/LICENSE +0 -0
- {gns3_server-3.0.0rc2.dist-info → gns3_server-3.0.2.dist-info}/entry_points.txt +0 -0
- {gns3_server-3.0.0rc2.dist-info → gns3_server-3.0.2.dist-info}/top_level.txt +0 -0
|
@@ -97,18 +97,10 @@ async def wait_for_process_termination(process, timeout=10):
|
|
|
97
97
|
:param timeout: Timeout in seconds
|
|
98
98
|
"""
|
|
99
99
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return
|
|
105
|
-
else:
|
|
106
|
-
while timeout > 0:
|
|
107
|
-
if process.returncode is not None:
|
|
108
|
-
return
|
|
109
|
-
await asyncio.sleep(0.1)
|
|
110
|
-
timeout -= 0.1
|
|
111
|
-
raise asyncio.TimeoutError()
|
|
100
|
+
try:
|
|
101
|
+
await asyncio.wait_for(process.wait(), timeout=timeout)
|
|
102
|
+
except ProcessLookupError:
|
|
103
|
+
return
|
|
112
104
|
|
|
113
105
|
|
|
114
106
|
async def _check_process(process, termination_callback):
|
gns3server/utils/asyncio/pool.py
CHANGED
|
@@ -40,10 +40,7 @@ class Pool:
|
|
|
40
40
|
while len(self._tasks) > 0 or len(pending) > 0:
|
|
41
41
|
while len(self._tasks) > 0 and len(pending) < self._concurrency:
|
|
42
42
|
task, args, kwargs = self._tasks.pop(0)
|
|
43
|
-
|
|
44
|
-
t = asyncio.create_task(task(*args, **kwargs))
|
|
45
|
-
else:
|
|
46
|
-
t = asyncio.get_event_loop().create_task(task(*args, **kwargs))
|
|
43
|
+
t = asyncio.create_task(task(*args, **kwargs))
|
|
47
44
|
pending.add(t)
|
|
48
45
|
(done, pending) = await asyncio.wait(pending, return_when=asyncio.FIRST_COMPLETED)
|
|
49
46
|
for task in done:
|
gns3server/utils/images.py
CHANGED
|
@@ -20,6 +20,11 @@ import stat
|
|
|
20
20
|
import aiofiles
|
|
21
21
|
import shutil
|
|
22
22
|
|
|
23
|
+
try:
|
|
24
|
+
import importlib_resources
|
|
25
|
+
except ImportError:
|
|
26
|
+
from importlib import resources as importlib_resources
|
|
27
|
+
|
|
23
28
|
from typing import List, AsyncGenerator
|
|
24
29
|
from ..config import Config
|
|
25
30
|
from . import force_unix_path
|
|
@@ -62,45 +67,63 @@ async def list_images(image_type):
|
|
|
62
67
|
directory = os.path.normpath(directory)
|
|
63
68
|
for root, _, filenames in _os_walk(directory, recurse=recurse):
|
|
64
69
|
for filename in filenames:
|
|
65
|
-
if filename
|
|
66
|
-
|
|
70
|
+
if filename in files:
|
|
71
|
+
log.debug("File {} has already been found, skipping...".format(filename))
|
|
72
|
+
continue
|
|
73
|
+
if filename.endswith(".md5sum") or filename.startswith("."):
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
files.add(filename)
|
|
77
|
+
|
|
78
|
+
# It the image is located in the standard directory the path is relative
|
|
79
|
+
if os.path.commonprefix([root, default_directory]) != default_directory:
|
|
80
|
+
path = os.path.join(root, filename)
|
|
81
|
+
else:
|
|
82
|
+
path = os.path.relpath(os.path.join(root, filename), default_directory)
|
|
83
|
+
|
|
84
|
+
filesize = os.stat(os.path.join(root, filename)).st_size
|
|
85
|
+
if filesize < 7:
|
|
86
|
+
log.debug(f"File {filename} is too small to be an image, skipping...")
|
|
87
|
+
continue
|
|
88
|
+
|
|
89
|
+
try:
|
|
90
|
+
with open(os.path.join(root, filename), "rb") as f:
|
|
91
|
+
# read the first 7 bytes of the file.
|
|
92
|
+
elf_header_start = f.read(7)
|
|
93
|
+
if image_type == "dynamips" and elf_header_start != b'\x7fELF\x01\x02\x01':
|
|
94
|
+
# IOS images must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1
|
|
95
|
+
log.warning(f"IOS image {filename} does not start with a valid ELF magic number, skipping...")
|
|
96
|
+
continue
|
|
97
|
+
elif image_type == "iou" and elf_header_start != b'\x7fELF\x02\x01\x01' and elf_header_start != b'\x7fELF\x01\x01\x01':
|
|
98
|
+
# IOU images must start with the ELF magic number, be 32-bit or 64-bit, little endian and have an ELF version of 1
|
|
99
|
+
log.warning(f"IOU image {filename} does not start with a valid ELF magic number, skipping...")
|
|
100
|
+
continue
|
|
101
|
+
elif image_type == "qemu" and elf_header_start[:4] == b'\x7fELF':
|
|
102
|
+
# QEMU images should not start with an ELF magic number
|
|
103
|
+
log.warning(f"QEMU image {filename} starts with an ELF magic number, skipping...")
|
|
67
104
|
continue
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
path = os.path.relpath(os.path.join(root, filename), default_directory)
|
|
80
|
-
|
|
81
|
-
try:
|
|
82
|
-
if image_type in ["dynamips", "iou"]:
|
|
83
|
-
with open(os.path.join(root, filename), "rb") as f:
|
|
84
|
-
# read the first 7 bytes of the file.
|
|
85
|
-
elf_header_start = f.read(7)
|
|
86
|
-
# valid IOU or IOS images must start with the ELF magic number, be 32-bit or 64-bit,
|
|
87
|
-
# little endian and have an ELF version of 1
|
|
88
|
-
if elf_header_start != b'\x7fELF\x02\x01\x01' and elf_header_start != b'\x7fELF\x01\x01\x01':
|
|
89
|
-
continue
|
|
90
|
-
|
|
91
|
-
images.append(
|
|
92
|
-
{
|
|
93
|
-
"filename": filename,
|
|
94
|
-
"path": force_unix_path(path),
|
|
95
|
-
"md5sum": await wait_run_in_executor(md5sum, os.path.join(root, filename)),
|
|
96
|
-
"filesize": os.stat(os.path.join(root, filename)).st_size,
|
|
97
|
-
}
|
|
98
|
-
)
|
|
99
|
-
except OSError as e:
|
|
100
|
-
log.warning(f"Can't add image {path}: {str(e)}")
|
|
105
|
+
|
|
106
|
+
images.append(
|
|
107
|
+
{
|
|
108
|
+
"filename": filename,
|
|
109
|
+
"path": force_unix_path(path),
|
|
110
|
+
"md5sum": await wait_run_in_executor(md5sum, os.path.join(root, filename)),
|
|
111
|
+
"filesize": filesize,
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
except OSError as e:
|
|
115
|
+
log.warning(f"Can't add image {path}: {str(e)}")
|
|
101
116
|
return images
|
|
102
117
|
|
|
103
118
|
|
|
119
|
+
def get_builtin_disks() -> List[str]:
|
|
120
|
+
builtin_disks = []
|
|
121
|
+
for entry in importlib_resources.files('gns3server').joinpath("disks").iterdir():
|
|
122
|
+
if entry.is_file():
|
|
123
|
+
builtin_disks.append(entry.name)
|
|
124
|
+
return builtin_disks
|
|
125
|
+
|
|
126
|
+
|
|
104
127
|
async def read_image_info(path: str, expected_image_type: str = None) -> dict:
|
|
105
128
|
|
|
106
129
|
header_magic_len = 7
|
|
@@ -108,7 +131,7 @@ async def read_image_info(path: str, expected_image_type: str = None) -> dict:
|
|
|
108
131
|
async with aiofiles.open(path, "rb") as f:
|
|
109
132
|
image_header = await f.read(header_magic_len) # read the first 7 bytes of the file
|
|
110
133
|
if len(image_header) >= header_magic_len:
|
|
111
|
-
detected_image_type = check_valid_image_header(image_header)
|
|
134
|
+
detected_image_type = check_valid_image_header(path, image_header)
|
|
112
135
|
if expected_image_type and detected_image_type != expected_image_type:
|
|
113
136
|
raise InvalidImageError(f"Detected image type for '{path}' is {detected_image_type}, "
|
|
114
137
|
f"expected type is {expected_image_type}")
|
|
@@ -292,7 +315,7 @@ class InvalidImageError(Exception):
|
|
|
292
315
|
return self._message
|
|
293
316
|
|
|
294
317
|
|
|
295
|
-
def check_valid_image_header(data: bytes, allow_raw_image: bool = False) -> str:
|
|
318
|
+
def check_valid_image_header(path: str, data: bytes, allow_raw_image: bool = False) -> str:
|
|
296
319
|
|
|
297
320
|
if data[:7] == b'\x7fELF\x01\x02\x01':
|
|
298
321
|
# for IOS images: file must start with the ELF magic number, be 32-bit, big endian and have an ELF version of 1
|
|
@@ -307,7 +330,7 @@ def check_valid_image_header(data: bytes, allow_raw_image: bool = False) -> str:
|
|
|
307
330
|
else:
|
|
308
331
|
if allow_raw_image is True:
|
|
309
332
|
return "qemu"
|
|
310
|
-
raise InvalidImageError("
|
|
333
|
+
raise InvalidImageError(f"{path}: could not detect image type, please make sure it is a valid image")
|
|
311
334
|
|
|
312
335
|
|
|
313
336
|
async def write_image(
|
|
@@ -332,7 +355,7 @@ async def write_image(
|
|
|
332
355
|
async for chunk in stream:
|
|
333
356
|
if check_image_header and len(chunk) >= header_magic_len:
|
|
334
357
|
check_image_header = False
|
|
335
|
-
image_type = check_valid_image_header(chunk, allow_raw_image)
|
|
358
|
+
image_type = check_valid_image_header(image_path, chunk, allow_raw_image)
|
|
336
359
|
await f.write(chunk)
|
|
337
360
|
checksum.update(chunk)
|
|
338
361
|
|
gns3server/version.py
CHANGED
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
# or negative for a release candidate or beta (after the base version
|
|
23
23
|
# number has been incremented)
|
|
24
24
|
|
|
25
|
-
__version__ = "3.0.
|
|
26
|
-
__version_info__ = (3, 0,
|
|
25
|
+
__version__ = "3.0.2"
|
|
26
|
+
__version_info__ = (3, 0, 2, 0)
|
|
27
27
|
|
|
28
28
|
if "dev" in __version__:
|
|
29
29
|
try:
|