Statistics
| Branch: | Tag: | Revision:

root / lib / ovf.py @ 2cfbc784

History | View | Annotate | Download (64.7 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 ced78a66 Agata Murawska
57 ced78a66 Agata Murawska
58 864cf6bf Agata Murawska
# Schemas used in OVF format
59 864cf6bf Agata Murawska
GANETI_SCHEMA = "http://ganeti"
60 864cf6bf Agata Murawska
OVF_SCHEMA = "http://schemas.dmtf.org/ovf/envelope/1"
61 864cf6bf Agata Murawska
RASD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/"
62 864cf6bf Agata Murawska
               "CIM_ResourceAllocationSettingData")
63 0963b26a Agata Murawska
VSSD_SCHEMA = ("http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/"
64 0963b26a Agata Murawska
               "CIM_VirtualSystemSettingData")
65 0963b26a Agata Murawska
XML_SCHEMA = "http://www.w3.org/2001/XMLSchema-instance"
66 864cf6bf Agata Murawska
67 864cf6bf Agata Murawska
# File extensions in OVF package
68 864cf6bf Agata Murawska
OVA_EXT = ".ova"
69 864cf6bf Agata Murawska
OVF_EXT = ".ovf"
70 864cf6bf Agata Murawska
MF_EXT = ".mf"
71 864cf6bf Agata Murawska
CERT_EXT = ".cert"
72 99381e3b Agata Murawska
COMPRESSION_EXT = ".gz"
73 864cf6bf Agata Murawska
FILE_EXTENSIONS = [
74 864cf6bf Agata Murawska
  OVF_EXT,
75 864cf6bf Agata Murawska
  MF_EXT,
76 864cf6bf Agata Murawska
  CERT_EXT,
77 864cf6bf Agata Murawska
]
78 864cf6bf Agata Murawska
79 99381e3b Agata Murawska
COMPRESSION_TYPE = "gzip"
80 fa337742 Agata Murawska
NO_COMPRESSION = [None, "identity"]
81 99381e3b Agata Murawska
COMPRESS = "compression"
82 99381e3b Agata Murawska
DECOMPRESS = "decompression"
83 99381e3b Agata Murawska
ALLOWED_ACTIONS = [COMPRESS, DECOMPRESS]
84 99381e3b Agata Murawska
85 fa337742 Agata Murawska
VMDK = "vmdk"
86 fa337742 Agata Murawska
RAW = "raw"
87 fa337742 Agata Murawska
COW = "cow"
88 fa337742 Agata Murawska
ALLOWED_FORMATS = [RAW, COW, VMDK]
89 fa337742 Agata Murawska
90 7bde29b5 Agata Murawska
# ResourceType values
91 7bde29b5 Agata Murawska
RASD_TYPE = {
92 7bde29b5 Agata Murawska
  "vcpus": "3",
93 7bde29b5 Agata Murawska
  "memory": "4",
94 7432d332 Agata Murawska
  "scsi-controller": "6",
95 7432d332 Agata Murawska
  "ethernet-adapter": "10",
96 7432d332 Agata Murawska
  "disk": "17",
97 7bde29b5 Agata Murawska
}
98 7bde29b5 Agata Murawska
99 fa337742 Agata Murawska
SCSI_SUBTYPE = "lsilogic"
100 fa337742 Agata Murawska
VS_TYPE = {
101 fa337742 Agata Murawska
  "ganeti": "ganeti-ovf",
102 fa337742 Agata Murawska
  "external": "vmx-04",
103 fa337742 Agata Murawska
}
104 fa337742 Agata Murawska
105 7bde29b5 Agata Murawska
# AllocationUnits values and conversion
106 7bde29b5 Agata Murawska
ALLOCATION_UNITS = {
107 3ccb3a64 Michael Hanselmann
  "b": ["bytes", "b"],
108 3ccb3a64 Michael Hanselmann
  "kb": ["kilobytes", "kb", "byte * 2^10", "kibibytes", "kib"],
109 3ccb3a64 Michael Hanselmann
  "mb": ["megabytes", "mb", "byte * 2^20", "mebibytes", "mib"],
110 3ccb3a64 Michael Hanselmann
  "gb": ["gigabytes", "gb", "byte * 2^30", "gibibytes", "gib"],
111 7bde29b5 Agata Murawska
}
112 7bde29b5 Agata Murawska
CONVERT_UNITS_TO_MB = {
113 3ccb3a64 Michael Hanselmann
  "b": lambda x: x / (1024 * 1024),
114 3ccb3a64 Michael Hanselmann
  "kb": lambda x: x / 1024,
115 3ccb3a64 Michael Hanselmann
  "mb": lambda x: x,
116 3ccb3a64 Michael Hanselmann
  "gb": lambda x: x * 1024,
117 7bde29b5 Agata Murawska
}
118 7bde29b5 Agata Murawska
119 0963b26a Agata Murawska
# Names of the config fields
120 0963b26a Agata Murawska
NAME = "name"
121 b179ce72 Agata Murawska
OS = "os"
122 b179ce72 Agata Murawska
HYPERV = "hypervisor"
123 b179ce72 Agata Murawska
VCPUS = "vcpus"
124 b179ce72 Agata Murawska
MEMORY = "memory"
125 b179ce72 Agata Murawska
AUTO_BALANCE = "auto_balance"
126 b179ce72 Agata Murawska
DISK_TEMPLATE = "disk_template"
127 b179ce72 Agata Murawska
TAGS = "tags"
128 b179ce72 Agata Murawska
VERSION = "version"
129 0963b26a Agata Murawska
130 7432d332 Agata Murawska
# Instance IDs of System and SCSI controller
131 fa337742 Agata Murawska
INSTANCE_ID = {
132 fa337742 Agata Murawska
  "system": 0,
133 fa337742 Agata Murawska
  "vcpus": 1,
134 fa337742 Agata Murawska
  "memory": 2,
135 fa337742 Agata Murawska
  "scsi": 3,
136 fa337742 Agata Murawska
}
137 7432d332 Agata Murawska
138 7432d332 Agata Murawska
# Disk format descriptions
139 7432d332 Agata Murawska
DISK_FORMAT = {
140 fa337742 Agata Murawska
  RAW: "http://en.wikipedia.org/wiki/Byte",
141 fa337742 Agata Murawska
  VMDK: "http://www.vmware.com/interfaces/specifications/vmdk.html"
142 7432d332 Agata Murawska
          "#monolithicSparse",
143 fa337742 Agata Murawska
  COW: "http://www.gnome.org/~markmc/qcow-image-format.html",
144 7432d332 Agata Murawska
}
145 7432d332 Agata Murawska
146 487895ed Agata Murawska
147 a002ed79 Agata Murawska
def CheckQemuImg():
148 a002ed79 Agata Murawska
  """ Make sure that qemu-img is present before performing operations.
149 a002ed79 Agata Murawska

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

572 99381e3b Agata Murawska
    @rtype: list
573 99381e3b Agata Murawska
    @return: list of file names, as referenced in .ovf file
574 99381e3b Agata Murawska

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

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

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

619 0963b26a Agata Murawska
  """
