diff --git a/gns3server/modules/virtualbox/virtualbox_vm.py b/gns3server/modules/virtualbox/virtualbox_vm.py index c4705db9..fbd69039 100644 --- a/gns3server/modules/virtualbox/virtualbox_vm.py +++ b/gns3server/modules/virtualbox/virtualbox_vm.py @@ -158,7 +158,7 @@ class VirtualBoxVM(BaseVM): 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_hdds() + yield from self._reattach_linked_hdds() else: yield from self._create_linked_clone() @@ -313,7 +313,10 @@ class VirtualBoxVM(BaseVM): return hdds @asyncio.coroutine - def _reattach_hdds(self): + def _reattach_linked_hdds(self): + """ + Reattach linked cloned hard disks. + """ hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json") try: @@ -332,10 +335,67 @@ class VirtualBoxVM(BaseVM): device=hdd_info["device"], medium=hdd_file)) - yield from self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"], - hdd_info["port"], - hdd_info["device"], - hdd_file)) + try: + yield from self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium "{}"'.format(hdd_info["controller"], + hdd_info["port"], + hdd_info["device"], + hdd_file)) + + except VirtualBoxError as e: + log.warn("VirtualBox VM '{name}' [{id}] error reattaching HDD {controller} {port} {device} {medium}: {error}".format(name=self.name, + id=self.id, + controller=hdd_info["controller"], + port=hdd_info["port"], + device=hdd_info["device"], + medium=hdd_file, + error=e)) + continue + + @asyncio.coroutine + def save_linked_hdds_info(self): + """ + Save linked cloned hard disks information. + + :returns: disk table information + """ + + hdd_table = [] + if self._linked_clone: + if os.path.exists(self.working_dir): + hdd_files = yield from self._get_all_hdd_files() + vm_info = yield from self._get_vm_info() + for entry, value in vm_info.items(): + match = re.search("^([\s\w]+)\-(\d)\-(\d)$", entry) # match Controller-PortNumber-DeviceNumber entry + if match: + controller = match.group(1) + port = match.group(2) + device = match.group(3) + if value in hdd_files and os.path.exists(os.path.join(self.working_dir, self._vmname, "Snapshots", os.path.basename(value))): + log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name, + id=self.id, + controller=controller, + port=port, + device=device)) + hdd_table.append( + { + "hdd": os.path.basename(value), + "controller": controller, + "port": port, + "device": device, + } + ) + + if hdd_table: + try: + hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json") + with open(hdd_info_file, "w", encoding="utf-8") as f: + json.dump(hdd_table, f, indent=4) + except OSError as e: + log.warning("VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(name=self.name, + id=self.id, + error=e.strerror)) + + return hdd_table @asyncio.coroutine def close(self): @@ -343,9 +403,18 @@ class VirtualBoxVM(BaseVM): Closes this VirtualBox VM. """ + if self._closed: + # VM is already closed + return + if not (yield from super().close()): return False + log.debug("VirtualBox VM '{name}' [{id}] is closing".format(name=self.name, id=self.id)) + if self._console: + self._manager.port_manager.release_tcp_port(self._console, self._project) + self._console = None + for adapter in self._ethernet_adapters.values(): if adapter is not None: for nio in adapter.ports.values(): @@ -356,46 +425,31 @@ class VirtualBoxVM(BaseVM): yield from self.stop() if self._linked_clone: - hdd_table = [] - if os.path.exists(self.working_dir): - hdd_files = yield from self._get_all_hdd_files() - vm_info = yield from self._get_vm_info() - for entry, value in vm_info.items(): - match = re.search("^([\s\w]+)\-(\d)\-(\d)$", entry) - if match: - controller = match.group(1) - port = match.group(2) - device = match.group(3) - if value in hdd_files: - log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name, - id=self.id, - controller=controller, - port=port, - device=device)) - yield from self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(controller, - port, - device)) - hdd_table.append( - { - "hdd": os.path.basename(value), - "controller": controller, - "port": port, - "device": device, - } - ) + hdd_table = yield from self.save_linked_hdds_info() + for hdd in hdd_table.copy(): + log.info("VirtualBox VM '{name}' [{id}] detaching HDD {controller} {port} {device}".format(name=self.name, + id=self.id, + controller=hdd["controller"], + port=hdd["port"], + device=hdd["device"])) + try: + yield from self._storage_attach('--storagectl "{}" --port {} --device {} --type hdd --medium none'.format(hdd["controller"], + hdd["port"], + hdd["device"])) + except VirtualBoxError as e: + log.warn("VirtualBox VM '{name}' [{id}] error detaching HDD {controller} {port} {device}: {error}".format(name=self.name, + id=self.id, + controller=hdd["controller"], + port=hdd["port"], + device=hdd["device"], + error=e)) + continue log.info("VirtualBox VM '{name}' [{id}] unregistering".format(name=self.name, id=self.id)) yield from self.manager.execute("unregistervm", [self._name]) - if hdd_table: - try: - hdd_info_file = os.path.join(self.working_dir, self._vmname, "hdd_info.json") - with open(hdd_info_file, "w", encoding="utf-8") as f: - json.dump(hdd_table, f, indent=4) - except OSError as e: - log.warning("VirtualBox VM '{name}' [{id}] could not write HHD info file: {error}".format(name=self.name, - id=self.id, - error=e.strerror)) + log.info("VirtualBox VM '{name}' [{id}] closed".format(name=self.name, id=self.id)) + self._closed = True @property def headless(self): diff --git a/gns3server/modules/vmware/__init__.py b/gns3server/modules/vmware/__init__.py index 29150421..8825d853 100644 --- a/gns3server/modules/vmware/__init__.py +++ b/gns3server/modules/vmware/__init__.py @@ -108,7 +108,7 @@ class VMware(BaseManager): vmrun_path = shutil.which("vmrun") if not vmrun_path: - raise VMwareError("Could not find vmrun") + raise VMwareError("Could not find VMware vmrun, please make sure it is installed") if not os.path.isfile(vmrun_path): raise VMwareError("vmrun {} is not accessible".format(vmrun_path)) if not os.access(vmrun_path, os.X_OK): @@ -137,6 +137,50 @@ class VMware(BaseManager): version = match.group(1) return version + @asyncio.coroutine + def _check_vmware_player_requirements(self, player_version): + """ + Check minimum requirements to use VMware Player. + + VIX 1.13 was the release for Player 6. + VIX 1.14 was the release for Player 7. + VIX 1.15 was the release for Workstation Player 12. + + :param player_version: VMware Player major version. + """ + + player_version = int(player_version) + if player_version < 6: + raise VMwareError("Using VMware Player requires version 6 or above") + elif player_version == 6: + yield from self.check_vmrun_version(minimum_required_version="1.13") + elif player_version == 7: + yield from self.check_vmrun_version(minimum_required_version="1.14") + elif player_version >= 12: + yield from self.check_vmrun_version(minimum_required_version="1.15") + + @asyncio.coroutine + def _check_vmware_workstation_requirements(self, ws_version): + """ + Check minimum requirements to use VMware Workstation. + + VIX 1.13 was the release for Workstation 10. + VIX 1.14 was the release for Workstation 11. + VIX 1.15 was the release for Workstation Pro 12. + + :param ws_version: VMware Workstation major version. + """ + + ws_version = int(ws_version) + if ws_version < 10: + raise VMwareError("Using VMware Workstation requires version 10 or above") + elif ws_version == 10: + yield from self.check_vmrun_version(minimum_required_version="1.13") + elif ws_version == 11: + yield from self.check_vmrun_version(minimum_required_version="1.14") + elif ws_version >= 12: + yield from self.check_vmrun_version(minimum_required_version="1.15") + @asyncio.coroutine def check_vmware_version(self): """ @@ -150,15 +194,12 @@ class VMware(BaseManager): player_version = self._find_vmware_version_registry(r"SOFTWARE\Wow6432Node\VMware, Inc.\VMware Player") if player_version: log.debug("VMware Player version {} detected".format(player_version)) - if int(player_version) < 6: - raise VMwareError("Using VMware Player requires version 6 or above") + yield from self._check_vmware_player_requirements(player_version) else: log.warning("Could not find VMware version") else: log.debug("VMware Workstation version {} detected".format(ws_version)) - if int(ws_version) < 10: - raise VMwareError("Using VMware Workstation requires version 10 or above") - return + yield from self._check_vmware_workstation_requirements(ws_version) else: if sys.platform.startswith("darwin"): if not os.path.isdir("/Applications/VMware Fusion.app"): @@ -174,16 +215,16 @@ class VMware(BaseManager): match = re.search("VMware Workstation ([0-9]+)\.", output) version = None if match: + # VMware Workstation has been detected version = match.group(1) log.debug("VMware Workstation version {} detected".format(version)) - if int(version) < 10: - raise VMwareError("Using VMware Workstation requires version 10 or above") + yield from self._check_vmware_workstation_requirements(version) match = re.search("VMware Player ([0-9]+)\.", output) if match: + # VMware Player has been detected version = match.group(1) log.debug("VMware Player version {} detected".format(version)) - if int(version) < 6: - raise VMwareError("Using VMware Player requires version 6 or above") + yield from self._check_vmware_player_requirements(version) if version is None: log.warning("Could not find VMware version. Output of VMware: {}".format(output)) raise VMwareError("Could not find VMware version. Output of VMware: {}".format(output)) @@ -352,7 +393,17 @@ class VMware(BaseManager): return stdout_data.decode("utf-8", errors="ignore").splitlines() @asyncio.coroutine - def check_vmrun_version(self): + def check_vmrun_version(self, minimum_required_version="1.13"): + """ + 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 @@ -366,9 +417,9 @@ class VMware(BaseManager): if match: version = match.group(1) log.debug("VMware vmrun version {} detected".format(version)) - if parse_version(version) < parse_version("1.13"): - # VMware VIX library version must be at least >= 1.13 - raise VMwareError("VMware vmrun executable version must be >= version 1.13") + 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)) @@ -595,6 +646,7 @@ class VMware(BaseManager): if sys.platform.startswith("win"): import ctypes + import ctypes.wintypes path = ctypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) ctypes.windll.shell32.SHGetFolderPathW(None, 5, None, 0, path) documents_folder = path.value