Statistics
| Branch: | Tag: | Revision:

root / tools / move-instance @ 514dcbda

History | View | Annotate | Download (32.5 kB)

1 6bf273d5 Michael Hanselmann
#!/usr/bin/python
2 6bf273d5 Michael Hanselmann
#
3 6bf273d5 Michael Hanselmann
4 fc3f75dd Iustin Pop
# Copyright (C) 2010, 2011, 2012 Google Inc.
5 6bf273d5 Michael Hanselmann
#
6 6bf273d5 Michael Hanselmann
# This program is free software; you can redistribute it and/or modify
7 6bf273d5 Michael Hanselmann
# it under the terms of the GNU General Public License as published by
8 6bf273d5 Michael Hanselmann
# the Free Software Foundation; either version 2 of the License, or
9 6bf273d5 Michael Hanselmann
# (at your option) any later version.
10 6bf273d5 Michael Hanselmann
#
11 6bf273d5 Michael Hanselmann
# This program is distributed in the hope that it will be useful, but
12 6bf273d5 Michael Hanselmann
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 6bf273d5 Michael Hanselmann
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 6bf273d5 Michael Hanselmann
# General Public License for more details.
15 6bf273d5 Michael Hanselmann
#
16 6bf273d5 Michael Hanselmann
# You should have received a copy of the GNU General Public License
17 6bf273d5 Michael Hanselmann
# along with this program; if not, write to the Free Software
18 6bf273d5 Michael Hanselmann
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 6bf273d5 Michael Hanselmann
# 02110-1301, USA.
20 6bf273d5 Michael Hanselmann
21 6bf273d5 Michael Hanselmann
"""Tool to move instances from one cluster to another.
22 6bf273d5 Michael Hanselmann
23 6bf273d5 Michael Hanselmann
"""
24 6bf273d5 Michael Hanselmann
25 b459a848 Andrea Spadaccini
# pylint: disable=C0103
26 6bf273d5 Michael Hanselmann
# C0103: Invalid name move-instance
27 6bf273d5 Michael Hanselmann
28 6bf273d5 Michael Hanselmann
import os
29 6bf273d5 Michael Hanselmann
import sys
30 6bf273d5 Michael Hanselmann
import time
31 6bf273d5 Michael Hanselmann
import logging
32 6bf273d5 Michael Hanselmann
import optparse
33 6bf273d5 Michael Hanselmann
import threading
34 6bf273d5 Michael Hanselmann
35 6bf273d5 Michael Hanselmann
from ganeti import cli
36 6bf273d5 Michael Hanselmann
from ganeti import constants
37 6bf273d5 Michael Hanselmann
from ganeti import utils
38 6bf273d5 Michael Hanselmann
from ganeti import workerpool
39 a111ebde Michael Hanselmann
from ganeti import objects
40 6bf273d5 Michael Hanselmann
from ganeti import compat
41 6bf273d5 Michael Hanselmann
from ganeti import rapi
42 6bf273d5 Michael Hanselmann
43 b459a848 Andrea Spadaccini
import ganeti.rapi.client # pylint: disable=W0611
44 6bf273d5 Michael Hanselmann
import ganeti.rapi.client_utils
45 fc3f75dd Iustin Pop
from ganeti.rapi.client import UsesRapiClient
46 6bf273d5 Michael Hanselmann
47 6bf273d5 Michael Hanselmann
48 6bf273d5 Michael Hanselmann
SRC_RAPI_PORT_OPT = \
49 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-rapi-port", action="store", type="int",
50 6bf273d5 Michael Hanselmann
                 dest="src_rapi_port", default=constants.DEFAULT_RAPI_PORT,
51 6bf273d5 Michael Hanselmann
                 help=("Source cluster RAPI port (defaults to %s)" %
52 6bf273d5 Michael Hanselmann
                       constants.DEFAULT_RAPI_PORT))
53 6bf273d5 Michael Hanselmann
54 6bf273d5 Michael Hanselmann
SRC_CA_FILE_OPT = \
55 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-ca-file", action="store", type="string",
56 6bf273d5 Michael Hanselmann
                 dest="src_ca_file",
57 6bf273d5 Michael Hanselmann
                 help=("File containing source cluster Certificate"
58 6bf273d5 Michael Hanselmann
                       " Authority (CA) in PEM format"))
59 6bf273d5 Michael Hanselmann
60 6bf273d5 Michael Hanselmann
SRC_USERNAME_OPT = \
61 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-username", action="store", type="string",
62 6bf273d5 Michael Hanselmann
                 dest="src_username", default=None,
63 6bf273d5 Michael Hanselmann
                 help="Source cluster username")
64 6bf273d5 Michael Hanselmann
65 6bf273d5 Michael Hanselmann
SRC_PASSWORD_FILE_OPT = \
66 6bf273d5 Michael Hanselmann
  cli.cli_option("--src-password-file", action="store", type="string",
67 6bf273d5 Michael Hanselmann
                 dest="src_password_file",
68 6bf273d5 Michael Hanselmann
                 help="File containing source cluster password")
69 6bf273d5 Michael Hanselmann
70 6bf273d5 Michael Hanselmann
DEST_RAPI_PORT_OPT = \
71 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-rapi-port", action="store", type="int",
72 6bf273d5 Michael Hanselmann
                 dest="dest_rapi_port", default=constants.DEFAULT_RAPI_PORT,
73 6bf273d5 Michael Hanselmann
                 help=("Destination cluster RAPI port (defaults to source"
74 6bf273d5 Michael Hanselmann
                       " cluster RAPI port)"))
75 6bf273d5 Michael Hanselmann
76 6bf273d5 Michael Hanselmann
DEST_CA_FILE_OPT = \
77 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-ca-file", action="store", type="string",
78 6bf273d5 Michael Hanselmann
                 dest="dest_ca_file",
79 6bf273d5 Michael Hanselmann
                 help=("File containing destination cluster Certificate"
80 6bf273d5 Michael Hanselmann
                       " Authority (CA) in PEM format (defaults to source"
81 6bf273d5 Michael Hanselmann
                       " cluster CA)"))
82 6bf273d5 Michael Hanselmann
83 6bf273d5 Michael Hanselmann
DEST_USERNAME_OPT = \
84 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-username", action="store", type="string",
85 6bf273d5 Michael Hanselmann
                 dest="dest_username", default=None,
86 6bf273d5 Michael Hanselmann
                 help=("Destination cluster username (defaults to"
87 6bf273d5 Michael Hanselmann
                       " source cluster username)"))
88 6bf273d5 Michael Hanselmann
89 6bf273d5 Michael Hanselmann
DEST_PASSWORD_FILE_OPT = \
90 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-password-file", action="store", type="string",
91 6bf273d5 Michael Hanselmann
                 dest="dest_password_file",
92 6bf273d5 Michael Hanselmann
                 help=("File containing destination cluster password"
93 6bf273d5 Michael Hanselmann
                       " (defaults to source cluster password)"))
94 6bf273d5 Michael Hanselmann
95 6bf273d5 Michael Hanselmann
DEST_INSTANCE_NAME_OPT = \
96 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-instance-name", action="store", type="string",
97 6bf273d5 Michael Hanselmann
                 dest="dest_instance_name",
98 6bf273d5 Michael Hanselmann
                 help=("Instance name on destination cluster (only"
99 6bf273d5 Michael Hanselmann
                       " when moving exactly one instance)"))
100 6bf273d5 Michael Hanselmann
101 6bf273d5 Michael Hanselmann
DEST_PRIMARY_NODE_OPT = \
102 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-primary-node", action="store", type="string",
103 6bf273d5 Michael Hanselmann
                 dest="dest_primary_node",
104 6bf273d5 Michael Hanselmann
                 help=("Primary node on destination cluster (only"
105 6bf273d5 Michael Hanselmann
                       " when moving exactly one instance)"))
106 6bf273d5 Michael Hanselmann
107 6bf273d5 Michael Hanselmann
DEST_SECONDARY_NODE_OPT = \
108 6bf273d5 Michael Hanselmann
  cli.cli_option("--dest-secondary-node", action="store", type="string",
109 6bf273d5 Michael Hanselmann
                 dest="dest_secondary_node",
110 6bf273d5 Michael Hanselmann
                 help=("Secondary node on destination cluster (only"
111 6bf273d5 Michael Hanselmann
                       " when moving exactly one instance)"))
112 6bf273d5 Michael Hanselmann
113 c3761d99 Hrvoje Ribicic
DEST_DISK_TEMPLATE_OPT = \
114 c3761d99 Hrvoje Ribicic
  cli.cli_option("--dest-disk-template", action="store", type="string",
115 c3761d99 Hrvoje Ribicic
                 dest="dest_disk_template", default=None,
116 c3761d99 Hrvoje Ribicic
                 help="Disk template to use on destination cluster")
117 c3761d99 Hrvoje Ribicic
118 5c5c73fd Thomas Thrainer
COMPRESS_OPT = \
119 5c5c73fd Thomas Thrainer
  cli.cli_option("--compress", action="store", type="string",
120 5c5c73fd Thomas Thrainer
                 dest="compress", default="none",
121 5c5c73fd Thomas Thrainer
                 help="Compression mode to use during the move (this mode has"
122 5c5c73fd Thomas Thrainer
                      " to be supported by both clusters)")