620 0963b26a Agata Murawska
  def __init__(self, has_gnt_section):
621 0963b26a Agata Murawska
    """Initialize the writer - set the top element.
622 0963b26a Agata Murawska

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

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

649 7432d332 Agata Murawska
    @type disks: list
650 7432d332 Agata Murawska
    @param disks: list of dictionaries of disk options from config.ini
651 7432d332 Agata Murawska

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

688 7432d332 Agata Murawska
    @type networks: list
689 7432d332 Agata Murawska
    @param networks: list of dictionaries of network options form config.ini
690 7432d332 Agata Murawska

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

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

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

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

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

764 7432d332 Agata Murawska
    @type name: string
765 7432d332 Agata Murawska
    @param name: name of the instance
766 7432d332 Agata Murawska
    @type vcpus: int
767 7432d332 Agata Murawska
    @param vcpus: number of VCPUs
768 7432d332 Agata Murawska
    @type memory: int
769 7432d332 Agata Murawska
    @param memory: RAM memory in MB
770 7432d332 Agata Murawska

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

825 0963b26a Agata Murawska
    @rtype: string
826 0963b26a Agata Murawska
    @return: XML tree in the form of nicely-formatted string
827 0963b26a Agata Murawska

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

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

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

853 ced78a66 Agata Murawska
  """
