Signed-off-by: Agata Murawska <[email protected]>
---
lib/ovf.py | 345 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 343 insertions(+), 2 deletions(-)
diff --git a/lib/ovf.py b/lib/ovf.py
index 7c22946..1c4233a 100644
--- a/lib/ovf.py
+++ b/lib/ovf.py
@@ -137,6 +137,27 @@ class OVFReader:
results = [x.get(attribute) for x in current_list]
return filter(None, results)
+ def _GetElementMatchingAttr(self, path, match_attr):
+ """Searches for element on a path that matches certain attribute value.
+
+ Function follows the path from root node to the desired tags using path,
+ then searches for the first one matching the attribute value.
+
+ @type path: string
+ @param path: path of nodes to visit
+ @type match_attr: tuple
+ @param match_attr: pair (attribute, value) for which we search
+ @rtype: L{ElementTree} or None
+ @return: first element matching match_attr or None if nothing matches
+
+ """
+ potential_elements = self.tree.findall(path)
+ (attr, val) = match_attr
+ for elem in potential_elements:
+ if elem.get(attr) == val:
+ return elem
+ return None
+
def VerifyManifest(self):
"""Verifies manifest for the OVF package, if one is given.
@@ -172,6 +193,48 @@ class OVFReader:
if "%s%s" % (self.schema_name, CERT_EXT) in self.files_list:
raise NotImplementedError()
+ def GetInstanceName(self):
+ """Provides information about instance name.
+
+ @rtype: string
+ @return: instance name string
+
+ """
+ find_name = "{%s}VirtualSystem/{%s}Name" % (OVF_SCHEMA, OVF_SCHEMA)
+ return self.tree.findtext(find_name)
+
+ def GetDiskTemplate(self):
+ """Returns disk template from .ovf file
+
+ @rtype: string or None
+ @return: name of the template
+ """
+ find_template = ("{%s}GanetiSection/{%s}DiskTemplate" %
+ (GANETI_SCHEMA, GANETI_SCHEMA))
+ return self.tree.findtext(find_template)
+
+ def GetDisksNames(self):
+ """Provides list of file names for the disks used by the instance.
+
+ @rtype: list
+ @return: list of file names, as referenced in .ovf file
+
+ """
+ results = []
+ disks_search = "{%s}DiskSection/{%s}Disk" % (OVF_SCHEMA, OVF_SCHEMA)
+ disk_ids = self._GetAttributes(disks_search, "{%s}fileRef" % OVF_SCHEMA)
+ for disk in disk_ids:
+ disk_search = "{%s}References/{%s}File" % (OVF_SCHEMA, OVF_SCHEMA)
+ disk_match = ("{%s}id" % OVF_SCHEMA, disk)
+ disk_elem = self._GetElementMatchingAttr(disk_search, disk_match)
+ if disk_elem is None:
+ raise errors.OpPrereqError("%s file corrupted - disk %s not found in"
+ " references" % (OVF_EXT, disk))
+ disk_name = disk_elem.get("{%s}href" % OVF_SCHEMA)
+ disk_compression = disk_elem.get("{%s}compression" % OVF_SCHEMA)
+ results.append((disk_name, disk_compression))
+ return results
+
class Converter(object):
"""Converter class for OVF packages.
@@ -239,10 +302,20 @@ class OVFImporter(Converter):
@type temp_dir: string or None
@ivar temp_dir: if the input was .ova archive, this variable points to the
temporary directory to which the OVA package was unpacked
+ @type temp_decompress: list
+ @ivar temp_decompress: list of disk files that were decompressed
@type input_path: string
@ivar input_path: complete path to the .ovf file
@type ovf_reader: L{OVFReader}
@ivar ovf_reader: OVF reader instance collects data from .ovf file
+ @type results_name: string
+ @ivar results_name: name of imported instance
+ @type results_template: string
+ @ivar results_template: disk template read from .ovf file or command line
+ arguments
+ @type results_disk: dict
+ @ivar results_disk: disk information gathered from .ovf file or command line
+ arguments
"""
def _ReadInputData(self, input_path):
@@ -335,10 +408,275 @@ class OVFImporter(Converter):
logging.info("OVA package extracted to %s directory", self.temp_dir)
def Parse(self):
- pass
+ """Parses the data and creates a structure containing all required info.
+
+ The method reads the information given either as a command line option or
as
+ a part of the OVF description.
+
+ @raise errors.OpPrereqError: if some required part of the description of
+ virtual instance is missing or unable to create output directory
+
+ """
+ self.results_name = self._GetInfo("instance name", self.options.name,
+ self._ParseNameOptions, self.ovf_reader.GetInstanceName)
+ if not self.results_name:
+ raise errors.OpPrereqError("Name of instance not provided")
+
+ self.output_dir = utils.PathJoin(self.output_dir, self.results_name)
+ try:
+ utils.Makedirs(self.output_dir)
+ except OSError, err:
+ raise errors.OpPrereqError("Failed to create directory %s:\n\t%s" %
+ (self.output_dir, err))
+
+ self.results_template = self._GetInfo("disk template",
+ self.options.disk_template, self._ParseTemplateOptions,
+ self.ovf_reader.GetDiskTemplate)
+ if not self.results_template:
+ logging.info("Disk template not given")
+
+ self.results_disk = self._GetInfo("disk", self.options.disks,
+ self._ParseDiskOptions, self._GetDiskInfo,
+ ignore_test=self.results_template == constants.DT_DISKLESS)
+
+ def _GetInfo(self, name, cmd_arg, cmd_function, nocmd_function,
+ ignore_test=False):
+ """Get information about some section - e.g. disk, network, hypervisor.
+
+ @type name: string
+ @param name: name of the section
+ @type cmd_arg: dict
+ @param cmd_arg: command line argument specific for section 'name'
+ @type cmd_function: callable
+ @param cmd_function: function to call if 'cmd_args' exists
+ @type nocmd_function: callable
+ @param nocmd_function: function to call if 'cmd_args' is not there
+
+ """
+ if ignore_test:
+ logging.info("Information for %s will be ignored", name)
+ return {}
+ if cmd_arg:
+ logging.info("Information for %s will be parsed from command line", name)
+ results = cmd_function()
+ else:
+ logging.info("Information for %s will be parsed from %s file",
+ name, OVF_EXT)
+ results = nocmd_function()
+ logging.info("Options for %s were succesfully read", name)
+ return results
+
+ def _ParseNameOptions(self):
+ """Returns name if one was given in command line.
+
+ @rtype: string
+ @return: name of an instance
+
+ """
+ return self.options.name
+
+ def _ParseTemplateOptions(self):
+ """Returns disk template if one was given in command line.
+
+ @rtype: string
+ @return: disk template name
+
+ """
+ return self.options.disk_template
+
+ def _ParseDiskOptions(self):
+ """Parses disk options given in a command line.
+
+ @rtype: dict
+ @return: dictionary of disk-related options
+
+ @raise errors.OpPrereqError: disk description does not contain size
+ information or size information is invalid or creation failed
+
+ """
+ assert self.options.disks
+ results = {}
+ for (disk_id, disk_desc) in self.options.disks:
+ results["disk%s_ivname" % disk_id] = "disk/%s" % disk_id
+ if disk_desc.get("size"):
+ try:
+ disk_size = utils.ParseUnit(disk_desc["size"])
+ except ValueError:
+ raise errors.OpPrereqError("Invalid disk size for disk %s: %s" %
+ (disk_id, disk_desc["size"]))
+ new_path = utils.PathJoin(self.output_dir, str(disk_id))
+ args = [
+ "qemu-img",
+ "create",
+ "-f",
+ "raw",
+ new_path,
+ disk_size,
+ ]
+ run_result = utils.RunCmd(args)
+ if run_result.failed:
+ raise errors.OpPrereqError("Creation of disk %s failed, output was:"
+ "\n\t%s" % (new_path, run_result.stderr))
+ results["disk%s_size" % disk_id] = str(disk_size)
+ results["disk%s_dump" % disk_id] = "disk%s.raw" % disk_id
+ else:
+ raise errors.OpPrereqError("Disks created for import must have their"
+ " size specified")
+ results["disk_count"] = str(len(self.options.disks))
+ return results
+
+ def _DecompressDisk(self, disk_path, compression):
+ """Performs decompression on the disk and returns the new path
+
+ @type disk_path: string
+ @param disk_path: path to the compressed disk
+ @type compression: string
+ @param compression: compression type
+ @rtype: string
+ @return: new disk path after decompression
+
+ @raise errors.OpPrereqError: disk decompression failed
+
+ """
+ # For now we only support gzip, as it is used in ovftool
+ if compression != "gzip":
+ raise errors.OpPrereqError("Unsupported compression: %s" % compression)
+ (disk_dir, disk_file) = os.path.split(disk_path)
+ (disk_name, _) = os.path.splitext(disk_file)
+ new_path = utils.PathJoin(disk_dir, disk_name)
+ args = ["gzip", "-c", disk_path]
+ run_result = utils.RunCmd(args, output=new_path)
+ if run_result.failed:
+ raise errors.OpPrereqError("Disk decompression failed with output:"
+ "\n\t%s" % run_result.stderr)
+ self.temp_decompress.append(new_path)
+ logging.info("Decompression of the disk completed")
+ return new_path
+
+ def _GetDiskFormat(self, disk_path):
+ """Figures out the format of the disk using qemu-img.
+
+ @type disk_path: string
+ @param disk_path: path to the disk we want to know the format of
+ @rtype: string
+ @return: disk format
+
+ @raise errors.OpPrereqError: format information cannot be retrieved
+
+ """
+ args = ["qemu-img", "info", disk_path]
+ run_result = utils.RunCmd(args, cwd=os.getcwd())
+ if run_result.failed:
+ raise errors.OpPrereqError("Gathering info about the disk using qemu-img"
+ " failed, output was:\n\t%s" %
+ run_result.stderr)
+ result = run_result.output
+ regexp = r"file format: (\S+)"
+ match = re.search(regexp, result)
+ if match:
+ disk_format = match.group(1)
+ else:
+ raise errors.OpPrereqError("No file format information found in:\n%s" %
+ result)
+ # TODO: checking if disk_format is supported by qemu-img?
+ return disk_format
+
+ def _ConvertDiskToRaw(self, disk_path, new_disk_path):
+ """Performes conversion to raw format.
+
+ @type disk_path: string
+ @param disk_path: path to the disk that should be converted
+ @type new_disk_path: string
+ @param new_disk_path: path to the output disk
+
+ @raise errors.OpPrereqError: convertion of the disk failed
+
+ """
+ args = [
+ "qemu-img",
+ "convert",
+ "-O",
+ "raw",
+ disk_path,
+ new_disk_path,
+ ]
+ run_result = utils.RunCmd(args, cwd=os.getcwd())
+ if run_result.failed:
+ raise errors.OpPrereqError("Convertion to raw failed, qemu-img output
was"
+ ":\n\t%s" % run_result.stderr)
+
+ def _GetDiskInfo(self):
+ """Gathers information about disks used by instance, perfomes conversion.
+
+ @rtype: dict
+ @return: dictionary of disk-related options
+
+ @raise errors.OpPrereqError: disk is not in the same directory as .ovf file
+
+ """
+ results = {}
+ disks_list = self.ovf_reader.GetDisksNames()
+ self.temp_decompress = []
+ for (counter, (disk_name, disk_compression)) in enumerate(disks_list):
+ if os.path.dirname(disk_name):
+ raise errors.OpPrereqError("Disks are not allowed to have absolute"
+ " paths or paths outside main OVF
directory")
+ disk_path = utils.PathJoin(self.input_dir, disk_name)
+ if disk_compression:
+ disk_path = self._DecompressDisk(disk_path, disk_compression)
+ (_, disk_name) = os.path.split(disk_path)
+ (name, _) = os.path.splitext(disk_name)
+ new_name = "%s.raw" % name
+ new_disk_path = utils.PathJoin(self.output_dir, new_name)
+ if self._GetDiskFormat(disk_path) != "raw":
+ logging.info("Conversion to raw format is required")
+ logging.warning("Conversion of disk image to raw format, this may take"
+ " a while")
+ self._ConvertDiskToRaw(disk_path, new_disk_path)
+ disk_size = os.path.getsize(new_disk_path) / (1024 * 1024)
+ results["disk%s_dump" % counter] = new_name
+ results["disk%s_size" % counter] = str(disk_size)
+ results["disk%s_ivname" % counter] = "disk/%s" % str(counter)
+ if disks_list:
+ results["disk_count"] = str(len(disks_list))
+ return results
def Save(self):
- pass
+ """Saves all the gathered information in a constant.EXPORT_CONF_FILE file.
+
+ """
+ logging.info("Conversion was succesfull, saving %s in %s directory",
+ constants.EXPORT_CONF_FILE, self.output_dir)
+ results = {
+ constants.INISECT_INS: {},
+ constants.INISECT_BEP: {},
+ constants.INISECT_EXP: {},
+ constants.INISECT_OSP: {},
+ constants.INISECT_HYP: {},
+ }
+
+ results[constants.INISECT_INS].update(self.results_disk)
+ results[constants.INISECT_INS]["name"] = self.results_name
+ if self.results_template:
+ results[constants.INISECT_INS]["disk_template"] = self.results_template
+
+ output_file_name = utils.PathJoin(self.output_dir,
+ constants.EXPORT_CONF_FILE)
+
+ output = []
+ for section, options in results.iteritems():
+ output.append("[%s]\n" % section)
+ for name, value in options.iteritems():
+ output.append("%s = %s\n" % (name, value))
+ output.append("\n")
+ output_contents = "".join(output)
+
+ try:
+ utils.WriteFile(output_file_name, data=output_contents)
+ except errors.ProgrammerError, err:
+ raise errors.OpPrereqError("Saving the config file failed:\n\t%s" % err)
+
+ self.Cleanup()
def Cleanup(self):
"""Cleanes the temporary directory, if one was created.
@@ -347,6 +685,9 @@ class OVFImporter(Converter):
if self.temp_dir:
shutil.rmtree(self.temp_dir)
self.temp_dir = None
+ else:
+ for file_name in self.temp_decompress:
+ utils.RemoveFile(file_name)
class OVFExporter(Converter):
--
1.7.3.1