I hope I did manage to apply all the listed changes. Thanks, Agata
Signed-off-by: Agata Murawska <[email protected]> --- lib/ovf.py | 300 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 298 insertions(+), 2 deletions(-) diff --git a/lib/ovf.py b/lib/ovf.py index e12bb65..22c4941 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. @@ -243,6 +306,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): @@ -328,10 +399,235 @@ class OVFImporter(Converter): (OVA_EXT, err)) 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 + + """ + 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) + utils.Makedirs(self.output_dir) + + 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 + + """ + 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 _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 + + """ + 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 + + """ + 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 + + """ + 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") + if disk_compression: + raise NotImplementedError() + disk_path = utils.PathJoin(self.input_dir, disk_name) + (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. -- 1.7.3.1