123 5c5c73fd Thomas Thrainer
124 6bf273d5 Michael Hanselmann
PARALLEL_OPT = \
125 6bf273d5 Michael Hanselmann
  cli.cli_option("-p", "--parallel", action="store", type="int", default=1,
126 6bf273d5 Michael Hanselmann
                 dest="parallel", metavar="<number>",
127 6bf273d5 Michael Hanselmann
                 help="Number of instances to be moved simultaneously")
128 6bf273d5 Michael Hanselmann
129 6bf273d5 Michael Hanselmann
130 6bf273d5 Michael Hanselmann
class Error(Exception):
131 6bf273d5 Michael Hanselmann
  """Generic error.
132 6bf273d5 Michael Hanselmann
133 6bf273d5 Michael Hanselmann
  """
134 6bf273d5 Michael Hanselmann
135 6bf273d5 Michael Hanselmann
136 6bf273d5 Michael Hanselmann
class Abort(Error):
137 6bf273d5 Michael Hanselmann
  """Special exception for aborting import/export.
138 6bf273d5 Michael Hanselmann
139 6bf273d5 Michael Hanselmann
  """
140 6bf273d5 Michael Hanselmann
141 6bf273d5 Michael Hanselmann
142 6bf273d5 Michael Hanselmann
class RapiClientFactory:
143 6bf273d5 Michael Hanselmann
  """Factory class for creating RAPI clients.
144 6bf273d5 Michael Hanselmann
145 6bf273d5 Michael Hanselmann
  @ivar src_cluster_name: Source cluster name
146 6bf273d5 Michael Hanselmann
  @ivar dest_cluster_name: Destination cluster name
147 6bf273d5 Michael Hanselmann
  @ivar GetSourceClient: Callable returning new client for source cluster
148 6bf273d5 Michael Hanselmann
  @ivar GetDestClient: Callable returning new client for destination cluster
149 6bf273d5 Michael Hanselmann
150 6bf273d5 Michael Hanselmann
  """
151 6bf273d5 Michael Hanselmann
  def __init__(self, options, src_cluster_name, dest_cluster_name):
152 6bf273d5 Michael Hanselmann
    """Initializes this class.
153 6bf273d5 Michael Hanselmann
154 6bf273d5 Michael Hanselmann
    @param options: Program options
155 6bf273d5 Michael Hanselmann
    @type src_cluster_name: string
156 6bf273d5 Michael Hanselmann
    @param src_cluster_name: Source cluster name
157 6bf273d5 Michael Hanselmann
    @type dest_cluster_name: string
158 6bf273d5 Michael Hanselmann
    @param dest_cluster_name: Destination cluster name
159 6bf273d5 Michael Hanselmann
160 6bf273d5 Michael Hanselmann
    """
161 6bf273d5 Michael Hanselmann
    self.src_cluster_name = src_cluster_name
162 6bf273d5 Michael Hanselmann
    self.dest_cluster_name = dest_cluster_name
163 6bf273d5 Michael Hanselmann
164 2a7c3583 Michael Hanselmann
    # TODO: Implement timeouts for RAPI connections
165 6bf273d5 Michael Hanselmann
    # TODO: Support for using system default paths for verifying SSL certificate
166 6bf273d5 Michael Hanselmann
    logging.debug("Using '%s' as source CA", options.src_ca_file)
167 2a7c3583 Michael Hanselmann
    src_curl_config = rapi.client.GenericCurlConfig(cafile=options.src_ca_file)
168 6bf273d5 Michael Hanselmann
169 6bf273d5 Michael Hanselmann
    if options.dest_ca_file:
170 6bf273d5 Michael Hanselmann
      logging.debug("Using '%s' as destination CA", options.dest_ca_file)
171 2a7c3583 Michael Hanselmann
      dest_curl_config = \
172 2a7c3583 Michael Hanselmann
        rapi.client.GenericCurlConfig(cafile=options.dest_ca_file)
173 6bf273d5 Michael Hanselmann
    else:
174 6bf273d5 Michael Hanselmann
      logging.debug("Using source CA for destination")
175 2a7c3583 Michael Hanselmann
      dest_curl_config = src_curl_config
176 6bf273d5 Michael Hanselmann
177 6bf273d5 Michael Hanselmann
    logging.debug("Source RAPI server is %s:%s",
178 6bf273d5 Michael Hanselmann
                  src_cluster_name, options.src_rapi_port)
179 6bf273d5 Michael Hanselmann
    logging.debug("Source username is '%s'", options.src_username)
180 6bf273d5 Michael Hanselmann
181 6bf273d5 Michael Hanselmann
    if options.src_username is None:
182 6bf273d5 Michael Hanselmann
      src_username = ""
183 6bf273d5 Michael Hanselmann
    else:
184 6bf273d5 Michael Hanselmann
      src_username = options.src_username
185 6bf273d5 Michael Hanselmann
186 6bf273d5 Michael Hanselmann
    if options.src_password_file:
187 6bf273d5 Michael Hanselmann
      logging.debug("Reading '%s' for source password",
188 6bf273d5 Michael Hanselmann
                    options.src_password_file)
189 6bf273d5 Michael Hanselmann
      src_password = utils.ReadOneLineFile(options.src_password_file,
190 6bf273d5 Michael Hanselmann
                                           strict=True)
191 6bf273d5 Michael Hanselmann
    else:
192 6bf273d5 Michael Hanselmann
      logging.debug("Source has no password")
193 6bf273d5 Michael Hanselmann
      src_password = None
194 6bf273d5 Michael Hanselmann
195 6bf273d5 Michael Hanselmann
    self.GetSourceClient = lambda: \
196 6bf273d5 Michael Hanselmann
      rapi.client.GanetiRapiClient(src_cluster_name,
197 6bf273d5 Michael Hanselmann
                                   port=options.src_rapi_port,
198 2a7c3583 Michael Hanselmann
                                   curl_config_fn=src_curl_config,
199 6bf273d5 Michael Hanselmann
                                   username=src_username,
200 6bf273d5 Michael Hanselmann
                                   password=src_password)
201 6bf273d5 Michael Hanselmann
202 6bf273d5 Michael Hanselmann
    if options.dest_rapi_port:
203 6bf273d5 Michael Hanselmann
      dest_rapi_port = options.dest_rapi_port
204 6bf273d5 Michael Hanselmann
    else:
205 6bf273d5 Michael Hanselmann
      dest_rapi_port = options.src_rapi_port
206 6bf273d5 Michael Hanselmann
207 6bf273d5 Michael Hanselmann
    if options.dest_username is None:
208 6bf273d5 Michael Hanselmann
      dest_username = src_username
209 6bf273d5 Michael Hanselmann
    else:
210 6bf273d5 Michael Hanselmann
      dest_username = options.dest_username
211 6bf273d5 Michael Hanselmann
212 6bf273d5 Michael Hanselmann
    logging.debug("Destination RAPI server is %s:%s",
213 6bf273d5 Michael Hanselmann
                  dest_cluster_name, dest_rapi_port)
214 6bf273d5 Michael Hanselmann
    logging.debug("Destination username is '%s'", dest_username)
215 6bf273d5 Michael Hanselmann
216 6bf273d5 Michael Hanselmann
    if options.dest_password_file:
217 6bf273d5 Michael Hanselmann
      logging.debug("Reading '%s' for destination password",
218 6bf273d5 Michael Hanselmann
                    options.dest_password_file)
219 6bf273d5 Michael Hanselmann
      dest_password = utils.ReadOneLineFile(options.dest_password_file,
220 6bf273d5 Michael Hanselmann
                                            strict=True)
221 6bf273d5 Michael Hanselmann
    else:
222 6bf273d5 Michael Hanselmann
      logging.debug("Using source password for destination")
223 6bf273d5 Michael Hanselmann
      dest_password = src_password
224 6bf273d5 Michael Hanselmann
225 6bf273d5 Michael Hanselmann
    self.GetDestClient = lambda: \
226 6bf273d5 Michael Hanselmann
      rapi.client.GanetiRapiClient(dest_cluster_name,
227 6bf273d5 Michael Hanselmann
                                   port=dest_rapi_port,
228 2a7c3583 Michael Hanselmann
                                   curl_config_fn=dest_curl_config,
229 6bf273d5 Michael Hanselmann
                                   username=dest_username,
230 6bf273d5 Michael Hanselmann
                                   password=dest_password)
231 6bf273d5 Michael Hanselmann
232 6bf273d5 Michael Hanselmann
233 6bf273d5 Michael Hanselmann
class MoveJobPollReportCb(cli.JobPollReportCbBase):
234 6bf273d5 Michael Hanselmann
  def __init__(self, abort_check_fn, remote_import_fn):
235 6bf273d5 Michael Hanselmann
    """Initializes this class.
236 6bf273d5 Michael Hanselmann
237 6bf273d5 Michael Hanselmann
    @type abort_check_fn: callable
238 6bf273d5 Michael Hanselmann
    @param abort_check_fn: Function to check whether move is aborted
239 6bf273d5 Michael Hanselmann
    @type remote_import_fn: callable or None
240 6bf273d5 Michael Hanselmann
    @param remote_import_fn: Callback for reporting received remote import
241 6bf273d5 Michael Hanselmann
                             information
242 6bf273d5 Michael Hanselmann
243 6bf273d5 Michael Hanselmann
    """