854 ced78a66 Agata Murawska
  def __init__(self, input_path, options):
855 ced78a66 Agata Murawska
    """Initialize the converter.
856 ced78a66 Agata Murawska

857 ced78a66 Agata Murawska
    @type input_path: string
858 ced78a66 Agata Murawska
    @param input_path: path to the Converter input file
859 ced78a66 Agata Murawska
    @type options: optparse.Values
860 ced78a66 Agata Murawska
    @param options: command line options
861 ced78a66 Agata Murawska

862 ced78a66 Agata Murawska
    @raise errors.OpPrereqError: if file does not exist
863 ced78a66 Agata Murawska

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

878 ced78a66 Agata Murawska
    @type input_path: string
879 ced78a66 Agata Murawska
    @param input_path: absolute path to the Converter input file
880 ced78a66 Agata Murawska

881 ced78a66 Agata Murawska
    """
882 ced78a66 Agata Murawska
    raise NotImplementedError()
883 ced78a66 Agata Murawska
884 99381e3b Agata Murawska
  def _CompressDisk(self, disk_path, compression, action):
885 99381e3b Agata Murawska
    """Performs (de)compression on the disk and returns the new path
886 99381e3b Agata Murawska

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

896 99381e3b Agata Murawska
    @raise errors.OpPrereqError: disk (de)compression failed or "compression"
897 99381e3b Agata Murawska
      is not supported
898 99381e3b Agata Murawska

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

926 99381e3b Agata Murawska
    @type disk_format: string
927 99381e3b Agata Murawska
    @param disk_format: format to which the disk should be converted
928 99381e3b Agata Murawska
    @type disk_path: string
929 99381e3b Agata Murawska
    @param disk_path: path to the disk that should be converted
930 99381e3b Agata Murawska
    @rtype: string
931 99381e3b Agata Murawska
    @return path to the output disk
932 99381e3b Agata Murawska

933 99381e3b Agata Murawska
    @raise errors.OpPrereqError: convertion of the disk failed
934 99381e3b Agata Murawska

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

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

972 99381e3b Agata Murawska
    @raise errors.OpPrereqError: format information cannot be retrieved
973 99381e3b Agata Murawska

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

996 ced78a66 Agata Murawska
    """
997 ced78a66 Agata Murawska
    raise NotImplementedError()
998 ced78a66 Agata Murawska
999 ced78a66 Agata Murawska
  def Save(self):
1000 ced78a66 Agata Murawska
    """Saves the gathered configuration in an apropriate format.
1001 ced78a66 Agata Murawska

1002 ced78a66 Agata Murawska
    """
1003 ced78a66 Agata Murawska
    raise NotImplementedError()
1004 ced78a66 Agata Murawska
1005 ced78a66 Agata Murawska
  def Cleanup(self):
1006 ced78a66 Agata Murawska
    """Cleans the temporary directory, if one was created.
1007 ced78a66 Agata Murawska

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

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

1052 864cf6bf Agata Murawska
  """
1053 ced78a66 Agata Murawska
  def _ReadInputData(self, input_path):
