1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Converter tools between ovf and ganeti config file
32
33 """
34
35
36
37
38
39
40
41 import ConfigParser
42 import errno
43 import logging
44 import os
45 import os.path
46 import re
47 import shutil
48 import tarfile
49 import tempfile
50 import xml.dom.minidom
51 import xml.parsers.expat
52 try:
53 import xml.etree.ElementTree as ET
54 except ImportError:
55 import elementtree.ElementTree as ET
56
57 try:
58 ParseError = ET.ParseError
59 except AttributeError:
60 ParseError = None
61
62 from ganeti import constants
63 from ganeti import errors
64 from ganeti import utils
65 from ganeti import pathutils
66
67
68
69 GANETI_SCHEMA = "http://ganeti"
70 OVF_SCHEMA = "http://schemas.dmtf.org/ovf/envelope/1"
71 RASD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/"
72 "CIM_ResourceAllocationSettingData")
73 VSSD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/"
74 "CIM_VirtualSystemSettingData")
75 XML_SCHEMA = "http://www.w3.org/2001/XMLSchema-instance"
76
77
78 OVA_EXT = ".ova"
79 OVF_EXT = ".ovf"
80 MF_EXT = ".mf"
81 CERT_EXT = ".cert"
82 COMPRESSION_EXT = ".gz"
83 FILE_EXTENSIONS = [
84 OVF_EXT,
85 MF_EXT,
86 CERT_EXT,
87 ]
88
89 COMPRESSION_TYPE = "gzip"
90 NO_COMPRESSION = [None, "identity"]
91 COMPRESS = "compression"
92 DECOMPRESS = "decompression"
93 ALLOWED_ACTIONS = [COMPRESS, DECOMPRESS]
94
95 VMDK = "vmdk"
96 RAW = "raw"
97 COW = "cow"
98 ALLOWED_FORMATS = [RAW, COW, VMDK]
99
100
101 RASD_TYPE = {
102 "vcpus": "3",
103 "memory": "4",
104 "scsi-controller": "6",
105 "ethernet-adapter": "10",
106 "disk": "17",
107 }
108
109 SCSI_SUBTYPE = "lsilogic"
110 VS_TYPE = {
111 "ganeti": "ganeti-ovf",
112 "external": "vmx-04",
113 }
114
115
116 ALLOCATION_UNITS = {
117 "b": ["bytes", "b"],
118 "kb": ["kilobytes", "kb", "byte * 2^10", "kibibytes", "kib"],
119 "mb": ["megabytes", "mb", "byte * 2^20", "mebibytes", "mib"],
120 "gb": ["gigabytes", "gb", "byte * 2^30", "gibibytes", "gib"],
121 }
122 CONVERT_UNITS_TO_MB = {
123 "b": lambda x: x / (1024 * 1024),
124 "kb": lambda x: x / 1024,
125 "mb": lambda x: x,
126 "gb": lambda x: x * 1024,
127 }
128
129
130 NAME = "name"
131 OS = "os"
132 HYPERV = "hypervisor"
133 VCPUS = "vcpus"
134 MEMORY = "memory"
135 AUTO_BALANCE = "auto_balance"
136 DISK_TEMPLATE = "disk_template"
137 TAGS = "tags"
138 VERSION = "version"
139
140
141 INSTANCE_ID = {
142 "system": 0,
143 "vcpus": 1,
144 "memory": 2,
145 "scsi": 3,
146 }
147
148
149 DISK_FORMAT = {
150 RAW: "http://en.wikipedia.org/wiki/Byte",
151 VMDK: "http://www.vmware.com/interfaces/specifications/vmdk.html"
152 "#monolithicSparse",
153 COW: "http://www.gnome.org/~markmc/qcow-image-format.html",
154 }
158 """ Make sure that qemu-img is present before performing operations.
159
160 @raise errors.OpPrereqError: when qemu-img was not found in the system
161
162 """
163 if not constants.QEMUIMG_PATH:
164 raise errors.OpPrereqError("qemu-img not found at build time, unable"
165 " to continue", errors.ECODE_STATE)
166
167
168 -def LinkFile(old_path, prefix=None, suffix=None, directory=None):
169 """Create link with a given prefix and suffix.
170
171 This is a wrapper over os.link. It tries to create a hard link for given file,
172 but instead of rising error when file exists, the function changes the name
173 a little bit.
174
175 @type old_path:string
176 @param old_path: path to the file that is to be linked
177 @type prefix: string
178 @param prefix: prefix of filename for the link
179 @type suffix: string
180 @param suffix: suffix of the filename for the link
181 @type directory: string
182 @param directory: directory of the link
183
184 @raise errors.OpPrereqError: when error on linking is different than
185 "File exists"
186
187 """
188 assert(prefix is not None or suffix is not None)
189 if directory is None:
190 directory = os.getcwd()
191 new_path = utils.PathJoin(directory, "%s%s" % (prefix, suffix))
192 counter = 1
193 while True:
194 try:
195 os.link(old_path, new_path)
196 break
197 except OSError, err:
198 if err.errno == errno.EEXIST:
199 new_path = utils.PathJoin(directory,
200 "%s_%s%s" % (prefix, counter, suffix))
201 counter += 1
202 else:
203 raise errors.OpPrereqError("Error moving the file %s to %s location:"
204 " %s" % (old_path, new_path, err),
205 errors.ECODE_ENVIRON)
206 return new_path
207
210 """Reader class for OVF files.
211
212 @type files_list: list
213 @ivar files_list: list of files in the OVF package
214 @type tree: ET.ElementTree
215 @ivar tree: XML tree of the .ovf file
216 @type schema_name: string
217 @ivar schema_name: name of the .ovf file
218 @type input_dir: string
219 @ivar input_dir: directory in which the .ovf file resides
220
221 """
223 """Initialiaze the reader - load the .ovf file to XML parser.
224
225 It is assumed that names of manifesto (.mf), certificate (.cert) and ovf
226 files are the same. In order to account any other files as part of the ovf
227 package, they have to be explicitly mentioned in the Resources section
228 of the .ovf file.
229
230 @type input_path: string
231 @param input_path: absolute path to the .ovf file
232
233 @raise errors.OpPrereqError: when .ovf file is not a proper XML file or some
234 of the files mentioned in Resources section do not exist
235
236 """
237 self.tree = ET.ElementTree()
238 try:
239 self.tree.parse(input_path)
240 except (ParseError, xml.parsers.expat.ExpatError), err:
241 raise errors.OpPrereqError("Error while reading %s file: %s" %
242 (OVF_EXT, err), errors.ECODE_ENVIRON)
243
244
245 (input_dir, input_file) = os.path.split(input_path)
246 (input_name, _) = os.path.splitext(input_file)
247 files_directory = utils.ListVisibleFiles(input_dir)
248 files_list = []
249 for file_name in files_directory:
250 (name, extension) = os.path.splitext(file_name)
251 if extension in FILE_EXTENSIONS and name == input_name:
252 files_list.append(file_name)
253 files_list += self._GetAttributes("{%s}References/{%s}File" %
254 (OVF_SCHEMA, OVF_SCHEMA),
255 "{%s}href" % OVF_SCHEMA)
256 for file_name in files_list:
257 file_path = utils.PathJoin(input_dir, file_name)
258 if not os.path.exists(file_path):
259 raise errors.OpPrereqError("File does not exist: %s" % file_path,
260 errors.ECODE_ENVIRON)
261 logging.info("Files in the OVF package: %s", " ".join(files_list))
262 self.files_list = files_list
263 self.input_dir = input_dir
264 self.schema_name = input_name
265
267 """Get specified attribute from all nodes accessible using given path.
268
269 Function follows the path from root node to the desired tags using path,
270 then reads the apropriate attribute values.
271
272 @type path: string
273 @param path: path of nodes to visit
274 @type attribute: string
275 @param attribute: attribute for which we gather the information
276 @rtype: list
277 @return: for each accessible tag with the attribute value set, value of the
278 attribute
279
280 """
281 current_list = self.tree.findall(path)
282 results = [x.get(attribute) for x in current_list]
283 return filter(None, results)
284
286 """Searches for element on a path that matches certain attribute value.
287
288 Function follows the path from root node to the desired tags using path,
289 then searches for the first one matching the attribute value.
290
291 @type path: string
292 @param path: path of nodes to visit
293 @type match_attr: tuple
294 @param match_attr: pair (attribute, value) for which we search
295 @rtype: ET.ElementTree or None
296 @return: first element matching match_attr or None if nothing matches
297
298 """
299 potential_elements = self.tree.findall(path)
300 (attr, val) = match_attr
301 for elem in potential_elements:
302 if elem.get(attr) == val:
303 return elem
304 return None
305
306 - def _GetElementMatchingText(self, path, match_text):
307 """Searches for element on a path that matches certain text value.
308
309 Function follows the path from root node to the desired tags using path,
310 then searches for the first one matching the text value.
311
312 @type path: string
313 @param path: path of nodes to visit
314 @type match_text: tuple
315 @param match_text: pair (node, text) for which we search
316 @rtype: ET.ElementTree or None
317 @return: first element matching match_text or None if nothing matches
318
319 """
320 potential_elements = self.tree.findall(path)
321 (node, text) = match_text
322 for elem in potential_elements:
323 if elem.findtext(node) == text:
324 return elem
325 return None
326
327 @staticmethod
329 """Reads text in all children and creates the dictionary from the contents.
330
331 @type root: ET.ElementTree or None
332 @param root: father of the nodes we want to collect data about
333 @type schema: string
334 @param schema: schema name to be removed from the tag
335 @rtype: dict
336 @return: dictionary containing tags and their text contents, tags have their
337 schema fragment removed or empty dictionary, when root is None
338
339 """
340 if root is None:
341 return {}
342 results = {}
343 for element in list(root):
344 pref_len = len("{%s}" % schema)
345 assert(schema in element.tag)
346 tag = element.tag[pref_len:]
347 results[tag] = element.text
348 return results
349
351 """Verifies manifest for the OVF package, if one is given.
352
353 @raise errors.OpPrereqError: if SHA1 checksums do not match
354
355 """
356 if "%s%s" % (self.schema_name, MF_EXT) in self.files_list:
357 logging.warning("Verifying SHA1 checksums, this may take a while")
358 manifest_filename = "%s%s" % (self.schema_name, MF_EXT)
359 manifest_path = utils.PathJoin(self.input_dir, manifest_filename)
360 manifest_content = utils.ReadFile(manifest_path).splitlines()
361 manifest_files = {}
362 regexp = r"SHA1\((\S+)\)= (\S+)"
363 for line in manifest_content:
364 match = re.match(regexp, line)
365 if match:
366 file_name = match.group(1)
367 sha1_sum = match.group(2)
368 manifest_files[file_name] = sha1_sum
369 files_with_paths = [utils.PathJoin(self.input_dir, file_name)
370 for file_name in self.files_list]
371 sha1_sums = utils.FingerprintFiles(files_with_paths)
372 for file_name, value in manifest_files.iteritems():
373 if sha1_sums.get(utils.PathJoin(self.input_dir, file_name)) != value:
374 raise errors.OpPrereqError("SHA1 checksum of %s does not match the"
375 " value in manifest file" % file_name,
376 errors.ECODE_ENVIRON)
377 logging.info("SHA1 checksums verified")
378
380 """Provides information about instance name.
381
382 @rtype: string
383 @return: instance name string
384
385 """
386 find_name = "{%s}VirtualSystem/{%s}Name" % (OVF_SCHEMA, OVF_SCHEMA)
387 return self.tree.findtext(find_name)
388
390 """Returns disk template from .ovf file
391
392 @rtype: string or None
393 @return: name of the template
394 """
395 find_template = ("{%s}GanetiSection/{%s}DiskTemplate" %
396 (GANETI_SCHEMA, GANETI_SCHEMA))
397 return self.tree.findtext(find_template)
398
400 """Provides hypervisor information - hypervisor name and options.
401
402 @rtype: dict
403 @return: dictionary containing name of the used hypervisor and all the
404 specified options
405
406 """
407 hypervisor_search = ("{%s}GanetiSection/{%s}Hypervisor" %
408 (GANETI_SCHEMA, GANETI_SCHEMA))
409 hypervisor_data = self.tree.find(hypervisor_search)
410 if hypervisor_data is None:
411 return {"hypervisor_name": constants.VALUE_AUTO}
412 results = {
413 "hypervisor_name": hypervisor_data.findtext("{%s}Name" % GANETI_SCHEMA,
414 default=constants.VALUE_AUTO),
415 }
416 parameters = hypervisor_data.find("{%s}Parameters" % GANETI_SCHEMA)
417 results.update(self._GetDictParameters(parameters, GANETI_SCHEMA))
418 return results
419
421 """ Provides operating system information - os name and options.
422
423 @rtype: dict
424 @return: dictionary containing name and options for the chosen OS
425
426 """
427 results = {}
428 os_search = ("{%s}GanetiSection/{%s}OperatingSystem" %
429 (GANETI_SCHEMA, GANETI_SCHEMA))
430 os_data = self.tree.find(os_search)
431 if os_data is not None:
432 results["os_name"] = os_data.findtext("{%s}Name" % GANETI_SCHEMA)
433 parameters = os_data.find("{%s}Parameters" % GANETI_SCHEMA)
434 results.update(self._GetDictParameters(parameters, GANETI_SCHEMA))
435 return results
436
438 """ Provides backend information - vcpus, memory, auto balancing options.
439
440 @rtype: dict
441 @return: dictionary containing options for vcpus, memory and auto balance
442 settings
443
444 """
445 results = {}
446
447 find_vcpus = ("{%s}VirtualSystem/{%s}VirtualHardwareSection/{%s}Item" %
448 (OVF_SCHEMA, OVF_SCHEMA, OVF_SCHEMA))
449 match_vcpus = ("{%s}ResourceType" % RASD_SCHEMA, RASD_TYPE["vcpus"])
450 vcpus = self._GetElementMatchingText(find_vcpus, match_vcpus)
451 if vcpus is not None:
452 vcpus_count = vcpus.findtext("{%s}VirtualQuantity" % RASD_SCHEMA,
453 default=constants.VALUE_AUTO)
454 else:
455 vcpus_count = constants.VALUE_AUTO
456 results["vcpus"] = str(vcpus_count)
457
458 find_memory = find_vcpus
459 match_memory = ("{%s}ResourceType" % RASD_SCHEMA, RASD_TYPE["memory"])
460 memory = self._GetElementMatchingText(find_memory, match_memory)
461 memory_raw = None
462 if memory is not None:
463 alloc_units = memory.findtext("{%s}AllocationUnits" % RASD_SCHEMA)
464 matching_units = [units for units, variants in ALLOCATION_UNITS.items()
465 if alloc_units.lower() in variants]
466 if matching_units == []:
467 raise errors.OpPrereqError("Unit %s for RAM memory unknown" %
468 alloc_units, errors.ECODE_INVAL)
469 units = matching_units[0]
470 memory_raw = int(memory.findtext("{%s}VirtualQuantity" % RASD_SCHEMA,
471 default=constants.VALUE_AUTO))
472 memory_count = CONVERT_UNITS_TO_MB[units](memory_raw)
473 else:
474 memory_count = constants.VALUE_AUTO
475 results["memory"] = str(memory_count)
476
477 find_balance = ("{%s}GanetiSection/{%s}AutoBalance" %
478 (GANETI_SCHEMA, GANETI_SCHEMA))
479 balance = self.tree.findtext(find_balance, default=constants.VALUE_AUTO)
480 results["auto_balance"] = balance
481
482 return results
483
497
499 """Provides version number read from .ovf file
500
501 @rtype: string
502 @return: string containing the version number
503
504 """
505 find_version = ("{%s}GanetiSection/{%s}Version" %
506 (GANETI_SCHEMA, GANETI_SCHEMA))
507 return self.tree.findtext(find_version)
508
510 """Provides data about the network in the OVF instance.
511
512 The method gathers the data about networks used by OVF instance. It assumes
513 that 'name' tag means something - in essence, if it contains one of the
514 words 'bridged' or 'routed' then that will be the mode of this network in
515 Ganeti. The information about the network can be either in GanetiSection or
516 VirtualHardwareSection.
517
518 @rtype: dict
519 @return: dictionary containing all the network information
520
521 """
522 results = {}
523 networks_search = ("{%s}NetworkSection/{%s}Network" %
524 (OVF_SCHEMA, OVF_SCHEMA))
525 network_names = self._GetAttributes(networks_search,
526 "{%s}name" % OVF_SCHEMA)
527 required = ["ip", "mac", "link", "mode", "network"]
528 for (counter, network_name) in enumerate(network_names):
529 network_search = ("{%s}VirtualSystem/{%s}VirtualHardwareSection/{%s}Item"
530 % (OVF_SCHEMA, OVF_SCHEMA, OVF_SCHEMA))
531 ganeti_search = ("{%s}GanetiSection/{%s}Network/{%s}Nic" %
532 (GANETI_SCHEMA, GANETI_SCHEMA, GANETI_SCHEMA))
533 network_match = ("{%s}Connection" % RASD_SCHEMA, network_name)
534 ganeti_match = ("{%s}name" % OVF_SCHEMA, network_name)
535 network_data = self._GetElementMatchingText(network_search, network_match)
536 network_ganeti_data = self._GetElementMatchingAttr(ganeti_search,
537 ganeti_match)
538
539 ganeti_data = {}
540 if network_ganeti_data is not None:
541 ganeti_data["mode"] = network_ganeti_data.findtext("{%s}Mode" %
542 GANETI_SCHEMA)
543 ganeti_data["mac"] = network_ganeti_data.findtext("{%s}MACAddress" %
544 GANETI_SCHEMA)
545 ganeti_data["ip"] = network_ganeti_data.findtext("{%s}IPAddress" %
546 GANETI_SCHEMA)
547 ganeti_data["link"] = network_ganeti_data.findtext("{%s}Link" %
548 GANETI_SCHEMA)
549 ganeti_data["network"] = network_ganeti_data.findtext("{%s}Net" %
550 GANETI_SCHEMA)
551 mac_data = None
552 if network_data is not None:
553 mac_data = network_data.findtext("{%s}Address" % RASD_SCHEMA)
554
555 network_name = network_name.lower()
556
557
558 if constants.NIC_MODE_BRIDGED in network_name:
559 results["nic%s_mode" % counter] = "bridged"
560 elif constants.NIC_MODE_ROUTED in network_name:
561 results["nic%s_mode" % counter] = "routed"
562 results["nic%s_mac" % counter] = mac_data
563
564
565 for name, value in ganeti_data.iteritems():
566 results["nic%s_%s" % (counter, name)] = value
567
568
569 if (results.get("nic%s_mode" % counter) == "bridged" and
570 not results.get("nic%s_ip" % counter)):
571 results["nic%s_ip" % counter] = constants.VALUE_NONE
572
573 for option in required:
574 if not results.get("nic%s_%s" % (counter, option)):
575 results["nic%s_%s" % (counter, option)] = constants.VALUE_AUTO
576
577 if network_names:
578 results["nic_count"] = str(len(network_names))
579 return results
580
582 """Provides list of file names for the disks used by the instance.
583
584 @rtype: list
585 @return: list of file names, as referenced in .ovf file
586
587 """
588 results = []
589 disks_search = "{%s}DiskSection/{%s}Disk" % (OVF_SCHEMA, OVF_SCHEMA)
590 disk_ids = self._GetAttributes(disks_search, "{%s}fileRef" % OVF_SCHEMA)
591 for disk in disk_ids:
592 disk_search = "{%s}References/{%s}File" % (OVF_SCHEMA, OVF_SCHEMA)
593 disk_match = ("{%s}id" % OVF_SCHEMA, disk)
594 disk_elem = self._GetElementMatchingAttr(disk_search, disk_match)
595 if disk_elem is None:
596 raise errors.OpPrereqError("%s file corrupted - disk %s not found in"
597 " references" % (OVF_EXT, disk),
598 errors.ECODE_ENVIRON)
599 disk_name = disk_elem.get("{%s}href" % OVF_SCHEMA)
600 disk_compression = disk_elem.get("{%s}compression" % OVF_SCHEMA)
601 results.append((disk_name, disk_compression))
602 return results
603
604
605 -def SubElementText(parent, tag, text, attrib={}, **extra):
606
607 """This is just a wrapper on ET.SubElement that always has text content.
608
609 """
610 if text is None:
611 return None
612 elem = ET.SubElement(parent, tag, attrib=attrib, **extra)
613 elem.text = str(text)
614 return elem
615
618 """Writer class for OVF files.
619
620 @type tree: ET.ElementTree
621 @ivar tree: XML tree that we are constructing
622 @type virtual_system_type: string
623 @ivar virtual_system_type: value of vssd:VirtualSystemType, for external usage
624 in VMWare this requires to be vmx
625 @type hardware_list: list
626 @ivar hardware_list: list of items prepared for VirtualHardwareSection
627 @type next_instance_id: int
628 @ivar next_instance_id: next instance id to be used when creating elements on
629 hardware_list
630
631 """
633 """Initialize the writer - set the top element.
634
635 @type has_gnt_section: bool
636 @param has_gnt_section: if the Ganeti schema should be added - i.e. this
637 means that Ganeti section will be present
638
639 """
640 env_attribs = {
641 "xmlns:xsi": XML_SCHEMA,
642 "xmlns:vssd": VSSD_SCHEMA,
643 "xmlns:rasd": RASD_SCHEMA,
644 "xmlns:ovf": OVF_SCHEMA,
645 "xmlns": OVF_SCHEMA,
646 "xml:lang": "en-US",
647 }
648 if has_gnt_section:
649 env_attribs["xmlns:gnt"] = GANETI_SCHEMA
650 self.virtual_system_type = VS_TYPE["ganeti"]
651 else:
652 self.virtual_system_type = VS_TYPE["external"]
653 self.tree = ET.Element("Envelope", attrib=env_attribs)
654 self.hardware_list = []
655
656 self.next_instance_id = len(INSTANCE_ID)
657
659 """Convert disk information to certain OVF sections.
660
661 @type disks: list
662 @param disks: list of dictionaries of disk options from config.ini
663
664 """
665 references = ET.SubElement(self.tree, "References")
666 disk_section = ET.SubElement(self.tree, "DiskSection")
667 SubElementText(disk_section, "Info", "Virtual disk information")
668 for counter, disk in enumerate(disks):
669 file_id = "file%s" % counter
670 disk_id = "disk%s" % counter
671 file_attribs = {
672 "ovf:href": disk["path"],
673 "ovf:size": str(disk["real-size"]),
674 "ovf:id": file_id,
675 }
676 disk_attribs = {
677 "ovf:capacity": str(disk["virt-size"]),
678 "ovf:diskId": disk_id,
679 "ovf:fileRef": file_id,
680 "ovf:format": DISK_FORMAT.get(disk["format"], disk["format"]),
681 }
682 if "compression" in disk:
683 file_attribs["ovf:compression"] = disk["compression"]
684 ET.SubElement(references, "File", attrib=file_attribs)
685 ET.SubElement(disk_section, "Disk", attrib=disk_attribs)
686
687
688 disk_item = ET.Element("Item")
689 SubElementText(disk_item, "rasd:ElementName", disk_id)
690 SubElementText(disk_item, "rasd:HostResource", "ovf:/disk/%s" % disk_id)
691 SubElementText(disk_item, "rasd:InstanceID", self.next_instance_id)
692 SubElementText(disk_item, "rasd:Parent", INSTANCE_ID["scsi"])
693 SubElementText(disk_item, "rasd:ResourceType", RASD_TYPE["disk"])
694 self.hardware_list.append(disk_item)
695 self.next_instance_id += 1
696
698 """Convert network information to NetworkSection.
699
700 @type networks: list
701 @param networks: list of dictionaries of network options form config.ini
702
703 """
704 network_section = ET.SubElement(self.tree, "NetworkSection")
705 SubElementText(network_section, "Info", "List of logical networks")
706 for counter, network in enumerate(networks):
707 network_name = "%s%s" % (network["mode"], counter)
708 network_attrib = {"ovf:name": network_name}
709 ET.SubElement(network_section, "Network", attrib=network_attrib)
710
711
712 network_item = ET.Element("Item")
713 SubElementText(network_item, "rasd:Address", network["mac"])
714 SubElementText(network_item, "rasd:Connection", network_name)
715 SubElementText(network_item, "rasd:ElementName", network_name)
716 SubElementText(network_item, "rasd:InstanceID", self.next_instance_id)
717 SubElementText(network_item, "rasd:ResourceType",
718 RASD_TYPE["ethernet-adapter"])
719 self.hardware_list.append(network_item)
720 self.next_instance_id += 1
721
722 @staticmethod
724 """Save name and parameters information under root using data.
725
726 @type root: ET.Element
727 @param root: root element for the Name and Parameters
728 @type data: dict
729 @param data: data from which we gather the values
730
731 """
732 assert(data.get("name"))
733 name = SubElementText(root, "gnt:Name", data["name"])
734 params = ET.SubElement(root, "gnt:Parameters")
735 for name, value in data.iteritems():
736 if name != "name":
737 SubElementText(params, "gnt:%s" % name, value)
738
740 """Convert Ganeti-specific information to GanetiSection.
741
742 @type ganeti: dict
743 @param ganeti: dictionary of Ganeti-specific options from config.ini
744 @type networks: list
745 @param networks: list of dictionaries of network options form config.ini
746
747 """
748 ganeti_section = ET.SubElement(self.tree, "gnt:GanetiSection")
749
750 SubElementText(ganeti_section, "gnt:Version", ganeti.get("version"))
751 SubElementText(ganeti_section, "gnt:DiskTemplate",
752 ganeti.get("disk_template"))
753 SubElementText(ganeti_section, "gnt:AutoBalance",
754 ganeti.get("auto_balance"))
755 SubElementText(ganeti_section, "gnt:Tags", ganeti.get("tags"))
756
757 osys = ET.SubElement(ganeti_section, "gnt:OperatingSystem")
758 self._SaveNameAndParams(osys, ganeti["os"])
759
760 hypervisor = ET.SubElement(ganeti_section, "gnt:Hypervisor")
761 self._SaveNameAndParams(hypervisor, ganeti["hypervisor"])
762
763 network_section = ET.SubElement(ganeti_section, "gnt:Network")
764 for counter, network in enumerate(networks):
765 network_name = "%s%s" % (network["mode"], counter)
766 nic_attrib = {"ovf:name": network_name}
767 nic = ET.SubElement(network_section, "gnt:Nic", attrib=nic_attrib)
768 SubElementText(nic, "gnt:Mode", network["mode"])
769 SubElementText(nic, "gnt:MACAddress", network["mac"])
770 SubElementText(nic, "gnt:IPAddress", network["ip"])
771 SubElementText(nic, "gnt:Link", network["link"])
772 SubElementText(nic, "gnt:Net", network["network"])
773
775 """Convert virtual system information to OVF sections.
776
777 @type name: string
778 @param name: name of the instance
779 @type vcpus: int
780 @param vcpus: number of VCPUs
781 @type memory: int
782 @param memory: RAM memory in MB
783
784 """
785 assert(vcpus > 0)
786 assert(memory > 0)
787 vs_attrib = {"ovf:id": name}
788 virtual_system = ET.SubElement(self.tree, "VirtualSystem", attrib=vs_attrib)
789 SubElementText(virtual_system, "Info", "A virtual machine")
790
791 name_section = ET.SubElement(virtual_system, "Name")
792 name_section.text = name
793 os_attrib = {"ovf:id": "0"}
794 os_section = ET.SubElement(virtual_system, "OperatingSystemSection",
795 attrib=os_attrib)
796 SubElementText(os_section, "Info", "Installed guest operating system")
797 hardware_section = ET.SubElement(virtual_system, "VirtualHardwareSection")
798 SubElementText(hardware_section, "Info", "Virtual hardware requirements")
799
800
801 system = ET.SubElement(hardware_section, "System")
802 SubElementText(system, "vssd:ElementName", "Virtual Hardware Family")
803 SubElementText(system, "vssd:InstanceID", INSTANCE_ID["system"])
804 SubElementText(system, "vssd:VirtualSystemIdentifier", name)
805 SubElementText(system, "vssd:VirtualSystemType", self.virtual_system_type)
806
807
808 vcpus_item = ET.SubElement(hardware_section, "Item")
809 SubElementText(vcpus_item, "rasd:ElementName",
810 "%s virtual CPU(s)" % vcpus)
811 SubElementText(vcpus_item, "rasd:InstanceID", INSTANCE_ID["vcpus"])
812 SubElementText(vcpus_item, "rasd:ResourceType", RASD_TYPE["vcpus"])
813 SubElementText(vcpus_item, "rasd:VirtualQuantity", vcpus)
814
815
816 memory_item = ET.SubElement(hardware_section, "Item")
817 SubElementText(memory_item, "rasd:AllocationUnits", "byte * 2^20")
818 SubElementText(memory_item, "rasd:ElementName", "%sMB of memory" % memory)
819 SubElementText(memory_item, "rasd:InstanceID", INSTANCE_ID["memory"])
820 SubElementText(memory_item, "rasd:ResourceType", RASD_TYPE["memory"])
821 SubElementText(memory_item, "rasd:VirtualQuantity", memory)
822
823
824 scsi_item = ET.SubElement(hardware_section, "Item")
825 SubElementText(scsi_item, "rasd:Address", INSTANCE_ID["system"])
826 SubElementText(scsi_item, "rasd:ElementName", "scsi_controller0")
827 SubElementText(scsi_item, "rasd:InstanceID", INSTANCE_ID["scsi"])
828 SubElementText(scsi_item, "rasd:ResourceSubType", SCSI_SUBTYPE)
829 SubElementText(scsi_item, "rasd:ResourceType", RASD_TYPE["scsi-controller"])
830
831
832 for item in self.hardware_list:
833 hardware_section.append(item)
834
836 """Formatter of the XML file.
837
838 @rtype: string
839 @return: XML tree in the form of nicely-formatted string
840
841 """
842 raw_string = ET.tostring(self.tree)
843 parsed_xml = xml.dom.minidom.parseString(raw_string)
844 xml_string = parsed_xml.toprettyxml(indent=" ")
845 text_re = re.compile(r">\n\s+([^<>\s].*?)\n\s+</", re.DOTALL)
846 return text_re.sub(r">\g<1></", xml_string)
847
850 """Converter class for OVF packages.
851
852 Converter is a class above both ImporterOVF and ExporterOVF. It's purpose is
853 to provide a common interface for the two.
854
855 @type options: optparse.Values
856 @ivar options: options parsed from the command line
857 @type output_dir: string
858 @ivar output_dir: directory to which the results of conversion shall be
859 written
860 @type temp_file_manager: L{utils.TemporaryFileManager}
861 @ivar temp_file_manager: container for temporary files created during
862 conversion
863 @type temp_dir: string
864 @ivar temp_dir: temporary directory created then we deal with OVA
865
866 """
867 - def __init__(self, input_path, options):
868 """Initialize the converter.
869
870 @type input_path: string
871 @param input_path: path to the Converter input file
872 @type options: optparse.Values
873 @param options: command line options
874
875 @raise errors.OpPrereqError: if file does not exist
876
877 """
878 input_path = os.path.abspath(input_path)
879 if not os.path.isfile(input_path):
880 raise errors.OpPrereqError("File does not exist: %s" % input_path,
881 errors.ECODE_ENVIRON)
882 self.options = options
883 self.temp_file_manager = utils.TemporaryFileManager()
884 self.temp_dir = None
885 self.output_dir = None
886 self._ReadInputData(input_path)
887
896
898 """Performs (de)compression on the disk and returns the new path
899
900 @type disk_path: string
901 @param disk_path: path to the disk
902 @type compression: string
903 @param compression: compression type
904 @type action: string
905 @param action: whether the action is compression or decompression
906 @rtype: string
907 @return: new disk path after (de)compression
908
909 @raise errors.OpPrereqError: disk (de)compression failed or "compression"
910 is not supported
911
912 """
913 assert(action in ALLOWED_ACTIONS)
914
915 if compression != COMPRESSION_TYPE:
916 raise errors.OpPrereqError("Unsupported compression type: %s"
917 % compression, errors.ECODE_INVAL)
918 disk_file = os.path.basename(disk_path)
919 if action == DECOMPRESS:
920 (disk_name, _) = os.path.splitext(disk_file)
921 prefix = disk_name
922 elif action == COMPRESS:
923 prefix = disk_file
924 new_path = utils.GetClosedTempfile(suffix=COMPRESSION_EXT, prefix=prefix,
925 dir=self.output_dir)
926 self.temp_file_manager.Add(new_path)
927 args = ["gzip", "-c", disk_path]
928 run_result = utils.RunCmd(args, output=new_path)
929 if run_result.failed:
930 raise errors.OpPrereqError("Disk %s failed with output: %s"
931 % (action, run_result.stderr),
932 errors.ECODE_ENVIRON)
933 logging.info("The %s of the disk is completed", action)
934 return (COMPRESSION_EXT, new_path)
935
937 """Performes conversion to specified format.
938
939 @type disk_format: string
940 @param disk_format: format to which the disk should be converted
941 @type disk_path: string
942 @param disk_path: path to the disk that should be converted
943 @rtype: string
944 @return path to the output disk
945
946 @raise errors.OpPrereqError: convertion of the disk failed
947
948 """
949 CheckQemuImg()
950 disk_file = os.path.basename(disk_path)
951 (disk_name, disk_extension) = os.path.splitext(disk_file)
952 if disk_extension != disk_format:
953 logging.warning("Conversion of disk image to %s format, this may take"
954 " a while", disk_format)
955
956 new_disk_path = utils.GetClosedTempfile(
957 suffix=".%s" % disk_format, prefix=disk_name, dir=self.output_dir)
958 self.temp_file_manager.Add(new_disk_path)
959 args = [
960 constants.QEMUIMG_PATH,
961 "convert",
962 "-O",
963 disk_format,
964 disk_path,
965 new_disk_path,
966 ]
967 run_result = utils.RunCmd(args, cwd=os.getcwd())
968 if run_result.failed:
969 raise errors.OpPrereqError("Convertion to %s failed, qemu-img output was"
970 ": %s" % (disk_format, run_result.stderr),
971 errors.ECODE_ENVIRON)
972 return (".%s" % disk_format, new_disk_path)
973
974 @staticmethod
976 """Figures out some information of the disk using qemu-img.
977
978 @type disk_path: string
979 @param disk_path: path to the disk we want to know the format of
980 @type regexp: string
981 @param regexp: string that has to be matched, it has to contain one group
982 @rtype: string
983 @return: disk format
984
985 @raise errors.OpPrereqError: format information cannot be retrieved
986
987 """
988 CheckQemuImg()
989 args = [constants.QEMUIMG_PATH, "info", disk_path]
990 run_result = utils.RunCmd(args, cwd=os.getcwd())
991 if run_result.failed:
992 raise errors.OpPrereqError("Gathering info about the disk using qemu-img"
993 " failed, output was: %s" % run_result.stderr,
994 errors.ECODE_ENVIRON)
995 result = run_result.output
996 regexp = r"%s" % regexp
997 match = re.search(regexp, result)
998 if match:
999 disk_format = match.group(1)
1000 else:
1001 raise errors.OpPrereqError("No file information matching %s found in:"
1002 " %s" % (regexp, result),
1003 errors.ECODE_ENVIRON)
1004 return disk_format
1005
1007 """Parses the data and creates a structure containing all required info.
1008
1009 """
1010 raise NotImplementedError()
1011
1013 """Saves the gathered configuration in an apropriate format.
1014
1015 """
1016 raise NotImplementedError()
1017
1019 """Cleans the temporary directory, if one was created.
1020
1021 """
1022 self.temp_file_manager.Cleanup()
1023 if self.temp_dir:
1024 shutil.rmtree(self.temp_dir)
1025 self.temp_dir = None
1026
1029 """Converter from OVF to Ganeti config file.
1030
1031 @type input_dir: string
1032 @ivar input_dir: directory in which the .ovf file resides
1033 @type output_dir: string
1034 @ivar output_dir: directory to which the results of conversion shall be
1035 written
1036 @type input_path: string
1037 @ivar input_path: complete path to the .ovf file
1038 @type ovf_reader: L{OVFReader}
1039 @ivar ovf_reader: OVF reader instance collects data from .ovf file
1040 @type results_name: string
1041 @ivar results_name: name of imported instance
1042 @type results_template: string
1043 @ivar results_template: disk template read from .ovf file or command line
1044 arguments
1045 @type results_hypervisor: dict
1046 @ivar results_hypervisor: hypervisor information gathered from .ovf file or
1047 command line arguments
1048 @type results_os: dict
1049 @ivar results_os: operating system information gathered from .ovf file or
1050 command line arguments
1051 @type results_backend: dict
1052 @ivar results_backend: backend information gathered from .ovf file or
1053 command line arguments
1054 @type results_tags: string
1055 @ivar results_tags: string containing instance-specific tags
1056 @type results_version: string
1057 @ivar results_version: version as required by Ganeti import
1058 @type results_network: dict
1059 @ivar results_network: network information gathered from .ovf file or command
1060 line arguments
1061 @type results_disk: dict
1062 @ivar results_disk: disk information gathered from .ovf file or command line
1063 arguments
1064
1065 """
1106
1108 """Unpacks the .ova package into temporary directory.
1109
1110 @type input_path: string
1111 @param input_path: path to the .ova package file
1112
1113 @raise errors.OpPrereqError: if file is not a proper tarball, one of the
1114 files in the archive seem malicious (e.g. path starts with '../') or
1115 .ova package does not contain .ovf file
1116
1117 """
1118 input_name = None
1119 if not tarfile.is_tarfile(input_path):
1120 raise errors.OpPrereqError("The provided %s file is not a proper tar"
1121 " archive" % OVA_EXT, errors.ECODE_ENVIRON)
1122 ova_content = tarfile.open(input_path)
1123 temp_dir = tempfile.mkdtemp()
1124 self.temp_dir = temp_dir
1125 for file_name in ova_content.getnames():
1126 file_normname = os.path.normpath(file_name)
1127 try:
1128 utils.PathJoin(temp_dir, file_normname)
1129 except ValueError, err:
1130 raise errors.OpPrereqError("File %s inside %s package is not safe" %
1131 (file_name, OVA_EXT), errors.ECODE_ENVIRON)
1132 if file_name.endswith(OVF_EXT):
1133 input_name = file_name
1134 if not input_name:
1135 raise errors.OpPrereqError("No %s file in %s package found" %
1136 (OVF_EXT, OVA_EXT), errors.ECODE_ENVIRON)
1137 logging.warning("Unpacking the %s archive, this may take a while",
1138 input_path)
1139 self.input_dir = temp_dir
1140 self.input_path = utils.PathJoin(self.temp_dir, input_name)
1141 try:
1142 try:
1143 extract = ova_content.extractall
1144 except AttributeError:
1145
1146 for member in ova_content.getmembers():
1147 ova_content.extract(member, path=self.temp_dir)
1148 else:
1149 extract(self.temp_dir)
1150 except tarfile.TarError, err:
1151 raise errors.OpPrereqError("Error while extracting %s archive: %s" %
1152 (OVA_EXT, err), errors.ECODE_ENVIRON)
1153 logging.info("OVA package extracted to %s directory", self.temp_dir)
1154
1156 """Parses the data and creates a structure containing all required info.
1157
1158 The method reads the information given either as a command line option or as
1159 a part of the OVF description.
1160
1161 @raise errors.OpPrereqError: if some required part of the description of
1162 virtual instance is missing or unable to create output directory
1163
1164 """
1165 self.results_name = self._GetInfo("instance name", self.options.name,
1166 self._ParseNameOptions,
1167 self.ovf_reader.GetInstanceName)
1168 if not self.results_name:
1169 raise errors.OpPrereqError("Name of instance not provided",
1170 errors.ECODE_INVAL)
1171
1172 self.output_dir = utils.PathJoin(self.output_dir, self.results_name)
1173 try:
1174 utils.Makedirs(self.output_dir)
1175 except OSError, err:
1176 raise errors.OpPrereqError("Failed to create directory %s: %s" %
1177 (self.output_dir, err), errors.ECODE_ENVIRON)
1178
1179 self.results_template = self._GetInfo(
1180 "disk template", self.options.disk_template, self._ParseTemplateOptions,
1181 self.ovf_reader.GetDiskTemplate)
1182 if not self.results_template:
1183 logging.info("Disk template not given")
1184
1185 self.results_hypervisor = self._GetInfo(
1186 "hypervisor", self.options.hypervisor, self._ParseHypervisorOptions,
1187 self.ovf_reader.GetHypervisorData)
1188 assert self.results_hypervisor["hypervisor_name"]
1189 if self.results_hypervisor["hypervisor_name"] == constants.VALUE_AUTO:
1190 logging.debug("Default hypervisor settings from the cluster will be used")
1191
1192 self.results_os = self._GetInfo(
1193 "OS", self.options.os, self._ParseOSOptions, self.ovf_reader.GetOSData)
1194 if not self.results_os.get("os_name"):
1195 raise errors.OpPrereqError("OS name must be provided",
1196 errors.ECODE_INVAL)
1197
1198 self.results_backend = self._GetInfo(
1199 "backend", self.options.beparams,
1200 self._ParseBackendOptions, self.ovf_reader.GetBackendData)
1201 assert self.results_backend.get("vcpus")
1202 assert self.results_backend.get("memory")
1203 assert self.results_backend.get("auto_balance") is not None
1204
1205 self.results_tags = self._GetInfo(
1206 "tags", self.options.tags, self._ParseTags, self.ovf_reader.GetTagsData)
1207
1208 ovf_version = self.ovf_reader.GetVersionData()
1209 if ovf_version:
1210 self.results_version = ovf_version
1211 else:
1212 self.results_version = constants.EXPORT_VERSION
1213
1214 self.results_network = self._GetInfo(
1215 "network", self.options.nics, self._ParseNicOptions,
1216 self.ovf_reader.GetNetworkData, ignore_test=self.options.no_nics)
1217
1218 self.results_disk = self._GetInfo(
1219 "disk", self.options.disks, self._ParseDiskOptions, self._GetDiskInfo,
1220 ignore_test=self.results_template == constants.DT_DISKLESS)
1221
1222 if not self.results_disk and not self.results_network:
1223 raise errors.OpPrereqError("Either disk specification or network"
1224 " description must be present",
1225 errors.ECODE_STATE)
1226
1227 @staticmethod
1228 - def _GetInfo(name, cmd_arg, cmd_function, nocmd_function,
1229 ignore_test=False):
1230 """Get information about some section - e.g. disk, network, hypervisor.
1231
1232 @type name: string
1233 @param name: name of the section
1234 @type cmd_arg: dict
1235 @param cmd_arg: command line argument specific for section 'name'
1236 @type cmd_function: callable
1237 @param cmd_function: function to call if 'cmd_args' exists
1238 @type nocmd_function: callable
1239 @param nocmd_function: function to call if 'cmd_args' is not there
1240
1241 """
1242 if ignore_test:
1243 logging.info("Information for %s will be ignored", name)
1244 return {}
1245 if cmd_arg:
1246 logging.info("Information for %s will be parsed from command line", name)
1247 results = cmd_function()
1248 else:
1249 logging.info("Information for %s will be parsed from %s file",
1250 name, OVF_EXT)
1251 results = nocmd_function()
1252 logging.info("Options for %s were succesfully read", name)
1253 return results
1254
1256 """Returns name if one was given in command line.
1257
1258 @rtype: string
1259 @return: name of an instance
1260
1261 """
1262 return self.options.name
1263
1265 """Returns disk template if one was given in command line.
1266
1267 @rtype: string
1268 @return: disk template name
1269
1270 """
1271 return self.options.disk_template
1272
1290
1292 """Parses OS options given in command line.
1293
1294 @rtype: dict
1295 @return: dictionary containing name of chosen OS and all its options
1296
1297 """
1298 assert self.options.os
1299 results = {}
1300 results["os_name"] = self.options.os
1301 results.update(self.options.osparams)
1302 return results
1303
1305 """Parses backend options given in command line.
1306
1307 @rtype: dict
1308 @return: dictionary containing vcpus, memory and auto-balance options
1309
1310 """
1311 assert self.options.beparams
1312 backend = {}
1313 backend.update(self.options.beparams)
1314 must_contain = ["vcpus", "memory", "auto_balance"]
1315 for element in must_contain:
1316 if backend.get(element) is None:
1317 backend[element] = constants.VALUE_AUTO
1318 return backend
1319
1328
1330 """Parses network options given in a command line or as a dictionary.
1331
1332 @rtype: dict
1333 @return: dictionary of network-related options
1334
1335 """
1336 assert self.options.nics
1337 results = {}
1338 for (nic_id, nic_desc) in self.options.nics:
1339 results["nic%s_mode" % nic_id] = \
1340 nic_desc.get("mode", constants.VALUE_AUTO)
1341 results["nic%s_mac" % nic_id] = nic_desc.get("mac", constants.VALUE_AUTO)
1342 results["nic%s_link" % nic_id] = \
1343 nic_desc.get("link", constants.VALUE_AUTO)
1344 results["nic%s_network" % nic_id] = \
1345 nic_desc.get("network", constants.VALUE_AUTO)
1346 if nic_desc.get("mode") == "bridged":
1347 results["nic%s_ip" % nic_id] = constants.VALUE_NONE
1348 else:
1349 results["nic%s_ip" % nic_id] = constants.VALUE_AUTO
1350 results["nic_count"] = str(len(self.options.nics))
1351 return results
1352
1354 """Parses disk options given in a command line.
1355
1356 @rtype: dict
1357 @return: dictionary of disk-related options
1358
1359 @raise errors.OpPrereqError: disk description does not contain size
1360 information or size information is invalid or creation failed
1361
1362 """
1363 CheckQemuImg()
1364 assert self.options.disks
1365 results = {}
1366 for (disk_id, disk_desc) in self.options.disks:
1367 results["disk%s_ivname" % disk_id] = "disk/%s" % disk_id
1368 if disk_desc.get("size"):
1369 try:
1370 disk_size = utils.ParseUnit(disk_desc["size"])
1371 except ValueError:
1372 raise errors.OpPrereqError("Invalid disk size for disk %s: %s" %
1373 (disk_id, disk_desc["size"]),
1374 errors.ECODE_INVAL)
1375 new_path = utils.PathJoin(self.output_dir, str(disk_id))
1376 args = [
1377 constants.QEMUIMG_PATH,
1378 "create",
1379 "-f",
1380 "raw",
1381 new_path,
1382 disk_size,
1383 ]
1384 run_result = utils.RunCmd(args)
1385 if run_result.failed:
1386 raise errors.OpPrereqError("Creation of disk %s failed, output was:"
1387 " %s" % (new_path, run_result.stderr),
1388 errors.ECODE_ENVIRON)
1389 results["disk%s_size" % disk_id] = str(disk_size)
1390 results["disk%s_dump" % disk_id] = "disk%s.raw" % disk_id
1391 else:
1392 raise errors.OpPrereqError("Disks created for import must have their"
1393 " size specified",
1394 errors.ECODE_INVAL)
1395 results["disk_count"] = str(len(self.options.disks))
1396 return results
1397
1399 """Gathers information about disks used by instance, perfomes conversion.
1400
1401 @rtype: dict
1402 @return: dictionary of disk-related options
1403
1404 @raise errors.OpPrereqError: disk is not in the same directory as .ovf file
1405
1406 """
1407 results = {}
1408 disks_list = self.ovf_reader.GetDisksNames()
1409 for (counter, (disk_name, disk_compression)) in enumerate(disks_list):
1410 if os.path.dirname(disk_name):
1411 raise errors.OpPrereqError("Disks are not allowed to have absolute"
1412 " paths or paths outside main OVF"
1413 " directory", errors.ECODE_ENVIRON)
1414 disk, _ = os.path.splitext(disk_name)
1415 disk_path = utils.PathJoin(self.input_dir, disk_name)
1416 if disk_compression not in NO_COMPRESSION:
1417 _, disk_path = self._CompressDisk(disk_path, disk_compression,
1418 DECOMPRESS)
1419 disk, _ = os.path.splitext(disk)
1420 if self._GetDiskQemuInfo(disk_path, r"file format: (\S+)") != "raw":
1421 logging.info("Conversion to raw format is required")
1422 ext, new_disk_path = self._ConvertDisk("raw", disk_path)
1423
1424 final_disk_path = LinkFile(new_disk_path, prefix=disk, suffix=ext,
1425 directory=self.output_dir)
1426 final_name = os.path.basename(final_disk_path)
1427 disk_size = os.path.getsize(final_disk_path) / (1024 * 1024)
1428 results["disk%s_dump" % counter] = final_name
1429 results["disk%s_size" % counter] = str(disk_size)
1430 results["disk%s_ivname" % counter] = "disk/%s" % str(counter)
1431 if disks_list:
1432 results["disk_count"] = str(len(disks_list))
1433 return results
1434
1436 """Saves all the gathered information in a constant.EXPORT_CONF_FILE file.
1437
1438 @raise errors.OpPrereqError: when saving to config file failed
1439
1440 """
1441 logging.info("Conversion was succesfull, saving %s in %s directory",
1442 constants.EXPORT_CONF_FILE, self.output_dir)
1443 results = {
1444 constants.INISECT_INS: {},
1445 constants.INISECT_BEP: {},
1446 constants.INISECT_EXP: {},
1447 constants.INISECT_OSP: {},
1448 constants.INISECT_HYP: {},
1449 }
1450
1451 results[constants.INISECT_INS].update(self.results_disk)
1452 results[constants.INISECT_INS].update(self.results_network)
1453 results[constants.INISECT_INS]["hypervisor"] = \
1454 self.results_hypervisor["hypervisor_name"]
1455 results[constants.INISECT_INS]["name"] = self.results_name
1456 if self.results_template:
1457 results[constants.INISECT_INS]["disk_template"] = self.results_template
1458 if self.results_tags:
1459 results[constants.INISECT_INS]["tags"] = self.results_tags
1460
1461 results[constants.INISECT_BEP].update(self.results_backend)
1462
1463 results[constants.INISECT_EXP]["os"] = self.results_os["os_name"]
1464 results[constants.INISECT_EXP]["version"] = self.results_version
1465
1466 del self.results_os["os_name"]
1467 results[constants.INISECT_OSP].update(self.results_os)
1468
1469 del self.results_hypervisor["hypervisor_name"]
1470 results[constants.INISECT_HYP].update(self.results_hypervisor)
1471
1472 output_file_name = utils.PathJoin(self.output_dir,
1473 constants.EXPORT_CONF_FILE)
1474
1475 output = []
1476 for section, options in results.iteritems():
1477 output.append("[%s]" % section)
1478 for name, value in options.iteritems():
1479 if value is None:
1480 value = ""
1481 output.append("%s = %s" % (name, value))
1482 output.append("")
1483 output_contents = "\n".join(output)
1484
1485 try:
1486 utils.WriteFile(output_file_name, data=output_contents)
1487 except errors.ProgrammerError, err:
1488 raise errors.OpPrereqError("Saving the config file failed: %s" % err,
1489 errors.ECODE_ENVIRON)
1490
1491 self.Cleanup()
1492
1495 """This is just a wrapper on SafeConfigParser, that uses default values
1496
1497 """
1498 - def get(self, section, options, raw=None, vars=None):
1499 try:
1500 result = ConfigParser.SafeConfigParser.get(self, section, options,
1501 raw=raw, vars=vars)
1502 except ConfigParser.NoOptionError:
1503 result = None
1504 return result
1505
1506 - def getint(self, section, options):
1507 try:
1508 result = ConfigParser.SafeConfigParser.get(self, section, options)
1509 except ConfigParser.NoOptionError:
1510 result = 0
1511 return int(result)
1512
1515 """Converter from Ganeti config file to OVF
1516
1517 @type input_dir: string
1518 @ivar input_dir: directory in which the config.ini file resides
1519 @type output_dir: string
1520 @ivar output_dir: directory to which the results of conversion shall be
1521 written
1522 @type packed_dir: string
1523 @ivar packed_dir: if we want OVA package, this points to the real (i.e. not
1524 temp) output directory
1525 @type input_path: string
1526 @ivar input_path: complete path to the config.ini file
1527 @type output_path: string
1528 @ivar output_path: complete path to .ovf file
1529 @type config_parser: L{ConfigParserWithDefaults}
1530 @ivar config_parser: parser for the config.ini file
1531 @type reference_files: list
1532 @ivar reference_files: files referenced in the ovf file
1533 @type results_disk: list
1534 @ivar results_disk: list of dictionaries of disk options from config.ini
1535 @type results_network: list
1536 @ivar results_network: list of dictionaries of network options form config.ini
1537 @type results_name: string
1538 @ivar results_name: name of the instance
1539 @type results_vcpus: string
1540 @ivar results_vcpus: number of VCPUs
1541 @type results_memory: string
1542 @ivar results_memory: RAM memory in MB
1543 @type results_ganeti: dict
1544 @ivar results_ganeti: dictionary of Ganeti-specific options from config.ini
1545
1546 """
1576
1594
1596 """Parses vcpus number from config file.
1597
1598 @rtype: int
1599 @return: number of virtual CPUs
1600
1601 @raise errors.OpPrereqError: if number of VCPUs equals 0
1602
1603 """
1604 vcpus = self.config_parser.getint(constants.INISECT_BEP, VCPUS)
1605 if vcpus == 0:
1606 raise errors.OpPrereqError("No CPU information found",
1607 errors.ECODE_ENVIRON)
1608 return vcpus
1609
1611 """Parses vcpus number from config file.
1612
1613 @rtype: int
1614 @return: amount of memory in MB
1615
1616 @raise errors.OpPrereqError: if amount of memory equals 0
1617
1618 """
1619 memory = self.config_parser.getint(constants.INISECT_BEP, MEMORY)
1620 if memory == 0:
1621 raise errors.OpPrereqError("No memory information found",
1622 errors.ECODE_ENVIRON)
1623 return memory
1624
1626 """Parses Ganeti data from config file.
1627
1628 @rtype: dictionary
1629 @return: dictionary of Ganeti-specific options
1630
1631 """
1632 results = {}
1633
1634 results["hypervisor"] = {}
1635 hyp_name = self.config_parser.get(constants.INISECT_INS, HYPERV)
1636 if hyp_name is None:
1637 raise errors.OpPrereqError("No hypervisor information found",
1638 errors.ECODE_ENVIRON)
1639 results["hypervisor"]["name"] = hyp_name
1640 pairs = self.config_parser.items(constants.INISECT_HYP)
1641 for (name, value) in pairs:
1642 results["hypervisor"][name] = value
1643
1644 results["os"] = {}
1645 os_name = self.config_parser.get(constants.INISECT_EXP, OS)
1646 if os_name is None:
1647 raise errors.OpPrereqError("No operating system information found",
1648 errors.ECODE_ENVIRON)
1649 results["os"]["name"] = os_name
1650 pairs = self.config_parser.items(constants.INISECT_OSP)
1651 for (name, value) in pairs:
1652 results["os"][name] = value
1653
1654 others = [
1655 (constants.INISECT_INS, DISK_TEMPLATE, "disk_template"),
1656 (constants.INISECT_BEP, AUTO_BALANCE, "auto_balance"),
1657 (constants.INISECT_INS, TAGS, "tags"),
1658 (constants.INISECT_EXP, VERSION, "version"),
1659 ]
1660 for (section, element, name) in others:
1661 results[name] = self.config_parser.get(section, element)
1662 return results
1663
1665 """Parses network data from config file.
1666
1667 @rtype: list
1668 @return: list of dictionaries of network options
1669
1670 @raise errors.OpPrereqError: then network mode is not recognized
1671
1672 """
1673 results = []
1674 counter = 0
1675 while True:
1676 data_link = \
1677 self.config_parser.get(constants.INISECT_INS,
1678 "nic%s_link" % counter)
1679 if data_link is None:
1680 break
1681 results.append({
1682 "mode": self.config_parser.get(constants.INISECT_INS,
1683 "nic%s_mode" % counter),
1684 "mac": self.config_parser.get(constants.INISECT_INS,
1685 "nic%s_mac" % counter),
1686 "ip": self.config_parser.get(constants.INISECT_INS,
1687 "nic%s_ip" % counter),
1688 "network": self.config_parser.get(constants.INISECT_INS,
1689 "nic%s_network" % counter),
1690 "link": data_link,
1691 })
1692 if results[counter]["mode"] not in constants.NIC_VALID_MODES:
1693 raise errors.OpPrereqError("Network mode %s not recognized"
1694 % results[counter]["mode"],
1695 errors.ECODE_INVAL)
1696 counter += 1
1697 return results
1698
1700 """Convert the disk and gather disk info for .ovf file.
1701
1702 @type disk_file: string
1703 @param disk_file: name of the disk (without the full path)
1704 @type compression: bool
1705 @param compression: whether the disk should be compressed or not
1706
1707 @raise errors.OpPrereqError: when disk image does not exist
1708
1709 """
1710 disk_path = utils.PathJoin(self.input_dir, disk_file)
1711 results = {}
1712 if not os.path.isfile(disk_path):
1713 raise errors.OpPrereqError("Disk image does not exist: %s" % disk_path,
1714 errors.ECODE_ENVIRON)
1715 if os.path.dirname(disk_file):
1716 raise errors.OpPrereqError("Path for the disk: %s contains a directory"
1717 " name" % disk_path, errors.ECODE_ENVIRON)
1718 disk_name, _ = os.path.splitext(disk_file)
1719 ext, new_disk_path = self._ConvertDisk(self.options.disk_format, disk_path)
1720 results["format"] = self.options.disk_format
1721 results["virt-size"] = self._GetDiskQemuInfo(
1722 new_disk_path, r"virtual size: \S+ \((\d+) bytes\)")
1723 if compression:
1724 ext2, new_disk_path = self._CompressDisk(new_disk_path, "gzip",
1725 COMPRESS)
1726 disk_name, _ = os.path.splitext(disk_name)
1727 results["compression"] = "gzip"
1728 ext += ext2
1729 final_disk_path = LinkFile(new_disk_path, prefix=disk_name, suffix=ext,
1730 directory=self.output_dir)
1731 final_disk_name = os.path.basename(final_disk_path)
1732 results["real-size"] = os.path.getsize(final_disk_path)
1733 results["path"] = final_disk_name
1734 self.references_files.append(final_disk_path)
1735 return results
1736
1738 """Parses disk data from config file.
1739
1740 @rtype: list
1741 @return: list of dictionaries of disk options
1742
1743 """
1744 results = []
1745 counter = 0
1746 while True:
1747 disk_file = \
1748 self.config_parser.get(constants.INISECT_INS, "disk%s_dump" % counter)
1749 if disk_file is None:
1750 break
1751 results.append(self._GetDiskOptions(disk_file, self.options.compression))
1752 counter += 1
1753 return results
1754
1756 """Parses the data and creates a structure containing all required info.
1757
1758 """
1759 try:
1760 utils.Makedirs(self.output_dir)
1761 except OSError, err:
1762 raise errors.OpPrereqError("Failed to create directory %s: %s" %
1763 (self.output_dir, err), errors.ECODE_ENVIRON)
1764
1765 self.references_files = []
1766 self.results_name = self._ParseName()
1767 self.results_vcpus = self._ParseVCPUs()
1768 self.results_memory = self._ParseMemory()
1769 if not self.options.ext_usage:
1770 self.results_ganeti = self._ParseGaneti()
1771 self.results_network = self._ParseNetworks()
1772 self.results_disk = self._ParseDisks()
1773
1775 """Creates manifest for all the files in OVF package.
1776
1777 @type path: string
1778 @param path: path to manifesto file
1779
1780 @raise errors.OpPrereqError: if error occurs when writing file
1781
1782 """
1783 logging.info("Preparing manifest for the OVF package")
1784 lines = []
1785 files_list = [self.output_path]
1786 files_list.extend(self.references_files)
1787 logging.warning("Calculating SHA1 checksums, this may take a while")
1788 sha1_sums = utils.FingerprintFiles(files_list)
1789 for file_path, value in sha1_sums.iteritems():
1790 file_name = os.path.basename(file_path)
1791 lines.append("SHA1(%s)= %s" % (file_name, value))
1792 lines.append("")
1793 data = "\n".join(lines)
1794 try:
1795 utils.WriteFile(path, data=data)
1796 except errors.ProgrammerError, err:
1797 raise errors.OpPrereqError("Saving the manifest file failed: %s" % err,
1798 errors.ECODE_ENVIRON)
1799
1800 @staticmethod
1802 """Creates tarfile from the files in OVF package.
1803
1804 @type tar_path: string
1805 @param tar_path: path to the resulting file
1806 @type files_list: list
1807 @param files_list: list of files in the OVF package
1808
1809 """
1810 logging.info("Preparing tarball for the OVF package")
1811 open(tar_path, mode="w").close()
1812 ova_package = tarfile.open(name=tar_path, mode="w")
1813 for file_path in files_list:
1814 file_name = os.path.basename(file_path)
1815 ova_package.add(file_path, arcname=file_name)
1816 ova_package.close()
1817
1819 """Saves the gathered configuration in an apropriate format.
1820
1821 @raise errors.OpPrereqError: if unable to create output directory
1822
1823 """
1824 output_file = "%s%s" % (self.results_name, OVF_EXT)
1825 output_path = utils.PathJoin(self.output_dir, output_file)
1826 self.ovf_writer = OVFWriter(not self.options.ext_usage)
1827 logging.info("Saving read data to %s", output_path)
1828
1829 self.output_path = utils.PathJoin(self.output_dir, output_file)
1830 files_list = [self.output_path]
1831
1832 self.ovf_writer.SaveDisksData(self.results_disk)
1833 self.ovf_writer.SaveNetworksData(self.results_network)
1834 if not self.options.ext_usage:
1835 self.ovf_writer.SaveGanetiData(self.results_ganeti, self.results_network)
1836
1837 self.ovf_writer.SaveVirtualSystemData(self.results_name, self.results_vcpus,
1838 self.results_memory)
1839
1840 data = self.ovf_writer.PrettyXmlDump()
1841 utils.WriteFile(self.output_path, data=data)
1842
1843 manifest_file = "%s%s" % (self.results_name, MF_EXT)
1844 manifest_path = utils.PathJoin(self.output_dir, manifest_file)
1845 self._PrepareManifest(manifest_path)
1846 files_list.append(manifest_path)
1847
1848 files_list.extend(self.references_files)
1849
1850 if self.options.ova_package:
1851 ova_file = "%s%s" % (self.results_name, OVA_EXT)
1852 packed_path = utils.PathJoin(self.packed_dir, ova_file)
1853 try:
1854 utils.Makedirs(self.packed_dir)
1855 except OSError, err:
1856 raise errors.OpPrereqError("Failed to create directory %s: %s" %
1857 (self.packed_dir, err),
1858 errors.ECODE_ENVIRON)
1859 self._PrepareTarFile(packed_path, files_list)
1860 logging.info("Creation of the OVF package was successfull")
1861 self.Cleanup()
1862