244 6bf273d5 Michael Hanselmann
    cli.JobPollReportCbBase.__init__(self)
245 6bf273d5 Michael Hanselmann
    self._abort_check_fn = abort_check_fn
246 6bf273d5 Michael Hanselmann
    self._remote_import_fn = remote_import_fn
247 6bf273d5 Michael Hanselmann
248 6bf273d5 Michael Hanselmann
  def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
249 6bf273d5 Michael Hanselmann
    """Handles a log message.
250 6bf273d5 Michael Hanselmann
251 6bf273d5 Michael Hanselmann
    """
252 6bf273d5 Michael Hanselmann
    if log_type == constants.ELOG_REMOTE_IMPORT:
253 6bf273d5 Michael Hanselmann
      logging.debug("Received remote import information")
254 6bf273d5 Michael Hanselmann
255 6bf273d5 Michael Hanselmann
      if not self._remote_import_fn:
256 6bf273d5 Michael Hanselmann
        raise RuntimeError("Received unexpected remote import information")
257 6bf273d5 Michael Hanselmann
258 6bf273d5 Michael Hanselmann
      assert "x509_ca" in log_msg
259 6bf273d5 Michael Hanselmann
      assert "disks" in log_msg
260 6bf273d5 Michael Hanselmann
261 6bf273d5 Michael Hanselmann
      self._remote_import_fn(log_msg)
262 6bf273d5 Michael Hanselmann
263 6bf273d5 Michael Hanselmann
      return
264 6bf273d5 Michael Hanselmann
265 6bf273d5 Michael Hanselmann
    logging.info("[%s] %s", time.ctime(utils.MergeTime(timestamp)),
266 8a7f1c61 Michael Hanselmann
                 cli.FormatLogMessage(log_type, log_msg))
267 6bf273d5 Michael Hanselmann
268 6bf273d5 Michael Hanselmann
  def ReportNotChanged(self, job_id, status):
269 6bf273d5 Michael Hanselmann
    """Called if a job hasn't changed in a while.
270 6bf273d5 Michael Hanselmann
271 6bf273d5 Michael Hanselmann
    """
272 6bf273d5 Michael Hanselmann
    try:
273 6bf273d5 Michael Hanselmann
      # Check whether we were told to abort by the other thread
274 6bf273d5 Michael Hanselmann
      self._abort_check_fn()
275 6bf273d5 Michael Hanselmann
    except Abort:
276 6bf273d5 Michael Hanselmann
      logging.warning("Aborting despite job %s still running", job_id)
277 6bf273d5 Michael Hanselmann
      raise
278 6bf273d5 Michael Hanselmann
279 6bf273d5 Michael Hanselmann
280 6bf273d5 Michael Hanselmann
class InstanceMove(object):
281 6bf273d5 Michael Hanselmann
  """Status class for instance moves.
282 6bf273d5 Michael Hanselmann
283 6bf273d5 Michael Hanselmann
  """
284 6bf273d5 Michael Hanselmann
  def __init__(self, src_instance_name, dest_instance_name,
285 5c5c73fd Thomas Thrainer
               dest_pnode, dest_snode, compress, dest_iallocator,
286 c3761d99 Hrvoje Ribicic
               dest_disk_template, hvparams,
287 c3761d99 Hrvoje Ribicic
               beparams, osparams, nics):
288 6bf273d5 Michael Hanselmann
    """Initializes this class.
289 6bf273d5 Michael Hanselmann
290 6bf273d5 Michael Hanselmann
    @type src_instance_name: string
291 6bf273d5 Michael Hanselmann
    @param src_instance_name: Instance name on source cluster
292 6bf273d5 Michael Hanselmann
    @type dest_instance_name: string
293 6bf273d5 Michael Hanselmann
    @param dest_instance_name: Instance name on destination cluster
294 6bf273d5 Michael Hanselmann
    @type dest_pnode: string or None
295 6bf273d5 Michael Hanselmann
    @param dest_pnode: Name of primary node on destination cluster
296 6bf273d5 Michael Hanselmann
    @type dest_snode: string or None
297 6bf273d5 Michael Hanselmann
    @param dest_snode: Name of secondary node on destination cluster
298 5c5c73fd Thomas Thrainer
    @type compress; string
299 5c5c73fd Thomas Thrainer
    @param compress: Compression mode to use (has to be supported on both
300 5c5c73fd Thomas Thrainer
                     clusters)
301 6bf273d5 Michael Hanselmann
    @type dest_iallocator: string or None
302 6bf273d5 Michael Hanselmann
    @param dest_iallocator: Name of iallocator to use
303 c3761d99 Hrvoje Ribicic
    @type dest_disk_template: string or None
304 c3761d99 Hrvoje Ribicic
    @param dest_disk_template: Disk template to use instead of the original one
305 a111ebde Michael Hanselmann
    @type hvparams: dict or None
306 a111ebde Michael Hanselmann
    @param hvparams: Hypervisor parameters to override
307 a111ebde Michael Hanselmann
    @type beparams: dict or None
308 a111ebde Michael Hanselmann
    @param beparams: Backend parameters to override
309 a111ebde Michael Hanselmann
    @type osparams: dict or None
310 a111ebde Michael Hanselmann
    @param osparams: OS parameters to override
311 a111ebde Michael Hanselmann
    @type nics: dict or None
312 a111ebde Michael Hanselmann
    @param nics: NICs to override
313 6bf273d5 Michael Hanselmann
314 6bf273d5 Michael Hanselmann
    """
315 6bf273d5 Michael Hanselmann
    self.src_instance_name = src_instance_name
316 6bf273d5 Michael Hanselmann
    self.dest_instance_name = dest_instance_name
317 6bf273d5 Michael Hanselmann
    self.dest_pnode = dest_pnode
318 6bf273d5 Michael Hanselmann
    self.dest_snode = dest_snode
319 5c5c73fd Thomas Thrainer
    self.compress = compress
320 6bf273d5 Michael Hanselmann
    self.dest_iallocator = dest_iallocator
321 c3761d99 Hrvoje Ribicic
    self.dest_disk_template = dest_disk_template
322 a111ebde Michael Hanselmann
    self.hvparams = hvparams
323 a111ebde Michael Hanselmann
    self.beparams = beparams
324 a111ebde Michael Hanselmann
    self.osparams = osparams
325 a111ebde Michael Hanselmann
    self.nics = nics
326 6bf273d5 Michael Hanselmann
327 6bf273d5 Michael Hanselmann
    self.error_message = None
328 6bf273d5 Michael Hanselmann
329 6bf273d5 Michael Hanselmann
330 6bf273d5 Michael Hanselmann
class MoveRuntime(object):
331 6bf273d5 Michael Hanselmann
  """Class to keep track of instance move.
332 6bf273d5 Michael Hanselmann
333 6bf273d5 Michael Hanselmann
  """
334 6bf273d5 Michael Hanselmann
  def __init__(self, move):
335 6bf273d5 Michael Hanselmann
    """Initializes this class.
336 6bf273d5 Michael Hanselmann
337 6bf273d5 Michael Hanselmann
    @type move: L{InstanceMove}
338 6bf273d5 Michael Hanselmann
339 6bf273d5 Michael Hanselmann
    """
340 6bf273d5 Michael Hanselmann
    self.move = move
341 6bf273d5 Michael Hanselmann
342 6bf273d5 Michael Hanselmann
    # Thread synchronization
343 6bf273d5 Michael Hanselmann
    self.lock = threading.Lock()
344 6bf273d5 Michael Hanselmann
    self.source_to_dest = threading.Condition(self.lock)
345 6bf273d5 Michael Hanselmann
    self.dest_to_source = threading.Condition(self.lock)
346 6bf273d5 Michael Hanselmann
347 6bf273d5 Michael Hanselmann
    # Source information
348 6bf273d5 Michael Hanselmann
    self.src_error_message = None
349 6bf273d5 Michael Hanselmann
    self.src_expinfo = None
350 6bf273d5 Michael Hanselmann
    self.src_instinfo = None
351 6bf273d5 Michael Hanselmann
352 6bf273d5 Michael Hanselmann
    # Destination information
353 6bf273d5 Michael Hanselmann
    self.dest_error_message = None
354 6bf273d5 Michael Hanselmann
    self.dest_impinfo = None
355 6bf273d5 Michael Hanselmann
356 6bf273d5 Michael Hanselmann
  def HandleErrors(self, prefix, fn, *args):
357 6bf273d5 Michael Hanselmann
    """Wrapper to catch errors and abort threads.
358 6bf273d5 Michael Hanselmann
359 6bf273d5 Michael Hanselmann
    @type prefix: string
360 6bf273d5 Michael Hanselmann
    @param prefix: Variable name prefix ("src" or "dest")
361 6bf273d5 Michael Hanselmann
    @type fn: callable
362 6bf273d5 Michael Hanselmann
    @param fn: Function
363 6bf273d5 Michael Hanselmann
364 6bf273d5 Michael Hanselmann
    """
365 6bf273d5 Michael Hanselmann
    assert prefix in ("dest", "src")