1054 864cf6bf Agata Murawska
    """Reads the data on which the conversion will take place.
1055 864cf6bf Agata Murawska

1056 864cf6bf Agata Murawska
    @type input_path: string
1057 864cf6bf Agata Murawska
    @param input_path: absolute path to the .ovf or .ova input file
1058 864cf6bf Agata Murawska

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

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

1097 864cf6bf Agata Murawska
    @type input_path: string
1098 864cf6bf Agata Murawska
    @param input_path: path to the .ova package file
1099 864cf6bf Agata Murawska

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

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

1145 99381e3b Agata Murawska
    The method reads the information given either as a command line option or as
1146 99381e3b Agata Murawska
    a part of the OVF description.
1147 99381e3b Agata Murawska

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

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

1217 99381e3b Agata Murawska
    @type name: string
1218 99381e3b Agata Murawska
    @param name: name of the section
1219 99381e3b Agata Murawska
    @type cmd_arg: dict
1220 99381e3b Agata Murawska
    @param cmd_arg: command line argument specific for section 'name'
1221 99381e3b Agata Murawska
    @type cmd_function: callable
1222 99381e3b Agata Murawska
    @param cmd_function: function to call if 'cmd_args' exists
1223 99381e3b Agata Murawska
    @type nocmd_function: callable
1224 99381e3b Agata Murawska
    @param nocmd_function: function to call if 'cmd_args' is not there
1225 99381e3b Agata Murawska

1226 99381e3b Agata Murawska
    """
1227 99381e3b Agata Murawska
    if ignore_test:
1228 99381e3b Agata Murawska
      logging.info("Information for %s will be ignored", name)
1229 99381e3b Agata Murawska
      return {}
1230 99381e3b Agata Murawska
    if cmd_arg:
1231 99381e3b Agata Murawska
      logging.info("Information for %s will be parsed from command line", name)
1232 99381e3b Agata Murawska
      results = cmd_function()
1233 99381e3b Agata Murawska
    else:
1234 99381e3b Agata Murawska
      logging.info("Information for %s will be parsed from %s file",
1235 99381e3b Agata Murawska
        name, OVF_EXT)
1236 99381e3b Agata Murawska
      results = nocmd_function()
1237 99381e3b Agata Murawska
    logging.info("Options for %s were succesfully read", name)
1238 99381e3b Agata Murawska
    return results
1239 99381e3b Agata Murawska
1240 99381e3b Agata Murawska
  def _ParseNameOptions(self):
1241 99381e3b Agata Murawska
    """Returns name if one was given in command line.
1242 99381e3b Agata Murawska

1243 99381e3b Agata Murawska
    @rtype: string
1244 99381e3b Agata Murawska
    @return: name of an instance
1245 99381e3b Agata Murawska

1246 99381e3b Agata Murawska
    """
1247 99381e3b Agata Murawska
    return self.options.name
1248 99381e3b Agata Murawska
1249 99381e3b Agata Murawska
  def _ParseTemplateOptions(self):
1250 99381e3b Agata Murawska
    """Returns disk template if one was given in command line.
1251 99381e3b Agata Murawska

1252 99381e3b Agata Murawska
    @rtype: string
1253 99381e3b Agata Murawska
    @return: disk template name
1254 99381e3b Agata Murawska

1255 99381e3b Agata Murawska
    """
1256 99381e3b Agata Murawska
    return self.options.disk_template
1257 99381e3b Agata Murawska
1258 7bde29b5 Agata Murawska
  def _ParseHypervisorOptions(self):
1259 7bde29b5 Agata Murawska
    """Parses hypervisor options given in a command line.
1260 7bde29b5 Agata Murawska

1261 7bde29b5 Agata Murawska
    @rtype: dict
1262 7bde29b5 Agata Murawska
    @return: dictionary containing name of the chosen hypervisor and all the
1263 7bde29b5 Agata Murawska
      options
1264 7bde29b5 Agata Murawska

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

1279 7bde29b5 Agata Murawska
    @rtype: dict
1280 7bde29b5 Agata Murawska
    @return: dictionary containing name of chosen OS and all its options
1281 7bde29b5 Agata Murawska

1282 7bde29b5 Agata Murawska
    """
