gns3-server 3.0.0a6__py3-none-any.whl → 3.0.0b2__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.0a6.dist-info → gns3_server-3.0.0b2.dist-info}/METADATA +17 -18
- {gns3_server-3.0.0a6.dist-info → gns3_server-3.0.0b2.dist-info}/RECORD +68 -67
- {gns3_server-3.0.0a6.dist-info → gns3_server-3.0.0b2.dist-info}/WHEEL +1 -1
- gns3server/api/routes/compute/notifications.py +1 -5
- gns3server/api/routes/controller/acl.py +6 -2
- gns3server/api/routes/controller/controller.py +0 -5
- gns3server/api/routes/controller/projects.py +0 -4
- gns3server/appliances/asterfusion-vAsterNOS.gns3a +49 -0
- gns3server/appliances/bird.gns3a +1 -1
- gns3server/appliances/bird2.gns3a +14 -0
- gns3server/appliances/cisco-asav.gns3a +13 -0
- gns3server/appliances/cisco-c8000v.gns3a +13 -0
- gns3server/appliances/cisco-cat9k.gns3a +16 -3
- gns3server/appliances/cisco-csr1000v.gns3a +13 -0
- gns3server/appliances/cisco-iosv.gns3a +14 -0
- gns3server/appliances/cisco-iosxrv9k.gns3a +13 -0
- gns3server/appliances/cisco-iou-l2.gns3a +12 -0
- gns3server/appliances/cisco-iou-l3.gns3a +12 -0
- gns3server/appliances/cisco-nxosv9k.gns3a +70 -0
- gns3server/appliances/clavister-netsheild.gns3a +15 -2
- gns3server/appliances/clavister-netwall.gns3a +15 -2
- gns3server/appliances/debian.gns3a +7 -7
- gns3server/appliances/dell-ftos.gns3a +21 -4
- gns3server/appliances/exos.gns3a +13 -151
- gns3server/appliances/fedora-cloud.gns3a +12 -42
- gns3server/appliances/fortianalyzer.gns3a +42 -0
- gns3server/appliances/fortigate.gns3a +56 -0
- gns3server/appliances/fortimanager.gns3a +43 -1
- gns3server/appliances/freebsd.gns3a +17 -2
- gns3server/appliances/huawei-ne40e.gns3a +51 -2
- gns3server/appliances/mikrotik-chr.gns3a +9 -0
- gns3server/appliances/mikrotik-rb1100ahx4-dude-edition.gns3a +1 -1
- gns3server/appliances/mikrotik-rb450g.gns3a +1 -1
- gns3server/appliances/mikrotik-rb450gx4.gns3a +1 -1
- gns3server/appliances/open-media-vault.gns3a +16 -0
- gns3server/appliances/opnsense.gns3a +13 -0
- gns3server/appliances/ostinato.gns3a +13 -0
- gns3server/appliances/rhel.gns3a +17 -3
- gns3server/appliances/rockylinux.gns3a +30 -0
- gns3server/appliances/ubuntu-cloud.gns3a +5 -5
- gns3server/compute/docker/__init__.py +2 -2
- gns3server/compute/docker/docker_vm.py +12 -3
- gns3server/compute/dynamips/__init__.py +3 -0
- gns3server/compute/dynamips/nodes/router.py +1 -1
- gns3server/compute/qemu/__init__.py +10 -3
- gns3server/compute/qemu/qemu_vm.py +7 -1
- gns3server/compute/vpcs/vpcs_vm.py +3 -0
- gns3server/controller/__init__.py +6 -6
- gns3server/controller/export_project.py +5 -0
- gns3server/controller/import_project.py +5 -2
- gns3server/controller/project.py +19 -5
- gns3server/crash_report.py +1 -1
- gns3server/db/models/templates.py +2 -2
- gns3server/db_migrations/README +1 -0
- gns3server/main.py +5 -1
- gns3server/schemas/compute/docker_nodes.py +1 -1
- gns3server/schemas/controller/templates/docker_templates.py +1 -1
- gns3server/server.py +3 -4
- gns3server/services/authentication.py +5 -5
- gns3server/static/web-ui/3rdpartylicenses.txt +23 -0
- gns3server/static/web-ui/index.html +1 -1
- gns3server/static/web-ui/main.8e8e3d55e78d84fc.js +1 -0
- gns3server/static/web-ui/runtime.24fa95b7061d7056.js +1 -0
- gns3server/utils/asyncio/telnet_server.py +17 -4
- gns3server/version.py +1 -1
- gns3server/static/web-ui/main.5699f8c67ccc8359.js +0 -1
- gns3server/static/web-ui/runtime.53e0b4d68251ad21.js +0 -1
- {gns3_server-3.0.0a6.dist-info → gns3_server-3.0.0b2.dist-info}/LICENSE +0 -0
- {gns3_server-3.0.0a6.dist-info → gns3_server-3.0.0b2.dist-info}/entry_points.txt +0 -0
- {gns3_server-3.0.0a6.dist-info → gns3_server-3.0.0b2.dist-info}/top_level.txt +0 -0
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"documentation_url": "http://wiki.mikrotik.com/wiki/Manual:CHR",
|
|
9
9
|
"product_name": "MikroTik RouterBOARD 1100AHx4 Dude Edition",
|
|
10
10
|
"product_url": "http://www.mikrotik.com/download",
|
|
11
|
-
"registry_version":
|
|
11
|
+
"registry_version": 4,
|
|
12
12
|
"status": "stable",
|
|
13
13
|
"maintainer": "Azorian Solutions",
|
|
14
14
|
"maintainer_email": "help@azorian.solutions",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"documentation_url": "http://wiki.mikrotik.com/wiki/Manual:CHR",
|
|
9
9
|
"product_name": "MikroTik RouterBOARD 450G",
|
|
10
10
|
"product_url": "http://www.mikrotik.com/download",
|
|
11
|
-
"registry_version":
|
|
11
|
+
"registry_version": 4,
|
|
12
12
|
"status": "stable",
|
|
13
13
|
"maintainer": "Azorian Solutions",
|
|
14
14
|
"maintainer_email": "help@azorian.solutions",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"documentation_url": "http://wiki.mikrotik.com/wiki/Manual:CHR",
|
|
9
9
|
"product_name": "MikroTik RouterBOARD 450Gx4",
|
|
10
10
|
"product_url": "http://www.mikrotik.com/download",
|
|
11
|
-
"registry_version":
|
|
11
|
+
"registry_version": 4,
|
|
12
12
|
"status": "stable",
|
|
13
13
|
"maintainer": "Azorian Solutions",
|
|
14
14
|
"maintainer_email": "help@azorian.solutions",
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
"kvm": "require"
|
|
27
27
|
},
|
|
28
28
|
"images": [
|
|
29
|
+
{
|
|
30
|
+
"filename": "openmediavault_7.0.32-amd64.iso",
|
|
31
|
+
"version": "7.0.32",
|
|
32
|
+
"md5sum": "36d1fda7de0c8dd6806a65145845d362",
|
|
33
|
+
"filesize": 981467136,
|
|
34
|
+
"download_url": "https://www.openmediavault.org/download.html",
|
|
35
|
+
"direct_download_url": "https://sourceforge.net/projects/openmediavault/files/iso/7.0-32/openmediavault_7.0-32-amd64.iso"
|
|
36
|
+
},
|
|
29
37
|
{
|
|
30
38
|
"filename": "openmediavault_6.5.0-amd64.iso",
|
|
31
39
|
"version": "6.5.0",
|
|
@@ -68,6 +76,14 @@
|
|
|
68
76
|
}
|
|
69
77
|
],
|
|
70
78
|
"versions": [
|
|
79
|
+
{
|
|
80
|
+
"name": "7.0.32",
|
|
81
|
+
"images": {
|
|
82
|
+
"hda_disk_image": "empty30G.qcow2",
|
|
83
|
+
"hdb_disk_image": "empty30G.qcow2",
|
|
84
|
+
"cdrom_image": "openmediavault_7.0.32-amd64.iso"
|
|
85
|
+
}
|
|
86
|
+
},
|
|
71
87
|
{
|
|
72
88
|
"name": "6.5.0",
|
|
73
89
|
"images": {
|
|
@@ -25,6 +25,13 @@
|
|
|
25
25
|
"kvm": "require"
|
|
26
26
|
},
|
|
27
27
|
"images": [
|
|
28
|
+
{
|
|
29
|
+
"filename": "OPNsense-24.1-OpenSSL-nano-amd64.img",
|
|
30
|
+
"version": "24.1",
|
|
31
|
+
"md5sum": "ea8472df2c272419b7834cddaf68048d",
|
|
32
|
+
"filesize": 3221225472,
|
|
33
|
+
"download_url": "https://opnsense.c0urier.net/releases/24.1/"
|
|
34
|
+
},
|
|
28
35
|
{
|
|
29
36
|
"filename": "OPNsense-23.1-OpenSSL-nano-amd64.img",
|
|
30
37
|
"version": "23.1",
|
|
@@ -62,6 +69,12 @@
|
|
|
62
69
|
}
|
|
63
70
|
],
|
|
64
71
|
"versions": [
|
|
72
|
+
{
|
|
73
|
+
"name": "24.1",
|
|
74
|
+
"images": {
|
|
75
|
+
"hda_disk_image": "OPNsense-24.1-OpenSSL-nano-amd64.img"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
65
78
|
{
|
|
66
79
|
"name": "23.1",
|
|
67
80
|
"images": {
|
|
@@ -30,6 +30,13 @@
|
|
|
30
30
|
"options": "-vga std -usbdevice tablet"
|
|
31
31
|
},
|
|
32
32
|
"images": [
|
|
33
|
+
{
|
|
34
|
+
"version": "1.3.0",
|
|
35
|
+
"filename": "ostinatostd-1.3.0-1.qcow2",
|
|
36
|
+
"filesize": 173735936,
|
|
37
|
+
"md5sum": "ff25fed989c43aeac84bf0d542ad43ba",
|
|
38
|
+
"download_url": "https://ostinato.org/pricing/gns3"
|
|
39
|
+
},
|
|
33
40
|
{
|
|
34
41
|
"filename": "ostinatostd-1.2.0-1.qcow2",
|
|
35
42
|
"version": "1.2.0",
|
|
@@ -46,6 +53,12 @@
|
|
|
46
53
|
}
|
|
47
54
|
],
|
|
48
55
|
"versions": [
|
|
56
|
+
{
|
|
57
|
+
"name": "1.3.0",
|
|
58
|
+
"images": {
|
|
59
|
+
"hda_disk_image": "ostinatostd-1.3.0-1.qcow2"
|
|
60
|
+
}
|
|
61
|
+
},
|
|
49
62
|
{
|
|
50
63
|
"name": "1.2.0",
|
|
51
64
|
"images": {
|
gns3server/appliances/rhel.gns3a
CHANGED
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
"registry_version": 4,
|
|
12
12
|
"status": "stable",
|
|
13
13
|
"availability": "service-contract",
|
|
14
|
-
"maintainer": "
|
|
15
|
-
"maintainer_email": "
|
|
16
|
-
"usage": "You should download Red Hat Enterprise Linux KVM Guest Image from https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.
|
|
14
|
+
"maintainer": "Da-Geek",
|
|
15
|
+
"maintainer_email": "dageek@dageeks-geeks.gg",
|
|
16
|
+
"usage": "You should download Red Hat Enterprise Linux KVM Guest Image from https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.3/x86_64/product-software attach/customize rhel-cloud-init.iso and start.\nusername: cloud-user\npassword: redhat",
|
|
17
17
|
"qemu": {
|
|
18
18
|
"adapter_type": "virtio-net-pci",
|
|
19
19
|
"adapters": 1,
|
|
@@ -26,6 +26,13 @@
|
|
|
26
26
|
"options": "-cpu host -nographic"
|
|
27
27
|
},
|
|
28
28
|
"images": [
|
|
29
|
+
{
|
|
30
|
+
"filename": "rhel-9.3-x86_64-kvm.qcow2",
|
|
31
|
+
"version": "9.3",
|
|
32
|
+
"md5sum": "409d8d15f5177db2617b0e3e02139b5c",
|
|
33
|
+
"filesize": 858193920,
|
|
34
|
+
"download_url": "https://access.redhat.com/downloads/content/479/ver=/rhel---9/9.3/x86_64/product-software"
|
|
35
|
+
},
|
|
29
36
|
{
|
|
30
37
|
"filename": "rhel-9.2-x86_64-kvm.qcow2",
|
|
31
38
|
"version": "9.2",
|
|
@@ -112,6 +119,13 @@
|
|
|
112
119
|
}
|
|
113
120
|
],
|
|
114
121
|
"versions": [
|
|
122
|
+
{
|
|
123
|
+
"name": "9.3",
|
|
124
|
+
"images": {
|
|
125
|
+
"hda_disk_image": "rhel-9.3-x86_64-kvm.qcow2",
|
|
126
|
+
"cdrom_image": "rhel-cloud-init.iso"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
115
129
|
{
|
|
116
130
|
"name": "9.2",
|
|
117
131
|
"images": {
|
|
@@ -26,6 +26,14 @@
|
|
|
26
26
|
"options": "-nographic -cpu host"
|
|
27
27
|
},
|
|
28
28
|
"images": [
|
|
29
|
+
{
|
|
30
|
+
"filename": "Rocky-9-GenericCloud-Base-9.3-20231113.0.x86_64.qcow2",
|
|
31
|
+
"version": "9.3",
|
|
32
|
+
"md5sum": "48cdeb033364af5909e15ee48d0e144d",
|
|
33
|
+
"filesize": 1083965440,
|
|
34
|
+
"download_url": "https://download.rockylinux.org/pub/rocky/9/images/x86_64/",
|
|
35
|
+
"direct_download_url": "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.3-20231113.0.x86_64.qcow2"
|
|
36
|
+
},
|
|
29
37
|
{
|
|
30
38
|
"filename": "Rocky-9-GenericCloud-Base-9.2-20230513.0.x86_64.qcow2",
|
|
31
39
|
"version": "9.2",
|
|
@@ -34,6 +42,14 @@
|
|
|
34
42
|
"download_url": "https://download.rockylinux.org/pub/rocky/9/images/x86_64/",
|
|
35
43
|
"direct_download_url": "https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud-Base-9.2-20230513.0.x86_64.qcow2"
|
|
36
44
|
},
|
|
45
|
+
{
|
|
46
|
+
"filename": "Rocky-8-GenericCloud-Base-8.9-20231119.0.x86_64.qcow2",
|
|
47
|
+
"version": "8.9",
|
|
48
|
+
"md5sum": "50c44d8d9c4df43694372c13768f114c",
|
|
49
|
+
"filesize": 1971978240,
|
|
50
|
+
"download_url": "https://download.rockylinux.org/pub/rocky/8/images/x86_64/",
|
|
51
|
+
"direct_download_url": "https://download.rockylinux.org/pub/rocky/8/images/x86_64/Rocky-8-GenericCloud-Base-8.9-20231119.0.x86_64.qcow2"
|
|
52
|
+
},
|
|
37
53
|
{
|
|
38
54
|
"filename": "Rocky-8-GenericCloud-Base-8.8-20230518.0.x86_64.qcow2",
|
|
39
55
|
"version": "8.8",
|
|
@@ -52,6 +68,13 @@
|
|
|
52
68
|
}
|
|
53
69
|
],
|
|
54
70
|
"versions": [
|
|
71
|
+
{
|
|
72
|
+
"name": "9.3",
|
|
73
|
+
"images": {
|
|
74
|
+
"hda_disk_image": "Rocky-9-GenericCloud-Base-9.3-20231113.0.x86_64.qcow2",
|
|
75
|
+
"cdrom_image": "rocky-cloud-init-data.iso"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
55
78
|
{
|
|
56
79
|
"name": "9.2",
|
|
57
80
|
"images": {
|
|
@@ -59,6 +82,13 @@
|
|
|
59
82
|
"cdrom_image": "rocky-cloud-init-data.iso"
|
|
60
83
|
}
|
|
61
84
|
},
|
|
85
|
+
{
|
|
86
|
+
"name": "8.9",
|
|
87
|
+
"images": {
|
|
88
|
+
"hda_disk_image": "Rocky-8-GenericCloud-Base-8.9-20231119.0.x86_64.qcow2",
|
|
89
|
+
"cdrom_image": "rocky-cloud-init-data.iso"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
62
92
|
{
|
|
63
93
|
"name": "8.8",
|
|
64
94
|
"images": {
|
|
@@ -28,12 +28,12 @@
|
|
|
28
28
|
},
|
|
29
29
|
"images": [
|
|
30
30
|
{
|
|
31
|
-
"filename": "ubuntu-23.04-server-cloudimg-
|
|
31
|
+
"filename": "ubuntu-23.04-server-cloudimg-amd64.img",
|
|
32
32
|
"version": "Ubuntu 23.04 (Lunar Lobster)",
|
|
33
|
-
"md5sum": "
|
|
34
|
-
"filesize":
|
|
33
|
+
"md5sum": "369e3b1f68416c39245a8014172406dd",
|
|
34
|
+
"filesize": 756678656,
|
|
35
35
|
"download_url": "https://cloud-images.ubuntu.com/releases/23.04/release/",
|
|
36
|
-
"direct_download_url": "https://cloud-images.ubuntu.com/releases/23.04/release/ubuntu-23.04-server-cloudimg-
|
|
36
|
+
"direct_download_url": "https://cloud-images.ubuntu.com/releases/23.04/release/ubuntu-23.04-server-cloudimg-amd64.img"
|
|
37
37
|
},
|
|
38
38
|
{
|
|
39
39
|
"filename": "ubuntu-22.04-server-cloudimg-amd64.img",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
{
|
|
73
73
|
"name": "Ubuntu 23.04 (Lunar Lobster)",
|
|
74
74
|
"images": {
|
|
75
|
-
"hda_disk_image": "ubuntu-23.04-server-cloudimg-
|
|
75
|
+
"hda_disk_image": "ubuntu-23.04-server-cloudimg-amd64.img",
|
|
76
76
|
"cdrom_image": "ubuntu-cloud-init-data.iso"
|
|
77
77
|
}
|
|
78
78
|
},
|
|
@@ -193,8 +193,8 @@ class Docker(BaseManager):
|
|
|
193
193
|
if timeout is None:
|
|
194
194
|
timeout = 60 * 60 * 24 * 31 # One month timeout
|
|
195
195
|
|
|
196
|
-
if path ==
|
|
197
|
-
url = "http://docker/v1.
|
|
196
|
+
if path == 'version':
|
|
197
|
+
url = "http://docker/v1.24/" + path
|
|
198
198
|
else:
|
|
199
199
|
url = "http://docker/v" + DOCKER_MINIMUM_API_VERSION + "/" + path
|
|
200
200
|
try:
|
|
@@ -506,6 +506,10 @@ class DockerVM(BaseNode):
|
|
|
506
506
|
result = await self.manager.query("POST", "containers/create", data=params)
|
|
507
507
|
self._cid = result["Id"]
|
|
508
508
|
log.info(f"Docker container '{self._name}' [{self._id}] created")
|
|
509
|
+
if self._cpus > 0:
|
|
510
|
+
log.info(f"CPU limit set to {self._cpus} CPUs")
|
|
511
|
+
if self._memory > 0:
|
|
512
|
+
log.info(f"Memory limit set to {self._memory} MB")
|
|
509
513
|
return True
|
|
510
514
|
|
|
511
515
|
def _format_env(self, variables, env):
|
|
@@ -571,6 +575,9 @@ class DockerVM(BaseNode):
|
|
|
571
575
|
await self._start_vnc_process(restart=True)
|
|
572
576
|
monitor_process(self._vnc_process, self._vnc_callback)
|
|
573
577
|
|
|
578
|
+
if self._console_websocket:
|
|
579
|
+
await self._console_websocket.close()
|
|
580
|
+
self._console_websocket = None
|
|
574
581
|
await self._clean_servers()
|
|
575
582
|
|
|
576
583
|
await self.manager.query("POST", f"containers/{self._cid}/start")
|
|
@@ -833,9 +840,7 @@ class DockerVM(BaseNode):
|
|
|
833
840
|
f"containers/{self._cid}/attach/ws?stream=1&stdin=1&stdout=1&stderr=1"
|
|
834
841
|
)
|
|
835
842
|
input_stream.ws = self._console_websocket
|
|
836
|
-
|
|
837
843
|
output_stream.feed_data(self.name.encode() + b" console is now available... Press RETURN to get started.\r\n")
|
|
838
|
-
|
|
839
844
|
asyncio.ensure_future(self._read_console_output(self._console_websocket, output_stream))
|
|
840
845
|
|
|
841
846
|
async def _read_console_output(self, ws, out):
|
|
@@ -858,13 +863,14 @@ class DockerVM(BaseNode):
|
|
|
858
863
|
out.feed_eof()
|
|
859
864
|
await ws.close()
|
|
860
865
|
break
|
|
861
|
-
await self.stop()
|
|
862
866
|
|
|
863
867
|
async def reset_console(self):
|
|
864
868
|
"""
|
|
865
869
|
Reset the console.
|
|
866
870
|
"""
|
|
867
871
|
|
|
872
|
+
if self._console_websocket:
|
|
873
|
+
await self._console_websocket.close()
|
|
868
874
|
await self._clean_servers()
|
|
869
875
|
await self._start_console()
|
|
870
876
|
|
|
@@ -908,6 +914,9 @@ class DockerVM(BaseNode):
|
|
|
908
914
|
"""
|
|
909
915
|
|
|
910
916
|
try:
|
|
917
|
+
if self._console_websocket:
|
|
918
|
+
await self._console_websocket.close()
|
|
919
|
+
self._console_websocket = None
|
|
911
920
|
await self._clean_servers()
|
|
912
921
|
await self._stop_ubridge()
|
|
913
922
|
|
|
@@ -252,6 +252,9 @@ class Dynamips(BaseManager):
|
|
|
252
252
|
# look for Dynamips
|
|
253
253
|
dynamips_path = self.config.settings.Dynamips.dynamips_path
|
|
254
254
|
if not os.path.isabs(dynamips_path):
|
|
255
|
+
if sys.platform.startswith("win") and hasattr(sys, "frozen"):
|
|
256
|
+
dynamips_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "dynamips"))
|
|
257
|
+
os.environ["PATH"] = os.pathsep.join(dynamips_dir) + os.pathsep + os.environ.get("PATH", "")
|
|
255
258
|
dynamips_path = shutil.which(dynamips_path)
|
|
256
259
|
|
|
257
260
|
if not dynamips_path:
|
|
@@ -149,13 +149,20 @@ class Qemu(BaseManager):
|
|
|
149
149
|
for arch in archs:
|
|
150
150
|
if f.endswith(arch) or f.endswith(f"{arch}.exe") or f.endswith(f"{arch}w.exe"):
|
|
151
151
|
qemu_path = os.path.join(path, f)
|
|
152
|
-
|
|
152
|
+
try:
|
|
153
|
+
version = await Qemu.get_qemu_version(qemu_path)
|
|
154
|
+
except QemuError as e:
|
|
155
|
+
log.warning(str(e))
|
|
156
|
+
continue
|
|
153
157
|
qemus.append({"path": qemu_path, "version": version})
|
|
154
158
|
else:
|
|
155
159
|
qemu_path = os.path.join(path, f)
|
|
156
|
-
|
|
160
|
+
try:
|
|
161
|
+
version = await Qemu.get_qemu_version(qemu_path)
|
|
162
|
+
except QemuError as e:
|
|
163
|
+
log.warning(str(e))
|
|
164
|
+
continue
|
|
157
165
|
qemus.append({"path": qemu_path, "version": version})
|
|
158
|
-
|
|
159
166
|
except OSError:
|
|
160
167
|
continue
|
|
161
168
|
|
|
@@ -124,6 +124,8 @@ class QemuVM(BaseNode):
|
|
|
124
124
|
except QemuError:
|
|
125
125
|
# If the binary is not found for topologies 1.4 and later
|
|
126
126
|
# search via the platform otherwise use the binary name
|
|
127
|
+
log.warning(f"Could not find the QEMU binary {qemu_path} on this system, "
|
|
128
|
+
f"trying to find one using platform {platform}")
|
|
127
129
|
if platform:
|
|
128
130
|
self.platform = platform
|
|
129
131
|
else:
|
|
@@ -242,7 +244,7 @@ class QemuVM(BaseNode):
|
|
|
242
244
|
if qemu_path and os.pathsep not in qemu_path:
|
|
243
245
|
new_qemu_path = shutil.which(qemu_path, path=os.pathsep.join(self._manager.paths_list()))
|
|
244
246
|
if new_qemu_path is None:
|
|
245
|
-
raise QemuError(f"QEMU binary
|
|
247
|
+
raise QemuError(f"QEMU binary '{qemu_path}' cannot be found on the system")
|
|
246
248
|
qemu_path = new_qemu_path
|
|
247
249
|
|
|
248
250
|
self._check_qemu_path(qemu_path)
|
|
@@ -289,6 +291,7 @@ class QemuVM(BaseNode):
|
|
|
289
291
|
def platform(self, platform):
|
|
290
292
|
|
|
291
293
|
self._platform = platform
|
|
294
|
+
log.info(f"QEMU VM '{self._name}' [{self._id}] has set the platform {platform}")
|
|
292
295
|
self.qemu_path = f"qemu-system-{platform}"
|
|
293
296
|
|
|
294
297
|
def _disk_setter(self, variable, value):
|
|
@@ -2640,6 +2643,9 @@ class QemuVM(BaseNode):
|
|
|
2640
2643
|
command.extend(shlex.split(additional_options))
|
|
2641
2644
|
except ValueError as e:
|
|
2642
2645
|
raise QemuError(f"Invalid additional options: {additional_options} error {e}")
|
|
2646
|
+
# avoiding mouse offset (see https://github.com/GNS3/gns3-server/issues/2335)
|
|
2647
|
+
if self._console_type == "vnc":
|
|
2648
|
+
command.extend(['-machine', 'usb=on', '-device', 'usb-tablet'])
|
|
2643
2649
|
return command
|
|
2644
2650
|
|
|
2645
2651
|
def asdict(self):
|
|
@@ -142,6 +142,9 @@ class VPCSVM(BaseNode):
|
|
|
142
142
|
|
|
143
143
|
vpcs_path = self._manager.config.settings.VPCS.vpcs_path
|
|
144
144
|
if not os.path.isabs(vpcs_path):
|
|
145
|
+
if sys.platform.startswith("win") and hasattr(sys, "frozen"):
|
|
146
|
+
vpcs_dir = os.path.normpath(os.path.join(os.path.dirname(os.path.abspath(sys.executable)), "vpcs"))
|
|
147
|
+
os.environ["PATH"] = os.pathsep.join(vpcs_dir) + os.pathsep + os.environ.get("PATH", "")
|
|
145
148
|
vpcs_path = shutil.which(vpcs_path)
|
|
146
149
|
return vpcs_path
|
|
147
150
|
|
|
@@ -317,12 +317,12 @@ class Controller:
|
|
|
317
317
|
else:
|
|
318
318
|
for entry in importlib_resources.files('gns3server').joinpath(resource_name).iterdir():
|
|
319
319
|
full_path = os.path.join(dst_path, entry.name)
|
|
320
|
-
if not os.path.exists(full_path):
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
320
|
+
if entry.is_file() and not os.path.exists(full_path):
|
|
321
|
+
log.debug(f'Installing {resource_name} resource file "{entry.name}" to "{full_path}"')
|
|
322
|
+
shutil.copy(str(entry), os.path.join(dst_path, entry.name))
|
|
323
|
+
elif entry.is_dir():
|
|
324
|
+
os.makedirs(full_path, exist_ok=True)
|
|
325
|
+
Controller.install_resource_files(full_path, os.path.join(resource_name, entry.name))
|
|
326
326
|
|
|
327
327
|
def _install_base_configs(self):
|
|
328
328
|
"""
|
|
@@ -102,6 +102,11 @@ async def export_project(
|
|
|
102
102
|
continue
|
|
103
103
|
_patch_mtime(path)
|
|
104
104
|
zstream.write(path, os.path.relpath(path, project._path))
|
|
105
|
+
# save empty directories
|
|
106
|
+
for directory in dirs:
|
|
107
|
+
path = os.path.join(root, directory)
|
|
108
|
+
if not os.listdir(path):
|
|
109
|
+
zstream.write(path, os.path.relpath(path, project._path))
|
|
105
110
|
except FileNotFoundError as e:
|
|
106
111
|
log.warning(f"Cannot export local file: {e}")
|
|
107
112
|
continue
|
|
@@ -23,6 +23,7 @@ import shutil
|
|
|
23
23
|
import aiofiles
|
|
24
24
|
import itertools
|
|
25
25
|
import tempfile
|
|
26
|
+
import stat
|
|
26
27
|
import gns3server.utils.zipfile_zstd as zipfile_zstd
|
|
27
28
|
|
|
28
29
|
from .controller_error import ControllerError
|
|
@@ -235,7 +236,7 @@ async def _upload_file(compute, project_id, file_path, path):
|
|
|
235
236
|
|
|
236
237
|
async def _import_images(controller, images_path):
|
|
237
238
|
"""
|
|
238
|
-
Copy images to the images directory or delete them if they already
|
|
239
|
+
Copy images to the images directory or delete them if they already exist.
|
|
239
240
|
"""
|
|
240
241
|
|
|
241
242
|
image_dir = controller.images_path()
|
|
@@ -247,7 +248,9 @@ async def _import_images(controller, images_path):
|
|
|
247
248
|
continue
|
|
248
249
|
dst = os.path.join(image_dir, os.path.relpath(path, root))
|
|
249
250
|
os.makedirs(os.path.dirname(dst), exist_ok=True)
|
|
250
|
-
|
|
251
|
+
if not os.path.exists(dst):
|
|
252
|
+
await wait_run_in_executor(shutil.move, path, dst)
|
|
253
|
+
os.chmod(dst, stat.S_IWRITE | stat.S_IREAD | stat.S_IEXEC)
|
|
251
254
|
|
|
252
255
|
|
|
253
256
|
async def _import_snapshots(snapshots_path, project_name, project_id):
|
gns3server/controller/project.py
CHANGED
|
@@ -152,16 +152,27 @@ class Project:
|
|
|
152
152
|
|
|
153
153
|
self._iou_id_lock = asyncio.Lock()
|
|
154
154
|
log.debug(f'Project "{self.name}" [{self._id}] loaded')
|
|
155
|
+
self.emit_controller_notification("project.created", self.asdict())
|
|
155
156
|
|
|
156
157
|
def emit_notification(self, action, event):
|
|
157
158
|
"""
|
|
158
|
-
Emit a notification to all clients using this project.
|
|
159
|
+
Emit a project notification to all clients using this project.
|
|
159
160
|
|
|
160
161
|
:param action: Action name
|
|
161
162
|
:param event: Event to send
|
|
162
163
|
"""
|
|
163
164
|
|
|
164
|
-
self.
|
|
165
|
+
self._controller.notification.project_emit(action, event, project_id=self.id)
|
|
166
|
+
|
|
167
|
+
def emit_controller_notification(self, action, event):
|
|
168
|
+
"""
|
|
169
|
+
Emit a controller notification, all clients will see it.
|
|
170
|
+
|
|
171
|
+
:param action: Action name
|
|
172
|
+
:param event: Event to send
|
|
173
|
+
"""
|
|
174
|
+
|
|
175
|
+
self._controller.notification.controller_emit(action, event)
|
|
165
176
|
|
|
166
177
|
async def update(self, **kwargs):
|
|
167
178
|
"""
|
|
@@ -176,7 +187,7 @@ class Project:
|
|
|
176
187
|
|
|
177
188
|
# We send notif only if object has changed
|
|
178
189
|
if old_json != self.asdict():
|
|
179
|
-
self.
|
|
190
|
+
self.emit_controller_notification("project.updated", self.asdict())
|
|
180
191
|
self.dump()
|
|
181
192
|
|
|
182
193
|
# update on computes
|
|
@@ -812,7 +823,8 @@ class Project:
|
|
|
812
823
|
self._clean_pictures()
|
|
813
824
|
self._status = "closed"
|
|
814
825
|
if not ignore_notification:
|
|
815
|
-
self.
|
|
826
|
+
self.emit_controller_notification("project.closed", self.asdict())
|
|
827
|
+
|
|
816
828
|
self.reset()
|
|
817
829
|
self._closing = False
|
|
818
830
|
|
|
@@ -868,6 +880,7 @@ class Project:
|
|
|
868
880
|
shutil.rmtree(self.path)
|
|
869
881
|
except OSError as e:
|
|
870
882
|
raise ControllerError(f"Cannot delete project directory {self.path}: {str(e)}")
|
|
883
|
+
self.emit_controller_notification("project.deleted", self.asdict())
|
|
871
884
|
|
|
872
885
|
async def delete_on_computes(self):
|
|
873
886
|
"""
|
|
@@ -1001,7 +1014,7 @@ class Project:
|
|
|
1001
1014
|
await self.add_drawing(dump=False, **drawing_data)
|
|
1002
1015
|
|
|
1003
1016
|
self.dump()
|
|
1004
|
-
# We catch all error to be able to
|
|
1017
|
+
# We catch all error to be able to roll back the .gns3 to the previous state
|
|
1005
1018
|
except Exception as e:
|
|
1006
1019
|
for compute in list(self._project_created_on_compute):
|
|
1007
1020
|
try:
|
|
@@ -1026,6 +1039,7 @@ class Project:
|
|
|
1026
1039
|
pass
|
|
1027
1040
|
|
|
1028
1041
|
self._loading = False
|
|
1042
|
+
self.emit_controller_notification("project.opened", self.asdict())
|
|
1029
1043
|
# Should we start the nodes when project is open
|
|
1030
1044
|
if self._auto_start:
|
|
1031
1045
|
# Start all in the background without waiting for completion
|
gns3server/crash_report.py
CHANGED
|
@@ -58,7 +58,7 @@ class CrashReport:
|
|
|
58
58
|
Report crash to a third party service
|
|
59
59
|
"""
|
|
60
60
|
|
|
61
|
-
DSN = "https://
|
|
61
|
+
DSN = "https://395af26fb5b2245d4c9810095aa11da9@o19455.ingest.us.sentry.io/38482"
|
|
62
62
|
_instance = None
|
|
63
63
|
|
|
64
64
|
def __init__(self):
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
from sqlalchemy import Boolean, Column, String, Integer, ForeignKey, PickleType
|
|
19
|
+
from sqlalchemy import Boolean, Column, String, Integer, Float, ForeignKey, PickleType
|
|
20
20
|
from sqlalchemy.orm import relationship
|
|
21
21
|
|
|
22
22
|
from .base import BaseTable, generate_uuid, GUID
|
|
@@ -77,7 +77,7 @@ class DockerTemplate(Template):
|
|
|
77
77
|
extra_hosts = Column(String)
|
|
78
78
|
extra_volumes = Column(PickleType)
|
|
79
79
|
memory = Column(Integer)
|
|
80
|
-
cpus = Column(
|
|
80
|
+
cpus = Column(Float)
|
|
81
81
|
custom_adapters = Column(PickleType)
|
|
82
82
|
|
|
83
83
|
__mapper_args__ = {"polymorphic_identity": "docker", "polymorphic_load": "selectin"}
|
gns3server/db_migrations/README
CHANGED
gns3server/main.py
CHANGED
|
@@ -29,6 +29,7 @@ import gns3server.utils.get_resource
|
|
|
29
29
|
|
|
30
30
|
import os
|
|
31
31
|
import sys
|
|
32
|
+
import asyncio
|
|
32
33
|
|
|
33
34
|
|
|
34
35
|
def daemonize():
|
|
@@ -70,7 +71,10 @@ def main():
|
|
|
70
71
|
daemonize()
|
|
71
72
|
from gns3server.server import Server
|
|
72
73
|
|
|
73
|
-
|
|
74
|
+
try:
|
|
75
|
+
asyncio.run(Server().run())
|
|
76
|
+
except KeyboardInterrupt:
|
|
77
|
+
pass
|
|
74
78
|
|
|
75
79
|
|
|
76
80
|
if __name__ == "__main__":
|
|
@@ -43,7 +43,7 @@ class DockerBase(BaseModel):
|
|
|
43
43
|
extra_hosts: Optional[str] = Field(None, description="Docker extra hosts (added to /etc/hosts)")
|
|
44
44
|
extra_volumes: Optional[List[str]] = Field(None, description="Additional directories to make persistent")
|
|
45
45
|
memory: Optional[int] = Field(None, description="Maximum amount of memory the container can use in MB")
|
|
46
|
-
cpus: Optional[
|
|
46
|
+
cpus: Optional[float] = Field(None, description="Maximum amount of CPU resources the container can use")
|
|
47
47
|
custom_adapters: Optional[List[CustomAdapter]] = Field(None, description="Custom adapters")
|
|
48
48
|
|
|
49
49
|
|
|
@@ -49,7 +49,7 @@ class DockerTemplate(TemplateBase):
|
|
|
49
49
|
extra_hosts: Optional[str] = Field("", description="Docker extra hosts (added to /etc/hosts)")
|
|
50
50
|
extra_volumes: Optional[List] = Field([], description="Additional directories to make persistent")
|
|
51
51
|
memory: Optional[int] = Field(0, description="Maximum amount of memory the container can use in MB")
|
|
52
|
-
cpus: Optional[
|
|
52
|
+
cpus: Optional[float] = Field(0, description="Maximum amount of CPU resources the container can use")
|
|
53
53
|
custom_adapters: Optional[List[CustomAdapter]] = Field(default_factory=list, description="Custom adapters")
|
|
54
54
|
|
|
55
55
|
|
gns3server/server.py
CHANGED
|
@@ -182,7 +182,7 @@ class Server:
|
|
|
182
182
|
asyncio.ensure_future(Controller.instance().reload())
|
|
183
183
|
else:
|
|
184
184
|
log.info(f"Server has got signal {signame}, exiting...")
|
|
185
|
-
# send SIGTERM to the server PID so uvicorn can
|
|
185
|
+
# send SIGTERM to the server PID so uvicorn can shut down the process
|
|
186
186
|
os.kill(os.getpid(), signal.SIGTERM)
|
|
187
187
|
except asyncio.CancelledError:
|
|
188
188
|
pass
|
|
@@ -239,7 +239,7 @@ class Server:
|
|
|
239
239
|
log.critical("Can't write pid file %s: %s", path, str(e))
|
|
240
240
|
sys.exit(1)
|
|
241
241
|
|
|
242
|
-
def run(self):
|
|
242
|
+
async def run(self):
|
|
243
243
|
|
|
244
244
|
args = self._parse_arguments(sys.argv[1:])
|
|
245
245
|
|
|
@@ -333,8 +333,7 @@ class Server:
|
|
|
333
333
|
uvicorn_logger.propagate = False
|
|
334
334
|
|
|
335
335
|
server = uvicorn.Server(config)
|
|
336
|
-
|
|
337
|
-
loop.run_until_complete(server.serve())
|
|
336
|
+
await server.serve()
|
|
338
337
|
|
|
339
338
|
except Exception as e:
|
|
340
339
|
log.critical(f"Critical error while running the server: {e}", exc_info=1)
|