Statistics
| Branch: | Tag: | Revision:

root / lib / ovf.py @ 6b3f0d7e

History | View | Annotate | Download (66 kB)

1 ced78a66 Agata Murawska
#!/usr/bin/python
2 ced78a66 Agata Murawska
#
3 ced78a66 Agata Murawska
4 2cfbc784 Iustin Pop
# Copyright (C) 2011, 2012 Google Inc.
5 ced78a66 Agata Murawska
#
6 ced78a66 Agata Murawska
# This program is free software; you can redistribute it and/or modify
7 ced78a66 Agata Murawska
# it under the terms of the GNU General Public License as published by
8 ced78a66 Agata Murawska
# the Free Software Foundation; either version 2 of the License, or
9 ced78a66 Agata Murawska
# (at your option) any later version.
10 ced78a66 Agata Murawska
#
11 ced78a66 Agata Murawska
# This program is distributed in the hope that it will be useful, but
12 ced78a66 Agata Murawska
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 ced78a66 Agata Murawska
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 ced78a66 Agata Murawska
# General Public License for more details.
15 ced78a66 Agata Murawska
#
16 ced78a66 Agata Murawska
# You should have received a copy of the GNU General Public License
17 ced78a66 Agata Murawska
# along with this program; if not, write to the Free Software
18 ced78a66 Agata Murawska
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 ced78a66 Agata Murawska
# 02110-1301, USA.
20 ced78a66 Agata Murawska
21 ced78a66 Agata Murawska
22 ced78a66 Agata Murawska
"""Converter tools between ovf and ganeti config file
23 ced78a66 Agata Murawska

24 ced78a66 Agata Murawska
"""
25 ced78a66 Agata Murawska
26 864cf6bf Agata Murawska
# pylint: disable=F0401, E1101
27 864cf6bf Agata Murawska
28 864cf6bf Agata Murawska
# F0401 because ElementTree is not default for python 2.4
29 864cf6bf Agata Murawska
# E1101 makes no sense - pylint assumes that ElementTree object is a tuple
30 864cf6bf Agata Murawska
31 864cf6bf Agata Murawska
32 0963b26a Agata Murawska
import ConfigParser
33 99381e3b Agata Murawska
import errno
34 864cf6bf Agata Murawska
import logging
35 99381e3b Agata Murawska
import os
36 ced78a66 Agata Murawska
import os.path
37 864cf6bf Agata Murawska
import re
38 ced78a66 Agata Murawska
import shutil
39 864cf6bf Agata Murawska
import tarfile
40 864cf6bf Agata Murawska
import tempfile
41 0963b26a Agata Murawska
import xml.dom.minidom
42 864cf6bf Agata Murawska
import xml.parsers.expat
43 864cf6bf Agata Murawska
try:
44 864cf6bf Agata Murawska
  import xml.etree.ElementTree as ET
45 864cf6bf Agata Murawska
except ImportError:
46 864cf6bf Agata Murawska
  import elementtree.ElementTree as ET
47 ced78a66 Agata Murawska
48 d6f8db24 Guido Trotter
try:
49 d6f8db24 Guido Trotter
  ParseError = ET.ParseError # pylint: disable=E1103
50 d6f8db24 Guido Trotter
except AttributeError:
51 d6f8db24 Guido Trotter
  ParseError = None
52 d6f8db24 Guido Trotter
53 864cf6bf Agata Murawska
from ganeti import constants
54 ced78a66 Agata Murawska
from ganeti import errors
55 ced78a66 Agata Murawska
from ganeti import utils
56 fe496b25 Michael Hanselmann
from ganeti import pathutils
57 ced78a66 Agata Murawska
58 ced78a66 Agata Murawska
59 864cf6bf Agata Murawska
# Schemas used in OVF format
60 864cf6bf Agata Murawska
GANETI_SCHEMA = "http://ganeti"
61 864cf6bf Agata Murawska
OVF_SCHEMA = "http://schemas.dmtf.org/ovf/envelope/1"
62 864cf6bf Agata Murawska
RASD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/"
63 864cf6bf Agata Murawska
               "CIM_ResourceAllocationSettingData")
64 0963b26a Agata Murawska
VSSD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/"
65 0963b26a Agata Murawska
               "CIM_VirtualSystemSettingData")
66 0963b26a Agata Murawska
XML_SCHEMA = "http://www.w3.org/2001/XMLSchema-instance"
67 864cf6bf Agata Murawska
68 864cf6bf Agata Murawska
# File extensions in OVF package
69 864cf6bf Agata Murawska
OVA_EXT = ".ova"
70 864cf6bf Agata Murawska
OVF_EXT = ".ovf"
71 864cf6bf Agata Murawska
MF_EXT = ".mf"
72 864cf6bf Agata Murawska
CERT_EXT = ".cert"
73 99381e3b Agata Murawska
COMPRESSION_EXT = ".gz"
74 864cf6bf Agata Murawska
FILE_EXTENSIONS = [
75 864cf6bf Agata Murawska
  OVF_EXT,
76 864cf6bf Agata Murawska
  MF_EXT,
77 864cf6bf Agata Murawska
  CERT_EXT,
78 864cf6bf Agata Murawska
]
79 864cf6bf Agata Murawska
80 99381e3b Agata Murawska
COMPRESSION_TYPE = "gzip"
81 fa337742 Agata Murawska
NO_COMPRESSION = [None, "identity"]
82 99381e3b Agata Murawska
COMPRESS = "compression"
83 99381e3b Agata Murawska
DECOMPRESS = "decompression"
84 99381e3b Agata Murawska
ALLOWED_ACTIONS = [COMPRESS, DECOMPRESS]
85 99381e3b Agata Murawska
86 fa337742 Agata Murawska
VMDK = "vmdk"
87 fa337742 Agata Murawska
RAW = "raw"
88 fa337742 Agata Murawska
COW = "cow"
89 fa337742 Agata Murawska
ALLOWED_FORMATS = [RAW, COW, VMDK]
90 fa337742 Agata Murawska
91 7bde29b5 Agata Murawska
# ResourceType values
92 7bde29b5 Agata Murawska
RASD_TYPE = {
93 7bde29b5 Agata Murawska
  "vcpus": "3",
94 7bde29b5 Agata Murawska
  "memory": "4",
95 7432d332 Agata Murawska
  "scsi-controller": "6",
96 7432d332 Agata Murawska
  "ethernet-adapter": "10",
97 7432d332 Agata Murawska
  "disk": "17",
98 7bde29b5 Agata Murawska
}
99 7bde29b5 Agata Murawska
100 fa337742 Agata Murawska
SCSI_SUBTYPE = "lsilogic"
101 fa337742 Agata Murawska
VS_TYPE = {
102 fa337742 Agata Murawska
  "ganeti": "ganeti-ovf",
103 fa337742 Agata Murawska
  "external": "vmx-04",
104 fa337742 Agata Murawska
}
105 fa337742 Agata Murawska
106 7bde29b5 Agata Murawska
# AllocationUnits values and conversion
107 7bde29b5 Agata Murawska
ALLOCATION_UNITS = {
108 3ccb3a64 Michael Hanselmann
  "b": ["bytes", "b"],
109 3ccb3a64 Michael Hanselmann
  "kb": ["kilobytes", "kb", "byte * 2^10", "kibibytes", "kib"],
110 3ccb3a64 Michael Hanselmann
  "mb": ["megabytes", "mb", "byte * 2^20", "mebibytes", "mib"],
111 3ccb3a64 Michael Hanselmann
  "gb": ["gigabytes", "gb", "byte * 2^30", "gibibytes", "gib"],
112 7bde29b5 Agata Murawska
}
113 7bde29b5 Agata Murawska
CONVERT_UNITS_TO_MB = {
114 3ccb3a64 Michael Hanselmann
  "b": lambda x: x / (1024 * 1024),
115 3ccb3a64 Michael Hanselmann
  "kb": lambda x: x / 1024,
116 3ccb3a64 Michael Hanselmann
  "mb": lambda x: x,
117 3ccb3a64 Michael Hanselmann
  "gb": lambda x: x * 1024,
118 7bde29b5 Agata Murawska
}
119 7bde29b5 Agata Murawska
120 0963b26a Agata Murawska
# Names of the config fields
121 0963b26a Agata Murawska
NAME = "name"
122 b179ce72 Agata Murawska
OS = "os"
123 b179ce72 Agata Murawska
HYPERV = "hypervisor"
124 b179ce72 Agata Murawska
VCPUS = "vcpus"
125 b179ce72 Agata Murawska
MEMORY = "memory"
126 b179ce72 Agata Murawska
AUTO_BALANCE = "auto_balance"
127 b179ce72 Agata Murawska
DISK_TEMPLATE = "disk_template"
128 b179ce72 Agata Murawska
TAGS = "tags"
129 b179ce72 Agata Murawska
VERSION = "version"
130 0963b26a Agata Murawska
131 7432d332 Agata Murawska
# Instance IDs of System and SCSI controller
132 fa337742 Agata Murawska
INSTANCE_ID = {
133 fa337742 Agata Murawska
  "system": 0,
134 fa337742 Agata Murawska
  "vcpus": 1,
135 fa337742 Agata Murawska
  "memory": 2,
136 fa337742 Agata Murawska
  "scsi": 3,
137 fa337742 Agata Murawska
}
138 7432d332 Agata Murawska
139 7432d332 Agata Murawska
# Disk format descriptions
140 7432d332 Agata Murawska
DISK_FORMAT = {
141 fa337742 Agata Murawska
  RAW: "http://en.wikipedia.org/wiki/Byte",
142 fa337742 Agata Murawska
  VMDK: "http://www.vmware.com/interfaces/specifications/vmdk.html"
143 7432d332 Agata Murawska
          "#monolithicSparse",
144 fa337742 Agata Murawska
  COW: "http://www.gnome.org/~markmc/qcow-image-format.html",
145 7432d332 Agata Murawska
}
146 7432d332 Agata Murawska
147 487895ed Agata Murawska
148 a002ed79 Agata Murawska
def CheckQemuImg():
149 a002ed79 Agata Murawska
  """ Make sure that qemu-img is present before performing operations.
150 a002ed79 Agata Murawska

151 a002ed79 Agata Murawska
  @raise errors.OpPrereqError: when qemu-img was not found in the system
152 a002ed79 Agata Murawska

153 a002ed79 Agata Murawska
  """
154 a002ed79 Agata Murawska
  if not constants.QEMUIMG_PATH:
155 a002ed79 Agata Murawska
    raise errors.OpPrereqError("qemu-img not found at build time, unable"
156 2cfbc784 Iustin Pop
                               " to continue", errors.ECODE_STATE)
157 99381e3b Agata Murawska
158 487895ed Agata Murawska
159 99381e3b Agata Murawska
def LinkFile(old_path, prefix=None, suffix=None, directory=None):
160 99381e3b Agata Murawska
  """Create link with a given prefix and suffix.
161 99381e3b Agata Murawska

162 99381e3b Agata Murawska
  This is a wrapper over os.link. It tries to create a hard link for given file,
163 99381e3b Agata Murawska
  but instead of rising error when file exists, the function changes the name
164 99381e3b Agata Murawska
  a little bit.
165 99381e3b Agata Murawska

166 99381e3b Agata Murawska
  @type old_path:string
167 99381e3b Agata Murawska
  @param old_path: path to the file that is to be linked
168 99381e3b Agata Murawska
  @type prefix: string
169 99381e3b Agata Murawska
  @param prefix: prefix of filename for the link
170 99381e3b Agata Murawska
  @type suffix: string
171 99381e3b Agata Murawska
  @param suffix: suffix of the filename for the link
172 99381e3b Agata Murawska
  @type directory: string
173 99381e3b Agata Murawska
  @param directory: directory of the link
174 99381e3b Agata Murawska

175 99381e3b Agata Murawska
  @raise errors.OpPrereqError: when error on linking is different than
176 99381e3b Agata Murawska
    "File exists"
177 99381e3b Agata Murawska

178 99381e3b Agata Murawska
  """
179 99381e3b Agata Murawska
  assert(prefix is not None or suffix is not None)
180 99381e3b Agata Murawska
  if directory is None:
181 99381e3b Agata Murawska
    directory = os.getcwd()
182 99381e3b Agata Murawska
  new_path = utils.PathJoin(directory, "%s%s" % (prefix, suffix))
183 99381e3b Agata Murawska
  counter = 1
184 99381e3b Agata Murawska
  while True:
185 99381e3b Agata Murawska
    try:
186 99381e3b Agata Murawska
      os.link(old_path, new_path)
187 99381e3b Agata Murawska
      break
188 99381e3b Agata Murawska
    except OSError, err:
189 99381e3b Agata Murawska
      if err.errno == errno.EEXIST:
190 99381e3b Agata Murawska
        new_path = utils.PathJoin(directory,
191 5ae4945a Iustin Pop
                                  "%s_%s%s" % (prefix, counter, suffix))
192 99381e3b Agata Murawska
        counter += 1
193 99381e3b Agata Murawska
      else:
194 99381e3b Agata Murawska
        raise errors.OpPrereqError("Error moving the file %s to %s location:"
195 2cfbc784 Iustin Pop
                                   " %s" % (old_path, new_path, err),
196 2cfbc784 Iustin Pop
                                   errors.ECODE_ENVIRON)
197 99381e3b Agata Murawska
  return new_path
198 99381e3b Agata Murawska
199 864cf6bf Agata Murawska
200 864cf6bf Agata Murawska
class OVFReader(object):
201 864cf6bf Agata Murawska
  """Reader class for OVF files.
202 864cf6bf Agata Murawska

203 864cf6bf Agata Murawska
  @type files_list: list
204 864cf6bf Agata Murawska
  @ivar files_list: list of files in the OVF package
205 864cf6bf Agata Murawska
  @type tree: ET.ElementTree
206 864cf6bf Agata Murawska
  @ivar tree: XML tree of the .ovf file
207 864cf6bf Agata Murawska
  @type schema_name: string
208 864cf6bf Agata Murawska
  @ivar schema_name: name of the .ovf file
209 864cf6bf Agata Murawska
  @type input_dir: string
210 864cf6bf Agata Murawska
  @ivar input_dir: directory in which the .ovf file resides
211 864cf6bf Agata Murawska

212 864cf6bf Agata Murawska
  """
213 864cf6bf Agata Murawska
  def __init__(self, input_path):
214 864cf6bf Agata Murawska
    """Initialiaze the reader - load the .ovf file to XML parser.
215 864cf6bf Agata Murawska

216 864cf6bf Agata Murawska
    It is assumed that names of manifesto (.mf), certificate (.cert) and ovf
217 864cf6bf Agata Murawska
    files are the same. In order to account any other files as part of the ovf
218 864cf6bf Agata Murawska
    package, they have to be explicitly mentioned in the Resources section
219 864cf6bf Agata Murawska
    of the .ovf file.
220 864cf6bf Agata Murawska

221 864cf6bf Agata Murawska
    @type input_path: string
222 864cf6bf Agata Murawska
    @param input_path: absolute path to the .ovf file
223 864cf6bf Agata Murawska

224 864cf6bf Agata Murawska
    @raise errors.OpPrereqError: when .ovf file is not a proper XML file or some
225 864cf6bf Agata Murawska
      of the files mentioned in Resources section do not exist
226 864cf6bf Agata Murawska

227 864cf6bf Agata Murawska
    """
228 864cf6bf Agata Murawska
    self.tree = ET.ElementTree()
229 864cf6bf Agata Murawska
    try:
230 864cf6bf Agata Murawska
      self.tree.parse(input_path)
231 d6f8db24 Guido Trotter
    except (ParseError, xml.parsers.expat.ExpatError), err:
232 864cf6bf Agata Murawska
      raise errors.OpPrereqError("Error while reading %s file: %s" %
233 2cfbc784 Iustin Pop
                                 (OVF_EXT, err), errors.ECODE_ENVIRON)
234 864cf6bf Agata Murawska
235 864cf6bf Agata Murawska
    # Create a list of all files in the OVF package
236 864cf6bf Agata Murawska
    (input_dir, input_file) = os.path.split(input_path)
237 864cf6bf Agata Murawska
    (input_name, _) = os.path.splitext(input_file)
238 864cf6bf Agata Murawska
    files_directory = utils.ListVisibleFiles(input_dir)
239 864cf6bf Agata Murawska
    files_list = []
240 864cf6bf Agata Murawska
    for file_name in files_directory:
241 864cf6bf Agata Murawska
      (name, extension) = os.path.splitext(file_name)
242 864cf6bf Agata Murawska
      if extension in FILE_EXTENSIONS and name == input_name:
243 864cf6bf Agata Murawska
        files_list.append(file_name)
244 864cf6bf Agata Murawska
    files_list += self._GetAttributes("{%s}References/{%s}File" %
245 864cf6bf Agata Murawska
                                      (OVF_SCHEMA, OVF_SCHEMA),
246 864cf6bf Agata Murawska
                                      "{%s}href" % OVF_SCHEMA)
247 864cf6bf Agata Murawska
    for file_name in files_list:
