diff --git a/lib/ovf.py b/lib/ovf.py index 36e3e2de500570d36b3d60820f9b31ae39f0b884..20f539f2d645f62f81dd2cd0f52d848b387523af 100644 --- a/lib/ovf.py +++ b/lib/ovf.py @@ -208,6 +208,27 @@ class OVFReader(object): return elem return None + def _GetElementMatchingText(self, path, match_text): + """Searches for element on a path that matches certain text value. + + Function follows the path from root node to the desired tags using path, + then searches for the first one matching the text value. + + @type path: string + @param path: path of nodes to visit + @type match_text: tuple + @param match_text: pair (node, text) for which we search + @rtype: ET.ElementTree or None + @return: first element matching match_text or None if nothing matches + + """ + potential_elements = self.tree.findall(path) + (node, text) = match_text + for elem in potential_elements: + if elem.findtext(node) == text: + return elem + return None + @staticmethod def _GetDictParameters(root, schema): """Reads text in all children and creates the dictionary from the contents. @@ -279,6 +300,76 @@ class OVFReader(object): (GANETI_SCHEMA, GANETI_SCHEMA)) return self.tree.findtext(find_template) + def GetNetworkData(self): + """Provides data about the network in the OVF instance. + + The method gathers the data about networks used by OVF instance. It assumes + that 'name' tag means something - in essence, if it contains one of the + words 'bridged' or 'routed' then that will be the mode of this network in + Ganeti. The information about the network can be either in GanetiSection or + VirtualHardwareSection. + + @rtype: dict + @return: dictionary containing all the network information + + """ + results = {} + networks_search = ("{%s}NetworkSection/{%s}Network" % + (OVF_SCHEMA, OVF_SCHEMA)) + network_names = self._GetAttributes(networks_search, + "{%s}name" % OVF_SCHEMA) + required = ["ip", "mac", "link", "mode"] + for (counter, network_name) in enumerate(network_names): + network_search = ("{%s}VirtualSystem/{%s}VirtualHardwareSection/{%s}Item" + % (OVF_SCHEMA, OVF_SCHEMA, OVF_SCHEMA)) + ganeti_search = ("{%s}GanetiSection/{%s}Network/{%s}Nic" % + (GANETI_SCHEMA, GANETI_SCHEMA, GANETI_SCHEMA)) + network_match = ("{%s}Connection" % RASD_SCHEMA, network_name) + ganeti_match = ("{%s}name" % OVF_SCHEMA, network_name) + network_data = self._GetElementMatchingText(network_search, network_match) + network_ganeti_data = self._GetElementMatchingAttr(ganeti_search, + ganeti_match) + + ganeti_data = {} + if network_ganeti_data: + ganeti_data["mode"] = network_ganeti_data.findtext("{%s}Mode" % + GANETI_SCHEMA) + ganeti_data["mac"] = network_ganeti_data.findtext("{%s}MACAddress" % + GANETI_SCHEMA) + ganeti_data["ip"] = network_ganeti_data.findtext("{%s}IPAddress" % + GANETI_SCHEMA) + ganeti_data["link"] = network_ganeti_data.findtext("{%s}Link" % + GANETI_SCHEMA) + mac_data = None + if network_data: + mac_data = network_data.findtext("{%s}Address" % RASD_SCHEMA) + + network_name = network_name.lower() + + # First, some not Ganeti-specific information is collected + if constants.NIC_MODE_BRIDGED in network_name: + results["nic%s_mode" % counter] = "bridged" + elif constants.NIC_MODE_ROUTED in network_name: + results["nic%s_mode" % counter] = "routed" + results["nic%s_mac" % counter] = mac_data + + # GanetiSection data overrides 'manually' collected data + for name, value in ganeti_data.iteritems(): + results["nic%s_%s" % (counter, name)] = value + + # Bridged network has no IP - unless specifically stated otherwise + if (results.get("nic%s_mode" % counter) == "bridged" and + not results.get("nic%s_ip" % counter)): + results["nic%s_ip" % counter] = constants.VALUE_NONE + + for option in required: + if not results.get("nic%s_%s" % (counter, option)): + results["nic%s_%s" % (counter, option)] = constants.VALUE_AUTO + + if network_names: + results["nic_count"] = str(len(network_names)) + return results + def GetDisksNames(self): """Provides list of file names for the disks used by the instance. @@ -491,6 +582,9 @@ class OVFImporter(Converter): @type results_template: string @ivar results_template: disk template read from .ovf file or command line arguments + @type results_network: dict + @ivar results_network: network information gathered from .ovf file or command + line arguments @type results_disk: dict @ivar results_disk: disk information gathered from .ovf file or command line arguments @@ -612,10 +706,18 @@ class OVFImporter(Converter): if not self.results_template: logging.info("Disk template not given") + self.results_network = self._GetInfo("network", self.options.nics, + self._ParseNicOptions, self.ovf_reader.GetNetworkData, + ignore_test=self.options.no_nics) + self.results_disk = self._GetInfo("disk", self.options.disks, self._ParseDiskOptions, self._GetDiskInfo, ignore_test=self.results_template == constants.DT_DISKLESS) + if not self.results_disk and not self.results_network: + raise errors.OpPrereqError("Either disk specification or network" + " description must be present") + @staticmethod def _GetInfo(name, cmd_arg, cmd_function, nocmd_function, ignore_test=False): @@ -662,6 +764,28 @@ class OVFImporter(Converter): """ return self.options.disk_template + def _ParseNicOptions(self): + """Parses network options given in a command line or as a dictionary. + + @rtype: dict + @return: dictionary of network-related options + + """ + assert self.options.nics + results = {} + for (nic_id, nic_desc) in self.options.nics: + results["nic%s_mode" % nic_id] = \ + nic_desc.get("mode", constants.VALUE_AUTO) + results["nic%s_mac" % nic_id] = nic_desc.get("mac", constants.VALUE_AUTO) + results["nic%s_link" % nic_id] = \ + nic_desc.get("link", constants.VALUE_AUTO) + if nic_desc.get("mode") == "bridged": + results["nic%s_ip" % nic_id] = constants.VALUE_NONE + else: + results["nic%s_ip" % nic_id] = constants.VALUE_AUTO + results["nic_count"] = str(len(self.options.nics)) + return results + def _ParseDiskOptions(self): """Parses disk options given in a command line. @@ -756,6 +880,7 @@ class OVFImporter(Converter): } results[constants.INISECT_INS].update(self.results_disk) + results[constants.INISECT_INS].update(self.results_network) results[constants.INISECT_INS]["name"] = self.results_name if self.results_template: results[constants.INISECT_INS]["disk_template"] = self.results_template