1283 7bde29b5 Agata Murawska
    assert self.options.os
1284 7bde29b5 Agata Murawska
    results = {}
1285 7bde29b5 Agata Murawska
    results["os_name"] = self.options.os
1286 7bde29b5 Agata Murawska
    results.update(self.options.osparams)
1287 7bde29b5 Agata Murawska
    return results
1288 7bde29b5 Agata Murawska
1289 7bde29b5 Agata Murawska
  def _ParseBackendOptions(self):
1290 7bde29b5 Agata Murawska
    """Parses backend options given in command line.
1291 7bde29b5 Agata Murawska

1292 7bde29b5 Agata Murawska
    @rtype: dict
1293 7bde29b5 Agata Murawska
    @return: dictionary containing vcpus, memory and auto-balance options
1294 7bde29b5 Agata Murawska

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

1308 7bde29b5 Agata Murawska
    @rtype: string
1309 7bde29b5 Agata Murawska
    @return: string containing comma-separated tags
1310 7bde29b5 Agata Murawska

1311 7bde29b5 Agata Murawska
    """
1312 7bde29b5 Agata Murawska
    return self.options.tags
1313 7bde29b5 Agata Murawska
1314 24b9469d Agata Murawska
  def _ParseNicOptions(self):
1315 24b9469d Agata Murawska
    """Parses network options given in a command line or as a dictionary.
1316 24b9469d Agata Murawska

1317 24b9469d Agata Murawska
    @rtype: dict
1318 24b9469d Agata Murawska
    @return: dictionary of network-related options
1319 24b9469d Agata Murawska

1320 24b9469d Agata Murawska
    """
1321 24b9469d Agata Murawska
    assert self.options.nics
1322 24b9469d Agata Murawska
    results = {}
1323 24b9469d Agata Murawska
    for (nic_id, nic_desc) in self.options.nics:
1324 24b9469d Agata Murawska
      results["nic%s_mode" % nic_id] = \
1325 24b9469d Agata Murawska
        nic_desc.get("mode", constants.VALUE_AUTO)
1326 24b9469d Agata Murawska
      results["nic%s_mac" % nic_id] = nic_desc.get("mac", constants.VALUE_AUTO)
1327 24b9469d Agata Murawska
      results["nic%s_link" % nic_id] = \
1328 24b9469d Agata Murawska
        nic_desc.get("link", constants.VALUE_AUTO)
1329 24b9469d Agata Murawska
      if nic_desc.get("mode") == "bridged":
1330 24b9469d Agata Murawska
        results["nic%s_ip" % nic_id] = constants.VALUE_NONE
1331 24b9469d Agata Murawska
      else:
1332 24b9469d Agata Murawska
        results["nic%s_ip" % nic_id] = constants.VALUE_AUTO
1333 24b9469d Agata Murawska
    results["nic_count"] = str(len(self.options.nics))
1334 24b9469d Agata Murawska
    return results
1335 24b9469d Agata Murawska
1336 99381e3b Agata Murawska
  def _ParseDiskOptions(self):
1337 99381e3b Agata Murawska
    """Parses disk options given in a command line.
1338 99381e3b Agata Murawska

1339 99381e3b Agata Murawska
    @rtype: dict
1340 99381e3b Agata Murawska
    @return: dictionary of disk-related options
1341 99381e3b Agata Murawska

1342 99381e3b Agata Murawska
    @raise errors.OpPrereqError: disk description does not contain size
1343 99381e3b Agata Murawska
      information or size information is invalid or creation failed
1344 99381e3b Agata Murawska

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

1384 99381e3b Agata Murawska
    @rtype: dict
1385 99381e3b Agata Murawska
    @return: dictionary of disk-related options
1386 99381e3b Agata Murawska

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

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

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

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

1480 0963b26a Agata Murawska
  """
