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

Reply via email to