Support for VMware linked clones.

This commit is contained in:
grossmj 2015-05-30 20:26:38 -06:00
parent ada94d486a
commit a60389427b
2 changed files with 180 additions and 38 deletions

View File

@ -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.

View 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