1481 0963b26a Agata Murawska
  def get(self, section, options, raw=None, vars=None): # pylint: disable=W0622
1482 0963b26a Agata Murawska
    try:
1483 0963b26a Agata Murawska
      result = ConfigParser.SafeConfigParser.get(self, section, options, \
1484 0963b26a Agata Murawska
        raw=raw, vars=vars)
1485 0963b26a Agata Murawska
    except ConfigParser.NoOptionError:
1486 0963b26a Agata Murawska
      result = None
1487 0963b26a Agata Murawska
    return result
1488 0963b26a Agata Murawska
1489 0963b26a Agata Murawska
  def getint(self, section, options):
1490 0963b26a Agata Murawska
    try:
1491 0963b26a Agata Murawska
      result = ConfigParser.SafeConfigParser.get(self, section, options)
1492 0963b26a Agata Murawska
    except ConfigParser.NoOptionError:
1493 0963b26a Agata Murawska
      result = 0
1494 0963b26a Agata Murawska
    return int(result)
1495 0963b26a Agata Murawska
1496 0963b26a Agata Murawska
1497 ced78a66 Agata Murawska
class OVFExporter(Converter):
1498 0963b26a Agata Murawska
  """Converter from Ganeti config file to OVF
1499 0963b26a Agata Murawska

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

1529 0963b26a Agata Murawska
  """
1530 ced78a66 Agata Murawska
  def _ReadInputData(self, input_path):
1531 0963b26a Agata Murawska
    """Reads the data on which the conversion will take place.
1532 0963b26a Agata Murawska

1533 0963b26a Agata Murawska
    @type input_path: string
1534 0963b26a Agata Murawska
    @param input_path: absolute path to the config.ini input file
1535 0963b26a Agata Murawska

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

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

1563 0963b26a Agata Murawska
    @rtype: string
1564 0963b26a Agata Murawska
    @return: name of Ganeti instance
1565 0963b26a Agata Murawska

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

1568 0963b26a Agata Murawska
    """
1569 0963b26a Agata Murawska
    if self.options.name:
1570 0963b26a Agata Murawska
      name = self.options.name
1571 0963b26a Agata Murawska
    else:
1572 0963b26a Agata Murawska
      name = self.config_parser.get(constants.INISECT_INS, NAME)
1573 0963b26a Agata Murawska
    if name is None:
1574 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No instance name found",
1575 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1576 0963b26a Agata Murawska
    return name
1577 ced78a66 Agata Murawska
1578 b179ce72 Agata Murawska
  def _ParseVCPUs(self):
1579 b179ce72 Agata Murawska
    """Parses vcpus number from config file.
1580 b179ce72 Agata Murawska

1581 b179ce72 Agata Murawska
    @rtype: int
1582 b179ce72 Agata Murawska
    @return: number of virtual CPUs
1583 b179ce72 Agata Murawska

1584 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: if number of VCPUs equals 0
1585 b179ce72 Agata Murawska

1586 b179ce72 Agata Murawska
    """
1587 b179ce72 Agata Murawska
    vcpus = self.config_parser.getint(constants.INISECT_BEP, VCPUS)
1588 b179ce72 Agata Murawska
    if vcpus == 0:
1589 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No CPU information found",
1590 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1591 b179ce72 Agata Murawska
    return vcpus
1592 b179ce72 Agata Murawska
1593 b179ce72 Agata Murawska
  def _ParseMemory(self):
1594 b179ce72 Agata Murawska
    """Parses vcpus number from config file.
1595 b179ce72 Agata Murawska

1596 b179ce72 Agata Murawska
    @rtype: int
1597 b179ce72 Agata Murawska
    @return: amount of memory in MB
1598 b179ce72 Agata Murawska

1599 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: if amount of memory equals 0
1600 b179ce72 Agata Murawska

1601 b179ce72 Agata Murawska
    """
