mirror of
https://github.com/GNS3/gns3-server
synced 2024-11-28 11:18:11 +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):
|
||||
|
||||
super().__init__()
|
||||
self._execute_lock = asyncio.Lock()
|
||||
self._vmrun_path = None
|
||||
self._vmnets = []
|
||||
self._vmnet_start_range = 2
|
||||
@ -167,31 +168,32 @@ class VMware(BaseManager):
|
||||
@asyncio.coroutine
|
||||
def execute(self, subcommand, args, timeout=60, host_type=None):
|
||||
|
||||
vmrun_path = self.vmrun_path
|
||||
if not vmrun_path:
|
||||
vmrun_path = self.find_vmrun()
|
||||
if host_type is None:
|
||||
host_type = self.host_type
|
||||
with (yield from self._execute_lock):
|
||||
vmrun_path = self.vmrun_path
|
||||
if not vmrun_path:
|
||||
vmrun_path = self.find_vmrun()
|
||||
if host_type is None:
|
||||
host_type = self.host_type
|
||||
|
||||
command = [vmrun_path, "-T", host_type, subcommand]
|
||||
command.extend(args)
|
||||
log.debug("Executing vmrun with command: {}".format(command))
|
||||
try:
|
||||
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
raise VMwareError("Could not execute vmrun: {}".format(e))
|
||||
command = [vmrun_path, "-T", host_type, subcommand]
|
||||
command.extend(args)
|
||||
log.debug("Executing vmrun with command: {}".format(command))
|
||||
try:
|
||||
process = yield from asyncio.create_subprocess_exec(*command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE)
|
||||
except (OSError, subprocess.SubprocessError) as e:
|
||||
raise VMwareError("Could not execute vmrun: {}".format(e))
|
||||
|
||||
try:
|
||||
stdout_data, _ = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise VMwareError("vmrun has timed out after {} seconds!".format(timeout))
|
||||
try:
|
||||
stdout_data, _ = yield from asyncio.wait_for(process.communicate(), timeout=timeout)
|
||||
except asyncio.TimeoutError:
|
||||
raise VMwareError("vmrun has timed out after {} seconds!".format(timeout))
|
||||
|
||||
if process.returncode:
|
||||
# vmrun print errors on stdout
|
||||
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
|
||||
raise VMwareError("vmrun has returned an error: {}".format(vmrun_error))
|
||||
if process.returncode:
|
||||
# vmrun print errors on stdout
|
||||
vmrun_error = stdout_data.decode("utf-8", errors="ignore")
|
||||
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
|
||||
def parse_vmware_file(path):
|
||||
@ -213,6 +215,20 @@ class VMware(BaseManager):
|
||||
continue
|
||||
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
|
||||
def write_vmx_file(path, pairs):
|
||||
"""
|
||||
@ -289,31 +305,63 @@ class VMware(BaseManager):
|
||||
continue
|
||||
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):
|
||||
"""
|
||||
Gets VMware VM list.
|
||||
"""
|
||||
|
||||
if sys.platform.startswith("win"):
|
||||
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")
|
||||
|
||||
inventory_path = self.get_vmware_inventory_path()
|
||||
if os.path.exists(inventory_path):
|
||||
return self._get_vms_from_inventory(inventory_path)
|
||||
else:
|
||||
# VMware player has no inventory file, let's search the default location for VMs.
|
||||
if sys.platform.startswith("win"):
|
||||
vmware_preferences_path = os.path.expandvars(r"%APPDATA%\VMware\preferences.ini")
|
||||
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")
|
||||
vmware_preferences_path = self.get_vmware_preferences_path()
|
||||
default_vm_path = self.get_vmware_default_vm_path()
|
||||
|
||||
if os.path.exists(vmware_preferences_path):
|
||||
# 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))
|
||||
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):
|
||||
|
||||
if name in self._vmx_pairs:
|
||||
@ -342,7 +407,11 @@ class VMwareVM(BaseVM):
|
||||
|
||||
self._set_network_options()
|
||||
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()
|
||||
if self._headless:
|
||||
@ -465,6 +534,31 @@ class VMwareVM(BaseVM):
|
||||
except VMwareError:
|
||||
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))
|
||||
self._closed = True
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user