366 6bf273d5 Michael Hanselmann
367 6bf273d5 Michael Hanselmann
    try:
368 6bf273d5 Michael Hanselmann
      # Call inner function
369 6bf273d5 Michael Hanselmann
      fn(*args)
370 6bf273d5 Michael Hanselmann
371 6bf273d5 Michael Hanselmann
      errmsg = None
372 6bf273d5 Michael Hanselmann
    except Abort:
373 6bf273d5 Michael Hanselmann
      errmsg = "Aborted"
374 6bf273d5 Michael Hanselmann
    except Exception, err:
375 6bf273d5 Michael Hanselmann
      logging.exception("Caught unhandled exception")
376 6bf273d5 Michael Hanselmann
      errmsg = str(err)
377 6bf273d5 Michael Hanselmann
378 424f51ec Michael Hanselmann
    setattr(self, "%s_error_message" % prefix, errmsg)
379 424f51ec Michael Hanselmann
380 6bf273d5 Michael Hanselmann
    self.lock.acquire()
381 6bf273d5 Michael Hanselmann
    try:
382 6bf273d5 Michael Hanselmann
      self.source_to_dest.notifyAll()
383 6bf273d5 Michael Hanselmann
      self.dest_to_source.notifyAll()
384 6bf273d5 Michael Hanselmann
    finally:
385 6bf273d5 Michael Hanselmann
      self.lock.release()
386 6bf273d5 Michael Hanselmann
387 6bf273d5 Michael Hanselmann
  def CheckAbort(self):
388 6bf273d5 Michael Hanselmann
    """Check whether thread should be aborted.
389 6bf273d5 Michael Hanselmann
390 6bf273d5 Michael Hanselmann
    @raise Abort: When thread should be aborted
391 6bf273d5 Michael Hanselmann
392 6bf273d5 Michael Hanselmann
    """
393 424f51ec Michael Hanselmann
    if not (self.src_error_message is None and
394 424f51ec Michael Hanselmann
            self.dest_error_message is None):
395 6bf273d5 Michael Hanselmann
      logging.info("Aborting")
396 6bf273d5 Michael Hanselmann
      raise Abort()
397 6bf273d5 Michael Hanselmann
398 6bf273d5 Michael Hanselmann
  def Wait(self, cond, check_fn):
399 6bf273d5 Michael Hanselmann
    """Waits for a condition to become true.
400 6bf273d5 Michael Hanselmann
401 6bf273d5 Michael Hanselmann
    @type cond: threading.Condition
402 6bf273d5 Michael Hanselmann
    @param cond: Threading condition
403 6bf273d5 Michael Hanselmann
    @type check_fn: callable
404 6bf273d5 Michael Hanselmann
    @param check_fn: Function to check whether condition is true
405 6bf273d5 Michael Hanselmann
406 6bf273d5 Michael Hanselmann
    """
407 6bf273d5 Michael Hanselmann
    cond.acquire()
408 6bf273d5 Michael Hanselmann
    try:
409 6bf273d5 Michael Hanselmann
      while check_fn(self):
410 6bf273d5 Michael Hanselmann
        self.CheckAbort()
411 6bf273d5 Michael Hanselmann
        cond.wait()
412 6bf273d5 Michael Hanselmann
    finally:
413 6bf273d5 Michael Hanselmann
      cond.release()
414 6bf273d5 Michael Hanselmann
415 6bf273d5 Michael Hanselmann
  def PollJob(self, cl, job_id, remote_import_fn=None):
416 6bf273d5 Michael Hanselmann
    """Wrapper for polling a job.
417 6bf273d5 Michael Hanselmann
418 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
419 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
420 6bf273d5 Michael Hanselmann
    @type job_id: string
421 6bf273d5 Michael Hanselmann
    @param job_id: Job ID
422 6bf273d5 Michael Hanselmann
    @type remote_import_fn: callable or None
423 6bf273d5 Michael Hanselmann
    @param remote_import_fn: Callback for reporting received remote import
424 6bf273d5 Michael Hanselmann
                             information
425 6bf273d5 Michael Hanselmann
426 6bf273d5 Michael Hanselmann
    """
427 6bf273d5 Michael Hanselmann
    return rapi.client_utils.PollJob(cl, job_id,
428 6bf273d5 Michael Hanselmann
                                     MoveJobPollReportCb(self.CheckAbort,
429 6bf273d5 Michael Hanselmann
                                                         remote_import_fn))
430 6bf273d5 Michael Hanselmann
431 6bf273d5 Michael Hanselmann
432 6bf273d5 Michael Hanselmann
class MoveDestExecutor(object):
433 6bf273d5 Michael Hanselmann
  def __init__(self, dest_client, mrt):
434 6bf273d5 Michael Hanselmann
    """Destination side of an instance move.
435 6bf273d5 Michael Hanselmann
436 6bf273d5 Michael Hanselmann
    @type dest_client: L{rapi.client.GanetiRapiClient}
437 6bf273d5 Michael Hanselmann
    @param dest_client: RAPI client
438 6bf273d5 Michael Hanselmann
    @type mrt: L{MoveRuntime}
439 6bf273d5 Michael Hanselmann
    @param mrt: Instance move runtime information
440 6bf273d5 Michael Hanselmann
441 6bf273d5 Michael Hanselmann
    """
442 6bf273d5 Michael Hanselmann
    logging.debug("Waiting for instance information to become available")
443 6bf273d5 Michael Hanselmann
    mrt.Wait(mrt.source_to_dest,
444 6bf273d5 Michael Hanselmann
             lambda mrt: mrt.src_instinfo is None or mrt.src_expinfo is None)
445 6bf273d5 Michael Hanselmann
446 6bf273d5 Michael Hanselmann
    logging.info("Creating instance %s in remote-import mode",
447 6bf273d5 Michael Hanselmann
                 mrt.move.dest_instance_name)
448 6bf273d5 Michael Hanselmann
    job_id = self._CreateInstance(dest_client, mrt.move.dest_instance_name,
449 6bf273d5 Michael Hanselmann
                                  mrt.move.dest_pnode, mrt.move.dest_snode,
450 5c5c73fd Thomas Thrainer
                                  mrt.move.compress,
451 6bf273d5 Michael Hanselmann
                                  mrt.move.dest_iallocator,
452 c3761d99 Hrvoje Ribicic
                                  mrt.move.dest_disk_template,
453 a111ebde Michael Hanselmann
                                  mrt.src_instinfo, mrt.src_expinfo,
454 a111ebde Michael Hanselmann
                                  mrt.move.hvparams, mrt.move.beparams,
455 a111ebde Michael Hanselmann
                                  mrt.move.beparams, mrt.move.nics)
456 6bf273d5 Michael Hanselmann
    mrt.PollJob(dest_client, job_id,
457 6bf273d5 Michael Hanselmann
                remote_import_fn=compat.partial(self._SetImportInfo, mrt))
458 6bf273d5 Michael Hanselmann
459 6bf273d5 Michael Hanselmann
    logging.info("Import successful")
460 6bf273d5 Michael Hanselmann
461 6bf273d5 Michael Hanselmann
  @staticmethod
462 6bf273d5 Michael Hanselmann
  def _SetImportInfo(mrt, impinfo):
463 6bf273d5 Michael Hanselmann
    """Sets the remote import information and notifies source thread.
464 6bf273d5 Michael Hanselmann
465 6bf273d5 Michael Hanselmann
    @type mrt: L{MoveRuntime}
466 6bf273d5 Michael Hanselmann
    @param mrt: Instance move runtime information
467 6bf273d5 Michael Hanselmann
    @param impinfo: Remote import information
468 6bf273d5 Michael Hanselmann
469 6bf273d5 Michael Hanselmann
    """
470 6bf273d5 Michael Hanselmann
    mrt.dest_to_source.acquire()
471 6bf273d5 Michael Hanselmann
    try:
472 6bf273d5 Michael Hanselmann
      mrt.dest_impinfo = impinfo
473 6bf273d5 Michael Hanselmann
      mrt.dest_to_source.notifyAll()
474 6bf273d5 Michael Hanselmann
    finally:
475 6bf273d5 Michael Hanselmann
      mrt.dest_to_source.release()
476 6bf273d5 Michael Hanselmann
477 6bf273d5 Michael Hanselmann
  @staticmethod
478 5c5c73fd Thomas Thrainer
  def _CreateInstance(cl, name, pnode, snode, compress, iallocator,
479 5c5c73fd Thomas Thrainer
                      dest_disk_template, instance, expinfo, override_hvparams,
480 5c5c73fd Thomas Thrainer
                      override_beparams, override_osparams, override_nics):