1602 b179ce72 Agata Murawska
    memory = self.config_parser.getint(constants.INISECT_BEP, MEMORY)
1603 b179ce72 Agata Murawska
    if memory == 0:
1604 2cfbc784 Iustin Pop
      raise errors.OpPrereqError("No memory information found",
1605 2cfbc784 Iustin Pop
                                 errors.ECODE_ENVIRON)
1606 b179ce72 Agata Murawska
    return memory
1607 b179ce72 Agata Murawska
1608 b179ce72 Agata Murawska
  def _ParseGaneti(self):
1609 b179ce72 Agata Murawska
    """Parses Ganeti data from config file.
1610 b179ce72 Agata Murawska

1611 b179ce72 Agata Murawska
    @rtype: dictionary
1612 b179ce72 Agata Murawska
    @return: dictionary of Ganeti-specific options
1613 b179ce72 Agata Murawska

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

1650 b179ce72 Agata Murawska
    @rtype: list
1651 b179ce72 Agata Murawska
    @return: list of dictionaries of network options
1652 b179ce72 Agata Murawska

1653 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: then network mode is not recognized
1654 b179ce72 Agata Murawska

1655 b179ce72 Agata Murawska
    """
1656 b179ce72 Agata Murawska
    results = []
1657 b179ce72 Agata Murawska
    counter = 0
1658 b179ce72 Agata Murawska
    while True:
1659 b179ce72 Agata Murawska
      data_link = \
1660 b179ce72 Agata Murawska
        self.config_parser.get(constants.INISECT_INS, "nic%s_link" % counter)
1661 b179ce72 Agata Murawska
      if data_link is None:
1662 b179ce72 Agata Murawska
        break
1663 b179ce72 Agata Murawska
      results.append({
1664 b179ce72 Agata Murawska
        "mode": self.config_parser.get(constants.INISECT_INS,
1665 b179ce72 Agata Murawska
           "nic%s_mode" % counter),
1666 b179ce72 Agata Murawska
        "mac": self.config_parser.get(constants.INISECT_INS,
1667 b179ce72 Agata Murawska
           "nic%s_mac" % counter),
1668 b179ce72 Agata Murawska
        "ip": self.config_parser.get(constants.INISECT_INS,
1669 b179ce72 Agata Murawska
           "nic%s_ip" % counter),
1670 b179ce72 Agata Murawska
        "link": data_link,
1671 b179ce72 Agata Murawska
      })
1672 b179ce72 Agata Murawska
      if results[counter]["mode"] not in constants.NIC_VALID_MODES:
1673 b179ce72 Agata Murawska
        raise errors.OpPrereqError("Network mode %s not recognized"
1674 2cfbc784 Iustin Pop
                                   % results[counter]["mode"],
1675 2cfbc784 Iustin Pop
                                   errors.ECODE_INVAL)
1676 b179ce72 Agata Murawska
      counter += 1
1677 b179ce72 Agata Murawska
    return results
1678 b179ce72 Agata Murawska
1679 b179ce72 Agata Murawska
  def _GetDiskOptions(self, disk_file, compression):
1680 b179ce72 Agata Murawska
    """Convert the disk and gather disk info for .ovf file.
1681 b179ce72 Agata Murawska

1682 b179ce72 Agata Murawska
    @type disk_file: string
1683 b179ce72 Agata Murawska
    @param disk_file: name of the disk (without the full path)
1684 b179ce72 Agata Murawska
    @type compression: bool
1685 b179ce72 Agata Murawska
    @param compression: whether the disk should be compressed or not
1686 b179ce72 Agata Murawska

1687 b179ce72 Agata Murawska
    @raise errors.OpPrereqError: when disk image does not exist
1688 b179ce72 Agata Murawska

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

1720 b179ce72 Agata Murawska
    @rtype: list
1721 b179ce72 Agata Murawska
    @return: list of dictionaries of disk options
1722 b179ce72 Agata Murawska

1723 b179ce72 Agata Murawska
    """
