diff --git a/CHANGELOG b/CHANGELOG index d2e37292..c8392495 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,20 @@ # Change Log +## 2.2.40.1 10/06/2023 + +* Re-bundle Web-Ui v2.2.40. Fixes #2239 + +## 2.2.40 06/06/2023 + +* qemu : with network adapter_type equal to "virtio-net-pci", fix the speed to 10000 and duplex to full. The values are actually fake. (https://github.com/GNS3/gns3-gui/issues/3476) +* Parse name for request to node creation from template +* Remove Xvfb + x11vnc support +* Require a Host-Only Network to start the VirtualBox GNS3 VM on macOS with VirtualBox 7 +* Properly catch aiohttp client exception. Ref #2228 +* Catch ConnectionResetError when waiting for the wrap console +* Fix open IPv6 address for HTTP consoles on controller. Fixes https://github.com/GNS3/gns3-gui/issues/3448 +* Use proc.communicate() when checking for subprocess output As recommended in https://docs.python.org/3/library/asyncio-subprocess.html#asyncio.subprocess.Process.stderr + ## 2.2.39 08/05/2023 * Install web-ui v2.2.39 diff --git a/gns3server/appliances/almalinux.gns3a b/gns3server/appliances/almalinux.gns3a index 78c7cf43..0f60291b 100644 --- a/gns3server/appliances/almalinux.gns3a +++ b/gns3server/appliances/almalinux.gns3a @@ -37,7 +37,8 @@ "version": "1.0", "md5sum": "72fb52af76e9561d125dd99224e2c1d1", "filesize": 374784, - "download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/AlmaLinux/almalinux-cloud-init-data.iso" + "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/AlmaLinux", + "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/AlmaLinux/almalinux-cloud-init-data.iso" } ], "versions": [ diff --git a/gns3server/appliances/aruba-arubaoscx.gns3a b/gns3server/appliances/aruba-arubaoscx.gns3a index a0a08345..1fecc275 100644 --- a/gns3server/appliances/aruba-arubaoscx.gns3a +++ b/gns3server/appliances/aruba-arubaoscx.gns3a @@ -32,6 +32,13 @@ "process_priority": "normal" }, "images": [ + { + "filename": "arubaoscx-disk-image-genericx86-p4-20230531220439.vmdk", + "version": "10.12.0006", + "md5sum": "c4f80fecd02ef93b431b75dd610e0063", + "filesize": 384638464, + "download_url": "https://asp.arubanetworks.com/" + }, { "filename": "arubaoscx-disk-image-genericx86-p4-20221130174651.vmdk", "version": "10.11.0001", @@ -111,6 +118,12 @@ } ], "versions": [ + { + "name": "10.12.0006", + "images": { + "hda_disk_image": "arubaoscx-disk-image-genericx86-p4-20230531220439.vmdk" + } + }, { "name": "10.11.0001", "images": { diff --git a/gns3server/appliances/centos-cloud.gns3a b/gns3server/appliances/centos-cloud.gns3a index 52bfd90f..4a41ff7d 100644 --- a/gns3server/appliances/centos-cloud.gns3a +++ b/gns3server/appliances/centos-cloud.gns3a @@ -26,33 +26,37 @@ "options": "-nographic" }, "images": [ + { + "filename": "CentOS-8-GenericCloud-8.4.2105-20210603.0.x86_64.qcow2", + "version": "8.4 (2105)", + "md5sum": "032eed270415526546eac07628905a62", + "filesize": 1309652992, + "download_url": "https://cloud.centos.org/centos/8/x86_64/images", + "direct_download_url": "https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.4.2105-20210603.0.x86_64.qcow2" + }, { "filename": "CentOS-7-x86_64-GenericCloud-2111.qcow2", "version": "7 (2111)", "md5sum": "730b8662695831670721c8245be61dac", "filesize": 897384448, - "download_url": "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-2111.qcow2" + "download_url": "https://cloud.centos.org/centos/7/images", + "direct_download_url": "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-2111.qcow2" }, { "filename": "CentOS-7-x86_64-GenericCloud-1809.qcow2", "version": "7 (1809)", "md5sum": "da79108d1324b27bd1759362b82fbe40", "filesize": 914948096, - "download_url": "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1809.qcow2" - }, - { - "filename": "CentOS-8-GenericCloud-8.4.2105-20210603.0.x86_64.qcow2", - "version": "8.4 (2105)", - "md5sum": "032eed270415526546eac07628905a62", - "filesize": 1309652992, - "download_url": "https://cloud.centos.org/centos/8/x86_64/images/CentOS-8-GenericCloud-8.4.2105-20210603.0.x86_64.qcow2" + "download_url": "https://cloud.centos.org/centos/7/images", + "direct_download_url": "https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud-1809.qcow2" }, { "filename": "centos-cloud-init-data.iso", - "version": "1.0", - "md5sum": "15ca60c12db6d13b8eeae1a19613fd6e", - "filesize": 378880, - "download_url": "https://github.com/asenci/gns3-centos-cloud-init-data/raw/master/centos-cloud-init-data.iso" + "version": "1.1", + "md5sum": "59ea8223fd659d8bce9081ff175912e9", + "filesize": 374784, + "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/centos-cloud", + "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/centos-cloud/centos-cloud-init-data.iso" } ], "versions": [ diff --git a/gns3server/appliances/debian.gns3a b/gns3server/appliances/debian.gns3a index 6bee9798..49858688 100644 --- a/gns3server/appliances/debian.gns3a +++ b/gns3server/appliances/debian.gns3a @@ -44,7 +44,8 @@ "version": "1.0", "md5sum": "43f6bf70c178a9d3c270b5c24971e578", "filesize": 374784, - "download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/Debian/debian-cloud-init-data.iso" + "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/Debian", + "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/Debian/debian-cloud-init-data.iso" } ], "versions": [ diff --git a/gns3server/appliances/fedora-cloud.gns3a b/gns3server/appliances/fedora-cloud.gns3a index 9679fe69..93ec221b 100644 --- a/gns3server/appliances/fedora-cloud.gns3a +++ b/gns3server/appliances/fedora-cloud.gns3a @@ -31,14 +31,16 @@ "version": "35-1.2", "md5sum": "cfa9cdcfb946e5f4cf9dd4d7906008d0", "filesize": 376897536, - "download_url": "https://download.fedoraproject.org/pub/fedora/linux/releases/35/Cloud/x86_64/images/Fedora-Cloud-Base-35-1.2.x86_64.qcow2" + "download_url": "https://download.fedoraproject.org/pub/fedora/linux/releases/35/Cloud/x86_64/images", + "direct_download_url": "https://download.fedoraproject.org/pub/fedora/linux/releases/35/Cloud/x86_64/images/Fedora-Cloud-Base-35-1.2.x86_64.qcow2" }, { "filename": "fedora-cloud-init-data.iso", "version": "1.0", "md5sum": "3d0d6391d3f5ece1180c70b9667c4dca", "filesize": 374784, - "download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/fedora-cloud/fedora-cloud-init-data.iso" + "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/fedora-cloud", + "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/fedora-cloud/fedora-cloud-init-data.iso" } ], "versions": [ diff --git a/gns3server/appliances/mikrotik-winbox.gns3a b/gns3server/appliances/mikrotik-winbox.gns3a new file mode 100644 index 00000000..ce0f9caf --- /dev/null +++ b/gns3server/appliances/mikrotik-winbox.gns3a @@ -0,0 +1,19 @@ +{ + "appliance_id": "b770027f-1822-4ab6-b2f9-73336ca0983d", + "name": "Mikrotik WinBox", + "category": "guest", + "description": "Mikrotik's WinBox router management software for GNS3", + "vendor_name": "Mikrotik", + "vendor_url": "https://mikrotik.com", + "product_name": "Mikrotik WinBox", + "registry_version": 4, + "status": "stable", + "availability": "free", + "maintainer": "Alexander Horner", + "maintainer_email": "contact@alexhorner.cc", + "docker": { + "adapters": 1, + "image": "gns3/mikrotik-winbox", + "console_type": "vnc" + } +} diff --git a/gns3server/appliances/ntopng.gns3a b/gns3server/appliances/ntopng.gns3a index 78639951..c54cc217 100644 --- a/gns3server/appliances/ntopng.gns3a +++ b/gns3server/appliances/ntopng.gns3a @@ -14,7 +14,7 @@ "usage": "In the web interface login as admin/admin\n\nPersistent configuration:\n- Add \"/var/lib/redis\" as an additional persistent directory.\n- Use \"redis-cli save\" in an auxiliary console to save the configuration.", "docker": { "adapters": 1, - "image": "ntop/ntopng:latest", + "image": "ntop/ntopng:stable", "start_command": "--dns-mode 2 --interface eth0", "console_type": "http", "console_http_port": 3000, diff --git a/gns3server/appliances/oracle-linux-cloud.gns3a b/gns3server/appliances/oracle-linux-cloud.gns3a new file mode 100644 index 00000000..a9709bcc --- /dev/null +++ b/gns3server/appliances/oracle-linux-cloud.gns3a @@ -0,0 +1,70 @@ +{ + "appliance_id": "88e67f45-e0de-4e5e-9f36-2dc83e4a6c60", + "name": "Oracle Linux Cloud Guest", + "category": "guest", + "description": "A highly performant and secure operating environment, Oracle Linux delivers virtualization, management, automation, and cloud native computing tools, along with the operating system, in a single, easy-to-manage support offering. Oracle Linux provides a 100% application binary compatible alternative to Red Hat Enterprise Linux and CentOS Linux and is supported across both hybrid and multicloud environments.", + "vendor_name": "Oracle Corporation", + "vendor_url": "https://www.oracle.com", + "documentation_url": "https://docs.oracle.com/en-us/iaas/images/", + "product_name": "Oracle Linux Cloud Guest", + "product_url": "https://www.oracle.com/au/linux/", + "registry_version": 4, + "status": "stable", + "maintainer": "GNS3 Team", + "maintainer_email": "developers@gns3.net", + "usage": "Username: oracle\nPassword: oracle", + "port_name_format": "Ethernet{0}", + "qemu": { + "adapter_type": "virtio-net-pci", + "adapters": 1, + "ram": 1024, + "hda_disk_interface": "virtio", + "arch": "x86_64", + "console_type": "telnet", + "boot_priority": "c", + "kvm": "require", + "options": "-cpu host -nographic" + }, + "images": [ + { + "filename": "OL9U1_x86_64-kvm-b158.qcow", + "version": "9.1", + "md5sum": "9f32851b96fc38191892197fa11f7435", + "filesize": 539033600, + "download_url": "https://yum.oracle.com/oracle-linux-templates.html", + "direct_download_url": "https://yum.oracle.com/templates/OracleLinux/OL9/u1/x86_64/OL9U1_x86_64-kvm-b158.qcow" + }, + { + "filename": "OL8U7_x86_64-kvm-b148.qcow", + "version": "8.7", + "md5sum": "962cdde7e810888b9914e937222f687f", + "filesize": 913965056, + "download_url": "https://yum.oracle.com/oracle-linux-templates.html", + "direct_download_url": "https://yum.oracle.com/templates/OracleLinux/OL8/u7/x86_64/OL8U7_x86_64-kvm-b148.qcow" + }, + { + "filename": "oracle-cloud-init-data.iso", + "version": "1.1", + "md5sum": "cb51bc42ae9dfb7345bfa7362a313baf", + "filesize": 374784, + "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/oracle-cloud", + "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/oracle-cloud/oracle-cloud-init-data.iso" + } + ], + "versions": [ + { + "name": "9.1", + "images": { + "hda_disk_image": "OL9U1_x86_64-kvm-b158.qcow", + "cdrom_image": "oracle-cloud-init-data.iso" + } + }, + { + "name": "8.7", + "images": { + "hda_disk_image": "OL8U7_x86_64-kvm-b148.qcow", + "cdrom_image": "oracle-cloud-init-data.iso" + } + } + ] +} diff --git a/gns3server/appliances/rockylinux.gns3a b/gns3server/appliances/rockylinux.gns3a index 86900cb9..2dd3fd1b 100644 --- a/gns3server/appliances/rockylinux.gns3a +++ b/gns3server/appliances/rockylinux.gns3a @@ -39,7 +39,8 @@ "version": "1.0", "md5sum": "33ffda3a81436e305f37fb913edd6d43", "filesize": 374784, - "download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/rocky-cloud/rocky-cloud-init-data.iso" + "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/rocky-cloud", + "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/rocky-cloud/rocky-cloud-init-data.iso" } ], "versions": [ diff --git a/gns3server/appliances/ubuntu-cloud.gns3a b/gns3server/appliances/ubuntu-cloud.gns3a index 59aba8a7..ad8d2421 100644 --- a/gns3server/appliances/ubuntu-cloud.gns3a +++ b/gns3server/appliances/ubuntu-cloud.gns3a @@ -29,51 +29,34 @@ { "filename": "ubuntu-22.04-server-cloudimg-amd64.img", "version": "22.04 (LTS)", - "md5sum": "ac2351289daa173fa1ed6b2b81d81d7c", - "filesize": 624295936, - "download_url": "https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-amd64.img" + "md5sum": "3ce0b84f9592482fb645e8253b979827", + "filesize": 686096384, + "download_url": "https://cloud-images.ubuntu.com/releases/jammy/release", + "direct_download_url": "https://cloud-images.ubuntu.com/releases/jammy/release/ubuntu-22.04-server-cloudimg-amd64.img" }, { "filename": "ubuntu-20.04-server-cloudimg-amd64.img", "version": "20.04 (LTS)", "md5sum": "044bc979b2238192ee3edb44e2bb6405", "filesize": 552337408, - "download_url": "https://cloud-images.ubuntu.com/releases/focal/release-20210119.1/ubuntu-20.04-server-cloudimg-amd64.img" + "download_url": "https://cloud-images.ubuntu.com/releases/focal/release-20210119.1/", + "direct_download_url": "https://cloud-images.ubuntu.com/releases/focal/release-20210119.1/ubuntu-20.04-server-cloudimg-amd64.img" }, { "filename": "ubuntu-18.04-server-cloudimg-amd64.img", "version": "18.04 (LTS)", "md5sum": "f4134e7fa16d7fa766c7467cbe25c949", "filesize": 336134144, - "download_url": "https://cloud-images.ubuntu.com/releases/18.04/release-20180426.2/ubuntu-18.04-server-cloudimg-amd64.img" - }, - { - "filename": "ubuntu-17.10-server-cloudimg-amd64.img", - "version": "17.10", - "md5sum": "331b44f2b05858c251b3ea92c8b65152", - "filesize": 320405504, - "download_url": "https://cloud-images.ubuntu.com/releases/17.10/release-20180404/ubuntu-17.10-server-cloudimg-amd64.img" - }, - { - "filename": "ubuntu-16.04-server-cloudimg-amd64-disk1.img", - "version": "16.04 (LTS)", - "md5sum": "22c124ba65ea096cdef8b0a197dd613a", - "filesize": 290193408, - "download_url": "https://cloud-images.ubuntu.com/releases/16.04/release-20180405/ubuntu-16.04-server-cloudimg-amd64-disk1.img" - }, - { - "filename": "ubuntu-14.04-server-cloudimg-amd64-disk1.img", - "version": "14.04 (LTS)", - "md5sum": "d11b89321d41d0eeddcacf73bf0d2262", - "filesize": 262668800, - "download_url": "https://cloud-images.ubuntu.com/releases/14.04/release-20180404/ubuntu-14.04-server-cloudimg-amd64-disk1.img" + "download_url": "https://cloud-images.ubuntu.com/releases/18.04/release-20180426.2/", + "direct_download_url": "https://cloud-images.ubuntu.com/releases/18.04/release-20180426.2/ubuntu-18.04-server-cloudimg-amd64.img" }, { "filename": "ubuntu-cloud-init-data.iso", - "version": "1.0", - "md5sum": "328469100156ae8dbf262daa319c27ff", - "filesize": 131072, - "download_url": "https://github.com/asenci/gns3-ubuntu-cloud-init-data/raw/master/ubuntu-cloud-init-data.iso" + "version": "1.1", + "md5sum": "9a90ee8f88736204c756015b3cd86500", + "filesize": 374784, + "download_url": "https://github.com/GNS3/gns3-registry/tree/master/cloud-init/ubuntu-cloud", + "direct_download_url": "https://github.com/GNS3/gns3-registry/raw/master/cloud-init/ubuntu-cloud/ubuntu-cloud-init-data.iso" } ], "versions": [ @@ -97,27 +80,6 @@ "hda_disk_image": "ubuntu-18.04-server-cloudimg-amd64.img", "cdrom_image": "ubuntu-cloud-init-data.iso" } - }, - { - "name": "17.10", - "images": { - "hda_disk_image": "ubuntu-17.10-server-cloudimg-amd64.img", - "cdrom_image": "ubuntu-cloud-init-data.iso" - } - }, - { - "name": "16.04 (LTS)", - "images": { - "hda_disk_image": "ubuntu-16.04-server-cloudimg-amd64-disk1.img", - "cdrom_image": "ubuntu-cloud-init-data.iso" - } - }, - { - "name": "14.04 (LTS)", - "images": { - "hda_disk_image": "ubuntu-14.04-server-cloudimg-amd64-disk1.img", - "cdrom_image": "ubuntu-cloud-init-data.iso" - } } ] } diff --git a/gns3server/compute/docker/__init__.py b/gns3server/compute/docker/__init__.py index ad2324a1..6ed9ecb6 100644 --- a/gns3server/compute/docker/__init__.py +++ b/gns3server/compute/docker/__init__.py @@ -155,7 +155,7 @@ class Docker(BaseManager): ) except aiohttp.ClientError as e: raise DockerError(f"Docker has returned an error: {e}") - except (asyncio.TimeoutError): + except asyncio.TimeoutError: raise DockerError("Docker timeout " + method + " " + path) if response.status >= 300: body = await response.read() diff --git a/gns3server/compute/docker/docker_vm.py b/gns3server/compute/docker/docker_vm.py index 88e60ac5..b77bc00d 100644 --- a/gns3server/compute/docker/docker_vm.py +++ b/gns3server/compute/docker/docker_vm.py @@ -107,7 +107,6 @@ class DockerVM(BaseNode): self._ethernet_adapters = [] self._temporary_directory = None self._telnet_servers = [] - self._xvfb_process = None self._vnc_process = None self._vncconfig_process = None self._console_resolution = console_resolution @@ -681,8 +680,8 @@ class DockerVM(BaseNode): self._display = self._get_free_display_port() tigervnc_path = shutil.which("Xtigervnc") or shutil.which("Xvnc") - if not (tigervnc_path or shutil.which("Xvfb") and shutil.which("x11vnc")): - raise DockerError("Please install TigerVNC (recommended) or Xvfb + x11vnc before using VNC support") + if not tigervnc_path: + raise DockerError("Please install TigerVNC server before using VNC support") if tigervnc_path: with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd: @@ -696,29 +695,6 @@ class DockerVM(BaseNode): "-SecurityTypes", "None", ":{}".format(self._display), stdout=fd, stderr=subprocess.STDOUT) - else: - if restart is False: - self._xvfb_process = await asyncio.create_subprocess_exec("Xvfb", - "-nolisten", "tcp", - "-extension", "MIT-SHM", - ":{}".format(self._display), - "-screen", "0", - self._console_resolution + "x16") - - # We pass a port for TCPV6 due to a crash in X11VNC if not here: https://github.com/GNS3/gns3-server/issues/569 - with open(os.path.join(self.working_dir, "vnc.log"), "w") as fd: - self._vnc_process = await asyncio.create_subprocess_exec("x11vnc", - "-forever", - "-nopw", - "-shared", - "-noshm", - "-geometry", self._console_resolution, - "-display", "WAIT:{}".format(self._display), - "-rfbport", str(self.console), - "-rfbportv6", str(self.console), - "-noncache", - "-listen", self._manager.port_manager.console_host, - stdout=fd, stderr=subprocess.STDOUT) async def _start_vnc(self): """ @@ -727,8 +703,8 @@ class DockerVM(BaseNode): self._display = self._get_free_display_port() tigervnc_path = shutil.which("Xtigervnc") or shutil.which("Xvnc") - if not (tigervnc_path or shutil.which("Xvfb") and shutil.which("x11vnc")): - raise DockerError("Please install TigerVNC server (recommended) or Xvfb + x11vnc before using VNC support") + if not tigervnc_path: + raise DockerError("Please install TigerVNC server before using VNC support") await self._start_vnc_process() x11_socket = os.path.join("/tmp/.X11-unix/", f"X{self._display}") try: @@ -1002,12 +978,6 @@ class DockerVM(BaseNode): await self._vnc_process.wait() except ProcessLookupError: pass - if self._xvfb_process: - try: - self._xvfb_process.terminate() - await self._xvfb_process.wait() - except ProcessLookupError: - pass if self._display: display = f"/tmp/.X11-unix/X{self._display}" diff --git a/gns3server/compute/qemu/qemu_vm.py b/gns3server/compute/qemu/qemu_vm.py index 89b259a9..c8a0dbc8 100644 --- a/gns3server/compute/qemu/qemu_vm.py +++ b/gns3server/compute/qemu/qemu_vm.py @@ -2374,6 +2374,8 @@ class QemuVM(BaseNode): mac = int_to_macaddress(macaddress_to_int(custom_mac_address)) device_string = f"{adapter_type},mac={mac}" + if adapter_type == "virtio-net-pci": + device_string = "{},speed=10000,duplex=full".format(device_string) bridge_id = math.floor(pci_device_id / 32) if bridge_id > 0: if pci_bridges_created < bridge_id: diff --git a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py index 1329a31a..a8d3cb65 100644 --- a/gns3server/controller/gns3vm/virtualbox_gns3_vm.py +++ b/gns3server/controller/gns3vm/virtualbox_gns3_vm.py @@ -124,18 +124,18 @@ class VirtualBoxGNS3VM(BaseGNS3VM): continue return interface - async def _look_for_vboxnet(self, interface_number): + async def _look_for_vboxnet(self, backend_type, interface_number): """ - Look for the VirtualBox network name associated with a host only interface. + Look for the VirtualBox network name associated with an interface. :returns: None or vboxnet name """ result = await self._execute("showvminfo", [self._vmname, "--machinereadable"]) for info in result.splitlines(): - if "=" in info: - name, value = info.split("=", 1) - if name == f"hostonlyadapter{interface_number}": + if '=' in info: + name, value = info.split('=', 1) + if name == "{}{}".format(backend_type, interface_number): return value.strip('"') return None @@ -161,7 +161,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): return True return False - async def _check_vboxnet_exists(self, vboxnet): + async def _check_vboxnet_exists(self, vboxnet, vboxnet_type): """ Check if the vboxnet interface exists @@ -169,7 +169,7 @@ class VirtualBoxGNS3VM(BaseGNS3VM): :returns: boolean """ - properties = await self._execute("list", ["hostonlyifs"]) + properties = await self._execute("list", ["{}".format(vboxnet_type)]) for prop in properties.splitlines(): try: name, value = prop.split(":", 1) @@ -232,25 +232,43 @@ class VirtualBoxGNS3VM(BaseGNS3VM): if nat_interface_number < 0: raise GNS3VMError(f'VM "{self.vmname}" must have a NAT interface configured in order to start') - hostonly_interface_number = await self._look_for_interface("hostonly") - if hostonly_interface_number < 0: - raise GNS3VMError(f'VM "{self.vmname}" must have a host-only interface configured in order to start') + if sys.platform.startswith("darwin") and parse_version(self._system_properties["API version"]) >= parse_version("7_0"): + # VirtualBox 7.0+ on macOS requires a host-only network interface + backend_type = "hostonly-network" + backend_description = "host-only network" + vboxnet_type = "hostonlynets" + interface_number = await self._look_for_interface("hostonlynetwork") + if interface_number < 0: + raise GNS3VMError('VM "{}" must have a network adapter attached to a host-only network in order to start'.format(self.vmname)) + else: + backend_type = "hostonlyadapter" + backend_description = "host-only adapter" + vboxnet_type = "hostonlyifs" + interface_number = await self._look_for_interface("hostonly") - vboxnet = await self._look_for_vboxnet(hostonly_interface_number) + if interface_number < 0: + raise GNS3VMError('VM "{}" must have a network adapter attached to a {} in order to start'.format(self.vmname, backend_description)) + + vboxnet = await self._look_for_vboxnet(backend_type, interface_number) if vboxnet is None: - raise GNS3VMError( - f'A VirtualBox host-only network could not be found on network adapter {hostonly_interface_number} for "{self._vmname}"' - ) + raise GNS3VMError('A VirtualBox host-only network could not be found on network adapter {} for "{}"'.format(interface_number, self._vmname)) - if not (await self._check_vboxnet_exists(vboxnet)): - raise GNS3VMError( - 'VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format( - vboxnet, hostonly_interface_number, self._vmname - ) - ) + if not (await self._check_vboxnet_exists(vboxnet, vboxnet_type)): + if sys.platform.startswith("win") and vboxnet == "vboxnet0": + # The GNS3 VM is configured with vboxnet0 by default which is not available + # on Windows. Try to patch this with the first available vboxnet we find. + first_available_vboxnet = await self._find_first_available_vboxnet() + if first_available_vboxnet is None: + raise GNS3VMError('Please add a VirtualBox host-only network with DHCP enabled and attached it to network adapter {} for "{}"'.format(interface_number, self._vmname)) + await self.set_hostonly_network(interface_number, first_available_vboxnet) + vboxnet = first_available_vboxnet + else: + raise GNS3VMError('VirtualBox host-only network "{}" does not exist, please make the sure the network adapter {} configuration is valid for "{}"'.format(vboxnet, + interface_number, + self._vmname)) - if not (await self._check_dhcp_server(vboxnet)): - raise GNS3VMError(f'DHCP must be enabled on VirtualBox host-only network "{vboxnet}"') + if backend_type == "hostonlyadapter" and not (await self._check_dhcp_server(vboxnet)): + raise GNS3VMError('DHCP must be enabled on VirtualBox host-only network "{}"'.format(vboxnet)) vm_state = await self._get_state() log.info(f'"{self._vmname}" state is {vm_state}') @@ -295,8 +313,8 @@ class VirtualBoxGNS3VM(BaseGNS3VM): [self._vmname, f"natpf{nat_interface_number}", f"GNS3VM,tcp,{ip_address},{api_port},,{self.port}"], ) - self.ip_address = await self._get_ip(hostonly_interface_number, api_port) - log.info(f"GNS3 VM has been started with IP {self.ip_address}") + self.ip_address = await self._get_ip(interface_number, api_port) + log.info("GNS3 VM has been started with IP {}".format(self.ip_address)) self.running = True async def _get_ip(self, hostonly_interface_number, api_port): diff --git a/gns3server/static/web-ui/26.49028ab13de5de406c90.js b/gns3server/static/web-ui/26.49028ab13de5de406c90.js new file mode 100644 index 00000000..0c692c06 --- /dev/null +++ b/gns3server/static/web-ui/26.49028ab13de5de406c90.js @@ -0,0 +1 @@ +"use strict";(self.webpackChunkgns3_web_ui=self.webpackChunkgns3_web_ui||[]).push([[26],{91026:function(W,g,a){a.r(g),a.d(g,{TopologySummaryComponent:function(){return U}});var t=a(38999),_=a(96852),h=a(14200),f=a(36889),v=a(3941),y=a(15132),p=a(40098),x=a(39095),c=a(88802),S=a(73044),d=a(59412),T=a(93386);function C(i,e){if(1&i){var o=t.EpF();t.TgZ(0,"div",2),t.NdJ("mousemove",function(r){return t.CHM(o),t.oxw().dragWidget(r)},!1,t.evT)("mouseup",function(){return t.CHM(o),t.oxw().toggleDragging(!1)},!1,t.evT),t.qZA()}}function b(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",29),t.qZA())}function E(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",30),t.qZA())}function Z(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",31),t.qZA())}function O(i,e){if(1&i&&(t.TgZ(0,"div"),t._uU(1),t.qZA()),2&i){var o=t.oxw().$implicit;t.xp6(1),t.lnq(" ",o.console_type,"://",o.console_host,":",o.console," ")}}function P(i,e){1&i&&(t.TgZ(0,"div"),t._uU(1," none "),t.qZA())}function M(i,e){if(1&i&&(t.TgZ(0,"div",25),t.TgZ(1,"div"),t.YNc(2,b,2,0,"svg",26),t.YNc(3,E,2,0,"svg",26),t.YNc(4,Z,2,0,"svg",26),t._uU(5),t.qZA(),t.YNc(6,O,2,3,"div",27),t.YNc(7,P,2,0,"div",27),t.qZA()),2&i){var o=e.$implicit;t.xp6(2),t.Q6J("ngIf","started"===o.status),t.xp6(1),t.Q6J("ngIf","suspended"===o.status),t.xp6(1),t.Q6J("ngIf","stopped"===o.status),t.xp6(1),t.hij(" ",o.name," "),t.xp6(1),t.Q6J("ngIf",null!=o.console&&null!=o.console&&"none"!=o.console_type),t.xp6(1),t.Q6J("ngIf",null==o.console||"none"===o.console_type)}}function w(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",29),t.qZA())}function A(i,e){1&i&&(t.O4$(),t.TgZ(0,"svg",28),t._UZ(1,"rect",31),t.qZA())}function F(i,e){if(1&i&&(t.TgZ(0,"div",25),t.TgZ(1,"div"),t.YNc(2,w,2,0,"svg",26),t.YNc(3,A,2,0,"svg",26),t._uU(4),t.qZA(),t.TgZ(5,"div"),t._uU(6),t.qZA(),t.TgZ(7,"div"),t._uU(8),t.qZA(),t.qZA()),2&i){var o=e.$implicit,s=t.oxw(2);t.xp6(2),t.Q6J("ngIf",o.connected),t.xp6(1),t.Q6J("ngIf",!o.connected),t.xp6(1),t.hij(" ",o.name," "),t.xp6(2),t.hij(" ",o.host," "),t.xp6(2),t.hij(" ",s.server.location," ")}}var I=function(i){return{lightTheme:i}},D=function(){return{right:!0,left:!0,bottom:!0,top:!0}};function N(i,e){if(1&i){var o=t.EpF();t.TgZ(0,"div",3),t.NdJ("mousedown",function(){return t.CHM(o),t.oxw().toggleDragging(!0)})("resizeStart",function(){return t.CHM(o),t.oxw().toggleDragging(!1)})("resizeEnd",function(n){return t.CHM(o),t.oxw().onResizeEnd(n)}),t.TgZ(1,"div",4),t.TgZ(2,"mat-tab-group"),t.TgZ(3,"mat-tab",5),t.NdJ("click",function(){return t.CHM(o),t.oxw().toggleTopologyVisibility(!0)}),t.TgZ(4,"div",6),t.TgZ(5,"div",7),t.TgZ(6,"mat-select",8),t.TgZ(7,"mat-optgroup",9),t.TgZ(8,"mat-option",10),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyStatusFilter("started")}),t._uU(9,"started"),t.qZA(),t.TgZ(10,"mat-option",11),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyStatusFilter("suspended")}),t._uU(11,"suspended"),t.qZA(),t.TgZ(12,"mat-option",12),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyStatusFilter("stopped")}),t._uU(13,"stopped"),t.qZA(),t.qZA(),t.TgZ(14,"mat-optgroup",13),t.TgZ(15,"mat-option",14),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyCaptureFilter("capture")}),t._uU(16,"active capture(s)"),t.qZA(),t.TgZ(17,"mat-option",15),t.NdJ("onSelectionChange",function(){return t.CHM(o),t.oxw().applyCaptureFilter("packet")}),t._uU(18,"active packet captures"),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.TgZ(19,"div",16),t.TgZ(20,"mat-select",17),t.NdJ("selectionChange",function(){return t.CHM(o),t.oxw().setSortingOrder()})("valueChange",function(n){return t.CHM(o),t.oxw().sortingOrder=n}),t.TgZ(21,"mat-option",18),t._uU(22,"sort by name ascending"),t.qZA(),t.TgZ(23,"mat-option",19),t._uU(24,"sort by name descending"),t.qZA(),t.qZA(),t.qZA(),t._UZ(25,"mat-divider",20),t.TgZ(26,"div",21),t.YNc(27,M,8,6,"div",22),t.qZA(),t.qZA(),t.qZA(),t.TgZ(28,"mat-tab",23),t.NdJ("click",function(){return t.CHM(o),t.oxw().toggleTopologyVisibility(!1)}),t.TgZ(29,"div",6),t.TgZ(30,"div",24),t.YNc(31,F,9,5,"div",22),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.qZA(),t.qZA()}if(2&i){var s=t.oxw();t.Q6J("ngStyle",s.style)("ngClass",t.VKq(9,I,s.isLightThemeEnabled))("validateResize",s.validate)("resizeEdges",t.DdM(11,D))("enableGhostResize",!0),t.xp6(20),t.Q6J("value",s.sortingOrder),t.xp6(6),t.Q6J("ngStyle",s.styleInside),t.xp6(1),t.Q6J("ngForOf",s.filteredNodes),t.xp6(4),t.Q6J("ngForOf",s.computes)}}var U=function(){function i(e,o,s,r,n){this.nodesDataSource=e,this.projectService=o,this.computeService=s,this.linksDataSource=r,this.themeService=n,this.closeTopologySummary=new t.vpe,this.style={},this.styleInside={height:"280px"},this.subscriptions=[],this.nodes=[],this.filteredNodes=[],this.sortingOrder="asc",this.startedStatusFilterEnabled=!1,this.suspendedStatusFilterEnabled=!1,this.stoppedStatusFilterEnabled=!1,this.captureFilterEnabled=!1,this.packetFilterEnabled=!1,this.computes=[],this.isTopologyVisible=!0,this.isDraggingEnabled=!1,this.isLightThemeEnabled=!1}return i.prototype.ngOnInit=function(){var e=this;this.isLightThemeEnabled="light"===this.themeService.getActualTheme(),this.subscriptions.push(this.nodesDataSource.changes.subscribe(function(o){e.nodes=o,e.nodes.forEach(function(s){("0.0.0.0"===s.console_host||"0:0:0:0:0:0:0:0"===s.console_host||"::"===s.console_host)&&(s.console_host=e.server.host)}),e.filteredNodes=o.sort("asc"===e.sortingOrder?e.compareAsc:e.compareDesc)})),this.projectService.getStatistics(this.server,this.project.project_id).subscribe(function(o){e.projectsStatistics=o}),this.computeService.getComputes(this.server).subscribe(function(o){e.computes=o}),this.revertPosition()},i.prototype.revertPosition=function(){var e=localStorage.getItem("leftPosition"),o=localStorage.getItem("rightPosition"),s=localStorage.getItem("topPosition"),r=localStorage.getItem("widthOfWidget"),n=localStorage.getItem("heightOfWidget");this.style=s?{position:"fixed",left:+e+"px",right:+o+"px",top:+s+"px",width:+r+"px",height:+n+"px"}:{top:"60px",right:"0px",width:"320px",height:"400px"}},i.prototype.toggleDragging=function(e){this.isDraggingEnabled=e},i.prototype.dragWidget=function(e){var o=Number(e.movementX),s=Number(e.movementY),r=Number(this.style.width.split("px")[0]),n=Number(this.style.height.split("px")[0]),l=Number(this.style.top.split("px")[0])+s;if(this.style.left){var u=Number(this.style.left.split("px")[0])+o;this.style={position:"fixed",left:u+"px",top:l+"px",width:r+"px",height:n+"px"},localStorage.setItem("leftPosition",u.toString()),localStorage.setItem("topPosition",l.toString()),localStorage.setItem("widthOfWidget",r.toString()),localStorage.setItem("heightOfWidget",n.toString())}else{var m=Number(this.style.right.split("px")[0])-o;this.style={position:"fixed",right:m+"px",top:l+"px",width:r+"px",height:n+"px"},localStorage.setItem("rightPosition",m.toString()),localStorage.setItem("topPosition",l.toString()),localStorage.setItem("widthOfWidget",r.toString()),localStorage.setItem("heightOfWidget",n.toString())}},i.prototype.validate=function(e){return!(e.rectangle.width&&e.rectangle.height&&(e.rectangle.width<290||e.rectangle.height<260))},i.prototype.onResizeEnd=function(e){this.style={position:"fixed",left:e.rectangle.left+"px",top:e.rectangle.top+"px",width:e.rectangle.width+"px",height:e.rectangle.height+"px"},this.styleInside={height:e.rectangle.height-120+"px"}},i.prototype.toggleTopologyVisibility=function(e){this.isTopologyVisible=e,this.revertPosition()},i.prototype.compareAsc=function(e,o){return e.name=o)&&Object.keys(n.O).every(function(b){return n.O[b](t[f])})?t.splice(f--,1):(l=!1,o0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[t,u,o]},n.n=function(e){var a=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(a,{a:a}),a},n.d=function(e,a){for(var t in a)n.o(a,t)&&!n.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:a[t]})},n.f={},n.e=function(e){return Promise.all(Object.keys(n.f).reduce(function(a,t){return n.f[t](e,a),a},[]))},n.u=function(e){return e+".49028ab13de5de406c90.js"},n.miniCssF=function(e){return"styles.f8555f2eecf8cf87f666.css"},n.hmd=function(e){return(e=Object.create(e)).children||(e.children=[]),Object.defineProperty(e,"exports",{enumerable:!0,set:function(){throw new Error("ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: "+e.id)}}),e},n.o=function(e,a){return Object.prototype.hasOwnProperty.call(e,a)},function(){var e={},a="gns3-web-ui:";n.l=function(t,u,o,i){if(e[t])e[t].push(u);else{var r,l;if(void 0!==o)for(var f=document.getElementsByTagName("script"),s=0;s&2 echo "--with-iou: Install IOU" >&2 echo "--with-i386-repository: Add the i386 repositories required by IOU if they are not already available on the system. Warning: this will replace your source.list in order to use the official Ubuntu mirror" >&2 + echo "--with-welcome: Install GNS3-VM welcome.py script" >&2 echo "--without-kvm: Disable KVM, required if system do not support it (limitation in some hypervisors and cloud providers). Warning: only disable KVM if strictly necessary as this will degrade performance" >&2 echo "--unstable: Use the GNS3 unstable repository" echo "--help: This help" >&2 @@ -48,8 +49,9 @@ USE_IOU=0 I386_REPO=0 DISABLE_KVM=0 UNSTABLE=0 +WELCOME_SETUP=0 -TEMP=`getopt -o h --long with-openvpn,with-iou,with-i386-repository,without-kvm,unstable,help -n 'gns3-remote-install.sh' -- "$@"` +TEMP=`getopt -o h --long with-openvpn,with-iou,with-i386-repository,with-welcome,without-kvm,unstable,help -n 'gns3-remote-install.sh' -- "$@"` if [ $? != 0 ] then help @@ -72,6 +74,10 @@ while true ; do I386_REPO=1 shift ;; + --with-welcome) + WELCOME_SETUP=1 + shift + ;; --without-kvm) DISABLE_KVM=1 shift @@ -159,7 +165,7 @@ apt-get install -y gns3-server log "Create user GNS3 with /opt/gns3 as home directory" if [ ! -d "/opt/gns3/" ] then - useradd -d /opt/gns3/ -m gns3 + useradd -m -d /opt/gns3/ gns3 fi @@ -296,6 +302,37 @@ fi log "GNS3 installed with success" +if [ $WELCOME_SETUP == 1 ] +then +NEEDRESTART_MODE=a apt-get install -y net-tools +NEEDRESTART_MODE=a apt-get install -y python3-pip +NEEDRESTART_MODE=a apt-get install -y dialog +pip install --no-input --upgrade pip +pip install --no-input pythondialog + +#Pull down welcome script from repo +curl https://raw.githubusercontent.com/GNS3/gns3-server/master/scripts/welcome.py > /usr/local/bin/welcome.py + +chmod 755 /usr/local/bin/welcome.py +chown gns3:gns3 /usr/local/bin/welcome.py + +mkdir /etc/systemd/system/getty@tty1.service.d +cat < /etc/systemd/system/getty@tty1.service.d/override.conf +[Service] +ExecStart= +ExecStart=-/sbin/agetty -a gns3 --noclear %I \$TERM +EOFI + +chmod 755 /etc/systemd/system/getty@tty1.service.d/override.conf +chown root:root /etc/systemd/system/getty@tty1.service.d/override.conf + +echo "python3 /usr/local/bin/welcome.py" >> /opt/gns3/.bashrc +echo "gns3:gns3" | chpasswd +usermod --shell /bin/bash gns3 +usermod -aG sudo gns3 + +fi + if [ $USE_VPN == 1 ] then log "Setup VPN" @@ -417,3 +454,12 @@ service gns3 start log "Download http://$MY_IP_ADDR:8003/$UUID/$HOSTNAME.ovpn to setup your OpenVPN client after rebooting the server" fi + +if [ $WELCOME_SETUP == 1 ] +then +NEEDRESTART_MODE=a apt-get update +NEEDRESTART_MODE=a apt-get upgrade +python3 -c 'import sys; sys.path.append("/usr/local/bin/"); import welcome; ws = welcome.Welcome_dialog(); ws.repair_remote_install()' +cd /opt/gns3 +su gns3 +fi \ No newline at end of file diff --git a/scripts/welcome.py b/scripts/welcome.py new file mode 100644 index 00000000..821e4f97 --- /dev/null +++ b/scripts/welcome.py @@ -0,0 +1,479 @@ +#!/usr/bin/env python3 +# -*- 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 . + +import locale +import re +import os +import sys +import time +import subprocess +import configparser +from json import loads as convert +import urllib.request +from dialog import Dialog, PythonDialogBug + + +class Welcome_dialog: + def __init__(self): + try: + locale.setlocale(locale.LC_ALL, '') + except locale.Error: + # Not supported via SSH + pass + self.display = Dialog(dialog="dialog", autowidgetsize=True) + if self.gns3_version() is None: + self.display.set_background_title("GNS3") + else: + self.display.set_background_title("GNS3 {}".format(self.gns3_version())) + + def get_ip(self): + """ + Return the active IP + """ + #request 'ip addr' data in JSON format from shell + ip_addr_response = subprocess.run(['ip', '--json', 'addr'],capture_output=True) + + #process response, decode and use json.loads to convert the string to a dict + ip_addr_data = convert(ip_addr_response.stdout.decode("utf-8")) + + #search ip_addr_data for the first ip adress that is not under a virtual bridge or loopback interface + for i in ip_addr_data: + if ('virbr' in i['ifname']) or ('lo' in i['ifname']): + continue + try: + if 'UP' in i['flags']: + ip_addr = i['addr_info'][0]['local'] + break + except: + continue + ip_addr = None + + return ip_addr + + def repair_remote_install(self): + """ + This method is only called by remote-install.sh during setup to ensure it is setting the same IP as shown by Dialog + """ + ip_addr = self.get_ip() + subprocess.run(["sed", "-i", f"s/host = 0.0.0.0/host = {ip_addr}/", "/etc/gns3/gns3_server.conf"],capture_output=False) + subprocess.run(["service", "gns3", "stop"],capture_output=False) + subprocess.run(["service", "gns3", "start"],capture_output=False) + + + def get_config(self): + """ + Read the config + """ + config = configparser.RawConfigParser() + path = os.path.expanduser("~/.config/GNS3/gns3_server.conf") + config.read([path], encoding="utf-8") + return config + + + def write_config(self, config): + """ + Write the config file + """ + + with open(os.path.expanduser("~/.config/GNS3/gns3_server.conf"), 'w') as f: + config.write(f) + + + def gns3_major_version(self): + """ + Returns the GNS3 major server version + """ + + version = self.gns3_version() + if version: + match = re.search(r"\d+.\d+", version) + return match.group(0) + return "" + + + def gns3_version(self): + """ + Return the GNS3 server version + """ + try: + return subprocess.check_output(["gns3server", "--version"]).strip().decode() + except (subprocess.CalledProcessError, FileNotFoundError): + return None + + + def gns3vm_version(self): + """ + Return the GNS3 VM version + """ + try: + with open('/home/gns3/.config/GNS3/gns3vm_version') as f: + return f.read().strip() + except FileNotFoundError: + return "Remote Install" + + + def mode(self): + if self.display.yesno("This feature is for testers only. You may break your GNS3 installation. Are you REALLY sure you want to continue?", yes_label="Exit (Safe option)", no_label="Continue") == self.display.OK: + return + code, tag = self.display.menu("Select the GNS3 version", + choices=[("2.1", "Stable release for this GNS3 VM (RECOMMENDED)"), + ("2.1dev", "Development version for stable release"), + ("2.2", "Latest stable release")]) + self.display.clear() + if code == Dialog.OK: + os.makedirs(os.path.expanduser("~/.config/GNS3"), exist_ok=True) + with open(os.path.expanduser("~/.config/GNS3/gns3_release"), "w+") as f: + f.write(tag) + + self.update(force=True) + + + def get_release(self): + try: + with open(os.path.expanduser("~/.config/GNS3/gns3_release")) as f: + content = f.read() + + # Support old VM versions + if content == "stable": + content = "1.5" + elif content == "testing": + content = "1.5" + elif content == "unstable": + content = "1.5dev" + + return content + except OSError: + return "1.5" + + + def update(self, force=False): + if not force: + if self.display.yesno("PLEASE SNAPSHOT THE VM BEFORE RUNNING THE UPGRADE IN CASE OF FAILURE. The server will reboot at the end of the upgrade process. Continue?") != self.display.OK: + return + release = self.get_release() + if release == "2.2": + if self.display.yesno("It is recommended to run GNS3 version 2.2 with lastest GNS3 VM based on Ubuntu 18.04 LTS, please download this VM from our website or continue at your own risk!") != self.display.OK: + return + if release.endswith("dev"): + ret = os.system("curl -Lk https://raw.githubusercontent.com/GNS3/gns3-vm/unstable/scripts/update_{}.sh > /tmp/update.sh && bash -x /tmp/update.sh".format(release)) + else: + ret = os.system("curl -Lk https://raw.githubusercontent.com/GNS3/gns3-vm/master/scripts/update_{}.sh > /tmp/update.sh && bash -x /tmp/update.sh".format(release)) + if ret != 0: + print("ERROR DURING UPGRADE PROCESS PLEASE TAKE A SCREENSHOT IF YOU NEED SUPPORT") + time.sleep(15) + + + def migrate(self): + """ + Migrate GNS3 VM data. + """ + + code, option = self.display.menu("Select an option", + choices=[("Setup", "Configure this VM to send data to another GNS3 VM"), + ("Send", "Send images and projects to another GNS3 VM")]) + self.display.clear() + if code == Dialog.OK: + (answer, destination) = self.display.inputbox("What is IP address or hostname of the other GNS3 VM?", init="172.16.1.128") + if answer != self.display.OK: + return + if destination == self.get_ip(): + self.display.msgbox("The destination cannot be the same as this VM IP address ({})".format(destination)) + return + if option == "Send": + # first make sure they are no files belonging to root + os.system("sudo chown -R gns3:gns3 /opt/gns3") + # then rsync the data + command = r"rsync -az --progress -e 'ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /home/gns3/.ssh/gns3-vm-key' /opt/gns3 gns3@{}:/opt".format(destination) + ret = os.system('bash -c "{}"'.format(command)) + time.sleep(10) + if ret != 0: + self.display.msgbox("Could not send data to the other GNS3 VM located at {}".format(destination)) + else: + self.display.msgbox("Images and projects have been successfully sent to the other GNS3 VM located at {}".format(destination)) + elif option == "Setup": + script = """ + if [ ! -f ~/.ssh/gns3-vm-key ] + then + ssh-keygen -f ~/.ssh/gns3-vm-key -N '' -C gns3@{} + fi + ssh-copy-id -i ~/.ssh/gns3-vm-key gns3@{} + """.format(self.get_ip(), destination) + ret = os.system('bash -c "{}"'.format(script)) + time.sleep(10) + if ret != 0: + self.display.msgbox("Error while setting up the migrate feature") + else: + self.display.msgbox("Configuration successful, you can now send data to the GNS3 VM located at {} without password".format(destination)) + + + def shrink_disk(self): + + ret = os.system("lspci | grep -i vmware") + if ret != 0: + self.display.msgbox("Shrinking the disk is only supported when running inside VMware") + return + + if self.display.yesno("Would you like to shrink the VM disk? The VM will reboot at the end of the process. Continue?") != self.display.OK: + return + + os.system("sudo service gns3 stop") + os.system("sudo service docker stop") + os.system("sudo vmware-toolbox-cmd disk shrink /opt") + os.system("sudo vmware-toolbox-cmd disk shrink /") + + self.display.msgbox("The GNS3 VM will reboot") + os.execvp("sudo", ['/usr/bin/sudo', "reboot"]) + + def vm_information(self): + """ + Show IP, SSH settings.... + """ + + content = "Welcome to GNS3 appliance\n\n" + + version = self.gns3_version() + if version is None: + content += "GNS3 is not installed please install it with sudo pip3 install gns3-server. Or download a preinstalled VM.\n\n" + else: + content = "GNS3 version: {gns3_version}\nVM version: {gns3vm_version}\nKVM support available: {kvm}\n\n".format( + gns3vm_version=self.gns3vm_version(), + gns3_version=version, + kvm=self.kvm_support()) + + ip = self.get_ip() + + if ip: + content += f""" +IP: {ip} +Web UI: http://{ip}:3080 + +To log in using SSH: +ssh gns3@{ip} +Password: gns3 + +Images and projects are located in /opt/gns3 +""".strip() + + else: + content += "eth0 is not configured. Please manually configure it via the Networking menu." + + content += "\n\nRelease channel: " + self.get_release() + + try: + self.display.msgbox(content) + # If it's an scp command or any bugs + except: + os.execvp("bash", ['/bin/bash']) + + + def check_internet_connectivity(self): + self.display.pause("Please wait...\n\n") + try: + response = urllib.request.urlopen('http://pypi.python.org/', timeout=5) + except urllib.request.URLError as err: + self.display.infobox("Can't connect to Internet (pypi.python.org): {}".format(str(err))) + time.sleep(15) + return + self.display.infobox("Connection to Internet: OK") + time.sleep(2) + + + def keyboard_configuration(): + """ + Allow user to change the keyboard layout + """ + os.system("/usr/bin/sudo dpkg-reconfigure keyboard-configuration") + + + def set_security(self): + config = self.get_config() + if self.display.yesno("Enable server authentication?") == self.display.OK: + if not config.has_section("Server"): + config.add_section("Server") + config.set("Server", "auth", True) + (answer, text) = self.display.inputbox("Login?") + if answer != self.display.OK: + return + config.set("Server", "user", text) + (answer, text) = self.display.passwordbox("Password?") + if answer != self.display.OK: + return + config.set("Server", "password", text) + else: + config.set("Server", "auth", False) + + self.write_config(config) + + + def log(self): + os.system("/usr/bin/sudo chmod 755 /var/log/upstart/gns3.log") + with open("/var/log/upstart/gns3.log") as f: + try: + while True: + line = f.readline() + sys.stdout.write(line) + except (KeyboardInterrupt, MemoryError): + return + + + def edit_config(self): + """ + Edit GNS3 configuration file + """ + + major_version = self.gns3_major_version() + if major_version == "2.2": + os.system("nano ~/.config/GNS3/{}/gns3_server.conf".format(major_version)) + else: + os.system("nano ~/.config/GNS3/gns3_server.conf") + + + def edit_network(self): + """ + Edit network configuration file + """ + if self.display.yesno("The server will reboot at the end of the process. Continue?") != self.display.OK: + return + os.system("sudo nano /etc/network/interfaces") + os.execvp("sudo", ['/usr/bin/sudo', "reboot"]) + + + def edit_proxy(self): + """ + Configure proxy settings + """ + res, http_proxy = self.display.inputbox(text="HTTP proxy string, for example http://:@:. Leave empty for no proxy.") + if res != self.display.OK: + return + res, https_proxy = self.display.inputbox(text="HTTPS proxy string, for example http://:@:. Leave empty for no proxy.") + if res != self.display.OK: + return + + with open('/tmp/00proxy', 'w+') as f: + f.write('Acquire::http::Proxy "' + http_proxy + '";') + os.system("sudo mv /tmp/00proxy /etc/apt/apt.conf.d/00proxy") + os.system("sudo chown root /etc/apt/apt.conf.d/00proxy") + os.system("sudo chmod 744 /etc/apt/apt.conf.d/00proxy") + + with open('/tmp/proxy.sh', 'w+') as f: + f.write('export http_proxy="' + http_proxy + '"\n') + f.write('export https_proxy="' + https_proxy + '"\n') + f.write('export HTTP_PROXY="' + http_proxy + '"\n') + f.write('export HTTPS_PROXY="' + https_proxy + '"\n') + os.system("sudo mv /tmp/proxy.sh /etc/profile.d/proxy.sh") + os.system("sudo chown root /etc/profile.d/proxy.sh") + os.system("sudo chmod 744 /etc/profile.d/proxy.sh") + os.system("sudo cp /etc/profile.d/proxy.sh /etc/default/docker") + + self.display.msgbox("The GNS3 VM will reboot") + os.execvp("sudo", ['/usr/bin/sudo', "reboot"]) + + + def kvm_support(self): + """ + Returns true if KVM is available + """ + return subprocess.call("kvm-ok") == 0 + + + def kvm_control(self): + """ + Check if KVM is correctly configured + """ + + kvm_ok = self.kvm_support() + config = self.get_config() + try: + if config.getboolean("Qemu", "enable_kvm") is True: + if kvm_ok is False: + if self.display.yesno("KVM is not available!\n\nQemu VM will crash!!\n\nThe reason could be unsupported hardware or another virtualization solution is already running.\n\nDisable KVM and get lower performances?") == self.display.OK: + config.set("Qemu", "enable_kvm", False) + self.write_config(config) + os.execvp("sudo", ['/usr/bin/sudo', "reboot"]) + else: + if kvm_ok is True: + if self.display.yesno("KVM is available on your computer.\n\nEnable KVM and get better performances?") == self.display.OK: + config.set("Qemu", "enable_kvm", True) + self.write_config(config) + os.execvp("sudo", ['/usr/bin/sudo', "reboot"]) + except configparser.NoSectionError: + return + + + def display_loop(self): + try: + while True: + code, tag = self.display.menu("GNS3 {}".format(self.gns3_version()), + choices=[("Information", "Display VM information"), + ("Upgrade", "Upgrade GNS3"), + ("Migrate", "Migrate data to another GNS3 VM"), + ("Shell", "Open a console"), + ("Security", "Configure authentication"), + ("Keyboard", "Change keyboard layout"), + ("Configure", "Edit server configuration (advanced users ONLY)"), + ("Proxy", "Configure proxy settings"), + ("Networking", "Configure networking settings"), + ("Log", "Show server log"), + ("Test", "Check internet connection"), + ("Shrink", "Shrink the VM disk"), + ("Version", "Select the GNS3 version"), + ("Restore", "Restore the VM (if you have trouble for upgrade)"), + ("Reboot", "Reboot the VM"), + ("Shutdown", "Shutdown the VM")]) + self.display.clear() + if code == Dialog.OK: + if tag == "Shell": + os.execvp("bash", ['/bin/bash']) + elif tag == "Version": + self.mode() + elif tag == "Restore": + os.execvp("sudo", ['/usr/bin/sudo', "/usr/local/bin/gns3restore"]) + elif tag == "Reboot": + os.execvp("sudo", ['/usr/bin/sudo', "reboot"]) + elif tag == "Shutdown": + os.execvp("sudo", ['/usr/bin/sudo', "poweroff"]) + elif tag == "Upgrade": + self.update() + elif tag == "Information": + self.vm_information() + elif tag == "Log": + self.log() + elif tag == "Migrate": + self.migrate() + elif tag == "Configure": + self.edit_config() + elif tag == "Networking": + self.edit_network() + elif tag == "Security": + self.set_security() + elif tag == "Keyboard": + self.keyboard_configuration() + elif tag == "Test": + self.check_internet_connectivity() + elif tag == "Proxy": + self.edit_proxy() + elif tag == "Shrink": + self.shrink_disk() + except KeyboardInterrupt: + sys.exit(0) + +if __name__ == "__main__": + ws = Welcome_dialog() + ws.vm_information() + ws.kvm_control() + ws.display_loop() diff --git a/tests/compute/docker/test_docker_vm.py b/tests/compute/docker/test_docker_vm.py index ecab0ce0..cf0c63c2 100644 --- a/tests/compute/docker/test_docker_vm.py +++ b/tests/compute/docker/test_docker_vm.py @@ -1350,8 +1350,7 @@ async def test_close(vm, port_manager): async def test_close_vnc(vm): vm._console_type = "vnc" - vm._x11vnc_process = MagicMock() - vm._xvfb_process = MagicMock() + vm._vnc_process = MagicMock() with asyncio_patch("gns3server.compute.docker.DockerVM._get_container_state", return_value="stopped"): with asyncio_patch("gns3server.compute.docker.Docker.query") as mock_query: @@ -1359,7 +1358,7 @@ async def test_close_vnc(vm): mock_query.assert_called_with("DELETE", "containers/e90e34656842", params={"force": 1, "v": 1}) assert vm._closed is True - assert vm._xvfb_process.terminate.called + assert vm._vnc_process.terminate.called @pytest.mark.asyncio diff --git a/tests/compute/iou/test_iou_vm.py b/tests/compute/iou/test_iou_vm.py index 76764ff5..06cde930 100644 --- a/tests/compute/iou/test_iou_vm.py +++ b/tests/compute/iou/test_iou_vm.py @@ -98,6 +98,7 @@ async def test_start(vm): with asyncio_patch("asyncio.create_subprocess_exec", return_value=mock_process) as mock_exec: mock_process.returncode = None + mock_process.communicate = AsyncioMagicMock(return_value=(None, None)) await vm.start() assert vm.is_running() assert vm.command_line == ' '.join(mock_exec.call_args[0]) @@ -127,6 +128,7 @@ async def test_start_with_iourc(vm, tmpdir, config): config.settings.IOU.iourc_path = fake_file with asyncio_patch("asyncio.create_subprocess_exec", return_value=mock_process) as exec_mock: mock_process.returncode = None + mock_process.communicate = AsyncioMagicMock(return_value=(None, None)) await vm.start() assert vm.is_running() arsgs, kwargs = exec_mock.call_args @@ -164,11 +166,12 @@ async def test_stop(vm): future = asyncio.Future() future.set_result(True) process.wait.return_value = future + process.returncode = None + process.communicate = AsyncioMagicMock(return_value=(None, None)) with asyncio_patch("asyncio.create_subprocess_exec", return_value=process): with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): await vm.start() - process.returncode = None assert vm.is_running() await vm.stop() assert vm.is_running() is False @@ -190,6 +193,7 @@ async def test_reload(vm, fake_iou_bin): future.set_result(True) process.wait.return_value = future process.returncode = None + process.communicate = AsyncioMagicMock(return_value=(None, None)) with asyncio_patch("asyncio.create_subprocess_exec", return_value=process): with asyncio_patch("gns3server.utils.asyncio.wait_for_process_termination"): @@ -203,10 +207,13 @@ async def test_reload(vm, fake_iou_bin): @pytest.mark.asyncio async def test_close(vm, port_manager): + process = MagicMock() + process.returncode = None + process.communicate = AsyncioMagicMock(return_value=(None, None)) vm._start_ubridge = AsyncioMagicMock(return_value=True) vm._ubridge_send = AsyncioMagicMock() with asyncio_patch("gns3server.compute.iou.iou_vm.IOUVM._check_requirements", return_value=True): - with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): + with asyncio_patch("asyncio.create_subprocess_exec", return_value=process): await vm.start() port = vm.console await vm.close() diff --git a/tests/compute/qemu/test_qemu_vm.py b/tests/compute/qemu/test_qemu_vm.py index a7c0c63c..347f1e11 100644 --- a/tests/compute/qemu/test_qemu_vm.py +++ b/tests/compute/qemu/test_qemu_vm.py @@ -751,6 +751,20 @@ async def test_build_command_large_number_of_adapters(vm): with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): await vm._build_command() +@pytest.mark.asyncio +async def test_build_command_with_virtio_net_pci_adapter(vm): + """ + Test virtio-net-pci adapter which has parameters speed=1000 & duplex=full hard-coded + """ + + vm.manager.get_qemu_version = AsyncioMagicMock(return_value="2.4.0") + vm.adapters = 1 + vm.mac_address = "00:00:ab:0e:0f:09" + vm._adapter_type = "virtio-net-pci" + with asyncio_patch("asyncio.create_subprocess_exec", return_value=MagicMock()): + cmd = await vm._build_command() + assert "virtio-net-pci,mac=00:00:ab:0e:0f:09,speed=10000,duplex=full,netdev=gns3-0" in cmd + @pytest.mark.asyncio async def test_build_command_with_invalid_options(vm):