async def set_qinq_port(self, port_number, outer_vlan, ethertype): """ Sets the specified port as a trunk (QinQ) port. :param port_number: allocated port number :param outer_vlan: outer VLAN (transport VLAN) for this QinQ port """ if port_number not in self._nios: raise DynamipsError("Port {} is not allocated".format(port_number)) nio = self._nios[port_number] if ethertype != "0x8100" and parse_version(self.hypervisor.version) < parse_version('0.2.16'): raise DynamipsError("Dynamips version required is >= 0.2.16 to change the default QinQ Ethernet type, detected version is {}".format(self.hypervisor.version)) await self._hypervisor.send('ethsw set_qinq_port "{name}" {nio} {outer_vlan} {ethertype}'.format(name=self._name, nio=nio, outer_vlan=outer_vlan, ethertype=ethertype if ethertype != "0x8100" else "")) log.info('Ethernet switch "{name}" [{id}]: port {port} set as a QinQ ({ethertype}) port with outer VLAN {vlan_id}'.format(name=self._name, id=self._id, port=port_number, vlan_id=outer_vlan, ethertype=ethertype)) self._mappings[port_number] = ("qinq", outer_vlan, ethertype)
def check_vmrun_version(self, minimum_required_version="1.13.0"): """ Checks the vmrun version. VMware VIX library version must be at least >= 1.13 by default VIX 1.13 was the release for VMware Fusion 6, Workstation 10, and Player 6. VIX 1.14 was the release for VMware Fusion 7, Workstation 11 and Player 7. VIX 1.15 was the release for VMware Fusion 8, Workstation Pro 12 and Workstation Player 12. :param required_version: required vmrun version number """ vmrun_path = self.vmrun_path if not vmrun_path: vmrun_path = self.find_vmrun() try: output = yield from subprocess_check_output(vmrun_path) match = re.search("vmrun version ([0-9\.]+)", output) version = None if match: version = match.group(1) log.debug("VMware vmrun version {} detected, minimum required: {}".format(version, minimum_required_version)) if parse_version(version) < parse_version(minimum_required_version): raise VMwareError("VMware vmrun executable version must be >= version {}".format(minimum_required_version)) if version is None: log.warning("Could not find VMware vmrun version. Output: {}".format(output)) raise VMwareError("Could not find VMware vmrun version. Output: {}".format(output)) except (OSError, subprocess.SubprocessError) as e: log.error("Error while looking for the VMware vmrun version: {}".format(e)) raise VMwareError("Error while looking for the VMware vmrun version: {}".format(e))
def create(self): if not self.linked_clone: yield from self._check_duplicate_linked_clone() yield from self._get_system_properties() if "API version" not in self._system_properties: raise VirtualBoxError("Can't access to VirtualBox API version:\n{}".format(self._system_properties)) if parse_version(self._system_properties["API version"]) < parse_version("4_3"): raise VirtualBoxError("The VirtualBox API version is lower than 4.3") log.info("VirtualBox VM '{name}' [{id}] created".format(name=self.name, id=self.id)) if self.linked_clone: if self.id and os.path.isdir(os.path.join(self.working_dir, self._vmname)): self._patch_vm_uuid() yield from self.manager.execute("registervm", [self._linked_vbox_file()]) yield from self._reattach_linked_hdds() else: yield from self._create_linked_clone() if self._adapters: yield from self.set_adapters(self._adapters) vm_info = yield from self._get_vm_info() if "memory" in vm_info: self._ram = int(vm_info["memory"])
def _check_ubridge_version(self): """ Checks if the ubridge executable version is >= 0.9.4 """ try: output = yield from subprocess_check_output(self._path, "-v", cwd=self._working_dir) match = re.search("ubridge version ([0-9a-z\.]+)", output) if match: version = match.group(1) if parse_version(version) < parse_version("0.9.4"): raise UbridgeError("uBridge executable version must be >= 0.9.4") else: raise UbridgeError("Could not determine uBridge version for {}".format(self._path)) except (OSError, subprocess.SubprocessError) as e: raise UbridgeError("Error while looking for uBridge version: {}".format(e))
def test_start_0_6_1(loop, vm): """ Version 0.6.1 doesn't have the -R options. It's not require because GNS3 provide a patch for this. """ process = MagicMock() process.returncode = None queue = vm.project.get_listen_queue() vm._vpcs_version = parse_version("0.6.1") with asyncio_patch("gns3server.modules.vpcs.vpcs_vm.VPCSVM._check_requirements", return_value=True): with asyncio_patch("asyncio.create_subprocess_exec", return_value=process) as mock_exec: nio = VPCS.instance().create_nio(vm.vpcs_path, {"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) vm.port_add_nio_binding(0, nio) loop.run_until_complete(asyncio.async(vm.start())) assert mock_exec.call_args[0] == (vm.vpcs_path, '-p', str(vm.console), '-m', '1', '-i', '1', '-F', '-s', '4242', '-c', '4243', '-t', '127.0.0.1') assert vm.is_running() (action, event) = queue.get_nowait() assert action == "vm.started" assert event == vm
def test_start_0_6_1(loop, vm, async_run): """ Version 0.6.1 doesn't have the -R options. It's not require because GNS3 provide a patch for this. """ process = MagicMock() process.returncode = None vm._vpcs_version = parse_version("0.6.1") with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM._check_requirements", return_value=True): with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.VPCSVM.start_wrap_console"): with asyncio_patch("asyncio.create_subprocess_exec", return_value=process) as mock_exec: nio = VPCS.instance().create_nio({"type": "nio_udp", "lport": 4242, "rport": 4243, "rhost": "127.0.0.1"}) async_run(vm.port_add_nio_binding(0, nio)) async_run(vm.start()) assert mock_exec.call_args[0] == (vm._vpcs_path(), '-p', str(vm._internal_console_port), '-m', '1', '-i', '1', '-F', '-s', ANY, '-c', ANY, '-t', '127.0.0.1') assert vm.is_running()
def connector(self): if not self._connected or self._connector.closed: if not sys.platform.startswith("linux"): raise DockerError("Docker is supported only on Linux") try: self._connector = aiohttp.connector.UnixConnector(self._server_url, conn_timeout=2) self._connected = True version = yield from self.query("GET", "version") except (aiohttp.errors.ClientOSError, FileNotFoundError): self._connected = False raise DockerError("Can't connect to docker daemon") if parse_version(version["ApiVersion"]) < parse_version(DOCKER_MINIMUM_API_VERSION): raise DockerError("Docker API version is {}. GNS3 requires a minimum API version of {}".format(version["ApiVersion"], DOCKER_MINIMUM_API_VERSION)) return self._connector
def _check_vpcs_version(self): """ Checks if the VPCS executable version is >= 0.8b or == 0.6.1. """ try: output = yield from subprocess_check_output(self.vpcs_path, "-v", cwd=self.working_dir) match = re.search("Welcome to Virtual PC Simulator, version ([0-9a-z\.]+)", output) if match: version = match.group(1) self._vpcs_version = parse_version(version) if self._vpcs_version < parse_version("0.8b") and self._vpcs_version != parse_version("0.6.1"): raise VPCSError("VPCS executable version must be >= 0.8b or 0.6.1") else: raise VPCSError("Could not determine the VPCS version for {}".format(self.vpcs_path)) except (OSError, subprocess.SubprocessError) as e: raise VPCSError("Error while looking for the VPCS version: {}".format(e))
def vm(project, manager, ubridge_path): vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) vm._vpcs_version = parse_version("0.9") vm._start_ubridge = AsyncioMagicMock() vm._ubridge_hypervisor = MagicMock() vm._ubridge_hypervisor.is_running.return_value = True return vm
async def start_new_hypervisor(self, working_dir=None): """ Creates a new Dynamips process and start it. :param working_dir: working directory :returns: the new hypervisor instance """ if not self._dynamips_path: self.find_dynamips() if not working_dir: working_dir = tempfile.gettempdir() # FIXME: hypervisor should always listen to 127.0.0.1 # See https://github.com/GNS3/dynamips/issues/62 server_config = self.config.get_section_config("Server") server_host = server_config.get("host") try: info = socket.getaddrinfo(server_host, 0, socket.AF_UNSPEC, socket.SOCK_STREAM, 0, socket.AI_PASSIVE) if not info: raise DynamipsError("getaddrinfo returns an empty list on {}".format(server_host)) for res in info: af, socktype, proto, _, sa = res # let the OS find an unused port for the Dynamips hypervisor with socket.socket(af, socktype, proto) as sock: sock.bind(sa) port = sock.getsockname()[1] break except OSError as e: raise DynamipsError("Could not find free port for the Dynamips hypervisor: {}".format(e)) port_manager = PortManager.instance() hypervisor = Hypervisor(self._dynamips_path, working_dir, server_host, port, port_manager.console_host) log.info("Creating new hypervisor {}:{} with working directory {}".format(hypervisor.host, hypervisor.port, working_dir)) await hypervisor.start() log.info("Hypervisor {}:{} has successfully started".format(hypervisor.host, hypervisor.port)) await hypervisor.connect() if parse_version(hypervisor.version) < parse_version('0.2.11'): raise DynamipsError("Dynamips version must be >= 0.2.11, detected version is {}".format(hypervisor.version)) return hypervisor
def check_vmrun_version(self, minimum_required_version="1.13.0"): """ Checks the vmrun version. VMware VIX library version must be at least >= 1.13 by default VIX 1.13 was the release for VMware Fusion 6, Workstation 10, and Player 6. VIX 1.14 was the release for VMware Fusion 7, Workstation 11 and Player 7. VIX 1.15 was the release for VMware Fusion 8, Workstation Pro 12 and Workstation Player 12. :param required_version: required vmrun version number """ with (yield from self._execute_lock): vmrun_path = self.vmrun_path if not vmrun_path: vmrun_path = self.find_vmrun() try: output = yield from subprocess_check_output(vmrun_path) match = re.search("vmrun version ([0-9\.]+)", output) version = None if match: version = match.group(1) log.debug( "VMware vmrun version {} detected, minimum required: {}" .format(version, minimum_required_version)) if parse_version(version) < parse_version( minimum_required_version): raise VMwareError( "VMware vmrun executable version must be >= version {}" .format(minimum_required_version)) if version is None: log.warning( "Could not find VMware vmrun version. Output: {}". format(output)) raise VMwareError( "Could not find VMware vmrun version. Output: {}". format(output)) except (OSError, subprocess.SubprocessError) as e: log.error( "Error while looking for the VMware vmrun version: {}". format(e)) raise VMwareError( "Error while looking for the VMware vmrun version: {}". format(e))
def _check_connection(self): if not self._connected: try: self._connected = True connector = self.connector() version = yield from self.query("GET", "version") except (aiohttp.errors.ClientOSError, FileNotFoundError): self._connected = False raise DockerError("Can't connect to docker daemon") docker_version = parse_version(version['ApiVersion']) if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION): raise DockerError( "Docker version is {}. GNS3 requires a minimum version of {}".format( version["Version"], DOCKER_MINIMUM_VERSION)) preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION) if docker_version >= preferred_api_version: self._api_version = DOCKER_PREFERRED_API_VERSION
async def _check_requirements(self): """ Checks if the GNS3 VM can run on VirtualBox """ if not self._system_properties: await self._get_system_properties() if "API version" not in self._system_properties: raise VirtualBoxError("Can't access to VirtualBox API version:\n{}".format(self._system_properties)) from cpuinfo import get_cpu_info cpu_info = await wait_run_in_executor(get_cpu_info) vendor_id = cpu_info.get('vendor_id_raw') if vendor_id == "GenuineIntel": if parse_version(self._system_properties["API version"]) < parse_version("6_1"): raise VirtualBoxError("VirtualBox version 6.1 or above is required to run the GNS3 VM with nested virtualization enabled on Intel processors") elif vendor_id == "AuthenticAMD": if parse_version(self._system_properties["API version"]) < parse_version("6_0"): raise VirtualBoxError("VirtualBox version 6.0 or above is required to run the GNS3 VM with nested virtualization enabled on AMD processors") else: log.warning("Could not determine CPU vendor: {}".format(vendor_id))
def _check_connection(self): if not self._connected: try: self._connected = True connector = self.connector() version = yield from self.query("GET", "version") except (aiohttp.errors.ClientOSError, FileNotFoundError): self._connected = False raise DockerError("Can't connect to docker daemon") docker_version = parse_version(version['ApiVersion']) if docker_version < parse_version(DOCKER_MINIMUM_API_VERSION): raise DockerError( "Docker version is {}. GNS3 requires a minimum version of {}" .format(version["Version"], DOCKER_MINIMUM_VERSION)) preferred_api_version = parse_version(DOCKER_PREFERRED_API_VERSION) if docker_version >= preferred_api_version: self._api_version = DOCKER_PREFERRED_API_VERSION
def _check_ubridge_version(self): """ Checks if the ubridge executable version is >= 0.9.4 """ try: output = yield from subprocess_check_output(self._path, "-v", cwd=self._working_dir) match = re.search("ubridge version ([0-9a-z\.]+)", output) if match: version = match.group(1) if parse_version(version) < parse_version("0.9.4"): raise UbridgeError( "uBridge executable version must be >= 0.9.4") else: raise UbridgeError( "Could not determine uBridge version for {}".format( self._path)) except (OSError, subprocess.SubprocessError) as e: raise UbridgeError( "Error while looking for uBridge version: {}".format(e))
async def _check_ubridge_version(self, env=None): """ Checks if the ubridge executable version """ try: output = await subprocess_check_output(self._path, "-v", cwd=self._working_dir, env=env) match = re.search(r"ubridge version ([0-9a-z\.]+)", output) if match: self._version = match.group(1) if sys.platform.startswith("win") or sys.platform.startswith("darwin"): minimum_required_version = "0.9.12" else: # uBridge version 0.9.14 is required for packet filters # to work for IOU nodes. minimum_required_version = "0.9.14" if parse_version(self._version) < parse_version(minimum_required_version): raise UbridgeError("uBridge executable version must be >= {}".format(minimum_required_version)) else: raise UbridgeError("Could not determine uBridge version for {}".format(self._path)) except (OSError, subprocess.SubprocessError) as e: raise UbridgeError("Error while looking for uBridge version: {}".format(e))
def create(self): yield from self._get_system_properties() if "API version" not in self._system_properties: raise VirtualBoxError("Can't access to VirtualBox API version:\n{}".format(self._system_properties)) if parse_version(self._system_properties["API version"]) < parse_version("4_3"): raise VirtualBoxError("The VirtualBox API version is lower than 4.3") log.info("VirtualBox VM '{name}' [{id}] created".format(name=self.name, id=self.id)) if self._linked_clone: if self.id and os.path.isdir(os.path.join(self.working_dir, self._vmname)): vbox_file = os.path.join(self.working_dir, self._vmname, self._vmname + ".vbox") yield from self.manager.execute("registervm", [vbox_file]) yield from self._reattach_linked_hdds() else: yield from self._create_linked_clone() if self._adapters: yield from self.set_adapters(self._adapters) vm_info = yield from self._get_vm_info() if "memory" in vm_info: self._ram = int(vm_info["memory"])
def _build_command(self): """ Command to start the VPCS process. (to be passed to subprocess.Popen()) VPCS command line: usage: vpcs [options] [scriptfile] Option: -h print this help then exit -v print version information then exit -i num number of vpc instances to start (default is 9) -p port run as a daemon listening on the tcp 'port' -m num start byte of ether address, default from 0 -r file load and execute script file compatible with older versions, DEPRECATED. -e tap mode, using /dev/tapx by default (linux only) -u udp mode, default udp mode options: -s port local udp base port, default from 20000 -c port remote udp base port (dynamips udp port), default from 30000 -t ip remote host IP, default 127.0.0.1 tap mode options: -d vm device name, works only when -i is set to 1 hypervisor mode option: -H port run as the hypervisor listening on the tcp 'port' If no 'scriptfile' specified, vpcs will read and execute the file named 'startup.vpc' if it exsits in the current directory. """ command = [self.vpcs_path] command.extend(["-p", str(self._console)]) # listen to console port command.extend(["-m", str(self._manager.get_mac_id(self.id))]) # the unique ID is used to set the MAC address offset command.extend(["-i", "1"]) # option to start only one VPC instance command.extend(["-F"]) # option to avoid the daemonization of VPCS if self._vpcs_version >= parse_version("0.8b"): command.extend(["-R"]) # disable the relay feature of VPCS (starting with VPCS 0.8) else: log.warn("The VPCS relay feature could not be disabled because the VPCS version is below 0.8b") nio = self._ethernet_adapter.get_nio(0) if nio: if isinstance(nio, NIOUDP): # UDP tunnel command.extend(["-s", str(nio.lport)]) # source UDP port command.extend(["-c", str(nio.rport)]) # destination UDP port try: command.extend(["-t", socket.gethostbyname(nio.rhost)]) # destination host, we need to resolve the hostname because VPCS doesn't support it except socket.gaierror as e: raise VPCSError("Can't resolve hostname {}".format(nio.rhost)) elif isinstance(nio, NIOTAP): # TAP interface command.extend(["-e"]) command.extend(["-d", nio.tap_device]) if self.script_file: command.extend([os.path.basename(self.script_file)]) return command
def test_vm_check_vpcs_version_0_6_1(loop, vm, manager): with asyncio_patch("gns3server.compute.vpcs.vpcs_vm.subprocess_check_output", return_value="Welcome to Virtual PC Simulator, version 0.6.1"): loop.run_until_complete(asyncio.async(vm._check_vpcs_version())) assert vm._vpcs_version == parse_version("0.6.1")
def vm(project, manager): vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) vm._vpcs_version = parse_version("0.9") return vm
def _set_network_options(self): """ Set up VMware networking. """ # first some sanity checks for adapter_number in range(0, self._adapters): # we want the vmnet interface to be connected when starting the VM connected = "ethernet{}.startConnected".format(adapter_number) if self._get_vmx_setting(connected): del self._vmx_pairs[connected] use_ubridge = True # use alternative method to find vmnet interfaces on macOS >= 11.0 (BigSur) # because "bridge" interfaces are used instead and they are only created on the VM starts if sys.platform.startswith("darwin") and parse_version( platform.mac_ver()[0]) >= parse_version("11.0.0"): use_ubridge = False self.manager.refresh_vmnet_list(ubridge=use_ubridge) # then configure VMware network adapters for adapter_number in range(0, self._adapters): custom_adapter = self._get_custom_adapter_settings(adapter_number) adapter_type = custom_adapter.get("adapter_type", self._adapter_type) # add/update the interface if adapter_type == "default": # force default to e1000 because some guest OS don't detect the adapter (i.e. Windows 2012 server) # when 'virtualdev' is not set in the VMX file. vmware_adapter_type = "e1000" else: vmware_adapter_type = adapter_type ethernet_adapter = { "ethernet{}.present".format(adapter_number): "TRUE", "ethernet{}.addresstype".format(adapter_number): "generated", "ethernet{}.generatedaddressoffset".format(adapter_number): "0", "ethernet{}.virtualdev".format(adapter_number): vmware_adapter_type } self._vmx_pairs.update(ethernet_adapter) connection_type = "ethernet{}.connectiontype".format( adapter_number) if not self._use_any_adapter and connection_type in self._vmx_pairs and self._vmx_pairs[ connection_type] in ("nat", "bridged", "hostonly"): continue self._vmx_pairs["ethernet{}.connectiontype".format( adapter_number)] = "custom" # make sure we have a vmnet per adapter if we use uBridge allocate_vmnet = False # first check if a vmnet is already assigned to the adapter vnet = "ethernet{}.vnet".format(adapter_number) if vnet in self._vmx_pairs: vmnet = os.path.basename(self._vmx_pairs[vnet]) if self.manager.is_managed_vmnet(vmnet) or vmnet in ("vmnet0", "vmnet1", "vmnet8"): # vmnet already managed or a special vmnet, try to allocate a new one allocate_vmnet = True else: # otherwise allocate a new one allocate_vmnet = True if allocate_vmnet: try: vmnet = self.manager.allocate_vmnet() except BaseException: # clear everything up in case of error (e.g. no enough vmnets) self._vmnets.clear() raise # mark the vmnet as managed by us if vmnet not in self._vmnets: self._vmnets.append(vmnet) self._vmx_pairs["ethernet{}.vnet".format(adapter_number)] = vmnet # disable remaining network adapters for adapter_number in range(self._adapters, self._maximum_adapters): if self._get_vmx_setting( "ethernet{}.present".format(adapter_number), "TRUE"): log.debug( "disabling remaining adapter {}".format(adapter_number)) self._vmx_pairs["ethernet{}.startconnected".format( adapter_number)] = "FALSE"
def test_vm_check_vpcs_version_0_6_1(loop, vm, manager): with asyncio_patch( "gns3server.compute.vpcs.vpcs_vm.subprocess_check_output", return_value="Welcome to Virtual PC Simulator, version 0.6.1"): loop.run_until_complete(asyncio. async (vm._check_vpcs_version())) assert vm._vpcs_version == parse_version("0.6.1")
def _build_command(self): """ Command to start the VPCS process. (to be passed to subprocess.Popen()) VPCS command line: usage: vpcs [options] [scriptfile] Option: -h print this help then exit -v print version information then exit -i num number of vpc instances to start (default is 9) -p port run as a daemon listening on the tcp 'port' -m num start byte of ether address, default from 0 -r file load and execute script file compatible with older versions, DEPRECATED. -e tap mode, using /dev/tapx by default (linux only) -u udp mode, default udp mode options: -s port local udp base port, default from 20000 -c port remote udp base port (dynamips udp port), default from 30000 -t ip remote host IP, default 127.0.0.1 tap mode options: -d vm device name, works only when -i is set to 1 hypervisor mode option: -H port run as the hypervisor listening on the tcp 'port' If no 'scriptfile' specified, vpcs will read and execute the file named 'startup.vpc' if it exsits in the current directory. """ command = [self._vpcs_path()] command.extend(["-p", str(self._internal_console_port) ]) # listen to console port command.extend(["-m", str(self._manager.get_mac_id( self.id))]) # the unique ID is used to set the MAC address offset command.extend(["-i", "1"]) # option to start only one VPC instance command.extend(["-F"]) # option to avoid the daemonization of VPCS if self._vpcs_version >= parse_version("0.8b"): command.extend([ "-R" ]) # disable the relay feature of VPCS (starting with VPCS 0.8) else: log.warning( "The VPCS relay feature could not be disabled because the VPCS version is below 0.8b" ) # use the local UDP tunnel to uBridge instead if not self._local_udp_tunnel: self._local_udp_tunnel = self._create_local_udp_tunnel() nio = self._local_udp_tunnel[0] if nio and isinstance(nio, NIOUDP): # UDP tunnel command.extend(["-s", str(nio.lport)]) # source UDP port command.extend(["-c", str(nio.rport)]) # destination UDP port try: command.extend( ["-t", socket.gethostbyname(nio.rhost)] ) # destination host, we need to resolve the hostname because VPCS doesn't support it except socket.gaierror as e: raise VPCSError("Can't resolve hostname {}".format(nio.rhost)) if self.script_file: command.extend([os.path.basename(self.script_file)]) return command
def vm(project, manager, ubridge_path): vm = VPCSVM("test", "00010203-0405-0607-0809-0a0b0c0d0e0f", project, manager) vm._vpcs_version = parse_version("0.9") vm._start_ubridge = AsyncioMagicMock() return vm