248 864cf6bf Agata Murawska
      file_path = utils.PathJoin(input_dir, file_name)
249 864cf6bf Agata Murawska
      if not os.path.exists(file_path):
250 2cfbc784 Iustin Pop
        raise errors.OpPrereqError("File does not exist: %s" % file_path,
251 2cfbc784 Iustin Pop
                                   errors.ECODE_ENVIRON)
252 864cf6bf Agata Murawska
    logging.info("Files in the OVF package: %s", " ".join(files_list))
253 864cf6bf Agata Murawska
    self.files_list = files_list
254 864cf6bf Agata Murawska
    self.input_dir = input_dir
255 864cf6bf Agata Murawska
    self.schema_name = input_name
256 864cf6bf Agata Murawska
257 864cf6bf Agata Murawska
  def _GetAttributes(self, path, attribute):
258 864cf6bf Agata Murawska
    """Get specified attribute from all nodes accessible using given path.
259 864cf6bf Agata Murawska

260 864cf6bf Agata Murawska
    Function follows the path from root node to the desired tags using path,
261 864cf6bf Agata Murawska
    then reads the apropriate attribute values.
262 864cf6bf Agata Murawska

263 864cf6bf Agata Murawska
    @type path: string
264 864cf6bf Agata Murawska
    @param path: path of nodes to visit
265 864cf6bf Agata Murawska
    @type attribute: string
266 864cf6bf Agata Murawska
    @param attribute: attribute for which we gather the information
267 864cf6bf Agata Murawska
    @rtype: list
268 864cf6bf Agata Murawska
    @return: for each accessible tag with the attribute value set, value of the
269 864cf6bf Agata Murawska
      attribute
270 864cf6bf Agata Murawska

271 864cf6bf Agata Murawska
    """
272 864cf6bf Agata Murawska
    current_list = self.tree.findall(path)
273 864cf6bf Agata Murawska
    results = [x.get(attribute) for x in current_list]
274 864cf6bf Agata Murawska
    return filter(None, results)
275 864cf6bf Agata Murawska
276 99381e3b Agata Murawska
  def _GetElementMatchingAttr(self, path, match_attr):
277 99381e3b Agata Murawska
    """Searches for element on a path that matches certain attribute value.
278 99381e3b Agata Murawska

279 99381e3b Agata Murawska
    Function follows the path from root node to the desired tags using path,
280 99381e3b Agata Murawska
    then searches for the first one matching the attribute value.
281 99381e3b Agata Murawska

282 99381e3b Agata Murawska
    @type path: string
283 99381e3b Agata Murawska
    @param path: path of nodes to visit
284 99381e3b Agata Murawska
    @type match_attr: tuple
285 99381e3b Agata Murawska
    @param match_attr: pair (attribute, value) for which we search
286 99381e3b Agata Murawska
    @rtype: ET.ElementTree or None
287 99381e3b Agata Murawska
    @return: first element matching match_attr or None if nothing matches
288 99381e3b Agata Murawska

289 99381e3b Agata Murawska
    """
290 99381e3b Agata Murawska
    potential_elements = self.tree.findall(path)
291 99381e3b Agata Murawska
    (attr, val) = match_attr
292 99381e3b Agata Murawska
    for elem in potential_elements:
293 99381e3b Agata Murawska
      if elem.get(attr) == val:
294 99381e3b Agata Murawska
        return elem
295 99381e3b Agata Murawska
    return None
296 99381e3b Agata Murawska
297 24b9469d Agata Murawska
  def _GetElementMatchingText(self, path, match_text):
298 24b9469d Agata Murawska
    """Searches for element on a path that matches certain text value.
299 24b9469d Agata Murawska

300 24b9469d Agata Murawska
    Function follows the path from root node to the desired tags using path,
301 24b9469d Agata Murawska
    then searches for the first one matching the text value.
302 24b9469d Agata Murawska

303 24b9469d Agata Murawska
    @type path: string
304 24b9469d Agata Murawska
    @param path: path of nodes to visit
305 24b9469d Agata Murawska
    @type match_text: tuple
306 24b9469d Agata Murawska
    @param match_text: pair (node, text) for which we search
307 24b9469d Agata Murawska
    @rtype: ET.ElementTree or None
308 24b9469d Agata Murawska
    @return: first element matching match_text or None if nothing matches
309 24b9469d Agata Murawska

310 24b9469d Agata Murawska
    """
311 24b9469d Agata Murawska
    potential_elements = self.tree.findall(path)
312 24b9469d Agata Murawska
    (node, text) = match_text
313 24b9469d Agata Murawska
    for elem in potential_elements:
314 24b9469d Agata Murawska
      if elem.findtext(node) == text:
315 24b9469d Agata Murawska
        return elem
316 24b9469d Agata Murawska
    return None
317 24b9469d Agata Murawska
318 99381e3b Agata Murawska
  @staticmethod
319 99381e3b Agata Murawska
  def _GetDictParameters(root, schema):
320 99381e3b Agata Murawska
    """Reads text in all children and creates the dictionary from the contents.
321 99381e3b Agata Murawska

322 99381e3b Agata Murawska
    @type root: ET.ElementTree or None
323 99381e3b Agata Murawska
    @param root: father of the nodes we want to collect data about
324 99381e3b Agata Murawska
    @type schema: string
325 99381e3b Agata Murawska
    @param schema: schema name to be removed from the tag
326 99381e3b Agata Murawska
    @rtype: dict
327 99381e3b Agata Murawska
    @return: dictionary containing tags and their text contents, tags have their
328 99381e3b Agata Murawska
      schema fragment removed or empty dictionary, when root is None
329 99381e3b Agata Murawska

330 99381e3b Agata Murawska
    """
331 99381e3b Agata Murawska
    if not root:
332 99381e3b Agata Murawska
      return {}
333 99381e3b Agata Murawska
    results = {}
334 99381e3b Agata Murawska
    for element in list(root):
335 99381e3b Agata Murawska
      pref_len = len("{%s}" % schema)
336 99381e3b Agata Murawska
      assert(schema in element.tag)
337 99381e3b Agata Murawska
      tag = element.tag[pref_len:]
338 99381e3b Agata Murawska
      results[tag] = element.text
339 99381e3b Agata Murawska
    return results
340 99381e3b Agata Murawska
341 864cf6bf Agata Murawska
  def VerifyManifest(self):
342 864cf6bf Agata Murawska
    """Verifies manifest for the OVF package, if one is given.
343 864cf6bf Agata Murawska

344 864cf6bf Agata Murawska
    @raise errors.OpPrereqError: if SHA1 checksums do not match
345 864cf6bf Agata Murawska

346 864cf6bf Agata Murawska
    """
347 864cf6bf Agata Murawska
    if "%s%s" % (self.schema_name, MF_EXT) in self.files_list:
348 864cf6bf Agata Murawska
      logging.warning("Verifying SHA1 checksums, this may take a while")
349 864cf6bf Agata Murawska
      manifest_filename = "%s%s" % (self.schema_name, MF_EXT)
350 864cf6bf Agata Murawska
      manifest_path = utils.PathJoin(self.input_dir, manifest_filename)
351 864cf6bf Agata Murawska
      manifest_content = utils.ReadFile(manifest_path).splitlines()
352 864cf6bf Agata Murawska
      manifest_files = {}
353 864cf6bf Agata Murawska
      regexp = r"SHA1\((\S+)\)= (\S+)"
354 864cf6bf Agata Murawska
      for line in manifest_content:
355 864cf6bf Agata Murawska
        match = re.match(regexp, line)
356 864cf6bf Agata Murawska
        if match:
357 864cf6bf Agata Murawska
          file_name = match.group(1)
358 864cf6bf Agata Murawska
          sha1_sum = match.group(2)
359 864cf6bf Agata Murawska
          manifest_files[file_name] = sha1_sum
360 864cf6bf Agata Murawska
      files_with_paths = [utils.PathJoin(self.input_dir, file_name)
361 5ae4945a Iustin Pop
                          for file_name in self.files_list]
362 864cf6bf Agata Murawska
      sha1_sums = utils.FingerprintFiles(files_with_paths)
363 864cf6bf Agata Murawska
      for file_name, value in manifest_files.iteritems():
364 864cf6bf Agata Murawska
        if sha1_sums.get(utils.PathJoin(self.input_dir, file_name)) != value:
365 864cf6bf Agata Murawska
          raise errors.OpPrereqError("SHA1 checksum of %s does not match the"
366 2cfbc784 Iustin Pop
                                     " value in manifest file" % file_name,
367 2cfbc784 Iustin Pop
                                     errors.ECODE_ENVIRON)
368 864cf6bf Agata Murawska
      logging.info("SHA1 checksums verified")
369 864cf6bf Agata Murawska
370 99381e3b Agata Murawska
  def GetInstanceName(self):
371 99381e3b Agata Murawska
    """Provides information about instance name.
372 99381e3b Agata Murawska

373 99381e3b Agata Murawska
    @rtype: string
374 99381e3b Agata Murawska
    @return: instance name string
375 99381e3b Agata Murawska

376 99381e3b Agata Murawska
    """
377 99381e3b Agata Murawska
    find_name = "{%s}VirtualSystem/{%s}Name" % (OVF_SCHEMA, OVF_SCHEMA)
378 99381e3b Agata Murawska
    return self.tree.findtext(find_name)
379 99381e3b Agata Murawska
380 99381e3b Agata Murawska
  def GetDiskTemplate(self):
381 99381e3b Agata Murawska
    """Returns disk template from .ovf file
382 99381e3b Agata Murawska

383 99381e3b Agata Murawska
    @rtype: string or None
384 99381e3b Agata Murawska
    @return: name of the template
385 99381e3b Agata Murawska
    """
386 99381e3b Agata Murawska
    find_template = ("{%s}GanetiSection/{%s}DiskTemplate" %
387 99381e3b Agata Murawska
                     (GANETI_SCHEMA, GANETI_SCHEMA))
388 99381e3b Agata Murawska
    return self.tree.findtext(find_template)
389 99381e3b Agata Murawska
390 7bde29b5 Agata Murawska
  def GetHypervisorData(self):
391 7bde29b5 Agata Murawska
    """Provides hypervisor information - hypervisor name and options.
392 7bde29b5 Agata Murawska

393 7bde29b5 Agata Murawska
    @rtype: dict
394 7bde29b5 Agata Murawska
    @return: dictionary containing name of the used hypervisor and all the
395 7bde29b5 Agata Murawska
      specified options
396 7bde29b5 Agata Murawska

397 7bde29b5 Agata Murawska
    """
398 7bde29b5 Agata Murawska
    hypervisor_search = ("{%s}GanetiSection/{%s}Hypervisor" %
399 7bde29b5 Agata Murawska
                         (GANETI_SCHEMA, GANETI_SCHEMA))
400 7bde29b5 Agata Murawska
    hypervisor_data = self.tree.find(hypervisor_search)
401 7bde29b5 Agata Murawska
    if not hypervisor_data:
402 7bde29b5 Agata Murawska
      return {"hypervisor_name": constants.VALUE_AUTO}
403 7bde29b5 Agata Murawska
    results = {
404 7bde29b5 Agata Murawska
      "hypervisor_name": hypervisor_data.findtext("{%s}Name" % GANETI_SCHEMA,
405 5ae4945a Iustin Pop
                                                  default=constants.VALUE_AUTO),
406 7bde29b5 Agata Murawska
    }
407 7bde29b5 Agata Murawska
    parameters = hypervisor_data.find("{%s}Parameters" % GANETI_SCHEMA)
408 7bde29b5 Agata Murawska
    results.update(self._GetDictParameters(parameters, GANETI_SCHEMA))
409 7bde29b5 Agata Murawska
    return results
410 7bde29b5 Agata Murawska
411 7bde29b5 Agata Murawska
  def GetOSData(self):
412 7bde29b5 Agata Murawska
    """ Provides operating system information - os name and options.
413 7bde29b5 Agata Murawska

414 7bde29b5 Agata Murawska
    @rtype: dict
415 7bde29b5 Agata Murawska
    @return: dictionary containing name and options for the chosen OS
416 7bde29b5 Agata Murawska

417 7bde29b5 Agata Murawska
    """
418 7bde29b5 Agata Murawska
    results = {}
419 7bde29b5 Agata Murawska
    os_search = ("{%s}GanetiSection/{%s}OperatingSystem" %
420 7bde29b5 Agata Murawska
                 (GANETI_SCHEMA, GANETI_SCHEMA))
421 7bde29b5 Agata Murawska
    os_data = self.tree.find(os_search)
422 7bde29b5 Agata Murawska
    if os_data:
423 7bde29b5 Agata Murawska
      results["os_name"] = os_data.findtext("{%s}Name" % GANETI_SCHEMA)
424 7bde29b5 Agata Murawska
      parameters = os_data.find("{%s}Parameters" % GANETI_SCHEMA)
425 7bde29b5 Agata Murawska
      results.update(self._GetDictParameters(parameters, GANETI_SCHEMA))
426 7bde29b5 Agata Murawska
    return results
427 7bde29b5 Agata Murawska
428 7bde29b5 Agata Murawska
  def GetBackendData(self):
429 7bde29b5 Agata Murawska
    """ Provides backend information - vcpus, memory, auto balancing options.
430 7bde29b5 Agata Murawska

431 7bde29b5 Agata Murawska
    @rtype: dict
432 7bde29b5 Agata Murawska
    @return: dictionary containing options for vcpus, memory and auto balance
433 7bde29b5 Agata Murawska
      settings
434 7bde29b5 Agata Murawska

435 7bde29b5 Agata Murawska
    """
436 7bde29b5 Agata Murawska
    results = {}
437 7bde29b5 Agata Murawska
438 7bde29b5 Agata Murawska
    find_vcpus = ("{%s}VirtualSystem/{%s}VirtualHardwareSection/{%s}Item" %
439 7bde29b5 Agata Murawska
                   (OVF_SCHEMA, OVF_SCHEMA, OVF_SCHEMA))
440 7bde29b5 Agata Murawska
    match_vcpus = ("{%s}ResourceType" % RASD_SCHEMA, RASD_TYPE["vcpus"])
441 7bde29b5 Agata Murawska
    vcpus = self._GetElementMatchingText(find_vcpus, match_vcpus)
442 7bde29b5 Agata Murawska
    if vcpus:
443 7bde29b5 Agata Murawska
      vcpus_count = vcpus.findtext("{%s}VirtualQuantity" % RASD_SCHEMA,
444 5ae4945a Iustin Pop
                                   default=constants.VALUE_AUTO)
445 7bde29b5 Agata Murawska
    else:
446 7bde29b5 Agata Murawska
      vcpus_count = constants.VALUE_AUTO
447 7bde29b5 Agata Murawska
    results["vcpus"] = str(vcpus_count)
448 7bde29b5 Agata Murawska
449 7bde29b5 Agata Murawska
    find_memory = find_vcpus
450 7bde29b5 Agata Murawska
    match_memory = ("{%s}ResourceType" % RASD_SCHEMA, RASD_TYPE["memory"])
451 7bde29b5 Agata Murawska
    memory = self._GetElementMatchingText(find_memory, match_memory)
452 7bde29b5 Agata Murawska
    memory_raw = None
453 7bde29b5 Agata Murawska
    if memory:
454 7bde29b5 Agata Murawska
      alloc_units = memory.findtext("{%s}AllocationUnits" % RASD_SCHEMA)
455 5ae4945a Iustin Pop
      matching_units = [units for units, variants in ALLOCATION_UNITS.items()
456 5ae4945a Iustin Pop
                        if alloc_units.lower() in variants]
457 7bde29b5 Agata Murawska
      if matching_units == []:
458 2cfbc784 Iustin Pop
        raise errors.OpPrereqError("Unit %s for RAM memory unknown" %
459 2cfbc784 Iustin Pop
                                   alloc_units, errors.ECODE_INVAL)
460 7bde29b5 Agata Murawska
      units = matching_units[0]
461 7bde29b5 Agata Murawska
      memory_raw = int(memory.findtext("{%s}VirtualQuantity" % RASD_SCHEMA,
462 5ae4945a Iustin Pop
                                       default=constants.VALUE_AUTO))
463 7bde29b5 Agata Murawska
      memory_count = CONVERT_UNITS_TO_MB[units](memory_raw)
464 7bde29b5 Agata Murawska
    else:
465 7bde29b5 Agata Murawska
      memory_count = constants.VALUE_AUTO
466 7bde29b5 Agata Murawska
    results["memory"] = str(memory_count)
467 7bde29b5 Agata Murawska
468 7bde29b5 Agata Murawska
    find_balance = ("{%s}GanetiSection/{%s}AutoBalance" %
469 5ae4945a Iustin Pop
                    (GANETI_SCHEMA, GANETI_SCHEMA))
470 7bde29b5 Agata Murawska
    balance = self.tree.findtext(find_balance, default=constants.VALUE_AUTO)
471 7bde29b5 Agata Murawska
    results["auto_balance"] = balance
472 7bde29b5 Agata Murawska
473 7bde29b5 Agata Murawska
    return results
474 7bde29b5 Agata Murawska
475 7bde29b5 Agata Murawska
  def GetTagsData(self):
