Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/bareos/bareos.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephan Duehr <stephan.duehr@bareos.com>2022-09-21 19:11:57 +0300
committerStephan Duehr <stephan.duehr@bareos.com>2022-09-21 19:11:57 +0300
commitc3e0a825ecb46e9af3ffd9243d1112847f5e7c74 (patch)
tree167d69f8a7680a467f6266a6321c9dc2510e52f5
parent6bf69897ca6784734d62bce849c3d8a7d4750d7f (diff)
VMware Plugin: Save VM configuration and recreate VM before restore
The plugin now also saves the VM configuration on backup and recreates the VM if it was delete before restoring the virtual disk. It is also possible now to restore to different folder, host, cluster or datastore. Custom attributes are saved and restored, uuid and MAC address are restored if there are no duplicate conflicts.
-rw-r--r--core/src/plugins/filed/python/vmware/BareosFdPluginVMware.py1779
1 files changed, 1673 insertions, 106 deletions
diff --git a/core/src/plugins/filed/python/vmware/BareosFdPluginVMware.py b/core/src/plugins/filed/python/vmware/BareosFdPluginVMware.py
index bde989b0c..42add2192 100644
--- a/core/src/plugins/filed/python/vmware/BareosFdPluginVMware.py
+++ b/core/src/plugins/filed/python/vmware/BareosFdPluginVMware.py
@@ -40,6 +40,7 @@ import signal
import ssl
import socket
import hashlib
+import re
from sys import version_info
try:
@@ -50,6 +51,8 @@ except ImportError:
from pyVim.connect import SmartConnect, SmartConnectNoSSL, Disconnect
from pyVmomi import vim
from pyVmomi import vmodl
+from pyVim.task import WaitForTask
+from pyVmomi.VmomiSupport import VmomiJSONEncoder
# if OrderedDict is not available from collection (eg. SLES11),
# the additional package python-ordereddict must be used
@@ -88,6 +91,7 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
self.events = []
self.events.append(bareosfd.bEventStartBackupJob)
self.events.append(bareosfd.bEventStartRestoreJob)
+ self.events.append(bareosfd.bEventEndRestoreJob)
bareosfd.RegisterEvents(self.events)
self.mandatory_options_default = ["vcserver", "vcuser", "vcpass"]
self.mandatory_options_vmname = ["dc", "folder", "vmname"]
@@ -100,6 +104,12 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
"vadp_dumper_verbose",
"verifyssl",
"quiesce",
+ "cleanup_tmp_files",
+ "restore_esxhost",
+ "restore_cluster",
+ "restore_resource_pool",
+ "restore_datastore",
+ "restore_powerstate",
]
self.utf8_options = ["vmname", "folder"]
self.config = None
@@ -107,6 +117,8 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
self.vadp = BareosVADPWrapper()
self.vadp.plugin = self
+ self.vm_config_info_saved = False
+ self.current_object_index = -1
def parse_plugin_definition(self, plugindef):
"""
@@ -259,6 +271,22 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
"Type of option %s is %s\n" % (option, type(self.options[option])),
)
+ # strip trailing "/" from folder option value value
+ if "folder" in self.options:
+ self.options["folder"] = self.options["folder"].rstrip("/")
+
+ if self.options.get("restore_powerstate"):
+ if self.options["restore_powerstate"] not in ["on", "off", "previous"]:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Invalid value '%s' for restore_powerstate, valid: on, off or previous.\n"
+ % self.options["restore_powerstate"],
+ )
+ return bareosfd.bRC_Error
+ else:
+ # restore previous power state by default
+ self.options["restore_powerstate"] = "previous"
+
bareosfd.DebugMessage(
100,
"Using Option %s=%s\n"
@@ -288,6 +316,15 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
if not self.vadp.connect_vmware():
return bareosfd.bRC_Error
+ if "uuid" in self.options:
+ self.vadp.backup_path = "/VMS/%s" % (self.options["uuid"])
+ else:
+ self.vadp.backup_path = "/VMS/%s/%s/%s" % (
+ self.options["dc"],
+ self.options["folder"].strip("/"),
+ self.options["vmname"],
+ )
+
return self.vadp.prepare_vm_backup()
def start_backup_file(self, savepkt):
@@ -299,24 +336,14 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
if not self.vadp.files_to_backup:
self.vadp.disk_device_to_backup = self.vadp.disk_devices.pop(0)
self.vadp.files_to_backup = []
- if "uuid" in self.options:
- self.vadp.files_to_backup.append(
- "/VMS/%s/%s"
- % (
- self.options["uuid"],
- self.vadp.disk_device_to_backup["fileNameRoot"],
- )
- )
- else:
- self.vadp.files_to_backup.append(
- "/VMS/%s%s/%s/%s"
- % (
- self.options["dc"],
- self.options["folder"].rstrip("/"),
- self.options["vmname"],
- self.vadp.disk_device_to_backup["fileNameRoot"],
- )
+
+ self.vadp.files_to_backup.append(
+ "%s/%s"
+ % (
+ self.vadp.backup_path,
+ self.vadp.disk_device_to_backup["fileNameRoot"],
)
+ )
self.vadp.files_to_backup.insert(
0, self.vadp.files_to_backup[0] + "_cbt.json"
@@ -328,32 +355,36 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
100,
"file: %s\n" % (StringCodec.encode(self.vadp.file_to_backup)),
)
- if self.vadp.file_to_backup.endswith("_cbt.json"):
+
+ if self.vadp.file_to_backup.endswith("vm_config.json"):
+ self.create_restoreobject_savepacket(
+ savepkt,
+ StringCodec.encode(self.vadp.file_to_backup),
+ bytearray(self.vadp.vm_config_info_json, "utf-8"),
+ )
+
+ elif self.vadp.file_to_backup.endswith("vm_info.json"):
+ self.create_restoreobject_savepacket(
+ savepkt,
+ StringCodec.encode(self.vadp.file_to_backup),
+ bytearray(self.vadp.vm_info_json, "utf-8"),
+ )
+ elif self.vadp.file_to_backup.endswith("vm_metadata.json"):
+ self.create_restoreobject_savepacket(
+ savepkt,
+ StringCodec.encode(self.vadp.file_to_backup),
+ bytearray(self.vadp.vm_metadata_json, "utf-8"),
+ )
+
+ elif self.vadp.file_to_backup.endswith("_cbt.json"):
if not self.vadp.get_vm_disk_cbt():
return bareosfd.bRC_Error
- # create a stat packet for a restore object
- statp = bareosfd.StatPacket()
- savepkt.statp = statp
- # see src/filed/backup.c how this is done in c
- savepkt.type = bareosfd.FT_RESTORE_FIRST
- # set the fname of the restore object to the vmdk name
- # by stripping of the _cbt.json suffix
- v_fname = StringCodec.encode(self.vadp.file_to_backup[: -len("_cbt.json")])
- if chr(self.level) != "F":
- # add level and timestamp to fname in savepkt
- savepkt.fname = "%s+%s+%s" % (
- v_fname,
- chr(self.level),
- repr(self.vadp.create_snap_tstamp),
- )
- else:
- savepkt.fname = v_fname
-
- savepkt.object_name = savepkt.fname
- savepkt.object = bytearray(self.vadp.changed_disk_areas_json, "utf-8")
- savepkt.object_len = len(savepkt.object)
- savepkt.object_index = int(time.time())
+ self.create_restoreobject_savepacket(
+ savepkt,
+ StringCodec.encode(self.vadp.file_to_backup[: -len("_cbt.json")]),
+ bytearray(self.vadp.changed_disk_areas_json, "utf-8"),
+ )
else:
# start bareos_vadp_dumper
@@ -429,7 +460,30 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
# existed when the backup was run. So the diskPath in JSON will
# be set to diskPathRoot
cbt_data = self.vadp.restore_objects_by_objectname[objectname]["data"]
+
+ # replace datastore when restoring to other datastore
+ # if self.options.get("restore_datastore"):
+ # cbt_data["DiskParams"]["diskPathRoot"] = self.vadp.datastore_rex.sub(
+ # "[" + self.options["restore_datastore"] + "]",
+ # cbt_data["DiskParams"]["diskPathRoot"],
+ # count=1,
+ # )
+
+ if self.vadp.vmfs_vm_path_changed:
+ cbt_data["DiskParams"][
+ "diskPathRoot"
+ ] = self.vadp.datastore_vm_path_rex.sub(
+ self.vadp.get_vmfs_vm_path(),
+ cbt_data["DiskParams"]["diskPathRoot"],
+ count=1,
+ )
+
cbt_data["DiskParams"]["diskPath"] = cbt_data["DiskParams"]["diskPathRoot"]
+
+ # If a new VM was created, its VM moref must be written to the JSON file for vadp dumper
+ # instead of the VM moref that was stored at backup time
+ cbt_data["ConnParams"]["VmMoRef"] = "moref=" + self.vadp.vm._GetMoId()
+
self.vadp.writeStringToFile(json_filename, json.dumps(cbt_data))
self.cbt_json_local_file_path = json_filename
@@ -692,9 +746,17 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
return self.start_restore_job()
+ elif event == bareosfd.bEventEndRestoreJob:
+ bareosfd.DebugMessage(
+ 100,
+ "handle_plugin_event() called with bEventEndRestoreJob\n",
+ )
+
+ return self.vadp.restore_power_state()
+
else:
bareosfd.DebugMessage(
- 100, "handle_plugin_event() called with event %s\n" % (event)
+ 100, "handle_plugin_event() called with unexpected event %s\n" % (event)
)
return bareosfd.bRC_OK
@@ -742,6 +804,19 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
bareosfd.DebugMessage(
100, "ROP.object(%s): %s\n" % (type(ROP.object), ROP.object)
)
+
+ if "vm_config.json" in ROP.object_name:
+ self.vadp.restore_vm_config_json = ROP.object
+ return bareosfd.bRC_OK
+
+ if "vm_info.json" in ROP.object_name:
+ self.vadp.restore_vm_info_json = ROP.object
+ return bareosfd.bRC_OK
+
+ if "vm_metadata.json" in ROP.object_name:
+ self.vadp.restore_vm_metadata_json = ROP.object
+ return bareosfd.bRC_OK
+
ro_data = self.vadp.json2cbt(ROP.object)
ro_filename = ro_data["DiskParams"]["diskPathRoot"]
# self.vadp.restore_objects_by_diskpath is used on backup
@@ -757,6 +832,39 @@ class BareosFdPluginVMware(BareosFdPluginBaseclass.BareosFdPluginBaseclass):
] = self.vadp.restore_objects_by_diskpath[ro_filename][-1]
return bareosfd.bRC_OK
+ def create_restoreobject_savepacket(self, savepkt, virtual_filename, object_data):
+ """
+ Create a savepacket for a restore object
+ """
+ # create a stat packet for a restore object
+ statp = bareosfd.StatPacket()
+ savepkt.statp = statp
+ # see src/filed/backup.c how this is done in c
+ savepkt.type = bareosfd.FT_RESTORE_FIRST
+ # set the fname of the restore object to the vmdk name
+ # by stripping of the _cbt.json suffix
+ if chr(self.level) != "F":
+ # add level and timestamp to fname in savepkt
+ savepkt.fname = "%s+%s+%s" % (
+ virtual_filename,
+ chr(self.level),
+ repr(self.vadp.create_snap_tstamp),
+ )
+ else:
+ savepkt.fname = virtual_filename
+
+ savepkt.object_name = savepkt.fname
+ savepkt.object = object_data
+ savepkt.object_len = len(savepkt.object)
+ savepkt.object_index = self.get_next_object_index()
+
+ def get_next_object_index(self):
+ """
+ Get the current object index and increase it
+ """
+ self.current_object_index += 1
+ return self.current_object_index
+
class BareosVADPWrapper(object):
"""
@@ -770,7 +878,7 @@ class BareosVADPWrapper(object):
self.create_snap_task = None
self.create_snap_result = None
self.file_to_backup = None
- self.files_to_backup = None
+ self.files_to_backup = []
self.disk_devices = None
self.disk_device_to_backup = None
self.cbt_json_local_file_path = None
@@ -783,6 +891,21 @@ class BareosVADPWrapper(object):
self.skip_disk_modes = ["independent_nonpersistent", "independent_persistent"]
self.restore_vmdk_file = None
self.plugin = None
+ self.vm_config_info_json = None
+ self.backup_path = None
+ self.vm_config_info_json = None
+ self.vm_info_json = None
+ self.vm_metadata_json = None
+ self.restore_vm_config_json = None
+ self.restore_vm_info_json = None
+ self.restore_vm_info = None
+ self.restore_vm_metadata_json = None
+ self.restore_vm_metadata = None
+ self.dc = None
+ self.vmfs_vm_path_changed = False
+ self.vmfs_vm_path = None
+ self.datastore_rex = re.compile(r"\[(.+?)\]")
+ self.datastore_vm_path_rex = re.compile(r"\[(.+?)\] (.+)\/")
def connect_vmware(self):
# this prevents from repeating on second call
@@ -861,13 +984,6 @@ class BareosVADPWrapper(object):
def cleanup(self):
Disconnect(self.si)
- # the Disconnect Method does not close the tcp connection
- # is that so intentionally?
- # However, explicitly closing it works like this:
- if self.si is not None:
- self.si._stub.DropConnections()
- self.si = None
- self.log = None
def keepalive(self):
# Prevent from vSphere API timeout by calling CurrentTime() every
@@ -889,95 +1005,101 @@ class BareosVADPWrapper(object):
- get disk devices
"""
if "uuid" in self.options:
- vmname = self.options["uuid"]
if not self.get_vm_details_by_uuid():
bareosfd.DebugMessage(
100,
- "Error getting details for VM with UUID %s\n" % (vmname),
+ "Error getting details for VM with UUID %s\n"
+ % (self.options["uuid"]),
)
bareosfd.JobMessage(
bareosfd.M_FATAL,
- "Error getting details for VM with UUID %s\n" % (vmname),
+ "Error getting details for VM with UUID %s\n"
+ % (self.options["uuid"]),
)
return bareosfd.bRC_Error
else:
- vmname = self.options["vmname"]
if not self.get_vm_details_dc_folder_vmname():
- bareosfd.DebugMessage(
- 100,
- "Error getting details for VM %s\n" % (StringCodec.encode(vmname)),
- )
- bareosfd.JobMessage(
- bareosfd.M_FATAL,
- "Error getting details for VM %s\n" % (StringCodec.encode(vmname)),
+ error_message = "No VM with Folder/Name %s/%s found in DC %s\n" % (
+ self.options["folder"],
+ self.options["vmname"],
+ self.options["dc"],
)
+ bareosfd.DebugMessage(100, StringCodec.encode(error_message))
+ bareosfd.JobMessage(bareosfd.M_FATAL, StringCodec.encode(error_message))
return bareosfd.bRC_Error
bareosfd.DebugMessage(
100,
- "Successfully got details for VM %s\n" % (StringCodec.encode(vmname)),
+ "Successfully got details for VM %s\n" % (StringCodec.encode(self.vm.name)),
)
# check if the VM supports CBT and that CBT is enabled
if not self.vm.capability.changeTrackingSupported:
bareosfd.DebugMessage(
100,
- "Error VM %s does not support CBT\n" % (StringCodec.encode(vmname)),
+ "Error VM %s does not support CBT\n"
+ % (StringCodec.encode(self.vm.name)),
)
bareosfd.JobMessage(
bareosfd.M_FATAL,
- "Error VM %s does not support CBT\n" % (StringCodec.encode(vmname)),
+ "Error VM %s does not support CBT\n"
+ % (StringCodec.encode(self.vm.name)),
)
return bareosfd.bRC_Error
if not self.vm.config.changeTrackingEnabled:
bareosfd.DebugMessage(
100,
- "Error vm %s is not cbt enabled\n" % (StringCodec.encode(vmname)),
+ "Error vm %s is not cbt enabled\n" % (StringCodec.encode(self.vm.name)),
)
bareosfd.JobMessage(
bareosfd.M_FATAL,
- "Error vm %s is not cbt enabled\n" % (StringCodec.encode(vmname)),
+ "Error vm %s is not cbt enabled\n" % (StringCodec.encode(self.vm.name)),
)
return bareosfd.bRC_Error
bareosfd.DebugMessage(
100,
- "Creating Snapshot on VM %s\n" % (StringCodec.encode(vmname)),
+ "Creating Snapshot on VM %s\n" % (StringCodec.encode(self.vm.name)),
)
if not self.create_vm_snapshot():
bareosfd.DebugMessage(
100,
- "Error creating snapshot on VM %s\n" % (StringCodec.encode(vmname)),
+ "Error creating snapshot on VM %s\n"
+ % (StringCodec.encode(self.vm.name)),
)
bareosfd.JobMessage(
bareosfd.M_FATAL,
- "Error creating snapshot on VM %s\n" % (StringCodec.encode(vmname)),
+ "Error creating snapshot on VM %s\n"
+ % (StringCodec.encode(self.vm.name)),
)
return bareosfd.bRC_Error
bareosfd.DebugMessage(
100,
- "Successfully created snapshot on VM %s\n" % (StringCodec.encode(vmname)),
+ "Successfully created snapshot on VM %s\n"
+ % (StringCodec.encode(self.vm.name)),
)
+ self.vmconfig2json()
+
bareosfd.DebugMessage(
100,
"Getting Disk Devices on VM %s from snapshot\n"
- % (StringCodec.encode(vmname)),
+ % (StringCodec.encode(self.vm.name)),
)
self.get_vm_snap_disk_devices()
if not self.disk_devices:
bareosfd.DebugMessage(
100,
"Error getting Disk Devices on VM %s from snapshot\n"
- % (StringCodec.encode(vmname)),
+ % (StringCodec.encode(self.vm.name)),
)
bareosfd.JobMessage(
bareosfd.M_FATAL,
"Error getting Disk Devices on VM %s from snapshot\n"
- % (StringCodec.encode(vmname)),
+ % (StringCodec.encode(self.vm.name)),
)
return bareosfd.bRC_Error
@@ -999,25 +1121,34 @@ class BareosVADPWrapper(object):
return bareosfd.bRC_OK
if "uuid" in self.options:
- vmname = self.options["uuid"]
if not self.get_vm_details_by_uuid():
- bareosfd.DebugMessage(
- 100,
- "Error getting details for VM %s\n" % (StringCodec.encode(vmname)),
+ error_message = (
+ "No VM with UUID %s found, create VM with UUID not implemented\n"
+ % (self.options["uuid"])
)
+ bareosfd.DebugMessage(100, StringCodec.encode(error_message))
+ bareosfd.JobMessage(bareosfd.M_FATAL, StringCodec.encode(error_message))
return bareosfd.bRC_Error
else:
- vmname = self.options["vmname"]
if not self.get_vm_details_dc_folder_vmname():
- bareosfd.DebugMessage(
- 100,
- "Error getting details for VM %s\n" % (StringCodec.encode(vmname)),
+ error_message = (
+ "No VM with Folder/Name %s/%s found in DC %s, recreating it now\n"
+ % (
+ self.options["folder"],
+ self.options["vmname"],
+ self.options["dc"],
+ )
)
- return bareosfd.bRC_Error
+ bareosfd.DebugMessage(100, StringCodec.encode(error_message))
+ if not self.create_vm():
+ # job message is already done within create_vm
+ return bareosfd.bRC_Error
+
+ self.restore_custom_fields()
bareosfd.DebugMessage(
100,
- "Successfully got details for VM %s\n" % (StringCodec.encode(vmname)),
+ "Successfully got details for VM %s\n" % (StringCodec.encode(self.vm.name)),
)
vm_power_state = self.vm.summary.runtime.powerState
@@ -1025,7 +1156,7 @@ class BareosVADPWrapper(object):
bareosfd.JobMessage(
bareosfd.M_FATAL,
"Error VM %s must be poweredOff for restore, but is %s\n"
- % (vmname.encode("utf-8"), vm_power_state),
+ % (self.vm.name.encode("utf-8"), vm_power_state),
)
return bareosfd.bRC_Error
@@ -1033,19 +1164,20 @@ class BareosVADPWrapper(object):
bareosfd.JobMessage(
bareosfd.M_FATAL,
"Error VM %s must not have any snapshots before restore\n"
- % (StringCodec.encode(vmname)),
+ % (StringCodec.encode(self.vm.name)),
)
return bareosfd.bRC_Error
bareosfd.DebugMessage(
100,
- "Getting Disk Devices on VM %s\n" % (StringCodec.encode(vmname)),
+ "Getting Disk Devices on VM %s\n" % (StringCodec.encode(self.vm.name)),
)
self.get_vm_disk_devices()
if not self.disk_devices:
bareosfd.JobMessage(
bareosfd.M_FATAL,
- "Error getting Disk Devices on VM %s\n" % (StringCodec.encode(vmname)),
+ "Error getting Disk Devices on VM %s\n"
+ % (StringCodec.encode(self.vm.name)),
)
return bareosfd.bRC_Error
@@ -1061,29 +1193,21 @@ class BareosVADPWrapper(object):
and save result in self.vm
Returns True on success, False otherwise
"""
- content = self.si.content
- dcView = content.viewManager.CreateContainerView(
- content.rootFolder, [vim.Datacenter], False
+ dcView = self.si.content.viewManager.CreateContainerView(
+ self.si.content.rootFolder, [vim.Datacenter], False
)
vmListWithFolder = {}
dcList = dcView.view
dcView.Destroy()
for dc in dcList:
if dc.name == self.options["dc"]:
+ self.dc = dc
folder = ""
self._get_dcftree(vmListWithFolder, folder, dc.vmFolder)
- if self.options["folder"].endswith("/"):
- vm_path = "%s%s" % (self.options["folder"], self.options["vmname"])
- else:
- vm_path = "%s/%s" % (self.options["folder"], self.options["vmname"])
+ vm_path = "%s/%s" % (self.options["folder"], self.options["vmname"])
if vm_path not in vmListWithFolder:
- bareosfd.JobMessage(
- bareosfd.M_FATAL,
- "No VM with Folder/Name %s found in DC %s\n"
- % (StringCodec.encode(vm_path), self.options["dc"]),
- )
return False
self.vm = vmListWithFolder[vm_path]
@@ -1116,6 +1240,330 @@ class BareosVADPWrapper(object):
for vapp_vm in vm_or_folder.vm:
dcf[folder + "/" + vm_or_folder.name + "/" + vapp_vm.name] = vapp_vm
+ def _get_vm_folders(self, vm_folders, current_folder_name, current_folder):
+ """
+ Recursive function to get all VM folders
+ """
+ vm_folders[current_folder_name] = current_folder
+ vm_folder_view = self.si.content.viewManager.CreateContainerView(
+ current_folder, [vim.Folder], False
+ )
+ for vm_folder in vm_folder_view.view:
+ # avoid "//" when current_folder_name is "/"
+ subfolder_name = (
+ "/" + (current_folder_name + "/").lstrip("/") + vm_folder.name
+ )
+ self._get_vm_folders(vm_folders, subfolder_name, vm_folder)
+ vm_folder_view.Destroy()
+
+ def find_or_create_vm_folder(self, folder_name):
+ all_vm_folders = {}
+ self._get_vm_folders(all_vm_folders, "/", self.dc.vmFolder)
+ if folder_name not in all_vm_folders:
+ bareosfd.JobMessage(
+ bareosfd.M_INFO,
+ "VM folder %s not found, creating it\n"
+ % (StringCodec.encode(folder_name)),
+ )
+ # the prefixed "/" will produce an empty string element in split result,
+ # so we must skip element 0
+ current_folder_path = "/"
+ current_folder = self.dc.vmFolder
+ for folder_part in folder_name.split("/")[1:]:
+ current_folder_path += "/" + folder_part
+ # avoid leading "//"
+ current_folder_path = "/" + current_folder_path.lstrip("/")
+ if current_folder_path in all_vm_folders:
+ current_folder = all_vm_folders[current_folder_path]
+ continue
+ current_folder = current_folder.CreateFolder(folder_part)
+ all_vm_folders[current_folder_path] = current_folder
+
+ return all_vm_folders[folder_name]
+
+ def get_objects_by_folder(self, start_folder, folder_name, objects_by_folder):
+ """
+ Generic Recursive function to get objects by folder.
+ Here, folder means a folder path, using "/" as a folder separator.
+ An empty dict must be passed initially, it will be filled with
+ folder path as key and managed object as value.
+ """
+ for current_object in start_folder.childEntity:
+ if isinstance(current_object, vim.Folder):
+ self.get_objects_by_folder(
+ current_object,
+ folder_name + "/" + current_object.name,
+ objects_by_folder,
+ )
+ else:
+ objects_by_folder[
+ "/" + (folder_name + "/").lstrip("/") + current_object.name
+ ] = current_object
+
+ return
+
+ def _get_resource_pools(self, cluster):
+ """
+ Get resource pools by folder like structure
+
+ Resource pools can be nested. Each cluster has a default resource pool
+ which is its resourcePool property and it's a single item.
+ But the resourcePool property of the default resource pool is a list
+ of resource pools, where each of this can have a list of resource pools.
+ Like this, resource pools can be arbitrarily nested.
+ """
+ resource_pools = {}
+ resource_pools["/"] = cluster.resourcePool
+ stack = []
+ for resource_pool in cluster.resourcePool.resourcePool:
+ stack.append(("/", resource_pool))
+ while stack:
+ folder, resource_pool = stack.pop()
+ path = "/" + (folder + "/").lstrip("/") + resource_pool.name
+ resource_pools[path] = resource_pool
+ for sub_resource_pool in resource_pool.resourcePool:
+ stack.append((path, sub_resource_pool))
+
+ return resource_pools
+
+ def _get_vm_folder_path(self, vm):
+ """
+ Get the folder path of a given vm
+ """
+ current_obj = vm
+ vm_folder_path = ""
+ while current_obj.parent:
+ current_obj = current_obj.parent
+ if isinstance(current_obj.parent, vim.Datacenter):
+ break
+ if (
+ hasattr(current_obj, "childType")
+ and "VirtualMachine" in current_obj.childType
+ ):
+ vm_folder_path = "/" + current_obj.name + vm_folder_path
+
+ vm_folder_path += "/" + vm.name
+ return vm_folder_path
+
+ def get_resource_pool_by_path(self, resource_pool_path, cluster):
+ """
+ Get resource pool by path
+
+ As resource pools can be nested, unique specfication requires a path-like
+ parameter.
+ """
+ resource_pools = self._get_resource_pools(cluster)
+ if resource_pool_path not in resource_pools:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Resource pool %s not found in cluster %s!\n"
+ % (
+ StringCodec.encode(resource_pool_path),
+ StringCodec.encode(cluster.name),
+ ),
+ )
+ return False
+
+ return resource_pools[resource_pool_path]
+
+ def create_vm(self):
+ """
+ Create a new VM using JSON configuration data
+ """
+
+ destination_host = None
+ resource_pool = None
+ datacenter = None
+ create_vm_task = None
+ cluster_path = None
+ datastore_name = None
+ vm_created = False
+
+ config_info = json.loads(self.restore_vm_config_json)
+
+ if self.options.get("restore_datastore"):
+ datastore_name = self.options["restore_datastore"]
+
+ # prevent from MAC address conflicts
+ self.check_mac_address(config_info)
+
+ # prevent from duplicate UUID
+ self.check_uuid(config_info)
+
+ transformer = BareosVmConfigInfoToSpec(config_info)
+ used_datastore_names = transformer.get_datastore_names()
+ if len(used_datastore_names) > 1:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "VM %s used multiple datastores (%s), restore not yet possible!\n"
+ % (
+ StringCodec.encode(self.options["vmname"]),
+ StringCodec.encode(used_datastore_names),
+ ),
+ )
+ return False
+
+ if datastore_name and datastore_name != used_datastore_names[0]:
+ if not self.find_managed_object_by_name([vim.Datastore], datastore_name):
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Datastore %s not found!\n" % (StringCodec.encode(datastore_name)),
+ )
+ return False
+ self.vmfs_vm_path_changed = True
+
+ config = transformer.transform(target_datastore_name=datastore_name)
+
+ for child in self.si.content.rootFolder.childEntity:
+ if child.name == self.options["dc"]:
+ datacenter = child
+ break
+ else:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Datacenter %s not found!\n" % (StringCodec.encode(self.options["dc"])),
+ )
+ return False
+
+ if self.options.get("restore_resource_pool") and not self.options.get(
+ "restore_cluster"
+ ):
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Option restore_restore_pool requires option restore_cluster!\n",
+ )
+ return False
+
+ if self.options.get("restore_cluster"):
+ clusters_by_folder = {}
+ self.get_objects_by_folder(datacenter.hostFolder, "/", clusters_by_folder)
+ cluster_path = "/" + self.options["restore_cluster"].lstrip("/")
+ if cluster_path in clusters_by_folder:
+ if self.options.get("restore_resource_pool"):
+ resource_pool = self.get_resource_pool_by_path(
+ self.options["restore_resource_pool"],
+ clusters_by_folder[cluster_path],
+ )
+ if not resource_pool:
+ return False
+
+ if resource_pool is None:
+ resource_pool = clusters_by_folder[cluster_path].resourcePool
+
+ else:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Could not find cluster %s in DC %s!\n"
+ % (cluster_path, self.options["dc"]),
+ )
+ return False
+
+ if self.options.get("restore_esxhost"):
+ destination_host = self.find_managed_object_by_name(
+ [vim.HostSystem], self.options["restore_esxhost"]
+ )
+
+ if not destination_host:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Could not find ESX host %s!\n" % (self.options["restore_esxhost"]),
+ )
+ return False
+
+ if not self.options.get("restore_cluster") and not self.options.get(
+ "restore_esxhost"
+ ):
+ # use saved esx host name from restore object
+ if not self.restore_vm_metadata:
+ self.restore_vm_metadata = json.loads(self.restore_vm_metadata_json)
+
+ destination_host = self.find_managed_object_by_name(
+ [vim.HostSystem], self.restore_vm_metadata["esx_host_name"]
+ )
+
+ if not destination_host:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Could not find ESX host %s!\n" % (self.options["restore_esxhost"]),
+ )
+ return False
+
+ if resource_pool is None:
+ resource_pool = destination_host.parent.resourcePool
+
+ target_folder = self.find_or_create_vm_folder(self.options["folder"])
+
+ bareosfd.JobMessage(
+ bareosfd.M_INFO,
+ "Creating VM %s in folder %s (%s)\n"
+ % (
+ StringCodec.encode(self.options["vmname"]),
+ StringCodec.encode(self.options["folder"]),
+ StringCodec.encode(target_folder.name),
+ ),
+ )
+
+ # DEBUG: Test Hack to see if it creates the directory in VMFS
+ # config.files = vim.vm.FileInfo()
+ # config.files.vmPathName = "[datastore1]"
+ # Yes, this implicitly creates the dir on VMFS, including _n suffix if needed.
+ while not vm_created:
+ try:
+ create_vm_task = target_folder.CreateVm(
+ config, pool=resource_pool, host=destination_host
+ )
+ WaitForTask(create_vm_task)
+ bareosfd.DebugMessage(
+ 100,
+ "VM created: %s\n" % (StringCodec.encode(self.options["vmname"])),
+ )
+ vm_created = True
+
+ except vim.fault.DuplicateName:
+ # VM with same name exists in target folder
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Error creating VM, duplicate name: %s\n"
+ % (StringCodec.encode(self.options["vmname"])),
+ )
+ return False
+
+ except vim.fault.AlreadyExists:
+ # VM directory already exists in VMFS
+ bareosfd.JobMessage(
+ bareosfd.M_WARNING,
+ "Error creating VM, name %s already exists in VMFS, retrying so VM path in VMFS will have _n suffix\n"
+ % (StringCodec.encode(self.options["vmname"])),
+ )
+ if datastore_name is None:
+ datastore_name = used_datastore_names[0]
+
+ # Setting files.vmPathName to datastore name in square brackets only,
+ # this implicitly creates the dir on VMFS, including _n suffix if needed.
+ config.files = vim.vm.FileInfo()
+ config.files.vmPathName = "[%s]" % (datastore_name)
+ self.vmfs_vm_path_changed = True
+
+ except Exception as ex:
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Error creating VM, name %s, exception %s\n"
+ % (
+ StringCodec.encode(self.options["vmname"]),
+ StringCodec.encode(str(ex)),
+ ),
+ )
+ return False
+
+ bareosfd.JobMessage(
+ bareosfd.M_INFO,
+ "Successfully created VM %s\n"
+ % (StringCodec.encode(self.options["vmname"])),
+ )
+ self.vm = create_vm_task.info.result
+
+ return True
+
def create_vm_snapshot(self):
"""
Creates a snapshot
@@ -1261,20 +1709,72 @@ class BareosVADPWrapper(object):
"get_vm_disk_cbt(): using changeId %s from restore object\n"
% (cbt_changeId),
)
- self.changed_disk_areas = self.vm.QueryChangedDiskAreas(
- snapshot=self.create_snap_result,
- deviceKey=self.disk_device_to_backup["deviceKey"],
- startOffset=0,
- changeId=cbt_changeId,
- )
+
+ try:
+ self.changed_disk_areas = self.vm.QueryChangedDiskAreas(
+ snapshot=self.create_snap_result,
+ deviceKey=self.disk_device_to_backup["deviceKey"],
+ startOffset=0,
+ changeId=cbt_changeId,
+ )
+ except vim.fault.FileFault as error:
+ if cbt_changeId == "*":
+ bareosfd.JobMessage(
+ bareosfd.M_FATAL,
+ "Get CBT failed: %s\n" % (error.faultMessage.message),
+ )
+ return False
+ else:
+ # Note: A job message with M_ERROR will result in termination
+ # Backup OK -- with warnings
+ bareosfd.JobMessage(bareosfd.M_ERROR, "Get CBT failed:\n")
+ for cbt_error_message in [
+ fault.message for fault in error.faultMessage
+ ]:
+ bareosfd.JobMessage(bareosfd.M_ERROR, cbt_error_message + "\n")
+ bareosfd.JobMessage(bareosfd.M_WARNING, "Falling back to full CBT\n")
+ self.changed_disk_areas = self.vm.QueryChangedDiskAreas(
+ snapshot=self.create_snap_result,
+ deviceKey=self.disk_device_to_backup["deviceKey"],
+ startOffset=0,
+ changeId="*",
+ )
+ bareosfd.JobMessage(bareosfd.M_INFO, "Successfully got full CBT\n")
+ bareosfd.JobMessage(
+ bareosfd.M_WARNING,
+ "Recommendation: Run Full level job as soon as possible\n",
+ )
+
self.cbt2json()
return True
def check_vm_disks_match(self):
"""
- Check if the backed up disks match selecte VM disks
+ Check if the backed up disks match selected VM disks
"""
- backed_up_disks = set(self.restore_objects_by_diskpath.keys())
+ backed_up_disks = set()
+ vmfs_vm_path = self.get_vmfs_vm_path()
+
+ for disk_path in self.restore_objects_by_diskpath.keys():
+ if self.vmfs_vm_path_changed:
+ # adapt for restore to different datastore or different VMFS path
+ bareosfd.DebugMessage(
+ 100,
+ "check_vm_disks_match(): adapting disk path %s to vm path %s\n"
+ % (disk_path, vmfs_vm_path),
+ )
+ disk_path = self.datastore_vm_path_rex.sub(
+ vmfs_vm_path,
+ disk_path,
+ count=1,
+ )
+ bareosfd.DebugMessage(
+ 100,
+ "check_vm_disks_match(): adapted disk path to %s\n" % (disk_path),
+ )
+
+ backed_up_disks.add(disk_path)
+
vm_disks = set([disk_dev["fileNameRoot"] for disk_dev in self.disk_devices])
if backed_up_disks == vm_disks:
@@ -1298,6 +1798,48 @@ class BareosVADPWrapper(object):
)
return False
+ def vmconfig2json(self):
+ """
+ Convert the VM config info from the snapshot to JSON
+ """
+ # This will be saved in a restore objects
+ self.vm_config_info_json = json.dumps(
+ self.create_snap_result.config, cls=VmomiJSONEncoder
+ )
+ self.files_to_backup.append("%s/%s" % (self.backup_path, "vm_config.json"))
+
+ # Note: the vm property of the snapshot result also contains a config property,
+ # but it can't be used to create a VM because it reflects the state after the
+ # snapshot, while the config property represents the state of the snapshot,
+ # so that it contains the correct disks.
+ # However, we also need the VM info to be able to restore custom attributes etc.
+ self.vm_info_json = json.dumps(self.create_snap_result.vm, cls=VmomiJSONEncoder)
+ self.files_to_backup.append("%s/%s" % (self.backup_path, "vm_info.json"))
+
+ # To be able to restore to the same host and same cluster:
+ # The runtime property (VirtualMachineRuntimeInfo) has the host property, but
+ # it's only the reference. Here the name is needed.
+ vm_metadata = {}
+ vm_metadata["esx_host_name"] = self.create_snap_result.vm.runtime.host.name
+ self.vm_metadata_json = json.dumps(vm_metadata)
+ self.files_to_backup.append("%s/%s" % (self.backup_path, "vm_metadata.json"))
+
+ def restore_custom_fields(self):
+ """
+ Restore Custom Fields
+ """
+
+ if not self.restore_vm_info:
+ self.restore_vm_info = json.loads(self.restore_vm_info_json)
+
+ available_fields = dict(
+ [(f["key"], f["name"]) for f in self.restore_vm_info["availableField"]]
+ )
+ for custom_field in self.restore_vm_info["customValue"]:
+ self.vm.setCustomValue(
+ key=available_fields[custom_field["key"]], value=custom_field["value"]
+ )
+
def cbt2json(self):
"""
Convert CBT data into json serializable structure and
@@ -1776,6 +2318,13 @@ class BareosVADPWrapper(object):
Cleanup temporary files
"""
+ if self.options.get("cleanup_tmp_files") == "no":
+ bareosfd.DebugMessage(
+ 100,
+ "end_dumper() not deleting temporary file, cleanup_tmp_files is set to no\n",
+ )
+ return True
+
# delete temporary json file
if not self.cbt_json_local_file_path:
# not set, nothing to do
@@ -1832,6 +2381,146 @@ class BareosVADPWrapper(object):
wrappedSocket.close()
return success
+ def find_managed_object_by_name(
+ self, object_types, object_name, start_folder=None, recursive=True
+ ):
+ """
+ Find a managed object by name
+ """
+ found_managed_object = None
+ if start_folder is None:
+ start_folder = self.si.content.rootFolder
+
+ view = self.si.content.viewManager.CreateContainerView(
+ start_folder, object_types, recursive
+ )
+
+ for managed_object in view.view:
+ if managed_object.name == object_name:
+ found_managed_object = managed_object
+ break
+
+ view.Destroy()
+ return found_managed_object
+
+ def get_vmfs_vm_path(self):
+ """
+ Get the VMs path in VMFS
+ """
+ if not self.vmfs_vm_path:
+ self.vmfs_vm_path = os.path.dirname(self.vm.config.files.vmPathName) + "/"
+ return self.vmfs_vm_path
+
+ def restore_power_state(self):
+ """
+ Restore power state according to restore_powerstate option
+ """
+
+ if not self.restore_vm_info:
+ self.restore_vm_info = json.loads(self.restore_vm_info_json)
+
+ if self.options["restore_powerstate"] == "off":
+ return bareosfd.bRC_OK
+
+ if self.options["restore_powerstate"] == "on" or (
+ self.options["restore_powerstate"] == "previous"
+ and self.restore_vm_info["runtime"]["powerState"] == "poweredOn"
+ ):
+ bareosfd.DebugMessage(
+ 100,
+ "restore_power_state(): Powering on VM %s\n" % (self.vm.name),
+ )
+ poweron_task = self.dc.PowerOnMultiVM_Task(vm=[self.vm])
+ self.vmomi_WaitForTasks([poweron_task])
+ bareosfd.DebugMessage(
+ 100,
+ "restore_power_state(): Power on Task status for VM %s: %s\n"
+ % (self.vm.name, poweron_task.info.state),
+ )
+
+ # Check if it was really powered on, as when DRS Automation is set
+ # to manual, the task will terminate with success, but VM is not powered on.
+ # Also when DRS Automation is set to fully automated, the task terminates
+ # with success state before the VM is powered on. So there seems to be
+ # no reliable solution for this problem. Workaround: poll power state
+ # with timeout. FIXME: The timeout should be made configurable later.
+
+ poweron_timeout = 10
+ poweron_start_time = time.time()
+ # This could probably be implemented better by using WaitForUpdates()
+ while self.vm.runtime.powerState != "poweredOn":
+ time.sleep(1)
+ if time.time() - poweron_start_time >= poweron_timeout:
+ break
+
+ if self.vm.runtime.powerState != "poweredOn":
+ # This warning will cause the job termination to be
+ # Restore OK -- with warnings
+ bareosfd.JobMessage(
+ bareosfd.M_WARNING,
+ "Could not power on VM %s: power state is %s\n"
+ % (self.vm.name, self.vm.runtime.powerState),
+ )
+ return bareosfd.bRC_Error
+
+ return bareosfd.bRC_OK
+
+ def check_mac_address(self, config_info):
+ """
+ Check if a MAC address in this config info exists and remove it from
+ this config info if applicable to avoid conflicts
+ """
+ all_mac_addr = {}
+ vm_view = self.si.content.viewManager.CreateContainerView(
+ self.si.content.rootFolder, [vim.VirtualMachine], True
+ )
+ all_vms = [vm for vm in vm_view.view]
+ vm_view.Destroy()
+
+ for vm in all_vms:
+ for dev in vm.config.hardware.device:
+ if isinstance(dev, vim.vm.device.VirtualEthernetCard):
+ if dev.macAddress not in all_mac_addr:
+ all_mac_addr[dev.macAddress] = []
+ all_mac_addr[dev.macAddress].append(vm)
+
+ for device in [
+ dev for dev in config_info["hardware"]["device"] if "macAddress" in dev
+ ]:
+ mac_address = device["macAddress"]
+ if mac_address in all_mac_addr:
+ bareosfd.JobMessage(
+ bareosfd.M_WARNING,
+ "MAC address %s already exists in VM(s): %s, restored VM will get generated MAC address\n"
+ % (
+ mac_address,
+ [
+ self._get_vm_folder_path(vm)
+ for vm in all_mac_addr[mac_address]
+ ],
+ ),
+ )
+ device["addressType"] = "generated"
+
+ def check_uuid(self, config_info):
+ """
+ Check for duplicate VM uuid
+ """
+ vms = self.si.content.searchIndex.FindAllByUuid(
+ uuid=config_info["uuid"], vmSearch=True
+ )
+ if not vms:
+ return
+
+ for vm in vms:
+ bareosfd.JobMessage(
+ bareosfd.M_WARNING,
+ "UUID %s already exists in VM: %s, restored VM will get new generated UUID\n"
+ % (config_info["uuid"], self._get_vm_folder_path(vm)),
+ )
+
+ del config_info["uuid"]
+
# helper functions ############
def mkdir(self, directory_name):
@@ -1854,4 +2543,882 @@ class StringCodec:
return var
+class BareosVmConfigInfoToSpec(object):
+ """
+ A class to transform serialized VirtualMachineConfigInfo(vim.vm.ConfigInfo)
+ into VirtualMachineConfigSpec(vim.vm.ConfigSpec)
+ """
+
+ def __init__(self, config_info):
+ self.config_info = config_info
+ self.datastore_rex = re.compile(r"\[(.+?)\]")
+ self.target_datastore_name = None
+
+ def transform(self, target_datastore_name=None):
+ config_spec = vim.vm.ConfigSpec()
+ self.target_datastore_name = target_datastore_name
+ config_spec.alternateGuestName = self.config_info["alternateGuestName"]
+ config_spec.annotation = self.config_info["annotation"]
+ config_spec.bootOptions = self._transform_bootOptions()
+ config_spec.changeTrackingEnabled = self.config_info["changeTrackingEnabled"]
+ config_spec.cpuAffinity = self._transform_cpuAffinity()
+ config_spec.cpuAllocation = self._transform_ResourceAllocationInfo(
+ "cpuAllocation"
+ )
+ config_spec.cpuFeatureMask = self._transform_cpuFeatureMask()
+ config_spec.cpuHotAddEnabled = self.config_info["cpuHotAddEnabled"]
+ config_spec.cpuHotRemoveEnabled = self.config_info["cpuHotRemoveEnabled"]
+ config_spec.deviceChange = self._transform_devices()
+ config_spec.extraConfig = self._transform_extraConfig()
+ config_spec.files = self._transform_files()
+ config_spec.firmware = self.config_info["firmware"]
+ config_spec.flags = self._transform_flags()
+ config_spec.ftEncryptionMode = self.config_info["ftEncryptionMode"]
+ config_spec.ftInfo = self._transform_ftInfo()
+ config_spec.guestAutoLockEnabled = self.config_info["guestAutoLockEnabled"]
+ config_spec.guestId = self.config_info["guestId"]
+ config_spec.guestMonitoringModeInfo = self._transform_guestMonitoringModeInfo()
+ config_spec.latencySensitivity = self._transform_latencySensitivity(
+ self.config_info["latencySensitivity"]
+ )
+ config_spec.managedBy = self._transform_managedBy()
+ config_spec.maxMksConnections = self.config_info["maxMksConnections"]
+ config_spec.memoryAllocation = self._transform_ResourceAllocationInfo(
+ "memoryAllocation"
+ )
+ config_spec.memoryHotAddEnabled = self.config_info["memoryHotAddEnabled"]
+ config_spec.memoryMB = self.config_info["hardware"]["memoryMB"]
+ config_spec.memoryReservationLockedToMax = self.config_info[
+ "memoryReservationLockedToMax"
+ ]
+ config_spec.messageBusTunnelEnabled = self.config_info[
+ "messageBusTunnelEnabled"
+ ]
+ config_spec.migrateEncryption = self.config_info["migrateEncryption"]
+ config_spec.name = self.config_info["name"]
+ config_spec.nestedHVEnabled = self.config_info["nestedHVEnabled"]
+ config_spec.numCoresPerSocket = self.config_info["hardware"][
+ "numCoresPerSocket"
+ ]
+ config_spec.numCPUs = self.config_info["hardware"]["numCPU"]
+ config_spec.pmem = self._transform_pmem()
+ config_spec.pmemFailoverEnabled = self.config_info["pmemFailoverEnabled"]
+ config_spec.powerOpInfo = self._transform_defaultPowerOps()
+ # Since vSphere API 7.0.1.0:
+ if "sevEnabled" in self.config_info:
+ config_spec.sevEnabled = self.config_info["sevEnabled"]
+ # Since vSphere API 7.0:
+ if self.config_info.get("sgxInfo"):
+ config_spec.sgxInfo = self._transform_sgxInfo()
+ config_spec.swapPlacement = self.config_info["swapPlacement"]
+ config_spec.tools = self._transform_tools()
+ config_spec.uuid = self.config_info.get("uuid")
+ config_spec.vAppConfig = self._transform_vAppConfig()
+ config_spec.vAssertsEnabled = self.config_info["vAssertsEnabled"]
+ # Since vSphere API 7.0:
+ if "vcpuConfig" in self.config_info:
+ config_spec.vcpuConfig = self._transform_VirtualMachineVcpuConfig()
+ config_spec.version = self.config_info["version"]
+ config_spec.virtualICH7MPresent = self.config_info["hardware"][
+ "virtualICH7MPresent"
+ ]
+ config_spec.virtualSMCPresent = self.config_info["hardware"][
+ "virtualSMCPresent"
+ ]
+ config_spec.vmOpNotificationToAppEnabled = self.config_info[
+ "vmOpNotificationToAppEnabled"
+ ]
+
+ return config_spec
+
+ def get_datastore_names(self):
+ datastore_names = set()
+ for file_property in [
+ "vmPathName",
+ "snapshotDirectory",
+ "suspendDirectory",
+ "logDirectory",
+ ]:
+ ds_match = self.datastore_rex.match(
+ self.config_info["files"][file_property]
+ )
+ if ds_match:
+ datastore_names.add(ds_match.group(1))
+ for device in self.config_info["hardware"]["device"]:
+ if device["_vimtype"] == "vim.vm.device.VirtualDisk":
+ ds_match = self.datastore_rex.match(device["backing"]["fileName"])
+ if ds_match:
+ datastore_names.add(ds_match.group(1))
+
+ return list(datastore_names)
+
+ def _transform_bootOptions(self):
+ config_info_boot_options = self.config_info["bootOptions"]
+ boot_options = vim.vm.BootOptions()
+ boot_options.bootRetryDelay = config_info_boot_options["bootRetryDelay"]
+ boot_options.bootRetryEnabled = config_info_boot_options["bootRetryEnabled"]
+ boot_options.efiSecureBootEnabled = config_info_boot_options[
+ "efiSecureBootEnabled"
+ ]
+ boot_options.enterBIOSSetup = config_info_boot_options["enterBIOSSetup"]
+ boot_options.networkBootProtocol = config_info_boot_options[
+ "networkBootProtocol"
+ ]
+ for boot_order in config_info_boot_options["bootOrder"]:
+ boot_device = None
+ if boot_order["_vimtype"] == "vim.vm.BootOptions.BootableCdromDevice":
+ boot_device = vim.vm.BootOptions.BootableCdromDevice()
+ elif boot_order["_vimtype"] == "vim.vm.BootOptions.BootableDiskDevice":
+ boot_device = vim.vm.BootOptions.BootableDiskDevice()
+ # TODO: check if device keys can be restored as backed up
+ boot_device.deviceKey = boot_order["deviceKey"]
+ elif boot_order["_vimtype"] == "vim.vm.BootOptions.BootableEthernetDevice":
+ boot_device = vim.vm.BootOptions.BootableEthernetDevice()
+ # TODO: check if device keys can be restored as backed up
+ boot_device.deviceKey = boot_order["deviceKey"]
+ elif boot_order["_vimtype"] == "vim.vm.BootOptions.BootableFloppyDevice":
+ boot_device = vim.vm.BootOptions.BootableFloppyDevice()
+
+ boot_options.bootOrder.append(boot_device)
+
+ return boot_options
+
+ def _transform_cpuAffinity(self):
+ if not self.config_info["cpuAffinity"]:
+ return None
+ cpu_affinity = vim.vm.AffinityInfo()
+ for node_nr in self.config_info["cpuAffinity"]["affinitySet"]:
+ cpu_affinity.affinitySet.append(node_nr)
+
+ return cpu_affinity
+
+ def _transform_cpuAllocation(self):
+ if not self.config_info["cpuAllocation"]:
+ return None
+
+ return self._transform_ResourceAllocationInfo(self.config_info["cpuAllocation"])
+
+ def _transform_memoryAllocation(self):
+ if not self.config_info["memoryAllocation"]:
+ return None
+
+ return self._transform_ResourceAllocationInfo(
+ self.config_info["memoryAllocation"]
+ )
+
+ def _transform_ResourceAllocationInfo(self, property_name):
+ if not self.config_info[property_name]:
+ return None
+
+ config_info_allocation = self.config_info[property_name]
+ resource_allocation_info = vim.ResourceAllocationInfo()
+ resource_allocation_info.expandableReservation = config_info_allocation[
+ "expandableReservation"
+ ]
+ resource_allocation_info.limit = config_info_allocation["limit"]
+ resource_allocation_info.reservation = config_info_allocation["reservation"]
+ resource_allocation_info.shares = vim.SharesInfo()
+ resource_allocation_info.shares.level = config_info_allocation["shares"][
+ "level"
+ ]
+ resource_allocation_info.shares.shares = config_info_allocation["shares"][
+ "shares"
+ ]
+
+ return resource_allocation_info
+
+ def _transform_cpuFeatureMask(self):
+ if not self.config_info["cpuFeatureMask"]:
+ return []
+ cpu_feature_mask = []
+ for cpu_id_info in self.config_info["cpuFeatureMask"]:
+ cpu_id_info_spec = vim.vm.ConfigSpec.CpuIdInfoSpec()
+ cpu_id_info_spec.info = vim.host.CpuIdInfo()
+ cpu_id_info_spec.info.eax = cpu_id_info["eax"]
+ cpu_id_info_spec.info.ebx = cpu_id_info["ebx"]
+ cpu_id_info_spec.info.ecx = cpu_id_info["ecx"]
+ cpu_id_info_spec.info.edx = cpu_id_info["edx"]
+ cpu_id_info_spec.operation = vim.option.ArrayUpdateSpec.Operation().add
+ cpu_feature_mask.append(cpu_id_info_spec)
+
+ return cpu_feature_mask
+
+ def _transform_devices(self):
+ device_change = []
+ default_devices = [
+ "vim.vm.device.VirtualIDEController",
+ "vim.vm.device.VirtualPS2Controller",
+ "vim.vm.device.VirtualPCIController",
+ "vim.vm.device.VirtualSIOController",
+ "vim.vm.device.VirtualKeyboard",
+ "vim.vm.device.VirtualVMCIDevice",
+ "vim.vm.device.VirtualPointingDevice",
+ ]
+
+ omitted_devices = [
+ "vim.vm.device.VirtualVideoCard",
+ ]
+
+ virtual_scsi_controllers = [
+ "vim.vm.device.ParaVirtualSCSIController",
+ "vim.vm.device.VirtualBusLogicController",
+ "vim.vm.device.VirtualLsiLogicController",
+ "vim.vm.device.VirtualLsiLogicSASController",
+ ]
+
+ virtual_misc_controllers = [
+ "vim.vm.device.VirtualNVDIMMController",
+ "vim.vm.device.VirtualNVMEController",
+ "vim.vm.device.VirtualAHCIController",
+ ]
+
+ virtual_usb_controllers = [
+ "vim.vm.device.VirtualUSBController",
+ "vim.vm.device.VirtualUSBXHCIController",
+ ]
+
+ virtual_ethernet_cards = [
+ "vim.vm.device.VirtualE1000",
+ "vim.vm.device.VirtualE1000e",
+ "vim.vm.device.VirtualPCNet32",
+ "vim.vm.device.VirtualSriovEthernetCard",
+ "vim.vm.device.VirtualVmxnet2",
+ "vim.vm.device.VirtualVmxnet3",
+ ]
+
+ for device in self.config_info["hardware"]["device"]:
+ if device["_vimtype"] in default_devices + omitted_devices:
+ continue
+ device_spec = vim.vm.device.VirtualDeviceSpec()
+ device_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation().add
+ add_device = None
+
+ if device["_vimtype"] in virtual_scsi_controllers:
+ add_device = self._transform_virtual_scsi_controller(device)
+ elif device["_vimtype"] in virtual_misc_controllers:
+ add_device = self._transform_virtual_misc_controller(device)
+ elif device["_vimtype"] in virtual_usb_controllers:
+ add_device = self._transform_virtual_usb_controller(device)
+ elif device["_vimtype"] == "vim.vm.device.VirtualCdrom":
+ add_device = self._transform_virtual_cdrom(device)
+ elif device["_vimtype"] == "vim.vm.device.VirtualDisk":
+ device_spec.fileOperation = (
+ vim.vm.device.VirtualDeviceSpec.FileOperation().create
+ )
+ add_device = self._transform_virtual_disk(device)
+ elif device["_vimtype"] in virtual_ethernet_cards:
+ add_device = self._transform_virtual_ethernet_card(device)
+ elif device["_vimtype"] == "vim.vm.device.VirtualFloppy":
+ add_device = self._transform_virtual_floppy(device)
+ else:
+ # TODO: raise error, unknown device
+ raise RuntimeError(
+ "Error: Unknown Device Type %s" % (device["_vimtype"])
+ )
+ return None
+
+ if add_device:
+ device_spec.device = add_device
+ device_change.append(device_spec)
+
+ return device_change
+
+ def _transform_virtual_scsi_controller(self, device):
+ add_device = None
+ if device["_vimtype"] == "vim.vm.device.ParaVirtualSCSIController":
+ add_device = vim.vm.device.ParaVirtualSCSIController()
+ elif device["_vimtype"] == "vim.vm.device.VirtualBusLogicController":
+ add_device = vim.vm.device.VirtualBusLogicController()
+ elif device["_vimtype"] == "vim.vm.device.VirtualLsiLogicController":
+ add_device = vim.vm.device.VirtualLsiLogicController()
+ elif device["_vimtype"] == "vim.vm.device.VirtualLsiLogicSASController":
+ add_device = vim.vm.device.VirtualLsiLogicSASController()
+ else:
+ # TODO: raise error
+ raise RuntimeError(
+ "Error: Unknown SCSI controller type %s" % (device["_vimtype"])
+ )
+ return None
+
+ add_device.key = device["key"] * -1
+ add_device.busNumber = device["busNumber"]
+ add_device.sharedBus = device["sharedBus"]
+
+ return add_device
+
+ def _transform_virtual_misc_controller(self, device):
+ add_device = None
+ if device["_vimtype"] == "vim.vm.device.VirtualNVDIMMController":
+ add_device = vim.vm.device.VirtualNVDIMMController()
+ elif device["_vimtype"] == "vim.vm.device.VirtualNVMEController":
+ add_device = vim.vm.device.VirtualNVMEController()
+ elif device["_vimtype"] == "vim.vm.device.VirtualAHCIController":
+ add_device = vim.vm.device.VirtualAHCIController()
+ else:
+ # TODO: raise error
+ raise RuntimeError(
+ "Error: Unknown controller type %s" % (device["_vimtype"])
+ )
+ return None
+
+ add_device.key = device["key"] * -1
+ add_device.busNumber = device["busNumber"]
+
+ return add_device
+
+ def _transform_connectable(self, device):
+ connectable = vim.vm.device.VirtualDevice.ConnectInfo()
+ connectable.allowGuestControl = device["connectable"]["allowGuestControl"]
+ connectable.startConnected = device["connectable"]["startConnected"]
+ # connected = True would only make sense for running VM
+ connectable.connected = False
+ return connectable
+
+ def _transform_virtual_usb_controller(self, device):
+ add_device = None
+ if device["_vimtype"] == "vim.vm.device.VirtualUSBController":
+ add_device = vim.vm.device.VirtualUSBController()
+ add_device.ehciEnabled = device["ehciEnabled"]
+ elif device["_vimtype"] == "vim.vm.device.VirtualUSBXHCIController":
+ add_device = vim.vm.device.VirtualUSBXHCIController()
+ else:
+ # TODO: unknown type, raise error
+ raise RuntimeError(
+ "Error: Unknown USB controller type %s" % (device["_vimtype"])
+ )
+ return None
+
+ add_device.key = device["key"] * -1
+ add_device.autoConnectDevices = device["autoConnectDevices"]
+
+ return add_device
+
+ def _transform_virtual_cdrom(self, device):
+ add_device = vim.vm.device.VirtualCdrom()
+ add_device.key = device["key"] * -1
+ if (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualCdrom.AtapiBackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualCdrom.AtapiBackingInfo()
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ elif (
+ device["backing"]["_vimtype"] == "vim.vm.device.VirtualCdrom.IsoBackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualCdrom.IsoBackingInfo()
+ add_device.backing.backingObjectId = device["backing"]["backingObjectId"]
+ add_device.backing.datastore = vim.Datastore(device["backing"]["datastore"])
+ add_device.backing.fileName = device["backing"]["fileName"]
+ elif (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualCdrom.PassthroughBackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualCdrom.PassthroughBackingInfo()
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ elif (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualCdrom.RemoteAtapiBackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualCdrom.RemoteAtapiBackingInfo()
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ elif (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo"
+ ):
+ add_device.backing = (
+ vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo()
+ )
+ add_device.backing.exclusive = device["backing"]["exclusive"]
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ else:
+ # TODO: unknown backing type, raise error
+ raise RuntimeError(
+ "Error: Unknown CDROM backing type %s" % (device["backing"]["_vimtype"])
+ )
+ return None
+
+ add_device.connectable = self._transform_connectable(device)
+
+ # TODO: Looks like negative values must not be used for default devices,
+ # the second VirtualIDEController seems to have key 201, but is that always the case?
+ # TODO: Same for VirtualCdrom, getting error "The device '1' is referring to a nonexisting controller '-200'."
+ add_device.controllerKey = device["controllerKey"]
+ if device["controllerKey"] not in [200, 201]:
+ add_device.controllerKey = device["controllerKey"] * -1
+ add_device.unitNumber = device["unitNumber"]
+
+ return add_device
+
+ def _transform_virtual_floppy(self, device):
+ add_device = vim.vm.device.VirtualFloppy()
+ add_device.key = device["key"] * -1
+ if (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualFloppy.DeviceBackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualFloppy.DeviceBackingInfo()
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ elif (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualFloppy.RemoteDeviceBackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualFloppy.RemoteDeviceBackingInfo()
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ else:
+ # TODO: unknown backing type, raise error
+ raise RuntimeError(
+ "Unknown Backing for Floppy: %s" % (device["backing"]["_vimtype"])
+ )
+ return None
+
+ add_device.connectable = self._transform_connectable(device)
+
+ # TODO: Looks like negative values must not be used for default devices,
+ # the VirtualSIOController seems to have key 400, but is that always the case?
+ add_device.controllerKey = device["controllerKey"]
+ if device["controllerKey"] not in [400]:
+ add_device.controllerKey = device["controllerKey"] * -1
+ add_device.unitNumber = device["unitNumber"]
+
+ return add_device
+
+ def _transform_virtual_disk(self, device):
+ add_device = vim.vm.device.VirtualDisk()
+ add_device.key = device["key"] * -1
+ if (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualDisk.FlatVer2BackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
+ # this is the datastore MoRef, eg. "vim.Datastore:datastore-13"
+ # must be converted to the correct type, otherwise getting
+ # TypeError: For "datastore" expected type vim.Datastore, but got str
+ # Solution: It works when not specifying the datastore here.
+ # add_device.backing.datastore = vim.Datastore(device["backing"]["datastore"])
+ if self.target_datastore_name:
+ # replace datastore name in backing fileName
+ device["backing"]["fileName"] = self.datastore_rex.sub(
+ "[" + self.target_datastore_name + "]",
+ device["backing"]["fileName"],
+ count=1,
+ )
+ add_device.backing.fileName = device["backing"]["fileName"]
+ add_device.backing.digestEnabled = device["backing"]["digestEnabled"]
+ add_device.backing.diskMode = device["backing"]["diskMode"]
+ add_device.backing.eagerlyScrub = device["backing"]["eagerlyScrub"]
+ # add_device.backing.keyId = device["backing"]["keyId"]
+ add_device.backing.sharing = device["backing"]["sharing"]
+ add_device.backing.thinProvisioned = device["backing"]["thinProvisioned"]
+ # add_device.backing.uuid = device["backing"]["uuid"]
+ add_device.backing.writeThrough = device["backing"]["writeThrough"]
+ else:
+ # TODO: unsupported backing type, raise error
+ raise RuntimeError(
+ "Unknown Backing for disk: %s" % (device["backing"]["_vimtype"])
+ )
+ return None
+
+ add_device.storageIOAllocation = vim.StorageResourceManager.IOAllocationInfo()
+ add_device.storageIOAllocation.shares = vim.SharesInfo()
+ add_device.storageIOAllocation.shares.level = device["storageIOAllocation"][
+ "shares"
+ ]["level"]
+ add_device.storageIOAllocation.shares.shares = device["storageIOAllocation"][
+ "shares"
+ ]["shares"]
+ add_device.storageIOAllocation.limit = device["storageIOAllocation"]["limit"]
+ add_device.storageIOAllocation.reservation = device["storageIOAllocation"][
+ "reservation"
+ ]
+
+ add_device.controllerKey = device["controllerKey"] * -1
+ add_device.unitNumber = device["unitNumber"]
+ add_device.capacityInBytes = device["capacityInBytes"]
+
+ return add_device
+
+ def _transform_virtual_ethernet_card(self, device):
+ add_device = None
+ if device["_vimtype"] == "vim.vm.device.VirtualE1000":
+ add_device = vim.vm.device.VirtualE1000()
+ elif device["_vimtype"] == "vim.vm.device.VirtualE1000e":
+ add_device = vim.vm.device.VirtualE1000e()
+ elif device["_vimtype"] == "vim.vm.device.VirtualPCNet32":
+ add_device = vim.vm.device.VirtualPCNet32()
+ elif device["_vimtype"] == "vim.vm.device.VirtualSriovEthernetCard":
+ add_device = vim.vm.device.VirtualSriovEthernetCard()
+ elif device["_vimtype"] == "vim.vm.device.VirtualVmxnet":
+ add_device = vim.vm.device.VirtualVmxnet()
+ elif device["_vimtype"] == "vim.vm.device.VirtualVmxnet2":
+ add_device = vim.vm.device.VirtualVmxnet2()
+ elif device["_vimtype"] == "vim.vm.device.VirtualVmxnet3":
+ add_device = vim.vm.device.VirtualVmxnet3()
+ elif device["_vimtype"] == "vim.vm.device.VirtualVmxnet3Vrdma":
+ add_device = vim.vm.device.VirtualVmxnet3Vrdma()
+ else:
+ # TODO: unknown ethernet card type, raise error
+ raise RuntimeError("Unknown ethernet card type: %s" % (device["_vimtype"]))
+ return None
+
+ if (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualEthernetCard.NetworkBackingInfo"
+ ):
+ add_device.backing = vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ add_device.backing.network = vim.Network(device["backing"]["network"])
+ elif (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo"
+ ):
+ add_device.backing = (
+ vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo()
+ )
+ add_device.backing.port = vim.dvs.PortConnection()
+ add_device.backing.port.portgroupKey = device["backing"]["port"][
+ "portgroupKey"
+ ]
+ add_device.backing.port.switchUuid = device["backing"]["port"]["switchUuid"]
+ elif (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualEthernetCard.OpaqueNetworkBackingInfo"
+ ):
+ add_device.backing = (
+ vim.vm.device.VirtualEthernetCard.OpaqueNetworkBackingInfo()
+ )
+ add_device.backing.opaqueNetworkId = device["backing"]["opaqueNetworkId"]
+ add_device.backing.opaqueNetworkType = device["backing"][
+ "opaqueNetworkType"
+ ]
+ elif (
+ device["backing"]["_vimtype"]
+ == "vim.vm.device.VirtualEthernetCard.LegacyNetworkBackingInfo"
+ ):
+ add_device.backing = (
+ vim.vm.device.VirtualEthernetCard.LegacyNetworkBackingInfo()
+ )
+ add_device.backing.deviceName = device["backing"]["deviceName"]
+ add_device.backing.useAutoDetect = device["backing"]["useAutoDetect"]
+ else:
+ # TODO: unknown backing type, raise error
+ raise RuntimeError(
+ "Unknown ethernet backing type: %s" % (device["backing"]["_vimtype"])
+ )
+ return None
+
+ add_device.key = device["key"] * -1
+ add_device.connectable = self._transform_connectable(device)
+ # TODO: Looks like negative values must not be used for default devices,
+ # the VirtualPCIController seems to have key 100, but is that always the case?
+ add_device.controllerKey = device["controllerKey"]
+ if device["controllerKey"] != 100:
+ add_device.controllerKey = device["controllerKey"] * -1
+ add_device.unitNumber = device["unitNumber"]
+ # Note: MAC address preservation is not safe with addressType "manual", the
+ # server does not check for conflicts. The calling code should check for
+ # MAC address conflicts before and set addressType to "generated" or "assigned"
+ # then the server will detect conflicts and generate a new MAC if needed.
+ if "macAddress" in device:
+ add_device.macAddress = device["macAddress"]
+ add_device.addressType = device["addressType"]
+ add_device.externalId = device["externalId"]
+ add_device.resourceAllocation = (
+ vim.vm.device.VirtualEthernetCard.ResourceAllocation()
+ )
+ add_device.resourceAllocation.limit = device["resourceAllocation"]["limit"]
+ add_device.resourceAllocation.reservation = device["resourceAllocation"][
+ "reservation"
+ ]
+ add_device.resourceAllocation.share = vim.SharesInfo()
+ add_device.resourceAllocation.share.shares = device["resourceAllocation"][
+ "share"
+ ]["shares"]
+ add_device.resourceAllocation.share.level = device["resourceAllocation"][
+ "share"
+ ]["level"]
+ add_device.uptCompatibilityEnabled = device["uptCompatibilityEnabled"]
+ add_device.wakeOnLanEnabled = device["wakeOnLanEnabled"]
+
+ return add_device
+
+ def _transform_extraConfig(self):
+ extra_config = []
+ for option_value in self.config_info["extraConfig"]:
+ extra_config.append(vim.option.OptionValue())
+ extra_config[-1].key = option_value["key"]
+ extra_config[-1].value = option_value["value"]
+
+ return extra_config
+
+ def _transform_files(self):
+ files = vim.vm.FileInfo()
+
+ if self.target_datastore_name:
+ # replace datastore name in all properties
+ for property_name in [
+ "ftMetadataDirectory",
+ "logDirectory",
+ "snapshotDirectory",
+ "suspendDirectory",
+ "vmPathName",
+ ]:
+ if self.config_info["files"].get(property_name):
+ self.config_info["files"][property_name] = self.datastore_rex.sub(
+ "[" + self.target_datastore_name + "]",
+ self.config_info["files"][property_name],
+ count=1,
+ )
+
+ files.ftMetadataDirectory = self.config_info["files"]["ftMetadataDirectory"]
+ files.logDirectory = self.config_info["files"]["logDirectory"]
+ files.snapshotDirectory = self.config_info["files"]["snapshotDirectory"]
+ files.suspendDirectory = self.config_info["files"]["suspendDirectory"]
+ files.vmPathName = self.config_info["files"]["vmPathName"]
+
+ return files
+
+ def _transform_flags(self):
+ flags = vim.vm.FlagInfo()
+ flags.cbrcCacheEnabled = self.config_info["flags"]["cbrcCacheEnabled"]
+ flags.disableAcceleration = self.config_info["flags"]["disableAcceleration"]
+ flags.diskUuidEnabled = self.config_info["flags"]["diskUuidEnabled"]
+ flags.enableLogging = self.config_info["flags"]["enableLogging"]
+ flags.faultToleranceType = self.config_info["flags"]["faultToleranceType"]
+ flags.monitorType = self.config_info["flags"]["monitorType"]
+ flags.snapshotLocked = self.config_info["flags"]["snapshotLocked"]
+ flags.snapshotPowerOffBehavior = self.config_info["flags"][
+ "snapshotPowerOffBehavior"
+ ]
+ flags.useToe = self.config_info["flags"]["useToe"]
+ flags.vbsEnabled = self.config_info["flags"]["vbsEnabled"]
+ flags.virtualExecUsage = self.config_info["flags"]["virtualExecUsage"]
+ flags.virtualMmuUsage = self.config_info["flags"]["virtualMmuUsage"]
+ flags.vvtdEnabled = self.config_info["flags"]["vvtdEnabled"]
+
+ return flags
+
+ def _transform_ftInfo(self):
+ if self.config_info["ftInfo"] is None:
+ return None
+
+ ft_info = vim.vm.FaultToleranceConfigInfo()
+ ft_info.configPaths = []
+ for config_path in self.config_info["ftInfo"]["configPaths"]:
+ ft_info.configPaths.append(config_path)
+ ft_info.instanceUuids = []
+ for instance_uuid in self.config_info["ftInfo"]["instanceUuids"]:
+ ft_info.instanceUuids.append(instance_uuid)
+ ft_info.role = self.config_info["ftInfo"]["role"]
+
+ return ft_info
+
+ def _transform_guestMonitoringModeInfo(self):
+ guest_monitoring_mode_info = vim.vm.GuestMonitoringModeInfo()
+ if self.config_info.get("guestMonitoringModeInfo"):
+ guest_monitoring_mode_info.gmmAppliance = self.config_info[
+ "guestMonitoringModeInfo"
+ ].get("gmmAppliance")
+ guest_monitoring_mode_info.gmmFile = self.config_info[
+ "guestMonitoringModeInfo"
+ ].get("gmmFile")
+
+ return guest_monitoring_mode_info
+
+ def _transform_latencySensitivity(self, latency_sensitivity_config):
+ latency_sensitivity = vim.LatencySensitivity()
+ if latency_sensitivity_config["level"] == "custom":
+ # Note: custom level is deprecrated since 5.5
+ # TODO: raise error
+ return None
+ latency_sensitivity.level = latency_sensitivity_config["level"]
+
+ return latency_sensitivity
+
+ def _transform_managedBy(self):
+ if self.config_info["managedBy"] is None:
+ return None
+
+ managed_by = vim.ext.ManagedByInfo()
+ managed_by.extensionKey = self.config_info["managedBy"]["extensionKey"]
+ managed_by.type = self.config_info["managedBy"]["type"]
+
+ return managed_by
+
+ def _transform_pmem(self):
+ if self.config_info["pmem"] is None:
+ return None
+
+ pmem = vim.vm.VirtualPMem()
+ pmem.snapshotMode = self.config_info["pmem"]["snapshotMode"]
+
+ return pmem
+
+ def _transform_defaultPowerOps(self):
+ power_ops = vim.vm.DefaultPowerOpInfo()
+ power_ops.defaultPowerOffType = self.config_info["defaultPowerOps"][
+ "defaultPowerOffType"
+ ]
+ power_ops.defaultResetType = self.config_info["defaultPowerOps"][
+ "defaultResetType"
+ ]
+ power_ops.defaultSuspendType = self.config_info["defaultPowerOps"][
+ "defaultSuspendType"
+ ]
+ power_ops.powerOffType = self.config_info["defaultPowerOps"]["powerOffType"]
+ power_ops.resetType = self.config_info["defaultPowerOps"]["resetType"]
+ power_ops.standbyAction = self.config_info["defaultPowerOps"]["standbyAction"]
+ power_ops.suspendType = self.config_info["defaultPowerOps"]["suspendType"]
+
+ return power_ops
+
+ def _transform_sgxInfo(self):
+ sgx_info = vim.vm.SgxInfo()
+ sgx_info.epcSize = self.config_info["sgxInfo"]["epcSize"]
+ sgx_info.flcMode = self.config_info["sgxInfo"].get("flcMode")
+ sgx_info.lePubKeyHash = self.config_info["sgxInfo"].get("lePubKeyHash")
+
+ return sgx_info
+
+ def _transform_tools(self):
+ tools_config_info = vim.vm.ToolsConfigInfo()
+ tools_config_info.afterPowerOn = self.config_info["tools"]["afterPowerOn"]
+ tools_config_info.afterResume = self.config_info["tools"]["afterResume"]
+ tools_config_info.beforeGuestReboot = self.config_info["tools"][
+ "beforeGuestReboot"
+ ]
+ tools_config_info.beforeGuestShutdown = self.config_info["tools"][
+ "beforeGuestShutdown"
+ ]
+ tools_config_info.beforeGuestStandby = self.config_info["tools"][
+ "beforeGuestStandby"
+ ]
+ tools_config_info.syncTimeWithHost = self.config_info["tools"][
+ "syncTimeWithHost"
+ ]
+ # Since vSphere API 7.0.1.0
+ if "syncTimeWithHostAllowed" in self.config_info["tools"]:
+ tools_config_info.syncTimeWithHostAllowed = self.config_info["tools"][
+ "syncTimeWithHostAllowed"
+ ]
+ tools_config_info.toolsUpgradePolicy = self.config_info["tools"][
+ "toolsUpgradePolicy"
+ ]
+
+ return tools_config_info
+
+ def _transform_vAppConfig(self):
+ if self.config_info["vAppConfig"] is None:
+ return None
+
+ vapp_config_spec = vim.vApp.VmConfigSpec()
+ vapp_config_spec.eula = self.config_info["vAppConfig"]["eula"]
+ vapp_config_spec.installBootRequired = self.config_info["vAppConfig"][
+ "installBootRequired"
+ ]
+ vapp_config_spec.installBootStopDelay = self.config_info["vAppConfig"][
+ "installBootStopDelay"
+ ]
+ vapp_config_spec.ipAssignment = self._transform_VAppIpAssignmentInfo(
+ self.config_info["vAppConfig"]["ipAssignment"]
+ )
+ vapp_config_spec.ovfEnvironmentTransport = []
+ for transport in self.config_info["vAppConfig"]["ovfEnvironmentTransport"]:
+ vapp_config_spec.ovfEnvironmentTransport.append(transport)
+
+ vapp_config_spec.ovfSection = self._transform_VAppOvfSectionInfo()
+ vapp_config_spec.product = self._transform_VAppProductInfo()
+ vapp_config_spec.property = self._transform_VAppPropertyInfo()
+
+ return vapp_config_spec
+
+ def _transform_VAppIpAssignmentInfo(self, ip_assignment_info):
+
+ ip_assignment = vim.vApp.IPAssignmentInfo()
+ ip_assignment.ipAllocationPolicy = ip_assignment_info["ipAllocationPolicy"]
+ ip_assignment.ipProtocol = ip_assignment_info["ipProtocol"]
+ ip_assignment.supportedAllocationScheme = []
+ for scheme in ip_assignment_info["supportedAllocationScheme"]:
+ ip_assignment.supportedAllocationScheme.append(scheme)
+ ip_assignment.supportedIpProtocol = []
+ for protocol in ip_assignment_info["supportedIpProtocol"]:
+ ip_assignment.supportedIpProtocol.append(protocol)
+
+ return ip_assignment
+
+ def _transform_VAppOvfSectionInfo(self):
+ ovf_section_specs = []
+ for ovf_section_info in self.config_info["vAppConfig"]["ovfSection"]:
+ ovf_section_spec = vim.vApp.OvfSectionSpec()
+ ovf_section_spec.operation = "add"
+ ovf_section_spec.info = vim.vApp.OvfSectionInfo()
+ ovf_section_spec.info.atEnvelopeLevel = ovf_section_info["atEnvelopeLevel"]
+ ovf_section_spec.info.contents = ovf_section_info["contents"]
+ ovf_section_spec.info.key = ovf_section_info["key"]
+ ovf_section_spec.info.namespace = ovf_section_info["namespace"]
+ ovf_section_spec.info.type = ovf_section_info["type"]
+ ovf_section_specs.append(ovf_section_spec)
+
+ return ovf_section_specs
+
+ def _transform_VAppProductInfo(self):
+ product_specs = []
+ for product_info in self.config_info["vAppConfig"]["product"]:
+ product_spec = vim.vApp.ProductSpec()
+ product_spec.operation = "add"
+ product_spec.info = vim.vApp.ProductInfo()
+ product_spec.info.appUrl = product_info["appUrl"]
+ product_spec.info.classId = product_info["classId"]
+ product_spec.info.fullVersion = product_info["fullVersion"]
+ product_spec.info.instanceId = product_info["instanceId"]
+ product_spec.info.key = product_info["key"]
+ product_spec.info.name = product_info["name"]
+ product_spec.info.productUrl = product_info["productUrl"]
+ product_spec.info.vendor = product_info["vendor"]
+ product_spec.info.vendorUrl = product_info["vendorUrl"]
+ product_spec.info.version = product_info["version"]
+ product_specs.append(product_spec)
+
+ return product_specs
+
+ def _transform_VAppPropertyInfo(self):
+ property_specs = []
+ for property_info in self.config_info["vAppConfig"]["property"]:
+ property_spec = vim.vApp.PropertySpec()
+ property_spec.operation = "add"
+ property_spec.info = vim.vApp.PropertyInfo()
+ property_spec.info.category = property_info["category"]
+ property_spec.info.classId = property_info["classId"]
+ property_spec.info.defaultValue = property_info["defaultValue"]
+ property_spec.info.description = property_info["description"]
+ property_spec.info.id = property_info["id"]
+ property_spec.info.instanceId = property_info["instanceId"]
+ property_spec.info.key = property_info["key"]
+ property_spec.info.label = property_info["label"]
+ property_spec.info.type = property_info["type"]
+ property_spec.info.typeReference = property_info["typeReference"]
+ property_spec.info.userConfigurable = property_info["userConfigurable"]
+ property_spec.info.value = property_info["value"]
+ property_specs.append(property_spec)
+
+ return property_specs
+
+ def _transform_VirtualMachineVcpuConfig(self):
+ spec_vcpu_configs = []
+ for vcpu_config in self.config_info["vcpuConfig"]:
+ spec_vcpu_config = vim.vm.VcpuConfig()
+ spec_vcpu_config.latencySensitivity = self._transform_latencySensitivity(
+ vcpu_config["latencySensitivity"]
+ )
+ spec_vcpu_configs.append(spec_vcpu_config)
+
+ return spec_vcpu_configs
+
+
# vim: tabstop=4 shiftwidth=4 softtabstop=4 expandtab