Signed-off-by: Agata Murawska <[email protected]>
---
 lib/ovf.py |  351 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 350 insertions(+), 1 deletions(-)

diff --git a/lib/ovf.py b/lib/ovf.py
index 7c35ac3..33a606f 100644
--- a/lib/ovf.py
+++ b/lib/ovf.py
@@ -57,12 +57,18 @@ OVA_EXT = ".ova"
 OVF_EXT = ".ovf"
 MF_EXT = ".mf"
 CERT_EXT = ".cert"
+COMPRESSION_EXT = ".gz"
 FILE_EXTENSIONS = [
   OVF_EXT,
   MF_EXT,
   CERT_EXT,
 ]
 
+COMPRESSION_TYPE = "gzip"
+COMPRESS = "compression"
+DECOMPRESS = "decompression"
+ALLOWED_ACTIONS = [COMPRESS, DECOMPRESS]
+
 
 class OVFReader(object):
   """Reader class for OVF files.
@@ -139,6 +145,50 @@ class OVFReader(object):
     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: ET.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
+
+  @staticmethod
+  def _GetDictParameters(root, schema):
+    """Reads text in all children and creates the dictionary from the contents.
+
+    @type root: ET.ElementTree or None
+    @param root: father of the nodes we want to collect data about
+    @type schema: string
+    @param schema: schema name to be removed from the tag
+    @rtype: dict
+    @return: dictionary containing tags and their text contents, tags have 
their
+      schema fragment removed or empty dictionary, when root is None
+
+    """
+    if not root:
+      return {}
+    results = {}
+    for element in list(root):
+      pref_len = len("{%s}" % schema)
+      assert(schema in element.tag)
+      tag = element.tag[pref_len:]
+      results[tag] = element.text
+    return results
+
   def VerifyManifest(self):
     """Verifies manifest for the OVF package, if one is given.
 
@@ -167,6 +217,48 @@ class OVFReader(object):
                                      " value in manifest file" % file_name)
       logging.info("SHA1 checksums verified")
 
+  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.
@@ -210,6 +302,103 @@ class Converter(object):
     """
     raise NotImplementedError()
 
+  def _CompressDisk(self, disk_path, compression, action, skip_removing=False):
+    """Performs (de)compression on the disk and returns the new path
+
+    @type disk_path: string
+    @param disk_path: path to the disk
+    @type compression: string
+    @param compression: compression type
+    @type action: string
+    @param action: whether the action is compression or decompression
+    @type skip_removing: bool
+    @param skip_removing: for export we want to allow skipping removal in order
+      to not delete the original disk
+    @rtype: string
+    @return: new disk path after (de)compression
+
+    @raise errors.OpPrereqError: disk (de)compression failed
+
+    """
+    assert(action in ALLOWED_ACTIONS)
+    # For now we only support gzip, as it is used in ovftool
+    if compression != COMPRESSION_TYPE:
+      raise errors.OpPrereqError("Unsupported compression type: %s"
+                                 % compression)
+    (disk_dir, disk_file) = os.path.split(disk_path)
+    if action == DECOMPRESS:
+      (disk_name, _) = os.path.splitext(disk_file)
+      new_path = utils.PathJoin(disk_dir, disk_name)
+      remove_path = new_path
+    elif action == COMPRESS:
+      new_path = utils.PathJoin(disk_dir, "%s%s" % (disk_file, 
COMPRESSION_EXT))
+      remove_path = disk_path
+    if not skip_removing:
+      self.temp_compress.append(remove_path)
+    args = ["gzip", "-c", disk_path]
+    run_result = utils.RunCmd(args, output=new_path)
+    if run_result.failed:
+      raise errors.OpPrereqError("Disk %s failed with output: %s"
+                                 % (action, run_result.stderr))
+    logging.info("The %s of the disk is completed", action)
+    return new_path
+
+  @staticmethod
+  def _ConvertDisk(disk_format, disk_path, new_disk_path):
+    """Performes conversion to specified format.
+
+    @type disk_format: string
+    @param disk_format: format to which the disk should be converted
+    @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",
+      disk_format,
+      disk_path,
+      new_disk_path,
+    ]
+    run_result = utils.RunCmd(args, cwd=os.getcwd())
+    if run_result.failed:
+      raise errors.OpPrereqError("Convertion to %s failed, qemu-img output was"
+                                 ": %s" % (disk_format, run_result.stderr))
+
+  @staticmethod
+  def _GetDiskQemuInfo(disk_path, regexp):
+    """Figures out some information of the disk using qemu-img.
+
+    @type disk_path: string
+    @param disk_path: path to the disk we want to know the format of
+    @type regexp: string
+    @param regexp: string that has to be matched, it has to contain one group
+    @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: %s" % run_result.stderr)
+    result = run_result.output
+    regexp = r"%s" % regexp
+    match = re.search(regexp, result)
+    if match:
+      disk_format = match.group(1)
+    else:
+      raise errors.OpPrereqError("No file information matching %s found in:"
+                                 " %s" % (regexp, result))
+    return disk_format
+
   def Parse(self):
     """Parses the data and creates a structure containing all required info.
 
@@ -246,6 +435,14 @@ class OVFImporter(Converter):
   @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):
@@ -337,7 +534,159 @@ 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: %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)
+
+  @staticmethod
+  def _GetInfo(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:"
+                                     " %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 _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()
+    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._CompressDisk(disk_path, disk_compression,
+          DECOMPRESS)
+        (_, 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._GetDiskQemuInfo(disk_path, "file format: (\S+)") != "raw":
+        logging.info("Conversion to raw format is required")
+        logging.warning("Conversion of disk image to raw format, this may take"
+                        " a while")
+      self._ConvertDisk("raw", 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):
     """Saves all the gathered information in a constant.EXPORT_CONF_FILE file.
-- 
1.7.3.1

Reply via email to