476 7bde29b5 Agata Murawska
    """Provides tags information for instance.
477 7bde29b5 Agata Murawska

478 7bde29b5 Agata Murawska
    @rtype: string or None
479 7bde29b5 Agata Murawska
    @return: string of comma-separated tags for the instance
480 7bde29b5 Agata Murawska

481 7bde29b5 Agata Murawska
    """
482 7bde29b5 Agata Murawska
    find_tags = "{%s}GanetiSection/{%s}Tags" % (GANETI_SCHEMA, GANETI_SCHEMA)
483 7bde29b5 Agata Murawska
    results = self.tree.findtext(find_tags)
484 7bde29b5 Agata Murawska
    if results:
485 7bde29b5 Agata Murawska
      return results
486 7bde29b5 Agata Murawska
    else:
487 7bde29b5 Agata Murawska
      return None
488 7bde29b5 Agata Murawska
489 7bde29b5 Agata Murawska
  def GetVersionData(self):
490 7bde29b5 Agata Murawska
    """Provides version number read from .ovf file
491 7bde29b5 Agata Murawska

492 7bde29b5 Agata Murawska
    @rtype: string
493 7bde29b5 Agata Murawska
    @return: string containing the version number
494 7bde29b5 Agata Murawska

495 7bde29b5 Agata Murawska
    """
496 7bde29b5 Agata Murawska
    find_version = ("{%s}GanetiSection/{%s}Version" %
497 7bde29b5 Agata Murawska
                    (GANETI_SCHEMA, GANETI_SCHEMA))
498 7bde29b5 Agata Murawska
    return self.tree.findtext(find_version)
499 7bde29b5 Agata Murawska
500 24b9469d Agata Murawska
  def GetNetworkData(self):
501 24b9469d Agata Murawska
    """Provides data about the network in the OVF instance.
502 24b9469d Agata Murawska

503 24b9469d Agata Murawska
    The method gathers the data about networks used by OVF instance. It assumes
504 24b9469d Agata Murawska
    that 'name' tag means something - in essence, if it contains one of the
505 24b9469d Agata Murawska
    words 'bridged' or 'routed' then that will be the mode of this network in
506 24b9469d Agata Murawska
    Ganeti. The information about the network can be either in GanetiSection or
507 24b9469d Agata Murawska
    VirtualHardwareSection.
508 24b9469d Agata Murawska

509 24b9469d Agata Murawska
    @rtype: dict
510 24b9469d Agata Murawska
    @return: dictionary containing all the network information
511 24b9469d Agata Murawska

512 24b9469d Agata Murawska
    """
513 24b9469d Agata Murawska
    results = {}
514 24b9469d Agata Murawska
    networks_search = ("{%s}NetworkSection/{%s}Network" %
515 24b9469d Agata Murawska
                       (OVF_SCHEMA, OVF_SCHEMA))
516 24b9469d Agata Murawska
    network_names = self._GetAttributes(networks_search,
517 5ae4945a Iustin Pop
                                        "{%s}name" % OVF_SCHEMA)
518 a5ad5e58 Apollon Oikonomopoulos
    required = ["ip", "mac", "link", "mode", "network"]
519 24b9469d Agata Murawska
    for (counter, network_name) in enumerate(network_names):
520 24b9469d Agata Murawska
      network_search = ("{%s}VirtualSystem/{%s}VirtualHardwareSection/{%s}Item"
521 24b9469d Agata Murawska
                        % (OVF_SCHEMA, OVF_SCHEMA, OVF_SCHEMA))
522 24b9469d Agata Murawska
      ganeti_search = ("{%s}GanetiSection/{%s}Network/{%s}Nic" %
523 24b9469d Agata Murawska
                       (GANETI_SCHEMA, GANETI_SCHEMA, GANETI_SCHEMA))
524 24b9469d Agata Murawska
      network_match = ("{%s}Connection" % RASD_SCHEMA, network_name)
525 24b9469d Agata Murawska
      ganeti_match = ("{%s}name" % OVF_SCHEMA, network_name)
526 24b9469d Agata Murawska
      network_data = self._GetElementMatchingText(network_search, network_match)
527 24b9469d Agata Murawska
      network_ganeti_data = self._GetElementMatchingAttr(ganeti_search,
528 5ae4945a Iustin Pop
                                                         ganeti_match)
529 24b9469d Agata Murawska
530 24b9469d Agata Murawska
      ganeti_data = {}
531 24b9469d Agata Murawska
      if network_ganeti_data:
532 24b9469d Agata Murawska
        ganeti_data["mode"] = network_ganeti_data.findtext("{%s}Mode" %
533 24b9469d Agata Murawska
                                                           GANETI_SCHEMA)
534 24b9469d Agata Murawska
        ganeti_data["mac"] = network_ganeti_data.findtext("{%s}MACAddress" %
535 24b9469d Agata Murawska
                                                          GANETI_SCHEMA)
536 24b9469d Agata Murawska
        ganeti_data["ip"] = network_ganeti_data.findtext("{%s}IPAddress" %
537 24b9469d Agata Murawska
                                                         GANETI_SCHEMA)
538 24b9469d Agata Murawska
        ganeti_data["link"] = network_ganeti_data.findtext("{%s}Link" %
539 24b9469d Agata Murawska
                                                           GANETI_SCHEMA)
540 08ed6998 Dimitris Aragiorgis
        ganeti_data["network"] = network_ganeti_data.findtext("{%s}Net" %
541 a5ad5e58 Apollon Oikonomopoulos
                                                              GANETI_SCHEMA)
542 24b9469d Agata Murawska
      mac_data = None
543 24b9469d Agata Murawska
      if network_data:
544 24b9469d Agata Murawska
        mac_data = network_data.findtext("{%s}Address" % RASD_SCHEMA)
545 24b9469d Agata Murawska
546 24b9469d Agata Murawska
      network_name = network_name.lower()
547 24b9469d Agata Murawska
548 24b9469d Agata Murawska
      # First, some not Ganeti-specific information is collected
549 24b9469d Agata Murawska
      if constants.NIC_MODE_BRIDGED in network_name:
550 24b9469d Agata Murawska
        results["nic%s_mode" % counter] = "bridged"
551 24b9469d Agata Murawska
      elif constants.NIC_MODE_ROUTED in network_name:
552 24b9469d Agata Murawska
        results["nic%s_mode" % counter] = "routed"
553 24b9469d Agata Murawska
      results["nic%s_mac" % counter] = mac_data
554 24b9469d Agata Murawska
555 24b9469d Agata Murawska
      # GanetiSection data overrides 'manually' collected data
556 24b9469d Agata Murawska
      for name, value in ganeti_data.iteritems():
557 24b9469d Agata Murawska
        results["nic%s_%s" % (counter, name)] = value
558 24b9469d Agata Murawska
559 24b9469d Agata Murawska
      # Bridged network has no IP - unless specifically stated otherwise
560 24b9469d Agata Murawska
      if (results.get("nic%s_mode" % counter) == "bridged" and
561 24b9469d Agata Murawska
          not results.get("nic%s_ip" % counter)):
562 24b9469d Agata Murawska
        results["nic%s_ip" % counter] = constants.VALUE_NONE
563 24b9469d Agata Murawska
564 24b9469d Agata Murawska
      for option in required:
565 24b9469d Agata Murawska
        if not results.get("nic%s_%s" % (counter, option)):
566 24b9469d Agata Murawska
          results["nic%s_%s" % (counter, option)] = constants.VALUE_AUTO
567 24b9469d Agata Murawska
568 24b9469d Agata Murawska
    if network_names:
569 24b9469d Agata Murawska
      results["nic_count"] = str(len(network_names))
570 24b9469d Agata Murawska
    return results
571 24b9469d Agata Murawska
572 99381e3b Agata Murawska
  def GetDisksNames(self):
573 99381e3b Agata Murawska
    """Provides list of file names for the disks used by the instance.
574 99381e3b Agata Murawska

575 99381e3b Agata Murawska
    @rtype: list
576 99381e3b Agata Murawska
    @return: list of file names, as referenced in .ovf file
577 99381e3b Agata Murawska

578 99381e3b Agata Murawska
    """
579 99381e3b Agata Murawska
    results = []
580 99381e3b Agata Murawska
    disks_search = "{%s}DiskSection/{%s}Disk" % (OVF_SCHEMA, OVF_SCHEMA)
581 99381e3b Agata Murawska
    disk_ids = self._GetAttributes(disks_search, "{%s}fileRef" % OVF_SCHEMA)
582 99381e3b Agata Murawska
    for disk in disk_ids:
583 99381e3b Agata Murawska
      disk_search = "{%s}References/{%s}File" % (OVF_SCHEMA, OVF_SCHEMA)
584 99381e3b Agata Murawska
      disk_match = ("{%s}id" % OVF_SCHEMA, disk)
585 99381e3b Agata Murawska
      disk_elem = self._GetElementMatchingAttr(disk_search, disk_match)
586 99381e3b Agata Murawska
      if disk_elem is None:
587 99381e3b Agata Murawska
        raise errors.OpPrereqError("%s file corrupted - disk %s not found in"
588 2cfbc784 Iustin Pop
                                   " references" % (OVF_EXT, disk),
589 2cfbc784 Iustin Pop
                                   errors.ECODE_ENVIRON)
590 99381e3b Agata Murawska
      disk_name = disk_elem.get("{%s}href" % OVF_SCHEMA)
591 99381e3b Agata Murawska
      disk_compression = disk_elem.get("{%s}compression" % OVF_SCHEMA)
592 99381e3b Agata Murawska
      results.append((disk_name, disk_compression))
593 99381e3b Agata Murawska
    return results
594 99381e3b Agata Murawska
595 864cf6bf Agata Murawska
596 7432d332 Agata Murawska
def SubElementText(parent, tag, text, attrib={}, **extra):
597 7432d332 Agata Murawska
# pylint: disable=W0102
598 7432d332 Agata Murawska
  """This is just a wrapper on ET.SubElement that always has text content.
599 7432d332 Agata Murawska

600 7432d332 Agata Murawska
  """
601 7432d332 Agata Murawska
  if text is None:
602 7432d332 Agata Murawska
    return None
603 7432d332 Agata Murawska
  elem = ET.SubElement(parent, tag, attrib=attrib, **extra)
604 7432d332 Agata Murawska
  elem.text = str(text)
605 7432d332 Agata Murawska
  return elem
606 7432d332 Agata Murawska
607 7432d332 Agata Murawska
608 0963b26a Agata Murawska
class OVFWriter(object):
609 0963b26a Agata Murawska
  """Writer class for OVF files.
610 0963b26a Agata Murawska

611 0963b26a Agata Murawska
  @type tree: ET.ElementTree
612 0963b26a Agata Murawska
  @ivar tree: XML tree that we are constructing
613 fa337742 Agata Murawska
  @type virtual_system_type: string
614 fa337742 Agata Murawska
  @ivar virtual_system_type: value of vssd:VirtualSystemType, for external usage
615 fa337742 Agata Murawska
    in VMWare this requires to be vmx
616 7432d332 Agata Murawska
  @type hardware_list: list
617 7432d332 Agata Murawska
  @ivar hardware_list: list of items prepared for VirtualHardwareSection
618 fa337742 Agata Murawska
  @type next_instance_id: int
619 fa337742 Agata Murawska
  @ivar next_instance_id: next instance id to be used when creating elements on
620 fa337742 Agata Murawska
    hardware_list
621 0963b26a Agata Murawska

622 0963b26a Agata Murawska
  """
623 0963b26a Agata Murawska
  def __init__(self, has_gnt_section):
624 0963b26a Agata Murawska
    """Initialize the writer - set the top element.
625 0963b26a Agata Murawska

626 0963b26a Agata Murawska
    @type has_gnt_section: bool
627 0963b26a Agata Murawska
    @param has_gnt_section: if the Ganeti schema should be added - i.e. this
628 0963b26a Agata Murawska
      means that Ganeti section will be present
629 0963b26a Agata Murawska

630 0963b26a Agata Murawska
    """
631 0963b26a Agata Murawska
    env_attribs = {
632 0963b26a Agata Murawska
      "xmlns:xsi": XML_SCHEMA,
633 0963b26a Agata Murawska
      "xmlns:vssd": VSSD_SCHEMA,
634 0963b26a Agata Murawska
      "xmlns:rasd": RASD_SCHEMA,
635 0963b26a Agata Murawska
      "xmlns:ovf": OVF_SCHEMA,
636 0963b26a Agata Murawska
      "xmlns": OVF_SCHEMA,
637 0963b26a Agata Murawska
      "xml:lang": "en-US",
638 0963b26a Agata Murawska
    }
639 0963b26a Agata Murawska
    if has_gnt_section:
640 0963b26a Agata Murawska
      env_attribs["xmlns:gnt"] = GANETI_SCHEMA
641 fa337742 Agata Murawska
      self.virtual_system_type = VS_TYPE["ganeti"]
642 fa337742 Agata Murawska
    else:
643 fa337742 Agata Murawska
      self.virtual_system_type = VS_TYPE["external"]
644 0963b26a Agata Murawska
    self.tree = ET.Element("Envelope", attrib=env_attribs)
645 7432d332 Agata Murawska
    self.hardware_list = []
646 fa337742 Agata Murawska
    # INSTANCE_ID contains statically assigned IDs, starting from 0
647 fa337742 Agata Murawska
    self.next_instance_id = len(INSTANCE_ID) # FIXME: hackish
648 7432d332 Agata Murawska
649 7432d332 Agata Murawska
  def SaveDisksData(self, disks):
650 7432d332 Agata Murawska
    """Convert disk information to certain OVF sections.
651 7432d332 Agata Murawska

652 7432d332 Agata Murawska
    @type disks: list
653 7432d332 Agata Murawska
    @param disks: list of dictionaries of disk options from config.ini
654 7432d332 Agata Murawska

655 7432d332 Agata Murawska
    """
656 7432d332 Agata Murawska
    references = ET.SubElement(self.tree, "References")
657 7432d332 Agata Murawska
    disk_section = ET.SubElement(self.tree, "DiskSection")
658 fa337742 Agata Murawska
    SubElementText(disk_section, "Info", "Virtual disk information")
659 7432d332 Agata Murawska
    for counter, disk in enumerate(disks):
660 7432d332 Agata Murawska
      file_id = "file%s" % counter
661 7432d332 Agata Murawska
      disk_id = "disk%s" % counter
662 7432d332 Agata Murawska
      file_attribs = {
663 7432d332 Agata Murawska
        "ovf:href": disk["path"],
664 7432d332 Agata Murawska
        "ovf:size": str(disk["real-size"]),
665 7432d332 Agata Murawska
        "ovf:id": file_id,
666 7432d332 Agata Murawska
      }
667 7432d332 Agata Murawska
      disk_attribs = {
668 7432d332 Agata Murawska
        "ovf:capacity": str(disk["virt-size"]),
669 7432d332 Agata Murawska
        "ovf:diskId": disk_id,
670 7432d332 Agata Murawska
        "ovf:fileRef": file_id,
671 7432d332 Agata Murawska
        "ovf:format": DISK_FORMAT.get(disk["format"], disk["format"]),
672 7432d332 Agata Murawska
      }
673 7432d332 Agata Murawska
      if "compression" in disk:
674 7432d332 Agata Murawska
        file_attribs["ovf:compression"] = disk["compression"]
675 7432d332 Agata Murawska
      ET.SubElement(references, "File", attrib=file_attribs)
676 7432d332 Agata Murawska
      ET.SubElement(disk_section, "Disk", attrib=disk_attribs)
677 7432d332 Agata Murawska
678 7432d332 Agata Murawska
      # Item in VirtualHardwareSection creation
679 7432d332 Agata Murawska
      disk_item = ET.Element("Item")
680 7432d332 Agata Murawska
      SubElementText(disk_item, "rasd:ElementName", disk_id)
681 7432d332 Agata Murawska
      SubElementText(disk_item, "rasd:HostResource", "ovf:/disk/%s" % disk_id)
682 fa337742 Agata Murawska
      SubElementText(disk_item, "rasd:InstanceID", self.next_instance_id)
683 fa337742 Agata Murawska
      SubElementText(disk_item, "rasd:Parent", INSTANCE_ID["scsi"])
684 fa337742 Agata Murawska
      SubElementText(disk_item, "rasd:ResourceType", RASD_TYPE["disk"])
685 7432d332 Agata Murawska
      self.hardware_list.append(disk_item)
686 fa337742 Agata Murawska
      self.next_instance_id += 1
687 7432d332 Agata Murawska
688 7432d332 Agata Murawska
  def SaveNetworksData(self, networks):
689 7432d332 Agata Murawska
    """Convert network information to NetworkSection.
690 7432d332 Agata Murawska

691 7432d332 Agata Murawska
    @type networks: list
692 7432d332 Agata Murawska
    @param networks: list of dictionaries of network options form config.ini
693 7432d332 Agata Murawska

694 7432d332 Agata Murawska
    """
695 7432d332 Agata Murawska
    network_section = ET.SubElement(self.tree, "NetworkSection")