481 6bf273d5 Michael Hanselmann
    """Starts the instance creation in remote import mode.
482 6bf273d5 Michael Hanselmann
483 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
484 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
485 6bf273d5 Michael Hanselmann
    @type name: string
486 6bf273d5 Michael Hanselmann
    @param name: Instance name
487 6bf273d5 Michael Hanselmann
    @type pnode: string or None
488 6bf273d5 Michael Hanselmann
    @param pnode: Name of primary node on destination cluster
489 6bf273d5 Michael Hanselmann
    @type snode: string or None
490 6bf273d5 Michael Hanselmann
    @param snode: Name of secondary node on destination cluster
491 5c5c73fd Thomas Thrainer
    @type compress: string
492 5c5c73fd Thomas Thrainer
    @param compress: Compression mode to use
493 6bf273d5 Michael Hanselmann
    @type iallocator: string or None
494 6bf273d5 Michael Hanselmann
    @param iallocator: Name of iallocator to use
495 c3761d99 Hrvoje Ribicic
    @type dest_disk_template: string or None
496 c3761d99 Hrvoje Ribicic
    @param dest_disk_template: Disk template to use instead of the original one
497 6bf273d5 Michael Hanselmann
    @type instance: dict
498 6bf273d5 Michael Hanselmann
    @param instance: Instance details from source cluster
499 6bf273d5 Michael Hanselmann
    @type expinfo: dict
500 6bf273d5 Michael Hanselmann
    @param expinfo: Prepared export information from source cluster
501 a111ebde Michael Hanselmann
    @type override_hvparams: dict or None
502 a111ebde Michael Hanselmann
    @param override_hvparams: Hypervisor parameters to override
503 a111ebde Michael Hanselmann
    @type override_beparams: dict or None
504 a111ebde Michael Hanselmann
    @param override_beparams: Backend parameters to override
505 a111ebde Michael Hanselmann
    @type override_osparams: dict or None
506 a111ebde Michael Hanselmann
    @param override_osparams: OS parameters to override
507 a111ebde Michael Hanselmann
    @type override_nics: dict or None
508 a111ebde Michael Hanselmann
    @param override_nics: NICs to override
509 6bf273d5 Michael Hanselmann
    @return: Job ID
510 6bf273d5 Michael Hanselmann
511 6bf273d5 Michael Hanselmann
    """
512 c3761d99 Hrvoje Ribicic
    if dest_disk_template:
513 c3761d99 Hrvoje Ribicic
      disk_template = dest_disk_template
514 c3761d99 Hrvoje Ribicic
    else:
515 c3761d99 Hrvoje Ribicic
      disk_template = instance["disk_template"]
516 6bf273d5 Michael Hanselmann
517 2590d723 Bernardo Dal Seno
    disks = []
518 2590d723 Bernardo Dal Seno
    for idisk in instance["disks"]:
519 2590d723 Bernardo Dal Seno
      odisk = {
520 2590d723 Bernardo Dal Seno
        constants.IDISK_SIZE: idisk["size"],
521 2590d723 Bernardo Dal Seno
        constants.IDISK_MODE: idisk["mode"],
522 2590d723 Bernardo Dal Seno
        constants.IDISK_NAME: str(idisk.get("name")),
523 2590d723 Bernardo Dal Seno
        }
524 2590d723 Bernardo Dal Seno
      spindles = idisk.get("spindles")
525 2590d723 Bernardo Dal Seno
      if spindles is not None:
526 2590d723 Bernardo Dal Seno
        odisk[constants.IDISK_SPINDLES] = spindles
527 2590d723 Bernardo Dal Seno
      disks.append(odisk)
528 6bf273d5 Michael Hanselmann
529 10937a16 Hrvoje Ribicic
    try:
530 10937a16 Hrvoje Ribicic
      nics = [{
531 10937a16 Hrvoje Ribicic
        constants.INIC_IP: ip,
532 10937a16 Hrvoje Ribicic
        constants.INIC_MAC: mac,
533 10937a16 Hrvoje Ribicic
        constants.INIC_MODE: mode,
534 10937a16 Hrvoje Ribicic
        constants.INIC_LINK: link,
535 10937a16 Hrvoje Ribicic
        constants.INIC_VLAN: vlan,
536 10937a16 Hrvoje Ribicic
        constants.INIC_NETWORK: network,
537 10937a16 Hrvoje Ribicic
        constants.INIC_NAME: nic_name
538 10937a16 Hrvoje Ribicic
        } for nic_name, _, ip, mac, mode, link, vlan, network, _
539 10937a16 Hrvoje Ribicic
          in instance["nics"]]
540 10937a16 Hrvoje Ribicic
    except ValueError:
541 10937a16 Hrvoje Ribicic
      raise Error("Received NIC information does not match expected format; "
542 10937a16 Hrvoje Ribicic
                  "Do the versions of this tool and the source cluster match?")
543 6bf273d5 Michael Hanselmann
544 a111ebde Michael Hanselmann
    if len(override_nics) > len(nics):
545 a111ebde Michael Hanselmann
      raise Error("Can not create new NICs")
546 a111ebde Michael Hanselmann
547 a111ebde Michael Hanselmann
    if override_nics:
548 a111ebde Michael Hanselmann
      assert len(override_nics) <= len(nics)
549 a111ebde Michael Hanselmann
      for idx, (nic, override) in enumerate(zip(nics, override_nics)):
550 a111ebde Michael Hanselmann
        nics[idx] = objects.FillDict(nic, override)
551 a111ebde Michael Hanselmann
552 6bf273d5 Michael Hanselmann
    # TODO: Should this be the actual up/down status? (run_state)
553 6bf273d5 Michael Hanselmann
    start = (instance["config_state"] == "up")
554 6bf273d5 Michael Hanselmann
555 6bf273d5 Michael Hanselmann
    assert len(disks) == len(instance["disks"])
556 6bf273d5 Michael Hanselmann
    assert len(nics) == len(instance["nics"])
557 6bf273d5 Michael Hanselmann
558 a111ebde Michael Hanselmann
    inst_beparams = instance["be_instance"]
559 a111ebde Michael Hanselmann
    if not inst_beparams:
560 a111ebde Michael Hanselmann
      inst_beparams = {}
561 a111ebde Michael Hanselmann
562 a111ebde Michael Hanselmann
    inst_hvparams = instance["hv_instance"]
563 a111ebde Michael Hanselmann
    if not inst_hvparams:
564 a111ebde Michael Hanselmann
      inst_hvparams = {}
565 a111ebde Michael Hanselmann
566 a111ebde Michael Hanselmann
    inst_osparams = instance["os_instance"]
567 a111ebde Michael Hanselmann
    if not inst_osparams:
568 a111ebde Michael Hanselmann
      inst_osparams = {}
569 a111ebde Michael Hanselmann
570 6bf273d5 Michael Hanselmann
    return cl.CreateInstance(constants.INSTANCE_REMOTE_IMPORT,
571 6bf273d5 Michael Hanselmann
                             name, disk_template, disks, nics,
572 6bf273d5 Michael Hanselmann
                             os=instance["os"],
573 6bf273d5 Michael Hanselmann
                             pnode=pnode,
574 6bf273d5 Michael Hanselmann
                             snode=snode,
575 6bf273d5 Michael Hanselmann
                             start=start,
576 6bf273d5 Michael Hanselmann
                             ip_check=False,
577 6bf273d5 Michael Hanselmann
                             iallocator=iallocator,
578 6bf273d5 Michael Hanselmann
                             hypervisor=instance["hypervisor"],
579 6bf273d5 Michael Hanselmann
                             source_handshake=expinfo["handshake"],
580 6bf273d5 Michael Hanselmann
                             source_x509_ca=expinfo["x509_ca"],
581 5c5c73fd Thomas Thrainer
                             compress=compress,
582 6bf273d5 Michael Hanselmann
                             source_instance_name=instance["name"],
583 a111ebde Michael Hanselmann
                             beparams=objects.FillDict(inst_beparams,
584 a111ebde Michael Hanselmann
                                                       override_beparams),
585 a111ebde Michael Hanselmann
                             hvparams=objects.FillDict(inst_hvparams,
586 a111ebde Michael Hanselmann
                                                       override_hvparams),
587 a111ebde Michael Hanselmann
                             osparams=objects.FillDict(inst_osparams,
588 a111ebde Michael Hanselmann
                                                       override_osparams))
589 6bf273d5 Michael Hanselmann
590 6bf273d5 Michael Hanselmann
591 6bf273d5 Michael Hanselmann
class MoveSourceExecutor(object):
592 6bf273d5 Michael Hanselmann
  def __init__(self, src_client, mrt):
593 6bf273d5 Michael Hanselmann
    """Source side of an instance move.
594 6bf273d5 Michael Hanselmann
595 6bf273d5 Michael Hanselmann
    @type src_client: L{rapi.client.GanetiRapiClient}
596 6bf273d5 Michael Hanselmann
    @param src_client: RAPI client
597 6bf273d5 Michael Hanselmann
    @type mrt: L{MoveRuntime}
598 6bf273d5 Michael Hanselmann
    @param mrt: Instance move runtime information
599 6bf273d5 Michael Hanselmann
600 6bf273d5 Michael Hanselmann
    """
601 6bf273d5 Michael Hanselmann
    logging.info("Checking whether instance exists")
602 6bf273d5 Michael Hanselmann
    self._CheckInstance(src_client, mrt.move.src_instance_name)
603 6bf273d5 Michael Hanselmann
604 6bf273d5 Michael Hanselmann
    logging.info("Retrieving instance information from source cluster")
605 6bf273d5 Michael Hanselmann
    instinfo = self._GetInstanceInfo(src_client, mrt.PollJob,
606 6bf273d5 Michael Hanselmann
                                     mrt.move.src_instance_name)
