Revision 0963b26a
b/lib/ovf.py | ||
---|---|---|
29 | 29 |
# E1101 makes no sense - pylint assumes that ElementTree object is a tuple |
30 | 30 |
|
31 | 31 |
|
32 |
import ConfigParser |
|
32 | 33 |
import errno |
33 | 34 |
import logging |
34 | 35 |
import os |
... | ... | |
37 | 38 |
import shutil |
38 | 39 |
import tarfile |
39 | 40 |
import tempfile |
41 |
import xml.dom.minidom |
|
40 | 42 |
import xml.parsers.expat |
41 | 43 |
try: |
42 | 44 |
import xml.etree.ElementTree as ET |
... | ... | |
53 | 55 |
OVF_SCHEMA = "http://schemas.dmtf.org/ovf/envelope/1" |
54 | 56 |
RASD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/" |
55 | 57 |
"CIM_ResourceAllocationSettingData") |
58 |
VSSD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/" |
|
59 |
"CIM_VirtualSystemSettingData") |
|
60 |
XML_SCHEMA = "http://www.w3.org/2001/XMLSchema-instance" |
|
56 | 61 |
|
57 | 62 |
# File extensions in OVF package |
58 | 63 |
OVA_EXT = ".ova" |
... | ... | |
91 | 96 |
'gb': lambda x: x * 1024, |
92 | 97 |
} |
93 | 98 |
|
99 |
# Names of the config fields |
|
100 |
NAME = "name" |
|
101 |
|
|
94 | 102 |
|
95 | 103 |
def LinkFile(old_path, prefix=None, suffix=None, directory=None): |
96 | 104 |
"""Create link with a given prefix and suffix. |
... | ... | |
523 | 531 |
return results |
524 | 532 |
|
525 | 533 |
|
534 |
class OVFWriter(object): |
|
535 |
"""Writer class for OVF files. |
|
536 |
|
|
537 |
@type tree: ET.ElementTree |
|
538 |
@ivar tree: XML tree that we are constructing |
|
539 |
|
|
540 |
""" |
|
541 |
def __init__(self, has_gnt_section): |
|
542 |
"""Initialize the writer - set the top element. |
|
543 |
|
|
544 |
@type has_gnt_section: bool |
|
545 |
@param has_gnt_section: if the Ganeti schema should be added - i.e. this |
|
546 |
means that Ganeti section will be present |
|
547 |
|
|
548 |
""" |
|
549 |
env_attribs = { |
|
550 |
"xmlns:xsi": XML_SCHEMA, |
|
551 |
"xmlns:vssd": VSSD_SCHEMA, |
|
552 |
"xmlns:rasd": RASD_SCHEMA, |
|
553 |
"xmlns:ovf": OVF_SCHEMA, |
|
554 |
"xmlns": OVF_SCHEMA, |
|
555 |
"xml:lang": "en-US", |
|
556 |
} |
|
557 |
if has_gnt_section: |
|
558 |
env_attribs["xmlns:gnt"] = GANETI_SCHEMA |
|
559 |
self.tree = ET.Element("Envelope", attrib=env_attribs) |
|
560 |
|
|
561 |
def PrettyXmlDump(self): |
|
562 |
"""Formatter of the XML file. |
|
563 |
|
|
564 |
@rtype: string |
|
565 |
@return: XML tree in the form of nicely-formatted string |
|
566 |
|
|
567 |
""" |
|
568 |
raw_string = ET.tostring(self.tree) |
|
569 |
parsed_xml = xml.dom.minidom.parseString(raw_string) |
|
570 |
xml_string = parsed_xml.toprettyxml(indent=" ") |
|
571 |
text_re = re.compile(">\n\s+([^<>\s].*?)\n\s+</", re.DOTALL) |
|
572 |
return text_re.sub(">\g<1></", xml_string) |
|
573 |
|
|
574 |
|
|
526 | 575 |
class Converter(object): |
527 | 576 |
"""Converter class for OVF packages. |
528 | 577 |
|
... | ... | |
1147 | 1196 |
self.Cleanup() |
1148 | 1197 |
|
1149 | 1198 |
|
1199 |
class ConfigParserWithDefaults(ConfigParser.SafeConfigParser): |
|
1200 |
"""This is just a wrapper on SafeConfigParser, that uses default values |
|
1201 |
|
|
1202 |
""" |
|
1203 |
def get(self, section, options, raw=None, vars=None): # pylint: disable=W0622 |
|
1204 |
try: |
|
1205 |
result = ConfigParser.SafeConfigParser.get(self, section, options, \ |
|
1206 |
raw=raw, vars=vars) |
|
1207 |
except ConfigParser.NoOptionError: |
|
1208 |
result = None |
|
1209 |
return result |
|
1210 |
|
|
1211 |
def getint(self, section, options): |
|
1212 |
try: |
|
1213 |
result = ConfigParser.SafeConfigParser.get(self, section, options) |
|
1214 |
except ConfigParser.NoOptionError: |
|
1215 |
result = 0 |
|
1216 |
return int(result) |
|
1217 |
|
|
1218 |
|
|
1150 | 1219 |
class OVFExporter(Converter): |
1220 |
"""Converter from Ganeti config file to OVF |
|
1221 |
|
|
1222 |
@type input_dir: string |
|
1223 |
@ivar input_dir: directory in which the config.ini file resides |
|
1224 |
@type output_dir: string |
|
1225 |
@ivar output_dir: directory to which the results of conversion shall be |
|
1226 |
written |
|
1227 |
@type packed_dir: string |
|
1228 |
@ivar packed_dir: if we want OVA package, this points to the real (i.e. not |
|
1229 |
temp) output directory |
|
1230 |
@type input_path: string |
|
1231 |
@ivar input_path: complete path to the config.ini file |
|
1232 |
@type output_path: string |
|
1233 |
@ivar output_path: complete path to .ovf file |
|
1234 |
@type config_parser: L{ConfigParserWithDefaults} |
|
1235 |
@ivar config_parser: parser for the config.ini file |
|
1236 |
@type results_name: string |
|
1237 |
@ivar results_name: name of the instance |
|
1238 |
|
|
1239 |
""" |
|
1151 | 1240 |
def _ReadInputData(self, input_path): |
1152 |
pass |
|
1241 |
"""Reads the data on which the conversion will take place. |
|
1242 |
|
|
1243 |
@type input_path: string |
|
1244 |
@param input_path: absolute path to the config.ini input file |
|
1245 |
|
|
1246 |
@raise errors.OpPrereqError: error when reading the config file |
|
1247 |
|
|
1248 |
""" |
|
1249 |
input_dir = os.path.dirname(input_path) |
|
1250 |
self.input_path = input_path |
|
1251 |
self.input_dir = input_dir |
|
1252 |
if self.options.output_dir: |
|
1253 |
self.output_dir = os.path.abspath(self.options.output_dir) |
|
1254 |
else: |
|
1255 |
self.output_dir = input_dir |
|
1256 |
self.config_parser = ConfigParserWithDefaults() |
|
1257 |
logging.info("Reading configuration from %s file", input_path) |
|
1258 |
try: |
|
1259 |
self.config_parser.read(input_path) |
|
1260 |
except ConfigParser.MissingSectionHeaderError, err: |
|
1261 |
raise errors.OpPrereqError("Error when trying to read %s: %s" % |
|
1262 |
(input_path, err)) |
|
1263 |
if self.options.ova_package: |
|
1264 |
self.temp_dir = tempfile.mkdtemp() |
|
1265 |
self.packed_dir = self.output_dir |
|
1266 |
self.output_dir = self.temp_dir |
|
1267 |
|
|
1268 |
self.ovf_writer = OVFWriter(not self.options.ext_usage) |
|
1269 |
|
|
1270 |
def _ParseName(self): |
|
1271 |
"""Parses name from command line options or config file. |
|
1272 |
|
|
1273 |
@rtype: string |
|
1274 |
@return: name of Ganeti instance |
|
1275 |
|
|
1276 |
@raise errors.OpPrereqError: if name of the instance is not provided |
|
1277 |
|
|
1278 |
""" |
|
1279 |
if self.options.name: |
|
1280 |
name = self.options.name |
|
1281 |
else: |
|
1282 |
name = self.config_parser.get(constants.INISECT_INS, NAME) |
|
1283 |
if name is None: |
|
1284 |
raise errors.OpPrereqError("No instance name found") |
|
1285 |
return name |
|
1153 | 1286 |
|
1154 | 1287 |
def Parse(self): |
1155 |
pass |
|
1288 |
"""Parses the data and creates a structure containing all required info. |
|
1289 |
|
|
1290 |
""" |
|
1291 |
try: |
|
1292 |
utils.Makedirs(self.output_dir) |
|
1293 |
except OSError, err: |
|
1294 |
raise errors.OpPrereqError("Failed to create directory %s: %s" % |
|
1295 |
(self.output_dir, err)) |
|
1296 |
|
|
1297 |
self.results_name = self._ParseName() |
|
1298 |
|
|
1299 |
def _PrepareManifest(self, path): |
|
1300 |
"""Creates manifest for all the files in OVF package. |
|
1301 |
|
|
1302 |
@type path: string |
|
1303 |
@param path: path to manifesto file |
|
1304 |
|
|
1305 |
@raise errors.OpPrereqError: if error occurs when writing file |
|
1306 |
|
|
1307 |
""" |
|
1308 |
logging.info("Preparing manifest for the OVF package") |
|
1309 |
lines = [] |
|
1310 |
files_list = [self.output_path] |
|
1311 |
files_list.extend(self.references_files) |
|
1312 |
logging.warning("Calculating SHA1 checksums, this may take a while") |
|
1313 |
sha1_sums = utils.FingerprintFiles(files_list) |
|
1314 |
for file_path, value in sha1_sums.iteritems(): |
|
1315 |
file_name = os.path.basename(file_path) |
|
1316 |
lines.append("SHA1(%s)= %s" % (file_name, value)) |
|
1317 |
lines.append("") |
|
1318 |
data = "\n".join(lines) |
|
1319 |
try: |
|
1320 |
utils.WriteFile(path, data=data) |
|
1321 |
except errors.ProgrammerError, err: |
|
1322 |
raise errors.OpPrereqError("Saving the manifest file failed: %s" % err) |
|
1323 |
|
|
1324 |
@staticmethod |
|
1325 |
def _PrepareTarFile(tar_path, files_list): |
|
1326 |
"""Creates tarfile from the files in OVF package. |
|
1327 |
|
|
1328 |
@type tar_path: string |
|
1329 |
@param tar_path: path to the resulting file |
|
1330 |
@type files_list: list |
|
1331 |
@param files_list: list of files in the OVF package |
|
1332 |
|
|
1333 |
""" |
|
1334 |
logging.info("Preparing tarball for the OVF package") |
|
1335 |
open(tar_path, mode="w").close() |
|
1336 |
ova_package = tarfile.open(name=tar_path, mode="w") |
|
1337 |
for file_name in files_list: |
|
1338 |
ova_package.add(file_name) |
|
1339 |
ova_package.close() |
|
1156 | 1340 |
|
1157 | 1341 |
def Save(self): |
1158 |
pass |
|
1342 |
"""Saves the gathered configuration in an apropriate format. |
|
1343 |
|
|
1344 |
@raise errors.OpPrereqError: if unable to create output directory |
|
1345 |
|
|
1346 |
""" |
|
1347 |
output_file = "%s%s" % (self.results_name, OVF_EXT) |
|
1348 |
output_path = utils.PathJoin(self.output_dir, output_file) |
|
1349 |
self.ovf_writer = OVFWriter(not self.options.ext_usage) |
|
1350 |
logging.info("Saving read data to %s", output_path) |
|
1351 |
|
|
1352 |
self.output_path = utils.PathJoin(self.output_dir, output_file) |
|
1353 |
files_list = [self.output_path] |
|
1354 |
|
|
1355 |
data = self.ovf_writer.PrettyXmlDump() |
|
1356 |
utils.WriteFile(self.output_path, data=data) |
|
1357 |
|
|
1358 |
manifest_file = "%s%s" % (self.results_name, MF_EXT) |
|
1359 |
manifest_path = utils.PathJoin(self.output_dir, manifest_file) |
|
1360 |
self._PrepareManifest(manifest_path) |
|
1361 |
files_list.append(manifest_path) |
|
1362 |
|
|
1363 |
files_list.extend(self.references_files) |
|
1364 |
|
|
1365 |
if self.options.ova_package: |
|
1366 |
ova_file = "%s%s" % (self.results_name, OVA_EXT) |
|
1367 |
packed_path = utils.PathJoin(self.packed_dir, ova_file) |
|
1368 |
try: |
|
1369 |
utils.Makedirs(self.packed_dir) |
|
1370 |
except OSError, err: |
|
1371 |
raise errors.OpPrereqError("Failed to create directory %s: %s" % |
|
1372 |
(self.packed_dir, err)) |
|
1373 |
self._PrepareTarFile(packed_path, files_list) |
|
1374 |
logging.info("Creation of the OVF package was successfull") |
|
1375 |
self.Cleanup() |
Also available in: Unified diff