696 fa337742 Agata Murawska
    SubElementText(network_section, "Info", "List of logical networks")
697 7432d332 Agata Murawska
    for counter, network in enumerate(networks):
698 7432d332 Agata Murawska
      network_name = "%s%s" % (network["mode"], counter)
699 7432d332 Agata Murawska
      network_attrib = {"ovf:name": network_name}
700 7432d332 Agata Murawska
      ET.SubElement(network_section, "Network", attrib=network_attrib)
701 7432d332 Agata Murawska
702 7432d332 Agata Murawska
      # Item in VirtualHardwareSection creation
703 7432d332 Agata Murawska
      network_item = ET.Element("Item")
704 fa337742 Agata Murawska
      SubElementText(network_item, "rasd:Address", network["mac"])
705 fa337742 Agata Murawska
      SubElementText(network_item, "rasd:Connection", network_name)
706 7432d332 Agata Murawska
      SubElementText(network_item, "rasd:ElementName", network_name)
707 fa337742 Agata Murawska
      SubElementText(network_item, "rasd:InstanceID", self.next_instance_id)
708 7432d332 Agata Murawska
      SubElementText(network_item, "rasd:ResourceType",
709 5ae4945a Iustin Pop
                     RASD_TYPE["ethernet-adapter"])
710 7432d332 Agata Murawska
      self.hardware_list.append(network_item)
711 fa337742 Agata Murawska
      self.next_instance_id += 1
712 7432d332 Agata Murawska
713 7432d332 Agata Murawska
  @staticmethod
714 7432d332 Agata Murawska
  def _SaveNameAndParams(root, data):
715 7432d332 Agata Murawska
    """Save name and parameters information under root using data.
716 7432d332 Agata Murawska

717 7432d332 Agata Murawska
    @type root: ET.Element
718 7432d332 Agata Murawska
    @param root: root element for the Name and Parameters
719 7432d332 Agata Murawska
    @type data: dict
720 7432d332 Agata Murawska
    @param data: data from which we gather the values
721 7432d332 Agata Murawska

722 7432d332 Agata Murawska
    """
723 7432d332 Agata Murawska
    assert(data.get("name"))
724 7432d332 Agata Murawska
    name = SubElementText(root, "gnt:Name", data["name"])
725 7432d332 Agata Murawska
    params = ET.SubElement(root, "gnt:Parameters")
726 7432d332 Agata Murawska
    for name, value in data.iteritems():
727 7432d332 Agata Murawska
      if name != "name":
728 7432d332 Agata Murawska
        SubElementText(params, "gnt:%s" % name, value)
729 7432d332 Agata Murawska
730 7432d332 Agata Murawska
  def SaveGanetiData(self, ganeti, networks):
731 7432d332 Agata Murawska
    """Convert Ganeti-specific information to GanetiSection.
732 7432d332 Agata Murawska

733 7432d332 Agata Murawska
    @type ganeti: dict
734 7432d332 Agata Murawska
    @param ganeti: dictionary of Ganeti-specific options from config.ini
735 7432d332 Agata Murawska
    @type networks: list
736 7432d332 Agata Murawska
    @param networks: list of dictionaries of network options form config.ini
737 7432d332 Agata Murawska

738 7432d332 Agata Murawska
    """
739 7432d332 Agata Murawska
    ganeti_section = ET.SubElement(self.tree, "gnt:GanetiSection")
740 7432d332 Agata Murawska
741 7432d332 Agata Murawska
    SubElementText(ganeti_section, "gnt:Version", ganeti.get("version"))
742 7432d332 Agata Murawska
    SubElementText(ganeti_section, "gnt:DiskTemplate",
743 5ae4945a Iustin Pop
                   ganeti.get("disk_template"))
744 7432d332 Agata Murawska
    SubElementText(ganeti_section, "gnt:AutoBalance",
745 5ae4945a Iustin Pop
                   ganeti.get("auto_balance"))
746 7432d332 Agata Murawska
    SubElementText(ganeti_section, "gnt:Tags", ganeti.get("tags"))
747 7432d332 Agata Murawska
748 7432d332 Agata Murawska
    osys = ET.SubElement(ganeti_section, "gnt:OperatingSystem")
749 7432d332 Agata Murawska
    self._SaveNameAndParams(osys, ganeti["os"])
750 7432d332 Agata Murawska
751 7432d332 Agata Murawska
    hypervisor = ET.SubElement(ganeti_section, "gnt:Hypervisor")
752 7432d332 Agata Murawska
    self._SaveNameAndParams(hypervisor, ganeti["hypervisor"])
753 7432d332 Agata Murawska
754 7432d332 Agata Murawska
    network_section = ET.SubElement(ganeti_section, "gnt:Network")
755 7432d332 Agata Murawska
    for counter, network in enumerate(networks):
756 7432d332 Agata Murawska
      network_name = "%s%s" % (network["mode"], counter)
757 7432d332 Agata Murawska
      nic_attrib = {"ovf:name": network_name}
758 7432d332 Agata Murawska
      nic = ET.SubElement(network_section, "gnt:Nic", attrib=nic_attrib)
759 7432d332 Agata Murawska
      SubElementText(nic, "gnt:Mode", network["mode"])
760 7432d332 Agata Murawska
      SubElementText(nic, "gnt:MACAddress", network["mac"])
761 7432d332 Agata Murawska
      SubElementText(nic, "gnt:IPAddress", network["ip"])
762 7432d332 Agata Murawska
      SubElementText(nic, "gnt:Link", network["link"])
763 6e8091f9 Dimitris Aragiorgis
      SubElementText(nic, "gnt:Net", network["network"])
764 7432d332 Agata Murawska
765 7432d332 Agata Murawska
  def SaveVirtualSystemData(self, name, vcpus, memory):
766 7432d332 Agata Murawska
    """Convert virtual system information to OVF sections.
767 7432d332 Agata Murawska

768 7432d332 Agata Murawska
    @type name: string
769 7432d332 Agata Murawska
    @param name: name of the instance
770 7432d332 Agata Murawska
    @type vcpus: int
771 7432d332 Agata Murawska
    @param vcpus: number of VCPUs
772 7432d332 Agata Murawska
    @type memory: int
773 7432d332 Agata Murawska
    @param memory: RAM memory in MB
774 7432d332 Agata Murawska

775 7432d332 Agata Murawska
    """
776 7432d332 Agata Murawska
    assert(vcpus > 0)
777 7432d332 Agata Murawska
    assert(memory > 0)
778 7432d332 Agata Murawska
    vs_attrib = {"ovf:id": name}
779 7432d332 Agata Murawska
    virtual_system = ET.SubElement(self.tree, "VirtualSystem", attrib=vs_attrib)
780 fa337742 Agata Murawska
    SubElementText(virtual_system, "Info", "A virtual machine")
781 7432d332 Agata Murawska
782 7432d332 Agata Murawska
    name_section = ET.SubElement(virtual_system, "Name")
783 7432d332 Agata Murawska
    name_section.text = name
784 7432d332 Agata Murawska
    os_attrib = {"ovf:id": "0"}
785 fa337742 Agata Murawska
    os_section = ET.SubElement(virtual_system, "OperatingSystemSection",
786 5ae4945a Iustin Pop
                               attrib=os_attrib)
787 fa337742 Agata Murawska
    SubElementText(os_section, "Info", "Installed guest operating system")
788 7432d332 Agata Murawska
    hardware_section = ET.SubElement(virtual_system, "VirtualHardwareSection")
789 fa337742 Agata Murawska
    SubElementText(hardware_section, "Info", "Virtual hardware requirements")
790 7432d332 Agata Murawska
791 7432d332 Agata Murawska
    # System description
792 7432d332 Agata Murawska
    system = ET.SubElement(hardware_section, "System")
793 7432d332 Agata Murawska
    SubElementText(system, "vssd:ElementName", "Virtual Hardware Family")
794 fa337742 Agata Murawska
    SubElementText(system, "vssd:InstanceID", INSTANCE_ID["system"])
795 7432d332 Agata Murawska
    SubElementText(system, "vssd:VirtualSystemIdentifier", name)
796 fa337742 Agata Murawska
    SubElementText(system, "vssd:VirtualSystemType", self.virtual_system_type)
797 7432d332 Agata Murawska
798 7432d332 Agata Murawska
    # Item for vcpus
799 7432d332 Agata Murawska
    vcpus_item = ET.SubElement(hardware_section, "Item")
800 7432d332 Agata Murawska
    SubElementText(vcpus_item, "rasd:ElementName",
801 5ae4945a Iustin Pop
                   "%s virtual CPU(s)" % vcpus)
802 fa337742 Agata Murawska
    SubElementText(vcpus_item, "rasd:InstanceID", INSTANCE_ID["vcpus"])
803 7432d332 Agata Murawska
    SubElementText(vcpus_item, "rasd:ResourceType", RASD_TYPE["vcpus"])
804 7432d332 Agata Murawska
    SubElementText(vcpus_item, "rasd:VirtualQuantity", vcpus)
805 7432d332 Agata Murawska
806 7432d332 Agata Murawska
    # Item for memory
807 7432d332 Agata Murawska
    memory_item = ET.SubElement(hardware_section, "Item")
808 7432d332 Agata Murawska
    SubElementText(memory_item, "rasd:AllocationUnits", "byte * 2^20")
809 7432d332 Agata Murawska
    SubElementText(memory_item, "rasd:ElementName", "%sMB of memory" % memory)
810 fa337742 Agata Murawska
    SubElementText(memory_item, "rasd:InstanceID", INSTANCE_ID["memory"])
811 7432d332 Agata Murawska
    SubElementText(memory_item, "rasd:ResourceType", RASD_TYPE["memory"])
812 7432d332 Agata Murawska
    SubElementText(memory_item, "rasd:VirtualQuantity", memory)
813 7432d332 Agata Murawska
814 7432d332 Agata Murawska
    # Item for scsi controller
815 7432d332 Agata Murawska
    scsi_item = ET.SubElement(hardware_section, "Item")
816 fa337742 Agata Murawska
    SubElementText(scsi_item, "rasd:Address", INSTANCE_ID["system"])
817 7432d332 Agata Murawska
    SubElementText(scsi_item, "rasd:ElementName", "scsi_controller0")
818 fa337742 Agata Murawska
    SubElementText(scsi_item, "rasd:InstanceID", INSTANCE_ID["scsi"])
819 fa337742 Agata Murawska
    SubElementText(scsi_item, "rasd:ResourceSubType", SCSI_SUBTYPE)
820 7432d332 Agata Murawska
    SubElementText(scsi_item, "rasd:ResourceType", RASD_TYPE["scsi-controller"])
821 7432d332 Agata Murawska
822 7432d332 Agata Murawska
    # Other items - from self.hardware_list
823 fa337742 Agata Murawska
    for item in self.hardware_list:
824 7432d332 Agata Murawska
      hardware_section.append(item)
825 0963b26a Agata Murawska
826 0963b26a Agata Murawska
  def PrettyXmlDump(self):
827 0963b26a Agata Murawska
    """Formatter of the XML file.
828 0963b26a Agata Murawska

829 0963b26a Agata Murawska
    @rtype: string
830 0963b26a Agata Murawska
    @return: XML tree in the form of nicely-formatted string
831 0963b26a Agata Murawska

832 0963b26a Agata Murawska
    """
833 0963b26a Agata Murawska
    raw_string = ET.tostring(self.tree)
834 0963b26a Agata Murawska
    parsed_xml = xml.dom.minidom.parseString(raw_string)
835 0963b26a Agata Murawska
    xml_string = parsed_xml.toprettyxml(indent="  ")
836 0963b26a Agata Murawska
    text_re = re.compile(">\n\s+([^<>\s].*?)\n\s+</", re.DOTALL)
837 0963b26a Agata Murawska
    return text_re.sub(">\g<1></", xml_string)
838 0963b26a Agata Murawska
839 0963b26a Agata Murawska
840 ced78a66 Agata Murawska
class Converter(object):
841 ced78a66 Agata Murawska
  """Converter class for OVF packages.
842 ced78a66 Agata Murawska

843 ced78a66 Agata Murawska
  Converter is a class above both ImporterOVF and ExporterOVF. It's purpose is
844 ced78a66 Agata Murawska
  to provide a common interface for the two.
845 ced78a66 Agata Murawska

846 ced78a66 Agata Murawska
  @type options: optparse.Values
847 ced78a66 Agata Murawska
  @ivar options: options parsed from the command line
848 ced78a66 Agata Murawska
  @type output_dir: string
849 ced78a66 Agata Murawska
  @ivar output_dir: directory to which the results of conversion shall be
850 ced78a66 Agata Murawska
    written
851 ced78a66 Agata Murawska
  @type temp_file_manager: L{utils.TemporaryFileManager}
852 ced78a66 Agata Murawska
  @ivar temp_file_manager: container for temporary files created during
853 ced78a66 Agata Murawska
    conversion
854 ced78a66 Agata Murawska
  @type temp_dir: string
855 ced78a66 Agata Murawska
  @ivar temp_dir: temporary directory created then we deal with OVA
856 ced78a66 Agata Murawska

857 ced78a66 Agata Murawska
  """
858 ced78a66 Agata Murawska
  def __init__(self, input_path, options):
859 ced78a66 Agata Murawska
    """Initialize the converter.
860 ced78a66 Agata Murawska

861 ced78a66 Agata Murawska
    @type input_path: string
862 ced78a66 Agata Murawska
    @param input_path: path to the Converter input file
863 ced78a66 Agata Murawska
    @type options: optparse.Values
864 ced78a66 Agata Murawska
    @param options: command line options
865 ced78a66 Agata Murawska

866 ced78a66 Agata Murawska
    @raise errors.OpPrereqError: if file does not exist
867 ced78a66 Agata Murawska

868 ced78a66 Agata Murawska
    """
869 ced78a66 Agata Murawska
    input_path = os.path.abspath(input_path)
870 ced78a66 Agata Murawska
    if not os.path.isfile(input_path):
