#!/usr/bin/env python # # Copyright (C) 2016 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 sys import copy import asyncio import aiohttp import ipaddress from ...utils.asyncio import locking from .vmware_gns3_vm import VMwareGNS3VM from .virtualbox_gns3_vm import VirtualBoxGNS3VM from .hyperv_gns3_vm import HyperVGNS3VM from .remote_gns3_vm import RemoteGNS3VM from .gns3_vm_error import GNS3VMError from ...version import __version__ from ..compute import ComputeError import logging log = logging.getLogger(__name__) class GNS3VM: """ Proxy between the controller and the GNS3 VM engine """ def __init__(self, controller, settings={}): self._controller = controller # Keep instance of the loaded engines self._engines = {} self._settings = { "vmname": None, "when_exit": "stop", "headless": False, "enable": False, "engine": "vmware", "ram": 2048, "vcpus": 1 } self.settings = settings def engine_list(self): """ :returns: Return list of engines supported by GNS3 for the GNS3VM """ download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VMware.Workstation.{version}.zip".format(version=__version__) vmware_info = { "engine_id": "vmware", "description": 'VMware is the recommended choice for best performances.
The GNS3 VM can be downloaded here.'.format(download_url), "support_when_exit": True, "support_headless": True, "support_ram": True } if sys.platform.startswith("darwin"): vmware_info["name"] = "VMware Fusion (recommended)" else: vmware_info["name"] = "VMware Workstation / Player (recommended)" hyperv_info = { "engine_id": "hyper-v", "name": "Hyper-V", "description": 'Hyper-V support (Windows 10/Server 2016 and above). Nested virtualization must be supported and enabled (Intel processor only)', "support_when_exit": True, "support_headless": False, "support_ram": True } download_url = "https://github.com/GNS3/gns3-gui/releases/download/v{version}/GNS3.VM.VirtualBox.{version}.zip".format(version=__version__) virtualbox_info = { "engine_id": "virtualbox", "name": "VirtualBox (deprecated)", "description": 'VirtualBox doesn\'t support nested virtualization, this means Qemu based VMs will run extremely slowly. This feature is marked as deprecated and support may be removed from future GNS3 releases.
The GNS3 VM can be downloaded here'.format(download_url), "support_when_exit": True, "support_headless": True, "support_ram": True } remote_info = { "engine_id": "remote", "name": "Remote", "description": "Use a remote GNS3 server as the GNS3 VM.", "support_when_exit": False, "support_headless": False, "support_ram": False } engines = [vmware_info, virtualbox_info, remote_info] if sys.platform.startswith("win"): engines.append(hyperv_info) return engines def current_engine(self): return self._get_engine(self._settings["engine"]) @property def engine(self): return self._settings["engine"] @property def ip_address(self): """ Returns the GNS3 VM IP address. :returns: VM IP address """ return self.current_engine().ip_address @property def running(self): """ Returns if the GNS3 VM is running. :returns: Boolean """ return self.current_engine().running @property def user(self): """ Returns the GNS3 VM user. :returns: VM user """ return self.current_engine().user @property def password(self): """ Returns the GNS3 VM password. :returns: VM password """ return self.current_engine().password @property def port(self): """ Returns the GNS3 VM port. :returns: VM port """ return self.current_engine().port @property def protocol(self): """ Returns the GNS3 VM protocol. :returns: VM protocol """ return self.current_engine().protocol @property def enable(self): """ The GNSVM is activated """ return self._settings.get("enable", False) @property def when_exit(self): """ What should be done when exit """ return self._settings["when_exit"] @property def settings(self): return self._settings @settings.setter def settings(self, val): self._settings.update(val) async def update_settings(self, settings): """ Update settings and will restart the VM if require """ new_settings = copy.copy(self._settings) new_settings.update(settings) if self.settings != new_settings: await self._stop() self._settings = settings self._controller.save() if self.enable: await self.start() else: # When user fix something on his system and try again if self.enable and not self.current_engine().running: await self.start() def _get_engine(self, engine): """ Load an engine """ if engine in self._engines: return self._engines[engine] if engine == "vmware": self._engines["vmware"] = VMwareGNS3VM(self._controller) return self._engines["vmware"] elif engine == "hyper-v": self._engines["hyper-v"] = HyperVGNS3VM(self._controller) return self._engines["hyper-v"] elif engine == "virtualbox": self._engines["virtualbox"] = VirtualBoxGNS3VM(self._controller) return self._engines["virtualbox"] elif engine == "remote": self._engines["remote"] = RemoteGNS3VM(self._controller) return self._engines["remote"] raise NotImplementedError("The engine {} for the GNS3 VM is not supported".format(engine)) def __json__(self): return self._settings async def list(self, engine): """ List VMS for an engine """ engine = self._get_engine(engine) vms = [] try: for vm in (await engine.list()): vms.append({"vmname": vm["vmname"]}) except GNS3VMError as e: # We raise error only if user activated the GNS3 VM # otherwise you have noise when VMware is not installed if self.enable: raise e return vms async def auto_start_vm(self): """ Auto start the GNS3 VM if require """ if self.enable: try: await self.start() except GNS3VMError as e: # User will receive the error later when they will try to use the node try: compute = await self._controller.add_compute(compute_id="vm", name="GNS3 VM ({})".format(self.current_engine().vmname), host=None, force=True) compute.set_last_error(str(e)) except aiohttp.web.HTTPConflict: pass log.error("Cannot start the GNS3 VM: {}".format(e)) async def exit_vm(self): if self.enable: try: if self._settings["when_exit"] == "stop": await self._stop() elif self._settings["when_exit"] == "suspend": await self._suspend() except GNS3VMError as e: log.warning(str(e)) @locking async def start(self): """ Start the GNS3 VM """ engine = self.current_engine() if not engine.running: if self._settings["vmname"] is None: return log.info("Start the GNS3 VM") engine.vmname = self._settings["vmname"] engine.ram = self._settings["ram"] engine.vcpus = self._settings["vcpus"] engine.headless = self._settings["headless"] compute = await self._controller.add_compute(compute_id="vm", name="GNS3 VM is starting ({})".format(engine.vmname), host=None, force=True, connect=False) try: await engine.start() except Exception as e: await self._controller.delete_compute("vm") log.error("Cannot start the GNS3 VM: {}".format(str(e))) await compute.update(name="GNS3 VM ({})".format(engine.vmname)) compute.set_last_error(str(e)) raise e await compute.connect() # we can connect now that the VM has started await compute.update(name="GNS3 VM ({})".format(engine.vmname), protocol=self.protocol, host=self.ip_address, port=self.port, user=self.user, password=self.password) # check if the VM is in the same subnet as the local server, start 10 seconds later to give # some time for the compute in the VM to be ready for requests asyncio.get_event_loop().call_later(10, lambda: asyncio.ensure_future(self._check_network(compute))) async def _check_network(self, compute): """ Check that the VM is in the same subnet as the local server """ try: vm_interfaces = await compute.interfaces() vm_interface_netmask = None for interface in vm_interfaces: if interface["ip_address"] == self.ip_address: vm_interface_netmask = interface["netmask"] break if vm_interface_netmask: vm_network = ipaddress.ip_interface("{}/{}".format(compute.host_ip, vm_interface_netmask)).network for compute_id in self._controller.computes: if compute_id == "local": compute = self._controller.get_compute(compute_id) interfaces = await compute.interfaces() netmask = None for interface in interfaces: if interface["ip_address"] == compute.host_ip: netmask = interface["netmask"] break if netmask: compute_network = ipaddress.ip_interface("{}/{}".format(compute.host_ip, netmask)).network if vm_network.compare_networks(compute_network) != 0: msg = "The GNS3 VM (IP={}, NETWORK={}) is not on the same network as the {} server (IP={}, NETWORK={}), please make sure the local server binding is in the same network as the GNS3 VM".format(self.ip_address, vm_network, compute_id, compute.host_ip, compute_network) self._controller.notification.controller_emit("log.warning", {"message": msg}) except ComputeError as e: log.warning("Could not check the VM is in the same subnet as the local server: {}".format(e)) except aiohttp.web.HTTPConflict as e: log.warning("Could not check the VM is in the same subnet as the local server: {}".format(e.text)) @locking async def _suspend(self): """ Suspend the GNS3 VM """ engine = self.current_engine() if "vm" in self._controller.computes: await self._controller.delete_compute("vm") if engine.running: log.info("Suspend the GNS3 VM") await engine.suspend() @locking async def _stop(self): """ Stop the GNS3 VM """ engine = self.current_engine() if "vm" in self._controller.computes: await self._controller.delete_compute("vm") if engine.running: log.info("Stop the GNS3 VM") await engine.stop()