mirror of
https://github.com/GNS3/gns3-server.git
synced 2024-12-24 06:56:42 +00:00
Support for VMware linked clones.
This commit is contained in:
parent
ada94d486a
commit
a60389427b
@ -44,6 +44,7 @@ class VMware(BaseManager):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
self._execute_lock = asyncio.Lock()
|
||||||
self._vmrun_path = None
|
self._vmrun_path = None
|
||||||
self._vmnets = []
|
self._vmnets = []
|
||||||
self._vmnet_start_range = 2
|
self._vmnet_start_range = 2
|
||||||
@ -167,31 +168,32 @@ class VMware(BaseManager):
|
|||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def execute(self, subcommand, args, timeout=60, host_type=None):
|
def execute(self, subcommand, args, timeout=60, host_type=None):
|
||||||
|
|
||||||
vmrun_path = self.vmrun_path
|
with (yield from self._execute_lock):
|
||||||
if not vmrun_path:
|
vmrun_path = self.vmrun_path
|
||||||
vmrun_path = self.find_vmrun()
|
if not vmrun_path:
|
||||||
if host_type is None:
|
vmrun_path = self.find_vmrun()
|
||||||
host_type = self.host_type
|
if host_type is None:
|
||||||
|
host_type = self.host_type
|
||||||
|
|
||||||
command = [vmrun_path, "-T", host_type, subcommand]
|
command = [vmrun_path, "-T", host_type, subcommand]
|
||||||
command.extend(args)
|
command.extend(args)
|
||||||
log.debug("Executing vmrun with command: {}".format(command))
|
log.debug("Executing vmrun with command: {}".format(command))
|
||||||
try:
|
try:
|
||||||
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||||
except (OSError, subprocess.SubprocessError) as e:
|
except (OSError, subprocess.SubprocessError) as e:
|
||||||
raise VMwareError("Could not execute vmrun: {}".format(e))
|
raise VMwareError("Could not execute vmrun: {}".format(e))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
stdout_data, _ = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
|
stdout_data, _ = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
raise VMwareError("vmrun has timed out after {} seconds!".format(timeout))
|
raise VMwareError("vmrun has timed out after {} seconds!".format(timeout))
|
||||||
|
|
||||||
if process.returncode:
|
if process.returncode:
|
||||||
# vmrun print errors on stdout
|
# vmrun print errors on stdout
|
||||||
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
|
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
|
||||||
raise VMwareError("vmrun has returned an error: {}".format(vmrun_error))
|
raise VMwareError("vmrun has returned an error: {}".format(vmrun_error))
|
||||||
|
|
||||||
return stdout_data.decode("utf-8", errors="ignore").splitlines()
|
return stdout_data.decode("utf-8", errors="ignore").splitlines()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_vmware_file(path):
|
def parse_vmware_file(path):
|
||||||
@ -213,6 +215,20 @@ class VMware(BaseManager):
|
|||||||
continue
|
continue
|
||||||
return pairs
|
return pairs
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write_vmware_file(path, pairs):
|
||||||
|
"""
|
||||||
|
Write a VMware file (excepting VMX file).
|
||||||
|
|
||||||
|
:param path: path to the VMware file
|
||||||
|
:param pairs: settings to write
|
||||||
|
"""
|
||||||
|
|
||||||
|
with open(path, "w", encoding="utf-8") as f:
|
||||||
|
for key, value in pairs.items():
|
||||||
|
entry = '{} = "{}"\n'.format(key, value)
|
||||||
|
f.write(entry)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_vmx_file(path, pairs):
|
def write_vmx_file(path, pairs):
|
||||||
"""
|
"""
|
||||||
@ -289,31 +305,63 @@ class VMware(BaseManager):
|
|||||||
continue
|
continue
|
||||||
return vms
|
return vms
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_vmware_inventory_path():
|
||||||
|
"""
|
||||||
|
Returns VMware inventory file path.
|
||||||
|
|
||||||
|
:returns: path to the inventory file
|
||||||
|
"""
|
||||||
|
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
return os.path.expandvars(r"%APPDATA%\Vmware\Inventory.vmls")
|
||||||
|
elif sys.platform.startswith("darwin"):
|
||||||
|
return os.path.expanduser("~/Library/Application\ Support/VMware Fusion/vmInventory")
|
||||||
|
else:
|
||||||
|
return os.path.expanduser("~/.vmware/inventory.vmls")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_vmware_preferences_path():
|
||||||
|
"""
|
||||||
|
Returns VMware preferences file path.
|
||||||
|
|
||||||
|
:returns: path to the preferences file
|
||||||
|
"""
|
||||||
|
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
return os.path.expandvars(r"%APPDATA%\VMware\preferences.ini")
|
||||||
|
elif sys.platform.startswith("darwin"):
|
||||||
|
return os.path.expanduser("~/Library/Preferences/VMware Fusion/preferences")
|
||||||
|
else:
|
||||||
|
return os.path.expanduser("~/.vmware/preferences")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_vmware_default_vm_path():
|
||||||
|
"""
|
||||||
|
Returns VMware default VM directory path.
|
||||||
|
|
||||||
|
:returns: path to the default VM directory
|
||||||
|
"""
|
||||||
|
|
||||||
|
if sys.platform.startswith("win"):
|
||||||
|
return os.path.expandvars(r"%USERPROFILE%\Documents\Virtual Machines")
|
||||||
|
elif sys.platform.startswith("darwin"):
|
||||||
|
return os.path.expanduser("~/Documents/Virtual Machines.localized")
|
||||||
|
else:
|
||||||
|
return os.path.expanduser("~/vmware")
|
||||||
|
|
||||||
def list_vms(self):
|
def list_vms(self):
|
||||||
"""
|
"""
|
||||||
Gets VMware VM list.
|
Gets VMware VM list.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if sys.platform.startswith("win"):
|
inventory_path = self.get_vmware_inventory_path()
|
||||||
inventory_path = os.path.expandvars(r"%APPDATA%\Vmware\Inventory.vmls")
|
|
||||||
elif sys.platform.startswith("darwin"):
|
|
||||||
inventory_path = os.path.expanduser("~/Library/Application\ Support/VMware Fusion/vmInventory")
|
|
||||||
else:
|
|
||||||
inventory_path = os.path.expanduser("~/.vmware/inventory.vmls")
|
|
||||||
|
|
||||||
if os.path.exists(inventory_path):
|
if os.path.exists(inventory_path):
|
||||||
return self._get_vms_from_inventory(inventory_path)
|
return self._get_vms_from_inventory(inventory_path)
|
||||||
else:
|
else:
|
||||||
# VMware player has no inventory file, let's search the default location for VMs.
|
# VMware player has no inventory file, let's search the default location for VMs.
|
||||||
if sys.platform.startswith("win"):
|
vmware_preferences_path = self.get_vmware_preferences_path()
|
||||||
vmware_preferences_path = os.path.expandvars(r"%APPDATA%\VMware\preferences.ini")
|
default_vm_path = self.get_vmware_default_vm_path()
|
||||||
default_vm_path = os.path.expandvars(r"%USERPROFILE%\Documents\Virtual Machines")
|
|
||||||
elif sys.platform.startswith("darwin"):
|
|
||||||
vmware_preferences_path = os.path.expanduser("~/Library/Preferences/VMware Fusion/preferences")
|
|
||||||
default_vm_path = os.path.expanduser("~/Documents/Virtual Machines.localized")
|
|
||||||
else:
|
|
||||||
vmware_preferences_path = os.path.expanduser("~/.vmware/preferences")
|
|
||||||
default_vm_path = os.path.expanduser("~/vmware")
|
|
||||||
|
|
||||||
if os.path.exists(vmware_preferences_path):
|
if os.path.exists(vmware_preferences_path):
|
||||||
# the default vm path may be present in VMware preferences file.
|
# the default vm path may be present in VMware preferences file.
|
||||||
|
@ -105,6 +105,71 @@ class VMwareVM(BaseVM):
|
|||||||
log.debug("Control VM '{}' result: {}".format(subcommand, result))
|
log.debug("Control VM '{}' result: {}".format(subcommand, result))
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
@asyncio.coroutine
|
||||||
|
def create(self):
|
||||||
|
|
||||||
|
if self._linked_clone and not os.path.exists(os.path.join(self.working_dir, os.path.basename(self._vmx_path))):
|
||||||
|
# create the base snapshot for linked clones
|
||||||
|
base_snapshot_name = "GNS3 Linked Base for clones"
|
||||||
|
vmsd_path = os.path.splitext(self._vmx_path)[0] + ".vmsd"
|
||||||
|
if not os.path.exists(vmsd_path):
|
||||||
|
raise VMwareError("{} doesn't not exist".format(vmsd_path))
|
||||||
|
try:
|
||||||
|
vmsd_pairs = self.manager.parse_vmware_file(vmsd_path)
|
||||||
|
except OSError as e:
|
||||||
|
raise VMwareError('Could not read VMware VMSD file "{}": {}'.format(vmsd_path, e))
|
||||||
|
gns3_snapshot_exists = False
|
||||||
|
for value in vmsd_pairs.values():
|
||||||
|
if value == base_snapshot_name:
|
||||||
|
gns3_snapshot_exists = True
|
||||||
|
break
|
||||||
|
if not gns3_snapshot_exists:
|
||||||
|
log.info("Creating snapshot '{}'".format(base_snapshot_name))
|
||||||
|
yield from self._control_vm("snapshot", base_snapshot_name)
|
||||||
|
|
||||||
|
# create the linked clone based on the base snapshot
|
||||||
|
new_vmx_path = os.path.join(self.working_dir, self.name + ".vmx")
|
||||||
|
yield from self._control_vm("clone",
|
||||||
|
new_vmx_path,
|
||||||
|
"linked",
|
||||||
|
"-snapshot={}".format(base_snapshot_name),
|
||||||
|
"-cloneName={}".format(self.name))
|
||||||
|
|
||||||
|
try:
|
||||||
|
vmsd_pairs = self.manager.parse_vmware_file(vmsd_path)
|
||||||
|
except OSError as e:
|
||||||
|
raise VMwareError('Could not read VMware VMSD file "{}": {}'.format(vmsd_path, e))
|
||||||
|
|
||||||
|
snapshot_name = None
|
||||||
|
for name, value in vmsd_pairs.items():
|
||||||
|
if value == base_snapshot_name:
|
||||||
|
snapshot_name = name.split(".", 1)[0]
|
||||||
|
break
|
||||||
|
|
||||||
|
if snapshot_name is None:
|
||||||
|
raise VMwareError("Could not find the linked base snapshot in {}".format(vmsd_path))
|
||||||
|
|
||||||
|
num_clones_entry = "{}.numClones".format(snapshot_name)
|
||||||
|
if num_clones_entry in vmsd_pairs:
|
||||||
|
try:
|
||||||
|
nb_of_clones = int(vmsd_pairs[num_clones_entry])
|
||||||
|
except ValueError:
|
||||||
|
raise VMwareError("Value of {} in {} is not a number".format(num_clones_entry, vmsd_path))
|
||||||
|
vmsd_pairs[num_clones_entry] = str(nb_of_clones - 1)
|
||||||
|
|
||||||
|
for clone_nb in range(0, nb_of_clones):
|
||||||
|
clone_entry = "{}.clone{}".format(snapshot_name, clone_nb)
|
||||||
|
if clone_entry in vmsd_pairs:
|
||||||
|
del vmsd_pairs[clone_entry]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.manager.write_vmware_file(vmsd_path, vmsd_pairs)
|
||||||
|
except OSError as e:
|
||||||
|
raise VMwareError('Could not write VMware VMSD file "{}": {}'.format(vmsd_path, e))
|
||||||
|
|
||||||
|
# update the VMX file path
|
||||||
|
self._vmx_path = new_vmx_path
|
||||||
|
|
||||||
def _get_vmx_setting(self, name, value=None):
|
def _get_vmx_setting(self, name, value=None):
|
||||||
|
|
||||||
if name in self._vmx_pairs:
|
if name in self._vmx_pairs:
|
||||||
@ -342,7 +407,11 @@ class VMwareVM(BaseVM):
|
|||||||
|
|
||||||
self._set_network_options()
|
self._set_network_options()
|
||||||
self._set_serial_console()
|
self._set_serial_console()
|
||||||
self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs)
|
|
||||||
|
try:
|
||||||
|
self.manager.write_vmx_file(self._vmx_path, self._vmx_pairs)
|
||||||
|
except OSError as e:
|
||||||
|
raise VMwareError('Could not write VMware VMX file "{}": {}'.format(self._vmx_path, e))
|
||||||
|
|
||||||
yield from self._start_ubridge()
|
yield from self._start_ubridge()
|
||||||
if self._headless:
|
if self._headless:
|
||||||
@ -465,6 +534,31 @@ class VMwareVM(BaseVM):
|
|||||||
except VMwareError:
|
except VMwareError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
if self._linked_clone:
|
||||||
|
# clean the VMware inventory path from this linked clone
|
||||||
|
inventory_path = self.manager.get_vmware_inventory_path()
|
||||||
|
if os.path.exists(inventory_path):
|
||||||
|
try:
|
||||||
|
inventory_pairs = self.manager.parse_vmware_file(inventory_path)
|
||||||
|
except OSError as e:
|
||||||
|
log.warning('Could not read VMware inventory file "{}": {}'.format(inventory_path, e))
|
||||||
|
|
||||||
|
vmlist_entry = None
|
||||||
|
for name, value in inventory_pairs.items():
|
||||||
|
if value == self._vmx_path:
|
||||||
|
vmlist_entry = name.split(".", 1)[0]
|
||||||
|
break
|
||||||
|
|
||||||
|
if vmlist_entry is not None:
|
||||||
|
for name in inventory_pairs.keys():
|
||||||
|
if name.startswith(vmlist_entry):
|
||||||
|
del inventory_pairs[name]
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.manager.write_vmware_file(inventory_path, inventory_pairs)
|
||||||
|
except OSError as e:
|
||||||
|
raise VMwareError('Could not write VMware inventory file "{}": {}'.format(inventory_path, e))
|
||||||
|
|
||||||
log.info("VirtualBox VM '{name}' [{id}] closed".format(name=self.name, id=self.id))
|
log.info("VirtualBox VM '{name}' [{id}] closed".format(name=self.name, id=self.id))
|
||||||
self._closed = True
|
self._closed = True
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user