871 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("File does not exist: %s" % input_path,
872 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
873 ced78a66 Agata Murawska
    self.options = options
874 ced78a66 Agata Murawska
    self.temp_file_manager = utils.TemporaryFileManager()
875 ced78a66 Agata Murawska
    self.temp_dir = None
876 ced78a66 Agata Murawska
    self.output_dir = None
877 ced78a66 Agata Murawska
    self._ReadInputData(input_path)
878 ced78a66 Agata Murawska
879 ced78a66 Agata Murawska
  def _ReadInputData(self, input_path):
880 ced78a66 Agata Murawska
    """Reads the data on which the conversion will take place.
881 ced78a66 Agata Murawska

882 ced78a66 Agata Murawska
    @type input_path: string
883 ced78a66 Agata Murawska
    @param input_path: absolute path to the Converter input file
884 ced78a66 Agata Murawska

885 ced78a66 Agata Murawska
    """
886 ced78a66 Agata Murawska
    raise NotImplementedError()
887 ced78a66 Agata Murawska
888 99381e3b Agata Murawska
  def _CompressDisk(self, disk_path, compression, action):
889 99381e3b Agata Murawska
    """Performs (de)compression on the disk and returns the new path
890 99381e3b Agata Murawska

891 99381e3b Agata Murawska
    @type disk_path: string
892 99381e3b Agata Murawska
    @param disk_path: path to the disk
893 99381e3b Agata Murawska
    @type compression: string
894 99381e3b Agata Murawska
    @param compression: compression type
895 99381e3b Agata Murawska
    @type action: string
896 99381e3b Agata Murawska
    @param action: whether the action is compression or decompression
897 99381e3b Agata Murawska
    @rtype: string
898 99381e3b Agata Murawska
    @return: new disk path after (de)compression
899 99381e3b Agata Murawska

900 99381e3b Agata Murawska
    @raise errors.OpPrereqError: disk (de)compression failed or "compression"
901 99381e3b Agata Murawska
      is not supported
902 99381e3b Agata Murawska

903 99381e3b Agata Murawska
    """
904 99381e3b Agata Murawska
    assert(action in ALLOWED_ACTIONS)
905 99381e3b Agata Murawska
    # For now we only support gzip, as it is used in ovftool
906 99381e3b Agata Murawska
    if compression != COMPRESSION_TYPE:
907 99381e3b Agata Murawska
      raise errors.OpPrereqError("Unsupported compression type: %s"
908 2cfbc784 Iustin Pop
                                 % compression, errors.ECODE_INVAL)
909 99381e3b Agata Murawska
    disk_file = os.path.basename(disk_path)
910 99381e3b Agata Murawska
    if action == DECOMPRESS:
911 99381e3b Agata Murawska
      (disk_name, _) = os.path.splitext(disk_file)
912 99381e3b Agata Murawska
      prefix = disk_name
913 99381e3b Agata Murawska
    elif action == COMPRESS:
914 99381e3b Agata Murawska
      prefix = disk_file
915 99381e3b Agata Murawska
    new_path = utils.GetClosedTempfile(suffix=COMPRESSION_EXT, prefix=prefix,
916 5ae4945a Iustin Pop
                                       dir=self.output_dir)
917 99381e3b Agata Murawska
    self.temp_file_manager.Add(new_path)
918 99381e3b Agata Murawska
    args = ["gzip", "-c", disk_path]
919 99381e3b Agata Murawska
    run_result = utils.RunCmd(args, output=new_path)
920 99381e3b Agata Murawska
    if run_result.failed:
921 99381e3b Agata Murawska
      raise errors.OpPrereqError("Disk %s failed with output: %s"
922 2cfbc784 Iustin Pop
                                 % (action, run_result.stderr),
923 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
924 99381e3b Agata Murawska
    logging.info("The %s of the disk is completed", action)
925 99381e3b Agata Murawska
    return (COMPRESSION_EXT, new_path)
926 99381e3b Agata Murawska
927 99381e3b Agata Murawska
  def _ConvertDisk(self, disk_format, disk_path):
928 99381e3b Agata Murawska
    """Performes conversion to specified format.
929 99381e3b Agata Murawska

930 99381e3b Agata Murawska
    @type disk_format: string
931 99381e3b Agata Murawska
    @param disk_format: format to which the disk should be converted
932 99381e3b Agata Murawska
    @type disk_path: string
933 99381e3b Agata Murawska
    @param disk_path: path to the disk that should be converted
934 99381e3b Agata Murawska
    @rtype: string
935 99381e3b Agata Murawska
    @return path to the output disk
936 99381e3b Agata Murawska

937 99381e3b Agata Murawska
    @raise errors.OpPrereqError: convertion of the disk failed
938 99381e3b Agata Murawska

939 99381e3b Agata Murawska
    """
940 a002ed79 Agata Murawska
    CheckQemuImg()
941 99381e3b Agata Murawska
    disk_file = os.path.basename(disk_path)
942 99381e3b Agata Murawska
    (disk_name, disk_extension) = os.path.splitext(disk_file)
943 99381e3b Agata Murawska
    if disk_extension != disk_format:
944 99381e3b Agata Murawska
      logging.warning("Conversion of disk image to %s format, this may take"
945 99381e3b Agata Murawska
                      " a while", disk_format)
946 99381e3b Agata Murawska
947 5ae4945a Iustin Pop
    new_disk_path = utils.GetClosedTempfile(
948 5ae4945a Iustin Pop
      suffix=".%s" % disk_format, prefix=disk_name, dir=self.output_dir)
949 99381e3b Agata Murawska
    self.temp_file_manager.Add(new_disk_path)
950 99381e3b Agata Murawska
    args = [
951 e68949b0 Agata Murawska
      constants.QEMUIMG_PATH,
952 99381e3b Agata Murawska
      "convert",
953 99381e3b Agata Murawska
      "-O",
954 99381e3b Agata Murawska
      disk_format,
955 99381e3b Agata Murawska
      disk_path,
956 99381e3b Agata Murawska
      new_disk_path,
957 99381e3b Agata Murawska
    ]
958 99381e3b Agata Murawska
    run_result = utils.RunCmd(args, cwd=os.getcwd())
959 99381e3b Agata Murawska
    if run_result.failed:
960 99381e3b Agata Murawska
      raise errors.OpPrereqError("Convertion to %s failed, qemu-img output was"
961 2cfbc784 Iustin Pop
                                 ": %s" % (disk_format, run_result.stderr),
962 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
963 99381e3b Agata Murawska
    return (".%s" % disk_format, new_disk_path)
964 99381e3b Agata Murawska
965 99381e3b Agata Murawska
  @staticmethod
966 99381e3b Agata Murawska
  def _GetDiskQemuInfo(disk_path, regexp):
967 99381e3b Agata Murawska
    """Figures out some information of the disk using qemu-img.
968 99381e3b Agata Murawska

969 99381e3b Agata Murawska
    @type disk_path: string
970 99381e3b Agata Murawska
    @param disk_path: path to the disk we want to know the format of
971 99381e3b Agata Murawska
    @type regexp: string
972 99381e3b Agata Murawska
    @param regexp: string that has to be matched, it has to contain one group
973 99381e3b Agata Murawska
    @rtype: string
974 99381e3b Agata Murawska
    @return: disk format
975 99381e3b Agata Murawska

976 99381e3b Agata Murawska
    @raise errors.OpPrereqError: format information cannot be retrieved
977 99381e3b Agata Murawska

978 99381e3b Agata Murawska
    """
979 a002ed79 Agata Murawska
    CheckQemuImg()
980 e68949b0 Agata Murawska
    args = [constants.QEMUIMG_PATH, "info", disk_path]
981 99381e3b Agata Murawska
    run_result = utils.RunCmd(args, cwd=os.getcwd())
982 99381e3b Agata Murawska
    if run_result.failed:
983 99381e3b Agata Murawska
      raise errors.OpPrereqError("Gathering info about the disk using qemu-img"
984 2cfbc784 Iustin Pop
                                 " failed, output was: %s" % run_result.stderr,
985 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
986 99381e3b Agata Murawska
    result = run_result.output
987 99381e3b Agata Murawska
    regexp = r"%s" % regexp
988 99381e3b Agata Murawska
    match = re.search(regexp, result)
989 99381e3b Agata Murawska
    if match:
990 99381e3b Agata Murawska
      disk_format = match.group(1)
991 99381e3b Agata Murawska
    else:
992 99381e3b Agata Murawska
      raise errors.OpPrereqError("No file information matching %s found in:"
993 2cfbc784 Iustin Pop
                                 " %s" % (regexp, result),
994 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
995 99381e3b Agata Murawska
    return disk_format
996 99381e3b Agata Murawska
997 ced78a66 Agata Murawska
  def Parse(self):
998 ced78a66 Agata Murawska
    """Parses the data and creates a structure containing all required info.
999 ced78a66 Agata Murawska

1000 ced78a66 Agata Murawska
    """
1001 ced78a66 Agata Murawska
    raise NotImplementedError()
1002 ced78a66 Agata Murawska
1003 ced78a66 Agata Murawska
  def Save(self):
1004 ced78a66 Agata Murawska
    """Saves the gathered configuration in an apropriate format.
1005 ced78a66 Agata Murawska

1006 ced78a66 Agata Murawska
    """
1007 ced78a66 Agata Murawska
    raise NotImplementedError()
1008 ced78a66 Agata Murawska
1009 ced78a66 Agata Murawska
  def Cleanup(self):
1010 ced78a66 Agata Murawska
    """Cleans the temporary directory, if one was created.
1011 ced78a66 Agata Murawska

1012 ced78a66 Agata Murawska
    """
1013 ced78a66 Agata Murawska
    self.temp_file_manager.Cleanup()
1014 ced78a66 Agata Murawska
    if self.temp_dir:
1015 ced78a66 Agata Murawska
      shutil.rmtree(self.temp_dir)
1016 ced78a66 Agata Murawska
      self.temp_dir = None
1017 ced78a66 Agata Murawska
1018 ced78a66 Agata Murawska
1019 ced78a66 Agata Murawska
class OVFImporter(Converter):
1020 864cf6bf Agata Murawska
  """Converter from OVF to Ganeti config file.
1021 864cf6bf Agata Murawska

1022 864cf6bf Agata Murawska
  @type input_dir: string
1023 864cf6bf Agata Murawska
  @ivar input_dir: directory in which the .ovf file resides
1024 864cf6bf Agata Murawska
  @type output_dir: string
1025 864cf6bf Agata Murawska
  @ivar output_dir: directory to which the results of conversion shall be
1026 864cf6bf Agata Murawska
    written
1027 864cf6bf Agata Murawska
  @type input_path: string
1028 864cf6bf Agata Murawska
  @ivar input_path: complete path to the .ovf file
1029 864cf6bf Agata Murawska
  @type ovf_reader: L{OVFReader}
1030 864cf6bf Agata Murawska
  @ivar ovf_reader: OVF reader instance collects data from .ovf file
1031 99381e3b Agata Murawska
  @type results_name: string
1032 99381e3b Agata Murawska
  @ivar results_name: name of imported instance
1033 99381e3b Agata Murawska
  @type results_template: string
1034 99381e3b Agata Murawska
  @ivar results_template: disk template read from .ovf file or command line
1035 99381e3b Agata Murawska
    arguments
1036 7bde29b5 Agata Murawska
  @type results_hypervisor: dict
1037 7bde29b5 Agata Murawska
  @ivar results_hypervisor: hypervisor information gathered from .ovf file or
1038 7bde29b5 Agata Murawska
    command line arguments
1039 7bde29b5 Agata Murawska
  @type results_os: dict
1040 7bde29b5 Agata Murawska
  @ivar results_os: operating system information gathered from .ovf file or
1041 7bde29b5 Agata Murawska
    command line arguments
1042 7bde29b5 Agata Murawska
  @type results_backend: dict
1043 7bde29b5 Agata Murawska
  @ivar results_backend: backend information gathered from .ovf file or
1044 7bde29b5 Agata Murawska
    command line arguments
1045 7bde29b5 Agata Murawska
  @type results_tags: string
1046 7bde29b5 Agata Murawska
  @ivar results_tags: string containing instance-specific tags
1047 7bde29b5 Agata Murawska
  @type results_version: string
1048 7bde29b5 Agata Murawska
  @ivar results_version: version as required by Ganeti import
1049 24b9469d Agata Murawska
  @type results_network: dict
1050 24b9469d Agata Murawska
  @ivar results_network: network information gathered from .ovf file or command
1051 24b9469d Agata Murawska
    line arguments
1052 99381e3b Agata Murawska
  @type results_disk: dict
1053 99381e3b Agata Murawska
  @ivar results_disk: disk information gathered from .ovf file or command line
1054 99381e3b Agata Murawska
    arguments
1055 864cf6bf Agata Murawska

1056 864cf6bf Agata Murawska
  """
1057 ced78a66 Agata Murawska
  def _ReadInputData(self, input_path):
1058 864cf6bf Agata Murawska
    """Reads the data on which the conversion will take place.
1059 864cf6bf Agata Murawska

1060 864cf6bf Agata Murawska
    @type input_path: string
1061 864cf6bf Agata Murawska
    @param input_path: absolute path to the .ovf or .ova input file
1062 864cf6bf Agata Murawska

1063 864cf6bf Agata Murawska
    @raise errors.OpPrereqError: if input file is neither .ovf nor .ova
1064 864cf6bf Agata Murawska

1065 864cf6bf Agata Murawska
    """
1066 864cf6bf Agata Murawska
    (input_dir, input_file) = os.path.split(input_path)
1067 864cf6bf Agata Murawska
    (_, input_extension) = os.path.splitext(input_file)
1068 864cf6bf Agata Murawska
1069 864cf6bf Agata Murawska
    if input_extension == OVF_EXT:
1070 864cf6bf Agata Murawska
      logging.info("%s file extension found, no unpacking necessary", OVF_EXT)
1071 864cf6bf Agata Murawska
      self.input_dir = input_dir
1072 864cf6bf Agata Murawska
      self.input_path = input_path
1073 864cf6bf Agata Murawska
      self.temp_dir = None
1074 864cf6bf Agata Murawska
    elif input_extension == OVA_EXT:
1075 864cf6bf Agata Murawska
      logging.info("%s file extension found, proceeding to unpacking", OVA_EXT)
1076 864cf6bf Agata Murawska
      self._UnpackOVA(input_path)
1077 864cf6bf Agata Murawska
    else:
1078 864cf6bf Agata Murawska
      raise errors.OpPrereqError("Unknown file extension; expected %s or %s"
1079 2cfbc784 Iustin Pop
                                 " file" % (OVA_EXT, OVF_EXT),
1080 2cfbc784 Iustin Pop
                                 errors.ECODE_INVAL)
1081 864cf6bf Agata Murawska
    assert ((input_extension == OVA_EXT and self.temp_dir) or
1082 864cf6bf Agata Murawska
            (input_extension == OVF_EXT and not self.temp_dir))
1083 864cf6bf Agata Murawska
    assert self.input_dir in self.input_path
1084 864cf6bf Agata Murawska
1085 864cf6bf Agata Murawska
    if self.options.output_dir:
1086 864cf6bf Agata Murawska
      self.output_dir = os.path.abspath(self.options.output_dir)
1087 fe496b25 Michael Hanselmann
      if (os.path.commonprefix([pathutils.EXPORT_DIR, self.output_dir]) !=
1088 fe496b25 Michael Hanselmann
          pathutils.EXPORT_DIR):
1089 864cf6bf Agata Murawska
        logging.warning("Export path is not under %s directory, import to"
1090 864cf6bf Agata Murawska
                        " Ganeti using gnt-backup may fail",
1091 fe496b25 Michael Hanselmann
                        pathutils.EXPORT_DIR)
1092 864cf6bf Agata Murawska
    else:
1093 fe496b25 Michael Hanselmann
      self.output_dir = pathutils.EXPORT_DIR
1094 864cf6bf Agata Murawska
1095 864cf6bf Agata Murawska
    self.ovf_reader = OVFReader(self.input_path)
1096 864cf6bf Agata Murawska
    self.ovf_reader.VerifyManifest()
1097 864cf6bf Agata Murawska
1098 864cf6bf Agata Murawska
  def _UnpackOVA(self, input_path):
1099 864cf6bf Agata Murawska
    """Unpacks the .ova package into temporary directory.
1100 864cf6bf Agata Murawska

1101 864cf6bf Agata Murawska
    @type input_path: string
1102 864cf6bf Agata Murawska
    @param input_path: path to the .ova package file
1103 864cf6bf Agata Murawska

1104 864cf6bf Agata Murawska
    @raise errors.OpPrereqError: if file is not a proper tarball, one of the
1105 864cf6bf Agata Murawska
        files in the archive seem malicious (e.g. path starts with '../') or
1106 864cf6bf Agata Murawska
        .ova package does not contain .ovf file
1107 864cf6bf Agata Murawska

1108 864cf6bf Agata Murawska
    """
1109 864cf6bf Agata Murawska
    input_name = None
1110 864cf6bf Agata Murawska
    if not tarfile.is_tarfile(input_path):
1111 864cf6bf Agata Murawska
      raise errors.OpPrereqError("The provided %s file is not a proper tar"
1112 2cfbc784 Iustin Pop
                                 " archive" % OVA_EXT, errors.ECODE_ENVIRON)
1113 864cf6bf Agata Murawska
    ova_content = tarfile.open(input_path)
1114 864cf6bf Agata Murawska
    temp_dir = tempfile.mkdtemp()
1115 864cf6bf Agata Murawska
    self.temp_dir = temp_dir
1116 864cf6bf Agata Murawska
    for file_name in ova_content.getnames():
1117 864cf6bf Agata Murawska
      file_normname = os.path.normpath(file_name)
1118 864cf6bf Agata Murawska
      try:
1119 864cf6bf Agata Murawska
        utils.PathJoin(temp_dir, file_normname)
1120 864cf6bf Agata Murawska
      except ValueError, err:
1121 864cf6bf Agata Murawska
        raise errors.OpPrereqError("File %s inside %s package is not safe" %
1122 2cfbc784 Iustin Pop
                                   (file_name, OVA_EXT), errors.ECODE_ENVIRON)
1123 864cf6bf Agata Murawska
      if file_name.endswith(OVF_EXT):
1124 864cf6bf Agata Murawska
        input_name = file_name
1125 864cf6bf Agata Murawska
    if not input_name:
1126 864cf6bf Agata Murawska
      raise errors.OpPrereqError("No %s file in %s package found" %
1127 2cfbc784 Iustin Pop
                                 (OVF_EXT, OVA_EXT), errors.ECODE_ENVIRON)
1128 864cf6bf Agata Murawska
    logging.warning("Unpacking the %s archive, this may take a while",
1129 5ae4945a Iustin Pop
                    input_path)
1130 864cf6bf Agata Murawska
    self.input_dir = temp_dir
1131 864cf6bf Agata Murawska
    self.input_path = utils.PathJoin(self.temp_dir, input_name)
1132 864cf6bf Agata Murawska
    try:
1133 864cf6bf Agata Murawska
      try:
1134 864cf6bf Agata Murawska
        extract = ova_content.extractall
1135 864cf6bf Agata Murawska
      except AttributeError:
1136 864cf6bf Agata Murawska
        # This is a prehistorical case of using python < 2.5
1137 864cf6bf Agata Murawska
        for member in ova_content.getmembers():
1138 864cf6bf Agata Murawska
          ova_content.extract(member, path=self.temp_dir)
1139 864cf6bf Agata Murawska
      else:
1140 864cf6bf Agata Murawska
        extract(self.temp_dir)
1141 864cf6bf Agata Murawska
    except tarfile.TarError, err:
1142 864cf6bf Agata Murawska
      raise errors.OpPrereqError("Error while extracting %s archive: %s" %
1143 2cfbc784 Iustin Pop
                                 (OVA_EXT, err), errors.ECODE_ENVIRON)
1144 864cf6bf Agata Murawska
    logging.info("OVA package extracted to %s directory", self.temp_dir)
1145 ced78a66 Agata Murawska
1146 ced78a66 Agata Murawska
  def Parse(self):
1147 99381e3b Agata Murawska
    """Parses the data and creates a structure containing all required info.
1148 99381e3b Agata Murawska

1149 99381e3b Agata Murawska
    The method reads the information given either as a command line option or as
1150 99381e3b Agata Murawska
    a part of the OVF description.
1151 99381e3b Agata Murawska

1152 99381e3b Agata Murawska
    @raise errors.OpPrereqError: if some required part of the description of
1153 99381e3b Agata Murawska
      virtual instance is missing or unable to create output directory
1154 99381e3b Agata Murawska

1155 99381e3b Agata Murawska
    """
1156 99381e3b Agata Murawska
    self.results_name = self._GetInfo("instance name", self.options.name,
1157 5ae4945a Iustin Pop
                                      self._ParseNameOptions,
1158 5ae4945a Iustin Pop
                                      self.ovf_reader.GetInstanceName)
1159 99381e3b Agata Murawska
    if not self.results_name:
1160 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("Name of instance not provided",
1161 2cfbc784 Iustin Pop
                                 errors.ECODE_INVAL)
1162 99381e3b Agata Murawska
1163 99381e3b Agata Murawska
    self.output_dir = utils.PathJoin(self.output_dir, self.results_name)
1164 99381e3b Agata Murawska
    try:
1165 99381e3b Agata Murawska
      utils.Makedirs(self.output_dir)
1166 99381e3b Agata Murawska
    except OSError, err:
1167 99381e3b Agata Murawska
      raise errors.OpPrereqError("Failed to create directory %s: %s" %
1168 2cfbc784 Iustin Pop
                                 (self.output_dir, err), errors.ECODE_ENVIRON)
1169 99381e3b Agata Murawska
1170 5ae4945a Iustin Pop
    self.results_template = self._GetInfo(
1171 5ae4945a Iustin Pop
      "disk template", self.options.disk_template, self._ParseTemplateOptions,
1172 99381e3b Agata Murawska
      self.ovf_reader.GetDiskTemplate)
1173 99381e3b Agata Murawska
    if not self.results_template:
1174 99381e3b Agata Murawska
      logging.info("Disk template not given")
1175 99381e3b Agata Murawska
1176 5ae4945a Iustin Pop
    self.results_hypervisor = self._GetInfo(
1177 5ae4945a Iustin Pop
      "hypervisor", self.options.hypervisor, self._ParseHypervisorOptions,
1178 7bde29b5 Agata Murawska
      self.ovf_reader.GetHypervisorData)
1179 7bde29b5 Agata Murawska
    assert self.results_hypervisor["hypervisor_name"]
1180 7bde29b5 Agata Murawska
    if self.results_hypervisor["hypervisor_name"] == constants.VALUE_AUTO:
1181 7bde29b5 Agata Murawska
      logging.debug("Default hypervisor settings from the cluster will be used")
1182 7bde29b5 Agata Murawska
1183 5ae4945a Iustin Pop
    self.results_os = self._GetInfo(
1184 5ae4945a Iustin Pop
      "OS", self.options.os, self._ParseOSOptions, self.ovf_reader.GetOSData)
1185 7bde29b5 Agata Murawska
    if not self.results_os.get("os_name"):
1186 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("OS name must be provided",
1187 2cfbc784 Iustin Pop
                                 errors.ECODE_INVAL)
1188 7bde29b5 Agata Murawska
1189 5ae4945a Iustin Pop
    self.results_backend = self._GetInfo(
1190 5ae4945a Iustin Pop
      "backend", self.options.beparams,
1191 7bde29b5 Agata Murawska
      self._ParseBackendOptions, self.ovf_reader.GetBackendData)
1192 7bde29b5 Agata Murawska
    assert self.results_backend.get("vcpus")
1193 7bde29b5 Agata Murawska
    assert self.results_backend.get("memory")
1194 7bde29b5 Agata Murawska
    assert self.results_backend.get("auto_balance") is not None
1195 7bde29b5 Agata Murawska
1196 5ae4945a Iustin Pop
    self.results_tags = self._GetInfo(
1197 5ae4945a Iustin Pop
      "tags", self.options.tags, self._ParseTags, self.ovf_reader.GetTagsData)
1198 7bde29b5 Agata Murawska
1199 7bde29b5 Agata Murawska
    ovf_version = self.ovf_reader.GetVersionData()
1200 7bde29b5 Agata Murawska
    if ovf_version:
1201 7bde29b5 Agata Murawska
      self.results_version = ovf_version
1202 7bde29b5 Agata Murawska
    else:
1203 7bde29b5 Agata Murawska
      self.results_version = constants.EXPORT_VERSION
1204 7bde29b5 Agata Murawska
1205 5ae4945a Iustin Pop
    self.results_network = self._GetInfo(
1206 5ae4945a Iustin Pop
      "network", self.options.nics, self._ParseNicOptions,
1207 5ae4945a Iustin Pop
      self.ovf_reader.GetNetworkData, ignore_test=self.options.no_nics)
1208 24b9469d Agata Murawska
1209 5ae4945a Iustin Pop
    self.results_disk = self._GetInfo(
1210 5ae4945a Iustin Pop
      "disk", self.options.disks, self._ParseDiskOptions, self._GetDiskInfo,
1211 99381e3b Agata Murawska
      ignore_test=self.results_template == constants.DT_DISKLESS)
1212 99381e3b Agata Murawska
1213 24b9469d Agata Murawska
    if not self.results_disk and not self.results_network:
1214 24b9469d Agata Murawska
      raise errors.OpPrereqError("Either disk specification or network"
1215 2cfbc784 Iustin Pop
                                 " description must be present",
1216 2cfbc784 Iustin Pop
                                 errors.ECODE_STATE)
1217 24b9469d Agata Murawska
1218 99381e3b Agata Murawska
  @staticmethod
1219 99381e3b Agata Murawska
  def _GetInfo(name, cmd_arg, cmd_function, nocmd_function,
1220 5ae4945a Iustin Pop
               ignore_test=False):
1221 99381e3b Agata Murawska
    """Get information about some section - e.g. disk, network, hypervisor.
1222 99381e3b Agata Murawska

1223 99381e3b Agata Murawska
    @type name: string
1224 99381e3b Agata Murawska
    @param name: name of the section
1225 99381e3b Agata Murawska
    @type cmd_arg: dict
1226 99381e3b Agata Murawska
    @param cmd_arg: command line argument specific for section 'name'
1227 99381e3b Agata Murawska
    @type cmd_function: callable
1228 99381e3b Agata Murawska
    @param cmd_function: function to call if 'cmd_args' exists
1229 99381e3b Agata Murawska
    @type nocmd_function: callable
1230 99381e3b Agata Murawska
    @param nocmd_function: function to call if 'cmd_args' is not there
1231 99381e3b Agata Murawska

1232 99381e3b Agata Murawska
    """
1233 99381e3b Agata Murawska
    if ignore_test:
1234 99381e3b Agata Murawska
      logging.info("Information for %s will be ignored", name)
1235 99381e3b Agata Murawska
      return {}
1236 99381e3b Agata Murawska
    if cmd_arg:
1237 99381e3b Agata Murawska
      logging.info("Information for %s will be parsed from command line", name)
1238 99381e3b Agata Murawska
      results = cmd_function()
1239 99381e3b Agata Murawska
    else:
1240 99381e3b Agata Murawska
      logging.info("Information for %s will be parsed from %s file",
1241 5ae4945a Iustin Pop
                   name, OVF_EXT)
1242 99381e3b Agata Murawska
      results = nocmd_function()
1243 99381e3b Agata Murawska
    logging.info("Options for %s were succesfully read", name)
1244 99381e3b Agata Murawska
    return results
1245 99381e3b Agata Murawska
1246 99381e3b Agata Murawska
  def _ParseNameOptions(self):
1247 99381e3b Agata Murawska
    """Returns name if one was given in command line.
1248 99381e3b Agata Murawska

1249 99381e3b Agata Murawska
    @rtype: string
1250 99381e3b Agata Murawska
    @return: name of an instance
1251 99381e3b Agata Murawska

1252 99381e3b Agata Murawska
    """
1253 99381e3b Agata Murawska
    return self.options.name
1254 99381e3b Agata Murawska
1255 99381e3b Agata Murawska
  def _ParseTemplateOptions(self):
1256 99381e3b Agata Murawska
    """Returns disk template if one was given in command line.
1257 99381e3b Agata Murawska

1258 99381e3b Agata Murawska
    @rtype: string
1259 99381e3b Agata Murawska
    @return: disk template name
1260 99381e3b Agata Murawska

1261 99381e3b Agata Murawska
    """
1262 99381e3b Agata Murawska
    return self.options.disk_template
1263 99381e3b Agata Murawska
1264 7bde29b5 Agata Murawska
  def _ParseHypervisorOptions(self):
1265 7bde29b5 Agata Murawska
    """Parses hypervisor options given in a command line.
1266 7bde29b5 Agata Murawska

1267 7bde29b5 Agata Murawska
    @rtype: dict
1268 7bde29b5 Agata Murawska
    @return: dictionary containing name of the chosen hypervisor and all the
1269 7bde29b5 Agata Murawska
      options
1270 7bde29b5 Agata Murawska

1271 7bde29b5 Agata Murawska
    """
1272 7bde29b5 Agata Murawska
    assert type(self.options.hypervisor) is tuple
1273 7bde29b5 Agata Murawska
    assert len(self.options.hypervisor) == 2
1274 7bde29b5 Agata Murawska
    results = {}
1275 7bde29b5 Agata Murawska
    if self.options.hypervisor[0]:
1276 7bde29b5 Agata Murawska
      results["hypervisor_name"] = self.options.hypervisor[0]
1277 7bde29b5 Agata Murawska
    else:
1278 7bde29b5 Agata Murawska
      results["hypervisor_name"] = constants.VALUE_AUTO
1279 7bde29b5 Agata Murawska
    results.update(self.options.hypervisor[1])
1280 7bde29b5 Agata Murawska
    return results
1281 7bde29b5 Agata Murawska
1282 7bde29b5 Agata Murawska
  def _ParseOSOptions(self):
1283 7bde29b5 Agata Murawska
    """Parses OS options given in command line.
1284 7bde29b5 Agata Murawska

1285 7bde29b5 Agata Murawska
    @rtype: dict
1286 7bde29b5 Agata Murawska
    @return: dictionary containing name of chosen OS and all its options
1287 7bde29b5 Agata Murawska

1288 7bde29b5 Agata Murawska
    """
1289 7bde29b5 Agata Murawska
    assert self.options.os
1290 7bde29b5 Agata Murawska
    results = {}
1291 7bde29b5 Agata Murawska
    results["os_name"] = self.options.os
1292 7bde29b5 Agata Murawska
    results.update(self.options.osparams)
1293 7bde29b5 Agata Murawska
    return results
1294 7bde29b5 Agata Murawska
1295 7bde29b5 Agata Murawska
  def _ParseBackendOptions(self):
1296 7bde29b5 Agata Murawska
    """Parses backend options given in command line.
1297 7bde29b5 Agata Murawska

1298 7bde29b5 Agata Murawska
    @rtype: dict
1299 7bde29b5 Agata Murawska
    @return: dictionary containing vcpus, memory and auto-balance options
1300 7bde29b5 Agata Murawska

1301 7bde29b5 Agata Murawska
    """
1302 7bde29b5 Agata Murawska
    assert self.options.beparams
1303 7bde29b5 Agata Murawska
    backend = {}
1304 7bde29b5 Agata Murawska
    backend.update(self.options.beparams)
1305 7bde29b5 Agata Murawska
    must_contain = ["vcpus", "memory", "auto_balance"]
1306 7bde29b5 Agata Murawska
    for element in must_contain:
1307 7bde29b5 Agata Murawska
      if backend.get(element) is None:
1308 7bde29b5 Agata Murawska
        backend[element] = constants.VALUE_AUTO
1309 7bde29b5 Agata Murawska
    return backend
1310 7bde29b5 Agata Murawska
1311 7bde29b5 Agata Murawska
  def _ParseTags(self):
1312 7bde29b5 Agata Murawska
    """Returns tags list given in command line.
1313 7bde29b5 Agata Murawska

1314 7bde29b5 Agata Murawska
    @rtype: string
1315 7bde29b5 Agata Murawska
    @return: string containing comma-separated tags
1316 7bde29b5 Agata Murawska

1317 7bde29b5 Agata Murawska
    """
1318 7bde29b5 Agata Murawska
    return self.options.tags
1319 7bde29b5 Agata Murawska
1320 24b9469d Agata Murawska
  def _ParseNicOptions(self):
1321 24b9469d Agata Murawska
    """Parses network options given in a command line or as a dictionary.
1322 24b9469d Agata Murawska

1323 24b9469d Agata Murawska
    @rtype: dict
1324 24b9469d Agata Murawska
    @return: dictionary of network-related options
1325 24b9469d Agata Murawska

1326 24b9469d Agata Murawska
    """
1327 24b9469d Agata Murawska
    assert self.options.nics
1328 24b9469d Agata Murawska
    results = {}
1329 24b9469d Agata Murawska
    for (nic_id, nic_desc) in self.options.nics:
1330 24b9469d Agata Murawska
      results["nic%s_mode" % nic_id] = \
1331 24b9469d Agata Murawska
        nic_desc.get("mode", constants.VALUE_AUTO)
1332 24b9469d Agata Murawska
      results["nic%s_mac" % nic_id] = nic_desc.get("mac", constants.VALUE_AUTO)
1333 24b9469d Agata Murawska
      results["nic%s_link" % nic_id] = \
1334 24b9469d Agata Murawska
        nic_desc.get("link", constants.VALUE_AUTO)
1335 a5ad5e58 Apollon Oikonomopoulos
      results["nic%s_network" % nic_id] = \
1336 a5ad5e58 Apollon Oikonomopoulos
        nic_desc.get("network", constants.VALUE_AUTO)
1337 24b9469d Agata Murawska
      if nic_desc.get("mode") == "bridged":
1338 24b9469d Agata Murawska
        results["nic%s_ip" % nic_id] = constants.VALUE_NONE
1339 24b9469d Agata Murawska
      else:
1340 24b9469d Agata Murawska
        results["nic%s_ip" % nic_id] = constants.VALUE_AUTO
1341 24b9469d Agata Murawska
    results["nic_count"] = str(len(self.options.nics))
1342 24b9469d Agata Murawska
    return results
1343 24b9469d Agata Murawska
1344 99381e3b Agata Murawska
  def _ParseDiskOptions(self):
1345 99381e3b Agata Murawska
    """Parses disk options given in a command line.
1346 99381e3b Agata Murawska

1347 99381e3b Agata Murawska
    @rtype: dict
1348 99381e3b Agata Murawska
    @return: dictionary of disk-related options
1349 99381e3b Agata Murawska

1350 99381e3b Agata Murawska
    @raise errors.OpPrereqError: disk description does not contain size
1351 99381e3b Agata Murawska
      information or size information is invalid or creation failed
1352 99381e3b Agata Murawska

1353 99381e3b Agata Murawska
    """
1354 a002ed79 Agata Murawska
    CheckQemuImg()
1355 99381e3b Agata Murawska
    assert self.options.disks
1356 99381e3b Agata Murawska
    results = {}
1357 99381e3b Agata Murawska
    for (disk_id, disk_desc) in self.options.disks:
1358 99381e3b Agata Murawska
      results["disk%s_ivname" % disk_id] = "disk/%s" % disk_id
1359 99381e3b Agata Murawska
      if disk_desc.get("size"):
1360 99381e3b Agata Murawska
        try:
1361 99381e3b Agata Murawska
          disk_size = utils.ParseUnit(disk_desc["size"])
1362 99381e3b Agata Murawska
        except ValueError:
1363 99381e3b Agata Murawska
          raise errors.OpPrereqError("Invalid disk size for disk %s: %s" %
1364 2cfbc784 Iustin Pop
                                     (disk_id, disk_desc["size"]),
1365 2cfbc784 Iustin Pop
                                     errors.ECODE_INVAL)
1366 99381e3b Agata Murawska
        new_path = utils.PathJoin(self.output_dir, str(disk_id))
1367 99381e3b Agata Murawska
        args = [
1368 e68949b0 Agata Murawska
          constants.QEMUIMG_PATH,
1369 99381e3b Agata Murawska
          "create",
1370 99381e3b Agata Murawska
          "-f",
1371 99381e3b Agata Murawska
          "raw",
1372 99381e3b Agata Murawska
          new_path,
1373 99381e3b Agata Murawska
          disk_size,
1374 99381e3b Agata Murawska
        ]
1375 99381e3b Agata Murawska
        run_result = utils.RunCmd(args)
1376 99381e3b Agata Murawska
        if run_result.failed:
1377 99381e3b Agata Murawska
          raise errors.OpPrereqError("Creation of disk %s failed, output was:"
1378 2cfbc784 Iustin Pop
                                     " %s" % (new_path, run_result.stderr),
1379 2cfbc784 Iustin Pop
                                     errors.ECODE_ENVIRON)
1380 99381e3b Agata Murawska
        results["disk%s_size" % disk_id] = str(disk_size)
1381 99381e3b Agata Murawska
        results["disk%s_dump" % disk_id] = "disk%s.raw" % disk_id
1382 99381e3b Agata Murawska
      else:
1383 99381e3b Agata Murawska
        raise errors.OpPrereqError("Disks created for import must have their"
1384 2cfbc784 Iustin Pop
                                   " size specified",
1385 2cfbc784 Iustin Pop
                                   errors.ECODE_INVAL)
1386 99381e3b Agata Murawska
    results["disk_count"] = str(len(self.options.disks))
1387 99381e3b Agata Murawska
    return results
1388 99381e3b Agata Murawska
1389 99381e3b Agata Murawska
  def _GetDiskInfo(self):
1390 99381e3b Agata Murawska
    """Gathers information about disks used by instance, perfomes conversion.
1391 99381e3b Agata Murawska

1392 99381e3b Agata Murawska
    @rtype: dict
1393 99381e3b Agata Murawska
    @return: dictionary of disk-related options
1394 99381e3b Agata Murawska

1395 99381e3b Agata Murawska
    @raise errors.OpPrereqError: disk is not in the same directory as .ovf file
1396 99381e3b Agata Murawska

1397 99381e3b Agata Murawska
    """
1398 99381e3b Agata Murawska
    results = {}
1399 99381e3b Agata Murawska
    disks_list = self.ovf_reader.GetDisksNames()
1400 99381e3b Agata Murawska
    for (counter, (disk_name, disk_compression)) in enumerate(disks_list):
1401 99381e3b Agata Murawska
      if os.path.dirname(disk_name):
1402 99381e3b Agata Murawska
        raise errors.OpPrereqError("Disks are not allowed to have absolute"
1403 2cfbc784 Iustin Pop
                                   " paths or paths outside main OVF"
1404 2cfbc784 Iustin Pop
                                   " directory", errors.ECODE_ENVIRON)
1405 99381e3b Agata Murawska
      disk, _ = os.path.splitext(disk_name)
1406 99381e3b Agata Murawska
      disk_path = utils.PathJoin(self.input_dir, disk_name)
1407 fa337742 Agata Murawska
      if disk_compression not in NO_COMPRESSION:
1408 99381e3b Agata Murawska
        _, disk_path = self._CompressDisk(disk_path, disk_compression,
1409 5ae4945a Iustin Pop
                                          DECOMPRESS)
1410 99381e3b Agata Murawska
        disk, _ = os.path.splitext(disk)
1411 99381e3b Agata Murawska
      if self._GetDiskQemuInfo(disk_path, "file format: (\S+)") != "raw":
1412 99381e3b Agata Murawska
        logging.info("Conversion to raw format is required")
1413 99381e3b Agata Murawska
      ext, new_disk_path = self._ConvertDisk("raw", disk_path)
1414 99381e3b Agata Murawska
1415 99381e3b Agata Murawska
      final_disk_path = LinkFile(new_disk_path, prefix=disk, suffix=ext,
1416 5ae4945a Iustin Pop
                                 directory=self.output_dir)
1417 99381e3b Agata Murawska
      final_name = os.path.basename(final_disk_path)
1418 99381e3b Agata Murawska
      disk_size = os.path.getsize(final_disk_path) / (1024 * 1024)
1419 99381e3b Agata Murawska
      results["disk%s_dump" % counter] = final_name
1420 99381e3b Agata Murawska
      results["disk%s_size" % counter] = str(disk_size)
1421 99381e3b Agata Murawska
      results["disk%s_ivname" % counter] = "disk/%s" % str(counter)
1422 99381e3b Agata Murawska
    if disks_list:
1423 99381e3b Agata Murawska
      results["disk_count"] = str(len(disks_list))
1424 99381e3b Agata Murawska
    return results
1425 ced78a66 Agata Murawska
1426 ced78a66 Agata Murawska
  def Save(self):
1427 864cf6bf Agata Murawska
    """Saves all the gathered information in a constant.EXPORT_CONF_FILE file.
1428 864cf6bf Agata Murawska

1429 864cf6bf Agata Murawska
    @raise errors.OpPrereqError: when saving to config file failed
1430 864cf6bf Agata Murawska

1431 864cf6bf Agata Murawska
    """
1432 864cf6bf Agata Murawska
    logging.info("Conversion was succesfull, saving %s in %s directory",
1433 864cf6bf Agata Murawska
                 constants.EXPORT_CONF_FILE, self.output_dir)
1434 864cf6bf Agata Murawska
    results = {
1435 864cf6bf Agata Murawska
      constants.INISECT_INS: {},
1436 864cf6bf Agata Murawska
      constants.INISECT_BEP: {},
1437 864cf6bf Agata Murawska
      constants.INISECT_EXP: {},
1438 864cf6bf Agata Murawska
      constants.INISECT_OSP: {},
1439 864cf6bf Agata Murawska
      constants.INISECT_HYP: {},
1440 864cf6bf Agata Murawska
    }
1441 864cf6bf Agata Murawska
1442 864cf6bf Agata Murawska
    results[constants.INISECT_INS].update(self.results_disk)
1443 24b9469d Agata Murawska
    results[constants.INISECT_INS].update(self.results_network)
1444 7bde29b5 Agata Murawska
    results[constants.INISECT_INS]["hypervisor"] = \
1445 7bde29b5 Agata Murawska
      self.results_hypervisor["hypervisor_name"]
1446 864cf6bf Agata Murawska
    results[constants.INISECT_INS]["name"] = self.results_name
1447 864cf6bf Agata Murawska
    if self.results_template:
1448 864cf6bf Agata Murawska
      results[constants.INISECT_INS]["disk_template"] = self.results_template
1449 7bde29b5 Agata Murawska
    if self.results_tags:
1450 7bde29b5 Agata Murawska
      results[constants.INISECT_INS]["tags"] = self.results_tags
1451 7bde29b5 Agata Murawska
1452 7bde29b5 Agata Murawska
    results[constants.INISECT_BEP].update(self.results_backend)
1453 7bde29b5 Agata Murawska
1454 7bde29b5 Agata Murawska
    results[constants.INISECT_EXP]["os"] = self.results_os["os_name"]
1455 7bde29b5 Agata Murawska
    results[constants.INISECT_EXP]["version"] = self.results_version
1456 7bde29b5 Agata Murawska
1457 7bde29b5 Agata Murawska
    del self.results_os["os_name"]
1458 7bde29b5 Agata Murawska
    results[constants.INISECT_OSP].update(self.results_os)
1459 7bde29b5 Agata Murawska
1460 7bde29b5 Agata Murawska
    del self.results_hypervisor["hypervisor_name"]
1461 7bde29b5 Agata Murawska
    results[constants.INISECT_HYP].update(self.results_hypervisor)
1462 864cf6bf Agata Murawska
1463 864cf6bf Agata Murawska
    output_file_name = utils.PathJoin(self.output_dir,
1464 5ae4945a Iustin Pop
                                      constants.EXPORT_CONF_FILE)
1465 864cf6bf Agata Murawska
1466 864cf6bf Agata Murawska
    output = []
1467 864cf6bf Agata Murawska
    for section, options in results.iteritems():
1468 864cf6bf Agata Murawska
      output.append("[%s]" % section)
1469 864cf6bf Agata Murawska
      for name, value in options.iteritems():
1470 99381e3b Agata Murawska
        if value is None:
1471 99381e3b Agata Murawska
          value = ""
1472 864cf6bf Agata Murawska
        output.append("%s = %s" % (name, value))
1473 864cf6bf Agata Murawska
      output.append("")
1474 864cf6bf Agata Murawska
    output_contents = "\n".join(output)
1475 864cf6bf Agata Murawska
1476 864cf6bf Agata Murawska
    try:
1477 864cf6bf Agata Murawska
      utils.WriteFile(output_file_name, data=output_contents)
1478 864cf6bf Agata Murawska
    except errors.ProgrammerError, err:
1479 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("Saving the config file failed: %s" % err,
1480 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1481 864cf6bf Agata Murawska
1482 864cf6bf Agata Murawska
    self.Cleanup()
1483 ced78a66 Agata Murawska
1484 ced78a66 Agata Murawska
1485 0963b26a Agata Murawska
class ConfigParserWithDefaults(ConfigParser.SafeConfigParser):
1486 0963b26a Agata Murawska
  """This is just a wrapper on SafeConfigParser, that uses default values
1487 0963b26a Agata Murawska

1488 0963b26a Agata Murawska
  """
1489 0963b26a Agata Murawska
  def get(self, section, options, raw=None, vars=None): # pylint: disable=W0622
1490 0963b26a Agata Murawska
    try:
1491 5ae4945a Iustin Pop
      result = ConfigParser.SafeConfigParser.get(self, section, options,
1492 5ae4945a Iustin Pop
                                                 raw=raw, vars=vars)
1493 0963b26a Agata Murawska
    except ConfigParser.NoOptionError:
1494 0963b26a Agata Murawska
      result = None
1495 0963b26a Agata Murawska
    return result
1496 0963b26a Agata Murawska
1497 0963b26a Agata Murawska
  def getint(self, section, options):
1498 0963b26a Agata Murawska
    try:
1499 0963b26a Agata Murawska
      result = ConfigParser.SafeConfigParser.get(self, section, options)
1500 0963b26a Agata Murawska
    except ConfigParser.NoOptionError:
1501 0963b26a Agata Murawska
      result = 0
1502 0963b26a Agata Murawska
    return int(result)
1503 0963b26a Agata Murawska
1504 0963b26a Agata Murawska
1505 ced78a66 Agata Murawska
class OVFExporter(Converter):
1506 0963b26a Agata Murawska
  """Converter from Ganeti config file to OVF
1507 0963b26a Agata Murawska

1508 0963b26a Agata Murawska
  @type input_dir: string
1509 0963b26a Agata Murawska
  @ivar input_dir: directory in which the config.ini file resides
1510 0963b26a Agata Murawska
  @type output_dir: string
1511 0963b26a Agata Murawska
  @ivar output_dir: directory to which the results of conversion shall be
1512 0963b26a Agata Murawska
    written
1513 0963b26a Agata Murawska
  @type packed_dir: string
1514 0963b26a Agata Murawska
  @ivar packed_dir: if we want OVA package, this points to the real (i.e. not
1515 0963b26a Agata Murawska
    temp) output directory
1516 0963b26a Agata Murawska
  @type input_path: string
1517 0963b26a Agata Murawska
  @ivar input_path: complete path to the config.ini file
1518 0963b26a Agata Murawska
  @type output_path: string
1519 0963b26a Agata Murawska
  @ivar output_path: complete path to .ovf file
1520 0963b26a Agata Murawska
  @type config_parser: L{ConfigParserWithDefaults}
1521 0963b26a Agata Murawska
  @ivar config_parser: parser for the config.ini file
1522 7432d332 Agata Murawska
  @type reference_files: list
1523 7432d332 Agata Murawska
  @ivar reference_files: files referenced in the ovf file
1524 b179ce72 Agata Murawska
  @type results_disk: list
1525 b179ce72 Agata Murawska
  @ivar results_disk: list of dictionaries of disk options from config.ini
1526 b179ce72 Agata Murawska
  @type results_network: list
1527 b179ce72 Agata Murawska
  @ivar results_network: list of dictionaries of network options form config.ini
1528 0963b26a Agata Murawska
  @type results_name: string
1529 0963b26a Agata Murawska
  @ivar results_name: name of the instance
1530 b179ce72 Agata Murawska
  @type results_vcpus: string
1531 b179ce72 Agata Murawska
  @ivar results_vcpus: number of VCPUs
1532 b179ce72 Agata Murawska
  @type results_memory: string
1533 b179ce72 Agata Murawska
  @ivar results_memory: RAM memory in MB
1534 b179ce72 Agata Murawska
  @type results_ganeti: dict
1535 b179ce72 Agata Murawska
  @ivar results_ganeti: dictionary of Ganeti-specific options from config.ini
1536 0963b26a Agata Murawska

1537 0963b26a Agata Murawska
  """
1538 ced78a66 Agata Murawska
  def _ReadInputData(self, input_path):
1539 0963b26a Agata Murawska
    """Reads the data on which the conversion will take place.
1540 0963b26a Agata Murawska

1541 0963b26a Agata Murawska
    @type input_path: string
1542 0963b26a Agata Murawska
    @param input_path: absolute path to the config.ini input file
1543 0963b26a Agata Murawska

1544 0963b26a Agata Murawska
    @raise errors.OpPrereqError: error when reading the config file
1545 0963b26a Agata Murawska

1546 0963b26a Agata Murawska
    """
1547 0963b26a Agata Murawska
    input_dir = os.path.dirname(input_path)
1548 0963b26a Agata Murawska
    self.input_path = input_path
1549 0963b26a Agata Murawska
    self.input_dir = input_dir
1550 0963b26a Agata Murawska
    if self.options.output_dir:
1551 0963b26a Agata Murawska
      self.output_dir = os.path.abspath(self.options.output_dir)
1552 0963b26a Agata Murawska
    else:
1553 0963b26a Agata Murawska
      self.output_dir = input_dir
1554 0963b26a Agata Murawska
    self.config_parser = ConfigParserWithDefaults()
1555 0963b26a Agata Murawska
    logging.info("Reading configuration from %s file", input_path)
1556 0963b26a Agata Murawska
    try:
1557 0963b26a Agata Murawska
      self.config_parser.read(input_path)
1558 0963b26a Agata Murawska
    except ConfigParser.MissingSectionHeaderError, err:
1559 0963b26a Agata Murawska
      raise errors.OpPrereqError("Error when trying to read %s: %s" %
1560 2cfbc784 Iustin Pop
                                 (input_path, err), errors.ECODE_ENVIRON)
1561 0963b26a Agata Murawska
    if self.options.ova_package:
1562 0963b26a Agata Murawska
      self.temp_dir = tempfile.mkdtemp()
1563 0963b26a Agata Murawska
      self.packed_dir = self.output_dir
1564 0963b26a Agata Murawska
      self.output_dir = self.temp_dir
1565 0963b26a Agata Murawska
1566 0963b26a Agata Murawska
    self.ovf_writer = OVFWriter(not self.options.ext_usage)
1567 0963b26a Agata Murawska
1568 0963b26a Agata Murawska
  def _ParseName(self):
1569 0963b26a Agata Murawska
    """Parses name from command line options or config file.
1570 0963b26a Agata Murawska

1571 0963b26a Agata Murawska
    @rtype: string
1572 0963b26a Agata Murawska
    @return: name of Ganeti instance
1573 0963b26a Agata Murawska

1574 0963b26a Agata Murawska
    @raise errors.OpPrereqError: if name of the instance is not provided
1575 0963b26a Agata Murawska

1576 0963b26a Agata Murawska
    """
1577 0963b26a Agata Murawska
    if self.options.name:
1578 0963b26a Agata Murawska
      name = self.options.name
1579 0963b26a Agata Murawska
    else:
1580 0963b26a Agata Murawska
      name = self.config_parser.get(constants.INISECT_INS, NAME)
1581 0963b26a Agata Murawska
    if name is None:
1582 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No instance name found",
1583 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1584 0963b26a Agata Murawska
    return name
1585 ced78a66 Agata Murawska
1586 b179ce72 Agata Murawska
  def _ParseVCPUs(self):
1587 b179ce72 Agata Murawska
    """Parses vcpus number from config file.
1588 b179ce72 Agata Murawska

1589 b179ce72 Agata Murawska
    @rtype: int
1590 b179ce72 Agata Murawska
    @return: number of virtual CPUs
1591 b179ce72 Agata Murawska

1592 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: if number of VCPUs equals 0
1593 b179ce72 Agata Murawska

1594 b179ce72 Agata Murawska
    """
1595 b179ce72 Agata Murawska
    vcpus = self.config_parser.getint(constants.INISECT_BEP, VCPUS)
1596 b179ce72 Agata Murawska
    if vcpus == 0:
1597 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No CPU information found",
1598 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1599 b179ce72 Agata Murawska
    return vcpus
1600 b179ce72 Agata Murawska
1601 b179ce72 Agata Murawska
  def _ParseMemory(self):
1602 b179ce72 Agata Murawska
    """Parses vcpus number from config file.
1603 b179ce72 Agata Murawska

1604 b179ce72 Agata Murawska
    @rtype: int
1605 b179ce72 Agata Murawska
    @return: amount of memory in MB
1606 b179ce72 Agata Murawska

1607 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: if amount of memory equals 0
1608 b179ce72 Agata Murawska

1609 b179ce72 Agata Murawska
    """
1610 b179ce72 Agata Murawska
    memory = self.config_parser.getint(constants.INISECT_BEP, MEMORY)
1611 b179ce72 Agata Murawska
    if memory == 0:
1612 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No memory information found",
1613 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1614 b179ce72 Agata Murawska
    return memory
1615 b179ce72 Agata Murawska
1616 b179ce72 Agata Murawska
  def _ParseGaneti(self):
1617 b179ce72 Agata Murawska
    """Parses Ganeti data from config file.
1618 b179ce72 Agata Murawska

1619 b179ce72 Agata Murawska
    @rtype: dictionary
1620 b179ce72 Agata Murawska
    @return: dictionary of Ganeti-specific options
1621 b179ce72 Agata Murawska

1622 b179ce72 Agata Murawska
    """
1623 b179ce72 Agata Murawska
    results = {}
1624 b179ce72 Agata Murawska
    # hypervisor
1625 b179ce72 Agata Murawska
    results["hypervisor"] = {}
1626 b179ce72 Agata Murawska
    hyp_name = self.config_parser.get(constants.INISECT_INS, HYPERV)
1627 b179ce72 Agata Murawska
    if hyp_name is None:
1628 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No hypervisor information found",
1629 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1630 b179ce72 Agata Murawska
    results["hypervisor"]["name"] = hyp_name
1631 b179ce72 Agata Murawska
    pairs = self.config_parser.items(constants.INISECT_HYP)
1632 b179ce72 Agata Murawska
    for (name, value) in pairs:
1633 b179ce72 Agata Murawska
      results["hypervisor"][name] = value
1634 b179ce72 Agata Murawska
    # os
1635 b179ce72 Agata Murawska
    results["os"] = {}
1636 b179ce72 Agata Murawska
    os_name = self.config_parser.get(constants.INISECT_EXP, OS)
1637 b179ce72 Agata Murawska
    if os_name is None:
1638 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No operating system information found",
1639 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1640 b179ce72 Agata Murawska
    results["os"]["name"] = os_name
1641 b179ce72 Agata Murawska
    pairs = self.config_parser.items(constants.INISECT_OSP)
1642 b179ce72 Agata Murawska
    for (name, value) in pairs:
1643 b179ce72 Agata Murawska
      results["os"][name] = value
1644 b179ce72 Agata Murawska
    # other
1645 b179ce72 Agata Murawska
    others = [
1646 b179ce72 Agata Murawska
      (constants.INISECT_INS, DISK_TEMPLATE, "disk_template"),
1647 b179ce72 Agata Murawska
      (constants.INISECT_BEP, AUTO_BALANCE, "auto_balance"),
1648 b179ce72 Agata Murawska
      (constants.INISECT_INS, TAGS, "tags"),
1649 b179ce72 Agata Murawska
      (constants.INISECT_EXP, VERSION, "version"),
1650 b179ce72 Agata Murawska
    ]
1651 b179ce72 Agata Murawska
    for (section, element, name) in others:
1652 b179ce72 Agata Murawska
      results[name] = self.config_parser.get(section, element)
1653 b179ce72 Agata Murawska
    return results
1654 b179ce72 Agata Murawska
1655 b179ce72 Agata Murawska
  def _ParseNetworks(self):
1656 b179ce72 Agata Murawska
    """Parses network data from config file.
1657 b179ce72 Agata Murawska

1658 b179ce72 Agata Murawska
    @rtype: list
1659 b179ce72 Agata Murawska
    @return: list of dictionaries of network options
1660 b179ce72 Agata Murawska

1661 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: then network mode is not recognized
1662 b179ce72 Agata Murawska

1663 b179ce72 Agata Murawska
    """
1664 b179ce72 Agata Murawska
    results = []
1665 b179ce72 Agata Murawska
    counter = 0
1666 b179ce72 Agata Murawska
    while True:
1667 b179ce72 Agata Murawska
      data_link = \
1668 6e8091f9 Dimitris Aragiorgis
        self.config_parser.get(constants.INISECT_INS,
1669 6e8091f9 Dimitris Aragiorgis
                               "nic%s_link" % counter)
1670 b179ce72 Agata Murawska
      if data_link is None:
1671 b179ce72 Agata Murawska
        break
1672 b179ce72 Agata Murawska
      results.append({
1673 b179ce72 Agata Murawska
        "mode": self.config_parser.get(constants.INISECT_INS,
1674 5ae4945a Iustin Pop
                                       "nic%s_mode" % counter),
1675 b179ce72 Agata Murawska
        "mac": self.config_parser.get(constants.INISECT_INS,
1676 5ae4945a Iustin Pop
                                      "nic%s_mac" % counter),
1677 b179ce72 Agata Murawska
        "ip": self.config_parser.get(constants.INISECT_INS,
1678 5ae4945a Iustin Pop
                                     "nic%s_ip" % counter),
1679 6e8091f9 Dimitris Aragiorgis
        "network": self.config_parser.get(constants.INISECT_INS,
1680 6e8091f9 Dimitris Aragiorgis
                                          "nic%s_network" % counter),
1681 6e8091f9 Dimitris Aragiorgis
        "link": data_link,
1682 b179ce72 Agata Murawska
      })
1683 b179ce72 Agata Murawska
      if results[counter]["mode"] not in constants.NIC_VALID_MODES:
1684 b179ce72 Agata Murawska
        raise errors.OpPrereqError("Network mode %s not recognized"
1685 2cfbc784 Iustin Pop
                                   % results[counter]["mode"],
1686 2cfbc784 Iustin Pop
                                   errors.ECODE_INVAL)
1687 b179ce72 Agata Murawska
      counter += 1
1688 b179ce72 Agata Murawska
    return results
1689 b179ce72 Agata Murawska
1690 b179ce72 Agata Murawska
  def _GetDiskOptions(self, disk_file, compression):
1691 b179ce72 Agata Murawska
    """Convert the disk and gather disk info for .ovf file.
1692 b179ce72 Agata Murawska

1693 b179ce72 Agata Murawska
    @type disk_file: string
1694 b179ce72 Agata Murawska
    @param disk_file: name of the disk (without the full path)
1695 b179ce72 Agata Murawska
    @type compression: bool
1696 b179ce72 Agata Murawska
    @param compression: whether the disk should be compressed or not
1697 b179ce72 Agata Murawska

1698 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: when disk image does not exist
1699 b179ce72 Agata Murawska

1700 b179ce72 Agata Murawska
    """
1701 b179ce72 Agata Murawska
    disk_path = utils.PathJoin(self.input_dir, disk_file)
1702 b179ce72 Agata Murawska
    results = {}
1703 b179ce72 Agata Murawska
    if not os.path.isfile(disk_path):
1704 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("Disk image does not exist: %s" % disk_path,
1705 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1706 b179ce72 Agata Murawska
    if os.path.dirname(disk_file):
1707 b179ce72 Agata Murawska
      raise errors.OpPrereqError("Path for the disk: %s contains a directory"
1708 2cfbc784 Iustin Pop
                                 " name" % disk_path, errors.ECODE_ENVIRON)
1709 b179ce72 Agata Murawska
    disk_name, _ = os.path.splitext(disk_file)
1710 b179ce72 Agata Murawska
    ext, new_disk_path = self._ConvertDisk(self.options.disk_format, disk_path)
1711 b179ce72 Agata Murawska
    results["format"] = self.options.disk_format
1712 5ae4945a Iustin Pop
    results["virt-size"] = self._GetDiskQemuInfo(
1713 5ae4945a Iustin Pop
      new_disk_path, "virtual size: \S+ \((\d+) bytes\)")
1714 b179ce72 Agata Murawska
    if compression:
1715 b179ce72 Agata Murawska
      ext2, new_disk_path = self._CompressDisk(new_disk_path, "gzip",
1716 5ae4945a Iustin Pop
                                               COMPRESS)
1717 b179ce72 Agata Murawska
      disk_name, _ = os.path.splitext(disk_name)
1718 b179ce72 Agata Murawska
      results["compression"] = "gzip"
1719 b179ce72 Agata Murawska
      ext += ext2
1720 b179ce72 Agata Murawska
    final_disk_path = LinkFile(new_disk_path, prefix=disk_name, suffix=ext,
1721 5ae4945a Iustin Pop
                               directory=self.output_dir)
1722 b179ce72 Agata Murawska
    final_disk_name = os.path.basename(final_disk_path)
1723 b179ce72 Agata Murawska
    results["real-size"] = os.path.getsize(final_disk_path)
1724 b179ce72 Agata Murawska
    results["path"] = final_disk_name
1725 b179ce72 Agata Murawska
    self.references_files.append(final_disk_path)
1726 b179ce72 Agata Murawska
    return results
1727 b179ce72 Agata Murawska
1728 b179ce72 Agata Murawska
  def _ParseDisks(self):
1729 b179ce72 Agata Murawska
    """Parses disk data from config file.
1730 b179ce72 Agata Murawska

1731 b179ce72 Agata Murawska
    @rtype: list
1732 b179ce72 Agata Murawska
    @return: list of dictionaries of disk options
1733 b179ce72 Agata Murawska

1734 b179ce72 Agata Murawska
    """
1735 b179ce72 Agata Murawska
    results = []
1736 b179ce72 Agata Murawska
    counter = 0
1737 b179ce72 Agata Murawska
    while True:
1738 b179ce72 Agata Murawska
      disk_file = \
1739 b179ce72 Agata Murawska
        self.config_parser.get(constants.INISECT_INS, "disk%s_dump" % counter)
1740 b179ce72 Agata Murawska
      if disk_file is None:
1741 b179ce72 Agata Murawska
        break
1742 b179ce72 Agata Murawska
      results.append(self._GetDiskOptions(disk_file, self.options.compression))
1743 b179ce72 Agata Murawska
      counter += 1
1744 b179ce72 Agata Murawska
    return results
1745 b179ce72 Agata Murawska
1746 ced78a66 Agata Murawska
  def Parse(self):
1747 0963b26a Agata Murawska
    """Parses the data and creates a structure containing all required info.
1748 0963b26a Agata Murawska

1749 0963b26a Agata Murawska
    """
1750 0963b26a Agata Murawska
    try:
1751 0963b26a Agata Murawska
      utils.Makedirs(self.output_dir)
1752 0963b26a Agata Murawska
    except OSError, err:
1753 0963b26a Agata Murawska
      raise errors.OpPrereqError("Failed to create directory %s: %s" %
1754 2cfbc784 Iustin Pop
                                 (self.output_dir, err), errors.ECODE_ENVIRON)
1755 0963b26a Agata Murawska
1756 b179ce72 Agata Murawska
    self.references_files = []
1757 0963b26a Agata Murawska
    self.results_name = self._ParseName()
1758 b179ce72 Agata Murawska
    self.results_vcpus = self._ParseVCPUs()
1759 b179ce72 Agata Murawska
    self.results_memory = self._ParseMemory()
1760 b179ce72 Agata Murawska
    if not self.options.ext_usage:
1761 b179ce72 Agata Murawska
      self.results_ganeti = self._ParseGaneti()
1762 b179ce72 Agata Murawska
    self.results_network = self._ParseNetworks()
1763 b179ce72 Agata Murawska
    self.results_disk = self._ParseDisks()
1764 0963b26a Agata Murawska
1765 0963b26a Agata Murawska
  def _PrepareManifest(self, path):
1766 0963b26a Agata Murawska
    """Creates manifest for all the files in OVF package.
1767 0963b26a Agata Murawska

1768 0963b26a Agata Murawska
    @type path: string
1769 0963b26a Agata Murawska
    @param path: path to manifesto file
1770 0963b26a Agata Murawska

1771 0963b26a Agata Murawska
    @raise errors.OpPrereqError: if error occurs when writing file
1772 0963b26a Agata Murawska

1773 0963b26a Agata Murawska
    """
1774 0963b26a Agata Murawska
    logging.info("Preparing manifest for the OVF package")
1775 0963b26a Agata Murawska
    lines = []
1776 0963b26a Agata Murawska
    files_list = [self.output_path]
1777 0963b26a Agata Murawska
    files_list.extend(self.references_files)
1778 0963b26a Agata Murawska
    logging.warning("Calculating SHA1 checksums, this may take a while")
1779 0963b26a Agata Murawska
    sha1_sums = utils.FingerprintFiles(files_list)
1780 0963b26a Agata Murawska
    for file_path, value in sha1_sums.iteritems():
1781 0963b26a Agata Murawska
      file_name = os.path.basename(file_path)
1782 0963b26a Agata Murawska
      lines.append("SHA1(%s)= %s" % (file_name, value))
1783 0963b26a Agata Murawska
    lines.append("")
1784 0963b26a Agata Murawska
    data = "\n".join(lines)
1785 0963b26a Agata Murawska
    try:
1786 0963b26a Agata Murawska
      utils.WriteFile(path, data=data)
1787 0963b26a Agata Murawska
    except errors.ProgrammerError, err:
1788 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("Saving the manifest file failed: %s" % err,
1789 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1790 0963b26a Agata Murawska
1791 0963b26a Agata Murawska
  @staticmethod
1792 0963b26a Agata Murawska
  def _PrepareTarFile(tar_path, files_list):
1793 0963b26a Agata Murawska
    """Creates tarfile from the files in OVF package.
1794 0963b26a Agata Murawska

1795 0963b26a Agata Murawska
    @type tar_path: string
1796 0963b26a Agata Murawska
    @param tar_path: path to the resulting file
1797 0963b26a Agata Murawska
    @type files_list: list
1798 0963b26a Agata Murawska
    @param files_list: list of files in the OVF package
1799 0963b26a Agata Murawska

1800 0963b26a Agata Murawska
    """
1801 0963b26a Agata Murawska
    logging.info("Preparing tarball for the OVF package")
1802 0963b26a Agata Murawska
    open(tar_path, mode="w").close()
1803 0963b26a Agata Murawska
    ova_package = tarfile.open(name=tar_path, mode="w")
1804 fa337742 Agata Murawska
    for file_path in files_list:
1805 fa337742 Agata Murawska
      file_name = os.path.basename(file_path)
1806 fa337742 Agata Murawska
      ova_package.add(file_path, arcname=file_name)
1807 0963b26a Agata Murawska
    ova_package.close()
1808 ced78a66 Agata Murawska
1809 ced78a66 Agata Murawska
  def Save(self):
1810 0963b26a Agata Murawska
    """Saves the gathered configuration in an apropriate format.
1811 0963b26a Agata Murawska

1812 0963b26a Agata Murawska
    @raise errors.OpPrereqError: if unable to create output directory
1813 0963b26a Agata Murawska

1814 0963b26a Agata Murawska
    """
1815 0963b26a Agata Murawska
    output_file = "%s%s" % (self.results_name, OVF_EXT)
1816 0963b26a Agata Murawska
    output_path = utils.PathJoin(self.output_dir, output_file)
1817 0963b26a Agata Murawska
    self.ovf_writer = OVFWriter(not self.options.ext_usage)
1818 0963b26a Agata Murawska
    logging.info("Saving read data to %s", output_path)
1819 0963b26a Agata Murawska
1820 0963b26a Agata Murawska
    self.output_path = utils.PathJoin(self.output_dir, output_file)
1821 0963b26a Agata Murawska
    files_list = [self.output_path]
1822 0963b26a Agata Murawska
1823 7432d332 Agata Murawska
    self.ovf_writer.SaveDisksData(self.results_disk)
1824 7432d332 Agata Murawska
    self.ovf_writer.SaveNetworksData(self.results_network)
1825 7432d332 Agata Murawska
    if not self.options.ext_usage:
1826 7432d332 Agata Murawska
      self.ovf_writer.SaveGanetiData(self.results_ganeti, self.results_network)
1827 7432d332 Agata Murawska
1828 7432d332 Agata Murawska
    self.ovf_writer.SaveVirtualSystemData(self.results_name, self.results_vcpus,
1829 5ae4945a Iustin Pop
                                          self.results_memory)
1830 7432d332 Agata Murawska
1831 0963b26a Agata Murawska
    data = self.ovf_writer.PrettyXmlDump()
1832 0963b26a Agata Murawska
    utils.WriteFile(self.output_path, data=data)
1833 0963b26a Agata Murawska
1834 0963b26a Agata Murawska
    manifest_file = "%s%s" % (self.results_name, MF_EXT)
1835 0963b26a Agata Murawska
    manifest_path = utils.PathJoin(self.output_dir, manifest_file)
1836 0963b26a Agata Murawska
    self._PrepareManifest(manifest_path)
1837 0963b26a Agata Murawska
    files_list.append(manifest_path)
1838 0963b26a Agata Murawska
1839 0963b26a Agata Murawska
    files_list.extend(self.references_files)
1840 0963b26a Agata Murawska
1841 0963b26a Agata Murawska
    if self.options.ova_package:
1842 0963b26a Agata Murawska
      ova_file = "%s%s" % (self.results_name, OVA_EXT)
1843 0963b26a Agata Murawska
      packed_path = utils.PathJoin(self.packed_dir, ova_file)
1844 0963b26a Agata Murawska
      try:
1845 0963b26a Agata Murawska
        utils.Makedirs(self.packed_dir)
1846 0963b26a Agata Murawska
      except OSError, err:
1847 0963b26a Agata Murawska
        raise errors.OpPrereqError("Failed to create directory %s: %s" %
1848 2cfbc784 Iustin Pop
                                   (self.packed_dir, err),
1849 2cfbc784 Iustin Pop
                                   errors.ECODE_ENVIRON)
1850 0963b26a Agata Murawska
      self._PrepareTarFile(packed_path, files_list)
1851 0963b26a Agata Murawska
    logging.info("Creation of the OVF package was successfull")
1852 0963b26a Agata Murawska
    self.Cleanup()