607 a09639d1 Santi Raffa
    if (instinfo["disk_template"] in constants.DTS_FILEBASED):
608 fc2318f7 Helga Velroyen
      raise Error("Inter-cluster move of file-based instances is not"
609 fc2318f7 Helga Velroyen
                  " supported.")
610 6bf273d5 Michael Hanselmann
611 6bf273d5 Michael Hanselmann
    logging.info("Preparing export on source cluster")
612 6bf273d5 Michael Hanselmann
    expinfo = self._PrepareExport(src_client, mrt.PollJob,
613 6bf273d5 Michael Hanselmann
                                  mrt.move.src_instance_name)
614 6bf273d5 Michael Hanselmann
    assert "handshake" in expinfo
615 6bf273d5 Michael Hanselmann
    assert "x509_key_name" in expinfo
616 6bf273d5 Michael Hanselmann
    assert "x509_ca" in expinfo
617 6bf273d5 Michael Hanselmann
618 6bf273d5 Michael Hanselmann
    # Hand information to destination thread
619 6bf273d5 Michael Hanselmann
    mrt.source_to_dest.acquire()
620 6bf273d5 Michael Hanselmann
    try:
621 6bf273d5 Michael Hanselmann
      mrt.src_instinfo = instinfo
622 6bf273d5 Michael Hanselmann
      mrt.src_expinfo = expinfo
623 6bf273d5 Michael Hanselmann
      mrt.source_to_dest.notifyAll()
624 6bf273d5 Michael Hanselmann
    finally:
625 6bf273d5 Michael Hanselmann
      mrt.source_to_dest.release()
626 6bf273d5 Michael Hanselmann
627 6bf273d5 Michael Hanselmann
    logging.info("Waiting for destination information to become available")
628 6bf273d5 Michael Hanselmann
    mrt.Wait(mrt.dest_to_source, lambda mrt: mrt.dest_impinfo is None)
629 6bf273d5 Michael Hanselmann
630 6bf273d5 Michael Hanselmann
    logging.info("Starting remote export on source cluster")
631 6bf273d5 Michael Hanselmann
    self._ExportInstance(src_client, mrt.PollJob, mrt.move.src_instance_name,
632 5c5c73fd Thomas Thrainer
                         expinfo["x509_key_name"], mrt.move.compress,
633 5c5c73fd Thomas Thrainer
                         mrt.dest_impinfo)
634 6bf273d5 Michael Hanselmann
635 6bf273d5 Michael Hanselmann
    logging.info("Export successful")
636 6bf273d5 Michael Hanselmann
637 6bf273d5 Michael Hanselmann
  @staticmethod
638 6bf273d5 Michael Hanselmann
  def _CheckInstance(cl, name):
639 6bf273d5 Michael Hanselmann
    """Checks whether the instance exists on the source cluster.
640 6bf273d5 Michael Hanselmann
641 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
642 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
643 6bf273d5 Michael Hanselmann
    @type name: string
644 6bf273d5 Michael Hanselmann
    @param name: Instance name
645 6bf273d5 Michael Hanselmann
646 6bf273d5 Michael Hanselmann
    """
647 6bf273d5 Michael Hanselmann
    try:
648 6bf273d5 Michael Hanselmann
      cl.GetInstance(name)
649 6bf273d5 Michael Hanselmann
    except rapi.client.GanetiApiError, err:
650 6bf273d5 Michael Hanselmann
      if err.code == rapi.client.HTTP_NOT_FOUND:
651 6bf273d5 Michael Hanselmann
        raise Error("Instance %s not found (%s)" % (name, str(err)))
652 6bf273d5 Michael Hanselmann
      raise
653 6bf273d5 Michael Hanselmann
654 6bf273d5 Michael Hanselmann
  @staticmethod
655 6bf273d5 Michael Hanselmann
  def _GetInstanceInfo(cl, poll_job_fn, name):
656 6bf273d5 Michael Hanselmann
    """Retrieves detailed instance information from source cluster.
657 6bf273d5 Michael Hanselmann
658 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
659 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
660 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
661 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
662 6bf273d5 Michael Hanselmann
    @type name: string
663 6bf273d5 Michael Hanselmann
    @param name: Instance name
664 6bf273d5 Michael Hanselmann
665 6bf273d5 Michael Hanselmann
    """
666 6bf273d5 Michael Hanselmann
    job_id = cl.GetInstanceInfo(name, static=True)
667 6bf273d5 Michael Hanselmann
    result = poll_job_fn(cl, job_id)
668 6bf273d5 Michael Hanselmann
    assert len(result[0].keys()) == 1
669 6bf273d5 Michael Hanselmann
    return result[0][result[0].keys()[0]]
670 6bf273d5 Michael Hanselmann
671 6bf273d5 Michael Hanselmann
  @staticmethod
672 6bf273d5 Michael Hanselmann
  def _PrepareExport(cl, poll_job_fn, name):
673 6bf273d5 Michael Hanselmann
    """Prepares export on source cluster.
674 6bf273d5 Michael Hanselmann
675 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
676 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
677 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
678 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
679 6bf273d5 Michael Hanselmann
    @type name: string
680 6bf273d5 Michael Hanselmann
    @param name: Instance name
681 6bf273d5 Michael Hanselmann
682 6bf273d5 Michael Hanselmann
    """
683 6bf273d5 Michael Hanselmann
    job_id = cl.PrepareExport(name, constants.EXPORT_MODE_REMOTE)
684 6bf273d5 Michael Hanselmann
    return poll_job_fn(cl, job_id)[0]
685 6bf273d5 Michael Hanselmann
686 6bf273d5 Michael Hanselmann
  @staticmethod
687 5c5c73fd Thomas Thrainer
  def _ExportInstance(cl, poll_job_fn, name, x509_key_name, compress, impinfo):
688 6bf273d5 Michael Hanselmann
    """Exports instance from source cluster.
689 6bf273d5 Michael Hanselmann
690 6bf273d5 Michael Hanselmann
    @type cl: L{rapi.client.GanetiRapiClient}
691 6bf273d5 Michael Hanselmann
    @param cl: RAPI client
692 6bf273d5 Michael Hanselmann
    @type poll_job_fn: callable
693 6bf273d5 Michael Hanselmann
    @param poll_job_fn: Function to poll for job result
694 6bf273d5 Michael Hanselmann
    @type name: string
695 6bf273d5 Michael Hanselmann
    @param name: Instance name
696 6bf273d5 Michael Hanselmann
    @param x509_key_name: Source X509 key
697 5c5c73fd Thomas Thrainer
    @type compress: string
698 5c5c73fd Thomas Thrainer
    @param compress: Compression mode to use
699 6bf273d5 Michael Hanselmann
    @param impinfo: Import information from destination cluster
700 6bf273d5 Michael Hanselmann
701 6bf273d5 Michael Hanselmann
    """
702 6bf273d5 Michael Hanselmann
    job_id = cl.ExportInstance(name, constants.EXPORT_MODE_REMOTE,
703 6bf273d5 Michael Hanselmann
                               impinfo["disks"], shutdown=True,
704 6bf273d5 Michael Hanselmann
                               remove_instance=True,
705 6bf273d5 Michael Hanselmann
                               x509_key_name=x509_key_name,
706 5c5c73fd Thomas Thrainer
                               destination_x509_ca=impinfo["x509_ca"],
707 5c5c73fd Thomas Thrainer
                               compress=compress)
708 6bf273d5 Michael Hanselmann
    (fin_resu, dresults) = poll_job_fn(cl, job_id)[0]
709 6bf273d5 Michael Hanselmann
710 6bf273d5 Michael Hanselmann
    if not (fin_resu and compat.all(dresults)):
711 6bf273d5 Michael Hanselmann
      raise Error("Export failed for disks %s" %
712 6bf273d5 Michael Hanselmann
                  utils.CommaJoin(str(idx) for idx, result
713 6bf273d5 Michael Hanselmann
                                  in enumerate(dresults) if not result))
714 6bf273d5 Michael Hanselmann
715 6bf273d5 Michael Hanselmann
716 6bf273d5 Michael Hanselmann
class MoveSourceWorker(workerpool.BaseWorker):
717 b459a848 Andrea Spadaccini
  def RunTask(self, rapi_factory, move): # pylint: disable=W0221
718 6bf273d5 Michael Hanselmann
    """Executes an instance move.
719 6bf273d5 Michael Hanselmann
720 6bf273d5 Michael Hanselmann
    @type rapi_factory: L{RapiClientFactory}
721 6bf273d5 Michael Hanselmann
    @param rapi_factory: RAPI client factory
722 6bf273d5 Michael Hanselmann
    @type move: L{InstanceMove}
723 6bf273d5 Michael Hanselmann
    @param move: Instance move information
724 6bf273d5 Michael Hanselmann
725 6bf273d5 Michael Hanselmann
    """
726 6bf273d5 Michael Hanselmann
    try:
727 6bf273d5 Michael Hanselmann
      logging.info("Preparing to move %s from cluster %s to %s as %s",
728 6bf273d5 Michael Hanselmann
                   move.src_instance_name, rapi_factory.src_cluster_name,
729 6bf273d5 Michael Hanselmann
                   rapi_factory.dest_cluster_name, move.dest_instance_name)