1724 b179ce72 Agata Murawska
    results = []
1725 b179ce72 Agata Murawska
    counter = 0
1726 b179ce72 Agata Murawska
    while True:
1727 b179ce72 Agata Murawska
      disk_file = \
1728 b179ce72 Agata Murawska
        self.config_parser.get(constants.INISECT_INS, "disk%s_dump" % counter)
1729 b179ce72 Agata Murawska
      if disk_file is None:
1730 b179ce72 Agata Murawska
        break
1731 b179ce72 Agata Murawska
      results.append(self._GetDiskOptions(disk_file, self.options.compression))
1732 b179ce72 Agata Murawska
      counter += 1
1733 b179ce72 Agata Murawska
    return results
1734 b179ce72 Agata Murawska
1735 ced78a66 Agata Murawska
  def Parse(self):
1736 0963b26a Agata Murawska
    """Parses the data and creates a structure containing all required info.
1737 0963b26a Agata Murawska

1738 0963b26a Agata Murawska
    """
1739 0963b26a Agata Murawska
    try:
1740 0963b26a Agata Murawska
      utils.Makedirs(self.output_dir)
1741 0963b26a Agata Murawska
    except OSError, err:
1742 0963b26a Agata Murawska
      raise errors.OpPrereqError("Failed to create directory %s: %s" %
1743 2cfbc784 Iustin Pop
                                 (self.output_dir, err), errors.ECODE_ENVIRON)
1744 0963b26a Agata Murawska
1745 b179ce72 Agata Murawska
    self.references_files = []
1746 0963b26a Agata Murawska
    self.results_name = self._ParseName()
1747 b179ce72 Agata Murawska
    self.results_vcpus = self._ParseVCPUs()
1748 b179ce72 Agata Murawska
    self.results_memory = self._ParseMemory()
1749 b179ce72 Agata Murawska
    if not self.options.ext_usage:
1750 b179ce72 Agata Murawska
      self.results_ganeti = self._ParseGaneti()
1751 b179ce72 Agata Murawska
    self.results_network = self._ParseNetworks()
1752 b179ce72 Agata Murawska
    self.results_disk = self._ParseDisks()
1753 0963b26a Agata Murawska
1754 0963b26a Agata Murawska
  def _PrepareManifest(self, path):
1755 0963b26a Agata Murawska
    """Creates manifest for all the files in OVF package.
1756 0963b26a Agata Murawska

1757 0963b26a Agata Murawska
    @type path: string
1758 0963b26a Agata Murawska
    @param path: path to manifesto file
1759 0963b26a Agata Murawska

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

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

1784 0963b26a Agata Murawska
    @type tar_path: string
1785 0963b26a Agata Murawska
    @param tar_path: path to the resulting file
1786 0963b26a Agata Murawska
    @type files_list: list
1787 0963b26a Agata Murawska
    @param files_list: list of files in the OVF package
1788 0963b26a Agata Murawska

1789 0963b26a Agata Murawska
    """
1790 0963b26a Agata Murawska
    logging.info("Preparing tarball for the OVF package")
1791 0963b26a Agata Murawska
    open(tar_path, mode="w").close()
1792 0963b26a Agata Murawska
    ova_package = tarfile.open(name=tar_path, mode="w")
1793 fa337742 Agata Murawska
    for file_path in files_list:
1794 fa337742 Agata Murawska
      file_name = os.path.basename(file_path)
1795 fa337742 Agata Murawska
      ova_package.add(file_path, arcname=file_name)
1796 0963b26a Agata Murawska
    ova_package.close()
1797 ced78a66 Agata Murawska
1798 ced78a66 Agata Murawska
  def Save(self):
1799 0963b26a Agata Murawska
    """Saves the gathered configuration in an apropriate format.
1800 0963b26a Agata Murawska

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

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