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() |