730 6bf273d5 Michael Hanselmann
731 6bf273d5 Michael Hanselmann
      mrt = MoveRuntime(move)
732 6bf273d5 Michael Hanselmann
733 6bf273d5 Michael Hanselmann
      logging.debug("Starting destination thread")
734 6bf273d5 Michael Hanselmann
      dest_thread = threading.Thread(name="DestFor%s" % self.getName(),
735 6bf273d5 Michael Hanselmann
                                     target=mrt.HandleErrors,
736 6bf273d5 Michael Hanselmann
                                     args=("dest", MoveDestExecutor,
737 6bf273d5 Michael Hanselmann
                                           rapi_factory.GetDestClient(),
738 6bf273d5 Michael Hanselmann
                                           mrt, ))
739 6bf273d5 Michael Hanselmann
      dest_thread.start()
740 6bf273d5 Michael Hanselmann
      try:
741 6bf273d5 Michael Hanselmann
        mrt.HandleErrors("src", MoveSourceExecutor,
742 6bf273d5 Michael Hanselmann
                         rapi_factory.GetSourceClient(), mrt)
743 6bf273d5 Michael Hanselmann
      finally:
744 6bf273d5 Michael Hanselmann
        dest_thread.join()
745 6bf273d5 Michael Hanselmann
746 6bf273d5 Michael Hanselmann
      if mrt.src_error_message or mrt.dest_error_message:
747 6bf273d5 Michael Hanselmann
        move.error_message = ("Source error: %s, destination error: %s" %
748 6bf273d5 Michael Hanselmann
                              (mrt.src_error_message, mrt.dest_error_message))
749 6bf273d5 Michael Hanselmann
      else:
750 6bf273d5 Michael Hanselmann
        move.error_message = None
751 b459a848 Andrea Spadaccini
    except Exception, err: # pylint: disable=W0703
752 6bf273d5 Michael Hanselmann
      logging.exception("Caught unhandled exception")
753 6bf273d5 Michael Hanselmann
      move.error_message = str(err)
754 6bf273d5 Michael Hanselmann
755 6bf273d5 Michael Hanselmann
756 6bf273d5 Michael Hanselmann
def CheckRapiSetup(rapi_factory):
757 6bf273d5 Michael Hanselmann
  """Checks the RAPI setup by retrieving the version.
758 6bf273d5 Michael Hanselmann
759 6bf273d5 Michael Hanselmann
  @type rapi_factory: L{RapiClientFactory}
760 6bf273d5 Michael Hanselmann
  @param rapi_factory: RAPI client factory
761 6bf273d5 Michael Hanselmann
762 6bf273d5 Michael Hanselmann
  """
763 6bf273d5 Michael Hanselmann
  src_client = rapi_factory.GetSourceClient()
764 6bf273d5 Michael Hanselmann
  logging.info("Connecting to source RAPI server")
765 6bf273d5 Michael Hanselmann
  logging.info("Source cluster RAPI version: %s", src_client.GetVersion())
766 6bf273d5 Michael Hanselmann
767 6bf273d5 Michael Hanselmann
  dest_client = rapi_factory.GetDestClient()
768 6bf273d5 Michael Hanselmann
  logging.info("Connecting to destination RAPI server")
769 6bf273d5 Michael Hanselmann
  logging.info("Destination cluster RAPI version: %s", dest_client.GetVersion())
770 6bf273d5 Michael Hanselmann
771 6bf273d5 Michael Hanselmann
772 6bf273d5 Michael Hanselmann
def ParseOptions():
773 6bf273d5 Michael Hanselmann
  """Parses options passed to program.
774 6bf273d5 Michael Hanselmann
775 6bf273d5 Michael Hanselmann
  """
776 6bf273d5 Michael Hanselmann
  program = os.path.basename(sys.argv[0])
777 6bf273d5 Michael Hanselmann
778 6bf273d5 Michael Hanselmann
  parser = optparse.OptionParser(usage=("%prog [--debug|--verbose]"
779 6bf273d5 Michael Hanselmann
                                        " <source-cluster> <dest-cluster>"
780 6bf273d5 Michael Hanselmann
                                        " <instance...>"),
781 6bf273d5 Michael Hanselmann
                                 prog=program)
782 6bf273d5 Michael Hanselmann
  parser.add_option(cli.DEBUG_OPT)
783 6bf273d5 Michael Hanselmann
  parser.add_option(cli.VERBOSE_OPT)
784 6bf273d5 Michael Hanselmann
  parser.add_option(cli.IALLOCATOR_OPT)
785 a111ebde Michael Hanselmann
  parser.add_option(cli.BACKEND_OPT)
786 a111ebde Michael Hanselmann
  parser.add_option(cli.HVOPTS_OPT)
787 a111ebde Michael Hanselmann
  parser.add_option(cli.OSPARAMS_OPT)
788 a111ebde Michael Hanselmann
  parser.add_option(cli.NET_OPT)
789 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_RAPI_PORT_OPT)
790 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_CA_FILE_OPT)
791 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_USERNAME_OPT)
792 6bf273d5 Michael Hanselmann
  parser.add_option(SRC_PASSWORD_FILE_OPT)
793 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_RAPI_PORT_OPT)
794 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_CA_FILE_OPT)
795 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_USERNAME_OPT)
796 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_PASSWORD_FILE_OPT)
797 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_INSTANCE_NAME_OPT)
798 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_PRIMARY_NODE_OPT)
799 6bf273d5 Michael Hanselmann
  parser.add_option(DEST_SECONDARY_NODE_OPT)
800 c3761d99 Hrvoje Ribicic
  parser.add_option(DEST_DISK_TEMPLATE_OPT)
801 5c5c73fd Thomas Thrainer
  parser.add_option(COMPRESS_OPT)
802 6bf273d5 Michael Hanselmann
  parser.add_option(PARALLEL_OPT)
803 6bf273d5 Michael Hanselmann
804 6bf273d5 Michael Hanselmann
  (options, args) = parser.parse_args()
805 6bf273d5 Michael Hanselmann
806 6bf273d5 Michael Hanselmann
  return (parser, options, args)
807 6bf273d5 Michael Hanselmann
808 6bf273d5 Michael Hanselmann
809 6bf273d5 Michael Hanselmann
def CheckOptions(parser, options, args):
810 6bf273d5 Michael Hanselmann
  """Checks options and arguments for validity.
811 6bf273d5 Michael Hanselmann
812 6bf273d5 Michael Hanselmann
  """
813 6bf273d5 Michael Hanselmann
  if len(args) < 3:
814 6bf273d5 Michael Hanselmann
    parser.error("Not enough arguments")
815 6bf273d5 Michael Hanselmann
816 6bf273d5 Michael Hanselmann
  src_cluster_name = args.pop(0)
817 6bf273d5 Michael Hanselmann
  dest_cluster_name = args.pop(0)
818 6bf273d5 Michael Hanselmann
  instance_names = args
819 6bf273d5 Michael Hanselmann
820 6bf273d5 Michael Hanselmann
  assert len(instance_names) > 0
821 6bf273d5 Michael Hanselmann
822 6bf273d5 Michael Hanselmann
  # TODO: Remove once using system default paths for SSL certificate
823 6bf273d5 Michael Hanselmann
  # verification is implemented
824 6bf273d5 Michael Hanselmann
  if not options.src_ca_file:
825 6bf273d5 Michael Hanselmann
    parser.error("Missing source cluster CA file")
826 6bf273d5 Michael Hanselmann
827 6bf273d5 Michael Hanselmann
  if options.parallel < 1:
828 6bf273d5 Michael Hanselmann
    parser.error("Number of simultaneous moves must be >= 1")
829 6bf273d5 Michael Hanselmann
830 86610ed7 Hrvoje Ribicic
  if (bool(options.iallocator) and
831 86610ed7 Hrvoje Ribicic
      bool(options.dest_primary_node or options.dest_secondary_node)):
832 6bf273d5 Michael Hanselmann
    parser.error("Destination node and iallocator options exclude each other")
833 6bf273d5 Michael Hanselmann
834 6bf273d5 Michael Hanselmann
  if len(instance_names) == 1:
835 6bf273d5 Michael Hanselmann
    # Moving one instance only
836 a111ebde Michael Hanselmann
    if options.hvparams:
837 a111ebde Michael Hanselmann
      utils.ForceDictType(options.hvparams, constants.HVS_PARAMETER_TYPES)
838 a111ebde Michael Hanselmann
839 a111ebde Michael Hanselmann
    if options.beparams:
840 a111ebde Michael Hanselmann
      utils.ForceDictType(options.beparams, constants.BES_PARAMETER_TYPES)
841 a111ebde Michael Hanselmann
842 a111ebde Michael Hanselmann
    if options.nics:
843 a111ebde Michael Hanselmann
      options.nics = cli.ParseNicOption(options.nics)
844 6bf273d5 Michael Hanselmann
  else:
845 6bf273d5 Michael Hanselmann
    # Moving more than one instance
846 6bf273d5 Michael Hanselmann
    if (options.dest_instance_name or options.dest_primary_node or
847 a111ebde Michael Hanselmann
        options.dest_secondary_node or options.hvparams or
848 a111ebde Michael Hanselmann
        options.beparams or options.osparams or options.nics):
