1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31 """Logical units dealing with backup operations."""
32
33 import OpenSSL
34 import logging
35
36 from ganeti import compat
37 from ganeti import constants
38 from ganeti import errors
39 from ganeti import locking
40 from ganeti import masterd
41 from ganeti import utils
42
43 from ganeti.cmdlib.base import NoHooksLU, LogicalUnit
44 from ganeti.cmdlib.common import CheckNodeOnline, \
45 ExpandNodeUuidAndName
46 from ganeti.cmdlib.instance_storage import StartInstanceDisks, \
47 ShutdownInstanceDisks
48 from ganeti.cmdlib.instance_utils import GetClusterDomainSecret, \
49 BuildInstanceHookEnvByObject, CheckNodeNotDrained, RemoveInstance
50
51
53 """Prepares an instance for an export and returns useful information.
54
55 """
56 REQ_BGL = False
57
60
71
72 - def Exec(self, feedback_fn):
73 """Prepares an instance for an export.
74
75 """
76 if self.op.mode == constants.EXPORT_MODE_REMOTE:
77 salt = utils.GenerateSecret(8)
78
79 feedback_fn("Generating X509 certificate on %s" %
80 self.cfg.GetNodeName(self.instance.primary_node))
81 result = self.rpc.call_x509_cert_create(self.instance.primary_node,
82 constants.RIE_CERT_VALIDITY)
83 result.Raise("Can't create X509 key and certificate on %s" %
84 self.cfg.GetNodeName(result.node))
85
86 (name, cert_pem) = result.payload
87
88 cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
89 cert_pem)
90
91 return {
92 "handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
93 "x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
94 salt),
95 "x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
96 }
97
98 return None
99
100
102 """Export an instance to an image in the cluster.
103
104 """
105 HPATH = "instance-export"
106 HTYPE = constants.HTYPE_INSTANCE
107 REQ_BGL = False
108
124
147
149 """Last minute lock declaration."""
150
151
153 """Build hooks env.
154
155 This will run on the master, primary node and target node.
156
157 """
158 env = {
159 "EXPORT_MODE": self.op.mode,
160 "EXPORT_NODE": self.op.target_node,
161 "EXPORT_DO_SHUTDOWN": self.op.shutdown,
162 "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
163
164 "REMOVE_INSTANCE": str(bool(self.op.remove_instance)),
165 }
166
167 env.update(BuildInstanceHookEnvByObject(self, self.instance))
168
169 return env
170
181
183 """Check prerequisites.
184
185 This checks that the instance and node names are valid.
186
187 """
188 self.instance = self.cfg.GetInstanceInfoByName(self.op.instance_name)
189 assert self.instance is not None, \
190 "Cannot retrieve locked instance %s" % self.op.instance_name
191 CheckNodeOnline(self, self.instance.primary_node)
192
193 if (self.op.remove_instance and
194 self.instance.admin_state == constants.ADMINST_UP and
195 not self.op.shutdown):
196 raise errors.OpPrereqError("Can not remove instance without shutting it"
197 " down before", errors.ECODE_STATE)
198
199 if self.op.mode == constants.EXPORT_MODE_LOCAL:
200 self.dst_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
201 assert self.dst_node is not None
202
203 CheckNodeOnline(self, self.dst_node.uuid)
204 CheckNodeNotDrained(self, self.dst_node.uuid)
205
206 self._cds = None
207 self.dest_disk_info = None
208 self.dest_x509_ca = None
209
210 elif self.op.mode == constants.EXPORT_MODE_REMOTE:
211 self.dst_node = None
212
213 if len(self.op.target_node) != len(self.instance.disks):
214 raise errors.OpPrereqError(("Received destination information for %s"
215 " disks, but instance %s has %s disks") %
216 (len(self.op.target_node),
217 self.op.instance_name,
218 len(self.instance.disks)),
219 errors.ECODE_INVAL)
220
221 cds = GetClusterDomainSecret()
222
223
224 try:
225 (key_name, hmac_digest, hmac_salt) = self.x509_key_name
226 except (TypeError, ValueError), err:
227 raise errors.OpPrereqError("Invalid data for X509 key name: %s" % err,
228 errors.ECODE_INVAL)
229
230 if not utils.VerifySha1Hmac(cds, key_name, hmac_digest, salt=hmac_salt):
231 raise errors.OpPrereqError("HMAC for X509 key name is wrong",
232 errors.ECODE_INVAL)
233
234
235 try:
236 (cert, _) = utils.LoadSignedX509Certificate(self.dest_x509_ca_pem, cds)
237 except OpenSSL.crypto.Error, err:
238 raise errors.OpPrereqError("Unable to load destination X509 CA (%s)" %
239 (err, ), errors.ECODE_INVAL)
240
241 (errcode, msg) = utils.VerifyX509Certificate(cert, None, None)
242 if errcode is not None:
243 raise errors.OpPrereqError("Invalid destination X509 CA (%s)" %
244 (msg, ), errors.ECODE_INVAL)
245
246 self.dest_x509_ca = cert
247
248
249 disk_info = []
250 for idx, disk_data in enumerate(self.op.target_node):
251 try:
252 (host, port, magic) = \
253 masterd.instance.CheckRemoteExportDiskInfo(cds, idx, disk_data)
254 except errors.GenericError, err:
255 raise errors.OpPrereqError("Target info for disk %s: %s" %
256 (idx, err), errors.ECODE_INVAL)
257
258 disk_info.append((host, port, magic))
259
260 assert len(disk_info) == len(self.op.target_node)
261 self.dest_disk_info = disk_info
262
263 else:
264 raise errors.ProgrammerError("Unhandled export mode %r" %
265 self.op.mode)
266
267
268
269 for disk in self.instance.disks:
270 if disk.dev_type in constants.DTS_FILEBASED:
271 raise errors.OpPrereqError("Export not supported for instances with"
272 " file-based disks", errors.ECODE_INVAL)
273
275 """Removes exports of current instance from all other nodes.
276
277 If an instance in a cluster with nodes A..D was exported to node C, its
278 exports will be removed from the nodes A, B and D.
279
280 """
281 assert self.op.mode != constants.EXPORT_MODE_REMOTE
282
283 node_uuids = self.cfg.GetNodeList()
284 node_uuids.remove(self.dst_node.uuid)
285
286
287
288
289 iname = self.instance.name
290 if node_uuids:
291 feedback_fn("Removing old exports for instance %s" % iname)
292 exportlist = self.rpc.call_export_list(node_uuids)
293 for node_uuid in exportlist:
294 if exportlist[node_uuid].fail_msg:
295 continue
296 if iname in exportlist[node_uuid].payload:
297 msg = self.rpc.call_export_remove(node_uuid, iname).fail_msg
298 if msg:
299 self.LogWarning("Could not remove older export for instance %s"
300 " on node %s: %s", iname,
301 self.cfg.GetNodeName(node_uuid), msg)
302
303 - def Exec(self, feedback_fn):
304 """Export an instance to an image in the cluster.
305
306 """
307 assert self.op.mode in constants.EXPORT_MODES
308
309 src_node_uuid = self.instance.primary_node
310
311 if self.op.shutdown:
312
313 feedback_fn("Shutting down instance %s" % self.instance.name)
314 result = self.rpc.call_instance_shutdown(src_node_uuid, self.instance,
315 self.op.shutdown_timeout,
316 self.op.reason)
317
318 result.Raise("Could not shutdown instance %s on"
319 " node %s" % (self.instance.name,
320 self.cfg.GetNodeName(src_node_uuid)))
321
322 activate_disks = not self.instance.disks_active
323
324 if activate_disks:
325
326 feedback_fn("Activating disks for %s" % self.instance.name)
327 StartInstanceDisks(self, self.instance, None)
328
329 try:
330 helper = masterd.instance.ExportInstanceHelper(self, feedback_fn,
331 self.instance)
332
333 helper.CreateSnapshots()
334 try:
335 if (self.op.shutdown and
336 self.instance.admin_state == constants.ADMINST_UP and
337 not self.op.remove_instance):
338 assert not activate_disks
339 feedback_fn("Starting instance %s" % self.instance.name)
340 result = self.rpc.call_instance_start(src_node_uuid,
341 (self.instance, None, None),
342 False, self.op.reason)
343 msg = result.fail_msg
344 if msg:
345 feedback_fn("Failed to start instance: %s" % msg)
346 ShutdownInstanceDisks(self, self.instance)
347 raise errors.OpExecError("Could not start instance: %s" % msg)
348
349 if self.op.mode == constants.EXPORT_MODE_LOCAL:
350 (fin_resu, dresults) = helper.LocalExport(self.dst_node,
351 self.op.compress)
352 elif self.op.mode == constants.EXPORT_MODE_REMOTE:
353 connect_timeout = constants.RIE_CONNECT_TIMEOUT
354 timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
355
356 (key_name, _, _) = self.x509_key_name
357
358 dest_ca_pem = \
359 OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM,
360 self.dest_x509_ca)
361
362 (fin_resu, dresults) = helper.RemoteExport(self.dest_disk_info,
363 key_name, dest_ca_pem,
364 self.op.compress,
365 timeouts)
366 finally:
367 helper.Cleanup()
368
369
370 assert len(dresults) == len(self.instance.disks)
371 assert compat.all(isinstance(i, bool) for i in dresults), \
372 "Not all results are boolean: %r" % dresults
373
374 finally:
375 if activate_disks:
376 feedback_fn("Deactivating disks for %s" % self.instance.name)
377 ShutdownInstanceDisks(self, self.instance)
378
379 if not (compat.all(dresults) and fin_resu):
380 failures = []
381 if not fin_resu:
382 failures.append("export finalization")
383 if not compat.all(dresults):
384 fdsk = utils.CommaJoin(idx for (idx, dsk) in enumerate(dresults)
385 if not dsk)
386 failures.append("disk export: disk(s) %s" % fdsk)
387
388 raise errors.OpExecError("Export failed, errors in %s" %
389 utils.CommaJoin(failures))
390
391
392
393
394 if self.op.remove_instance:
395 feedback_fn("Removing instance %s" % self.instance.name)
396 RemoveInstance(self, feedback_fn, self.instance,
397 self.op.ignore_remove_failures)
398
399 if self.op.mode == constants.EXPORT_MODE_LOCAL:
400 self._CleanupExports(feedback_fn)
401
402 return fin_resu, dresults
403
404
406 """Remove exports related to the named instance.
407
408 """
409 REQ_BGL = False
410
425
426 - def Exec(self, feedback_fn):
427 """Remove any export.
428
429 """
430 (_, inst_name) = self.cfg.ExpandInstanceName(self.op.instance_name)
431
432
433 fqdn_warn = False
434 if not inst_name:
435 fqdn_warn = True
436 inst_name = self.op.instance_name
437
438 locked_nodes = self.owned_locks(locking.LEVEL_NODE)
439 exportlist = self.rpc.call_export_list(locked_nodes)
440 found = False
441 for node_uuid in exportlist:
442 msg = exportlist[node_uuid].fail_msg
443 if msg:
444 self.LogWarning("Failed to query node %s (continuing): %s",
445 self.cfg.GetNodeName(node_uuid), msg)
446 continue
447 if inst_name in exportlist[node_uuid].payload:
448 found = True
449 result = self.rpc.call_export_remove(node_uuid, inst_name)
450 msg = result.fail_msg
451 if msg:
452 logging.error("Could not remove export for instance %s"
453 " on node %s: %s", inst_name,
454 self.cfg.GetNodeName(node_uuid), msg)
455
456 if fqdn_warn and not found:
457 feedback_fn("Export not found. If trying to remove an export belonging"
458 " to a deleted instance please use its Fully Qualified"
459 " Domain Name.")
460