849 a111ebde Michael Hanselmann
      parser.error("The options --dest-instance-name, --dest-primary-node,"
850 a111ebde Michael Hanselmann
                   " --dest-secondary-node, --hypervisor-parameters,"
851 a111ebde Michael Hanselmann
                   " --backend-parameters, --os-parameters and --net can"
852 a111ebde Michael Hanselmann
                   " only be used when moving exactly one instance")
853 6bf273d5 Michael Hanselmann
854 6bf273d5 Michael Hanselmann
  return (src_cluster_name, dest_cluster_name, instance_names)
855 6bf273d5 Michael Hanselmann
856 6bf273d5 Michael Hanselmann
857 86610ed7 Hrvoje Ribicic
def DestClusterHasDefaultIAllocator(rapi_factory):
858 86610ed7 Hrvoje Ribicic
  """Determines if a given cluster has a default iallocator.
859 86610ed7 Hrvoje Ribicic
860 86610ed7 Hrvoje Ribicic
  """
861 86610ed7 Hrvoje Ribicic
  result = rapi_factory.GetDestClient().GetInfo()
862 86610ed7 Hrvoje Ribicic
  ia_name = "default_iallocator"
863 86610ed7 Hrvoje Ribicic
  return ia_name in result and result[ia_name]
864 86610ed7 Hrvoje Ribicic
865 86610ed7 Hrvoje Ribicic
866 86610ed7 Hrvoje Ribicic
def ExitWithError(message):
867 86610ed7 Hrvoje Ribicic
  """Exits after an error and shows a message.
868 86610ed7 Hrvoje Ribicic
869 86610ed7 Hrvoje Ribicic
  """
870 86610ed7 Hrvoje Ribicic
  sys.stderr.write("move-instance: error: " + message + "\n")
871 86610ed7 Hrvoje Ribicic
  sys.exit(constants.EXIT_FAILURE)
872 86610ed7 Hrvoje Ribicic
873 86610ed7 Hrvoje Ribicic
874 fc3f75dd Iustin Pop
@UsesRapiClient
875 6bf273d5 Michael Hanselmann
def main():
876 6bf273d5 Michael Hanselmann
  """Main routine.
877 6bf273d5 Michael Hanselmann
878 6bf273d5 Michael Hanselmann
  """
879 6bf273d5 Michael Hanselmann
  (parser, options, args) = ParseOptions()
880 6bf273d5 Michael Hanselmann
881 796b5152 Michael Hanselmann
  utils.SetupToolLogging(options.debug, options.verbose, threadname=True)
882 6bf273d5 Michael Hanselmann
883 6bf273d5 Michael Hanselmann
  (src_cluster_name, dest_cluster_name, instance_names) = \
884 6bf273d5 Michael Hanselmann
    CheckOptions(parser, options, args)
885 6bf273d5 Michael Hanselmann
886 6bf273d5 Michael Hanselmann
  logging.info("Source cluster: %s", src_cluster_name)
887 6bf273d5 Michael Hanselmann
  logging.info("Destination cluster: %s", dest_cluster_name)
888 6bf273d5 Michael Hanselmann
  logging.info("Instances to be moved: %s", utils.CommaJoin(instance_names))
889 6bf273d5 Michael Hanselmann
890 6bf273d5 Michael Hanselmann
  rapi_factory = RapiClientFactory(options, src_cluster_name, dest_cluster_name)
891 6bf273d5 Michael Hanselmann
892 6bf273d5 Michael Hanselmann
  CheckRapiSetup(rapi_factory)
893 6bf273d5 Michael Hanselmann
894 86610ed7 Hrvoje Ribicic
  has_iallocator = options.iallocator or \
895 86610ed7 Hrvoje Ribicic
                   DestClusterHasDefaultIAllocator(rapi_factory)
896 86610ed7 Hrvoje Ribicic
897 86610ed7 Hrvoje Ribicic
  if len(instance_names) > 1 and not has_iallocator:
898 86610ed7 Hrvoje Ribicic
    ExitWithError("When moving multiple nodes, an iallocator must be used. "
899 86610ed7 Hrvoje Ribicic
                  "None was provided and the target cluster does not have "
900 86610ed7 Hrvoje Ribicic
                  "a default iallocator.")
901 86610ed7 Hrvoje Ribicic
  if (len(instance_names) == 1 and not (has_iallocator or
902 86610ed7 Hrvoje Ribicic
      options.dest_primary_node or options.dest_secondary_node)):
903 86610ed7 Hrvoje Ribicic
    ExitWithError("Target cluster does not have a default iallocator, "
904 86610ed7 Hrvoje Ribicic
                  "please specify either destination nodes or an iallocator.")
905 6bf273d5 Michael Hanselmann
906 6bf273d5 Michael Hanselmann
  # Prepare list of instance moves
907 6bf273d5 Michael Hanselmann
  moves = []
908 6bf273d5 Michael Hanselmann
  for src_instance_name in instance_names:
909 6bf273d5 Michael Hanselmann
    if options.dest_instance_name:
910 6bf273d5 Michael Hanselmann
      assert len(instance_names) == 1
911 6bf273d5 Michael Hanselmann
      # Rename instance
912 6bf273d5 Michael Hanselmann
      dest_instance_name = options.dest_instance_name
913 6bf273d5 Michael Hanselmann
    else:
914 6bf273d5 Michael Hanselmann
      dest_instance_name = src_instance_name
915 6bf273d5 Michael Hanselmann
916 6bf273d5 Michael Hanselmann
    moves.append(InstanceMove(src_instance_name, dest_instance_name,
917 6bf273d5 Michael Hanselmann
                              options.dest_primary_node,
918 6bf273d5 Michael Hanselmann
                              options.dest_secondary_node,
919 5c5c73fd Thomas Thrainer
                              options.compress,
920 c3761d99 Hrvoje Ribicic
                              options.iallocator,
921 c3761d99 Hrvoje Ribicic
                              options.dest_disk_template,
922 c3761d99 Hrvoje Ribicic
                              options.hvparams,
923 c3761d99 Hrvoje Ribicic
                              options.beparams,
924 c3761d99 Hrvoje Ribicic
                              options.osparams,
925 a111ebde Michael Hanselmann
                              options.nics))
926 6bf273d5 Michael Hanselmann
927 6bf273d5 Michael Hanselmann
  assert len(moves) == len(instance_names)
928 6bf273d5 Michael Hanselmann
929 6bf273d5 Michael Hanselmann
  # Start workerpool
930 6bf273d5 Michael Hanselmann
  wp = workerpool.WorkerPool("Move", options.parallel, MoveSourceWorker)
931 6bf273d5 Michael Hanselmann
  try:
932 6bf273d5 Michael Hanselmann
    # Add instance moves to workerpool
933 6bf273d5 Michael Hanselmann
    for move in moves:
934 b2e8a4d9 Michael Hanselmann
      wp.AddTask((rapi_factory, move))
935 6bf273d5 Michael Hanselmann
936 6bf273d5 Michael Hanselmann
    # Wait for all moves to finish
937 6bf273d5 Michael Hanselmann
    wp.Quiesce()
938 6bf273d5 Michael Hanselmann
939 6bf273d5 Michael Hanselmann
  finally:
940 6bf273d5 Michael Hanselmann
    wp.TerminateWorkers()
941 6bf273d5 Michael Hanselmann
942 6bf273d5 Michael Hanselmann
  # There should be no threads running at this point, hence not using locks
943 6bf273d5 Michael Hanselmann
  # anymore
944 6bf273d5 Michael Hanselmann
945 6bf273d5 Michael Hanselmann
  logging.info("Instance move results:")
946 6bf273d5 Michael Hanselmann
947 6bf273d5 Michael Hanselmann
  for move in moves:
948 6bf273d5 Michael Hanselmann
    if move.dest_instance_name == move.src_instance_name:
949 6bf273d5 Michael Hanselmann
      name = move.src_instance_name
950 6bf273d5 Michael Hanselmann
    else:
951 6bf273d5 Michael Hanselmann
      name = "%s as %s" % (move.src_instance_name, move.dest_instance_name)
952 6bf273d5 Michael Hanselmann
953 424f51ec Michael Hanselmann
    if move.error_message:
954 6bf273d5 Michael Hanselmann
      msg = "Failed (%s)" % move.error_message
955 424f51ec Michael Hanselmann
    else:
956 424f51ec Michael Hanselmann
      msg = "Success"
957 6bf273d5 Michael Hanselmann
958 6bf273d5 Michael Hanselmann
    logging.info("%s: %s", name, msg)
959 6bf273d5 Michael Hanselmann
960 424f51ec Michael Hanselmann
  if compat.any(move.error_message for move in moves):
961 424f51ec Michael Hanselmann
    sys.exit(constants.EXIT_FAILURE)
962 6bf273d5 Michael Hanselmann
963 424f51ec Michael Hanselmann
  sys.exit(constants.EXIT_SUCCESS)
964 6bf273d5 Michael Hanselmann
965 6bf273d5 Michael Hanselmann
966 6bf273d5 Michael Hanselmann
if __name__ == "__main__":
967 6bf273d5 Michael Hanselmann
  main()