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 instances."""
32
33 import logging
34 import os
35
36 from ganeti import compat
37 from ganeti import constants
38 from ganeti import errors
39 from ganeti import locking
40 from ganeti.masterd import iallocator
41 from ganeti import masterd
42 from ganeti import netutils
43 from ganeti import objects
44 from ganeti import utils
45
46 from ganeti.cmdlib.base import NoHooksLU, LogicalUnit, ResultWithJobs
47
48 from ganeti.cmdlib.common import \
49 INSTANCE_NOT_RUNNING, CheckNodeOnline, \
50 ShareAll, GetDefaultIAllocator, CheckInstanceNodeGroups, \
51 LoadNodeEvacResult, \
52 ExpandInstanceUuidAndName, \
53 CheckInstanceState, ExpandNodeUuidAndName, \
54 CheckDiskTemplateEnabled
55 from ganeti.cmdlib.instance_storage import CreateDisks, \
56 ComputeDisks, \
57 StartInstanceDisks, ShutdownInstanceDisks, \
58 AssembleInstanceDisks
59 from ganeti.cmdlib.instance_utils import \
60 BuildInstanceHookEnvByObject,\
61 CheckNodeNotDrained, RemoveInstance, CopyLockList, \
62 CheckNodeVmCapable, CheckTargetNodeIPolicy, \
63 GetInstanceInfoText, RemoveDisks, CheckNodeFreeMemory, \
64 CheckInstanceBridgesExist, \
65 CheckInstanceExistence, \
66 CheckHostnameSane, CheckOpportunisticLocking, ComputeFullBeParams, \
67 ComputeNics, CreateInstanceAllocRequest
68 import ganeti.masterd.instance
69
70
72 """Rename an instance.
73
74 """
75 HPATH = "instance-rename"
76 HTYPE = constants.HTYPE_INSTANCE
77 REQ_BGL = False
78
80 """Check arguments.
81
82 """
83 if self.op.ip_check and not self.op.name_check:
84
85 raise errors.OpPrereqError("IP address check requires a name check",
86 errors.ECODE_INVAL)
87
88 self._new_name_resolved = False
89
91 """Build hooks env.
92
93 This runs on master, primary and secondary nodes of the instance.
94
95 """
96 env = BuildInstanceHookEnvByObject(self, self.instance)
97 env["INSTANCE_NEW_NAME"] = self.op.new_name
98 return env
99
107
123
155
170
171 - def Exec(self, feedback_fn):
172 """Rename the instance.
173
174 """
175 old_name = self.instance.name
176
177 rename_file_storage = False
178 disks = self.cfg.GetInstanceDisks(self.instance.uuid)
179 renamed_storage = [d for d in disks
180 if (d.dev_type in constants.DTS_FILEBASED and
181 d.dev_type != constants.DT_GLUSTER)]
182 if (renamed_storage and self.op.new_name != self.instance.name):
183 disks = self.cfg.GetInstanceDisks(self.instance.uuid)
184 old_file_storage_dir = os.path.dirname(disks[0].logical_id[1])
185 rename_file_storage = True
186
187 self.cfg.RenameInstance(self.instance.uuid, self.op.new_name)
188
189
190 assert old_name in self.owned_locks(locking.LEVEL_INSTANCE)
191 assert self.op.new_name in self.owned_locks(locking.LEVEL_INSTANCE)
192
193
194 renamed_inst = self.cfg.GetInstanceInfo(self.instance.uuid)
195 disks = self.cfg.GetInstanceDisks(renamed_inst.uuid)
196
197 if self.instance.forthcoming:
198 return renamed_inst.name
199
200 if rename_file_storage:
201 new_file_storage_dir = os.path.dirname(disks[0].logical_id[1])
202 result = self.rpc.call_file_storage_dir_rename(renamed_inst.primary_node,
203 old_file_storage_dir,
204 new_file_storage_dir)
205 result.Raise("Could not rename on node %s directory '%s' to '%s'"
206 " (but the instance has been renamed in Ganeti)" %
207 (self.cfg.GetNodeName(renamed_inst.primary_node),
208 old_file_storage_dir, new_file_storage_dir))
209
210 StartInstanceDisks(self, renamed_inst, None)
211 renamed_inst = self.cfg.GetInstanceInfo(renamed_inst.uuid)
212
213
214 info = GetInstanceInfoText(renamed_inst)
215 for (idx, disk) in enumerate(disks):
216 for node_uuid in self.cfg.GetInstanceNodes(renamed_inst.uuid):
217 result = self.rpc.call_blockdev_setinfo(node_uuid,
218 (disk, renamed_inst), info)
219 result.Warn("Error setting info on node %s for disk %s" %
220 (self.cfg.GetNodeName(node_uuid), idx), self.LogWarning)
221 try:
222 result = self.rpc.call_instance_run_rename(renamed_inst.primary_node,
223 renamed_inst, old_name,
224 self.op.debug_level)
225 result.Warn("Could not run OS rename script for instance %s on node %s"
226 " (but the instance has been renamed in Ganeti)" %
227 (renamed_inst.name,
228 self.cfg.GetNodeName(renamed_inst.primary_node)),
229 self.LogWarning)
230 finally:
231 ShutdownInstanceDisks(self, renamed_inst)
232
233 return renamed_inst.name
234
235
237 """Remove an instance.
238
239 """
240 HPATH = "instance-remove"
241 HTYPE = constants.HTYPE_INSTANCE
242 REQ_BGL = False
243
251
259
261 """Build hooks env.
262
263 This runs on master, primary and secondary nodes of the instance.
264
265 """
266 env = BuildInstanceHookEnvByObject(self, self.instance,
267 secondary_nodes=self.secondary_nodes,
268 disks=self.inst_disks)
269 env["SHUTDOWN_TIMEOUT"] = self.op.shutdown_timeout
270 return env
271
279
292
293 - def Exec(self, feedback_fn):
294 """Remove the instance.
295
296 """
297 assert (self.owned_locks(locking.LEVEL_NODE) ==
298 self.owned_locks(locking.LEVEL_NODE_RES))
299 assert not (set(self.cfg.GetInstanceNodes(self.instance.uuid)) -
300 self.owned_locks(locking.LEVEL_NODE)), \
301 "Not owning correct locks"
302
303 if not self.instance.forthcoming:
304 logging.info("Shutting down instance %s on node %s", self.instance.name,
305 self.cfg.GetNodeName(self.instance.primary_node))
306
307 result = self.rpc.call_instance_shutdown(self.instance.primary_node,
308 self.instance,
309 self.op.shutdown_timeout,
310 self.op.reason)
311 if self.op.ignore_failures:
312 result.Warn("Warning: can't shutdown instance", feedback_fn)
313 else:
314 result.Raise("Could not shutdown instance %s on node %s" %
315 (self.instance.name,
316 self.cfg.GetNodeName(self.instance.primary_node)))
317 else:
318 logging.info("Instance %s on node %s is forthcoming; not shutting down",
319 self.instance.name,
320 self.cfg.GetNodeName(self.instance.primary_node))
321
322 RemoveInstance(self, feedback_fn, self.instance, self.op.ignore_failures)
323
324
326 """Move an instance by data-copying.
327
328 This LU is only used if the instance needs to be moved by copying the data
329 from one node in the cluster to another. The instance is shut down and
330 the data is copied to the new node and the configuration change is propagated,
331 then the instance is started again.
332
333 See also:
334 L{LUInstanceFailover} for moving an instance on shared storage (no copying
335 required).
336
337 L{LUInstanceMigrate} for the live migration of an instance (no shutdown
338 required).
339 """
340 HPATH = "instance-move"
341 HTYPE = constants.HTYPE_INSTANCE
342 REQ_BGL = False
343
352
360
362 """Build hooks env.
363
364 This runs on master, primary and target nodes of the instance.
365
366 """
367 env = {
368 "TARGET_NODE": self.op.target_node,
369 "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
370 }
371 env.update(BuildInstanceHookEnvByObject(self, self.instance))
372 return env
373
375 """Build hooks nodes.
376
377 """
378 nl = [
379 self.cfg.GetMasterNode(),
380 self.instance.primary_node,
381 self.op.target_node_uuid,
382 ]
383 return (nl, nl)
384
386 """Check prerequisites.
387
388 This checks that the instance is in the cluster.
389
390 """
391 self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
392 assert self.instance is not None, \
393 "Cannot retrieve locked instance %s" % self.op.instance_name
394
395 disks = self.cfg.GetInstanceDisks(self.instance.uuid)
396 for idx, dsk in enumerate(disks):
397 if dsk.dev_type not in constants.DTS_COPYABLE:
398 raise errors.OpPrereqError("Instance disk %d has disk type %s and is"
399 " not suitable for copying"
400 % (idx, dsk.dev_type), errors.ECODE_STATE)
401
402 target_node = self.cfg.GetNodeInfo(self.op.target_node_uuid)
403 assert target_node is not None, \
404 "Cannot retrieve locked node %s" % self.op.target_node
405
406 self.target_node_uuid = target_node.uuid
407 if target_node.uuid == self.instance.primary_node:
408 raise errors.OpPrereqError("Instance %s is already on the node %s" %
409 (self.instance.name, target_node.name),
410 errors.ECODE_STATE)
411
412 cluster = self.cfg.GetClusterInfo()
413 bep = cluster.FillBE(self.instance)
414
415 CheckNodeOnline(self, target_node.uuid)
416 CheckNodeNotDrained(self, target_node.uuid)
417 CheckNodeVmCapable(self, target_node.uuid)
418 group_info = self.cfg.GetNodeGroup(target_node.group)
419 ipolicy = ganeti.masterd.instance.CalculateGroupIPolicy(cluster, group_info)
420 CheckTargetNodeIPolicy(self, ipolicy, self.instance, target_node, self.cfg,
421 ignore=self.op.ignore_ipolicy)
422
423 if self.instance.admin_state == constants.ADMINST_UP:
424
425 CheckNodeFreeMemory(
426 self, target_node.uuid, "failing over instance %s" %
427 self.instance.name, bep[constants.BE_MAXMEM],
428 self.instance.hypervisor,
429 cluster.hvparams[self.instance.hypervisor])
430 else:
431 self.LogInfo("Not checking memory on the secondary node as"
432 " instance will not be started")
433
434
435 CheckInstanceBridgesExist(self, self.instance, node_uuid=target_node.uuid)
436
437 - def Exec(self, feedback_fn):
438 """Move an instance.
439
440 The move is done by shutting it down on its present node, copying
441 the data over (slow) and starting it on the new node.
442
443 """
444 source_node = self.cfg.GetNodeInfo(self.instance.primary_node)
445 target_node = self.cfg.GetNodeInfo(self.target_node_uuid)
446
447 self.LogInfo("Shutting down instance %s on source node %s",
448 self.instance.name, source_node.name)
449
450 assert (self.owned_locks(locking.LEVEL_NODE) ==
451 self.owned_locks(locking.LEVEL_NODE_RES))
452
453 result = self.rpc.call_instance_shutdown(source_node.uuid, self.instance,
454 self.op.shutdown_timeout,
455 self.op.reason)
456 if self.op.ignore_consistency:
457 result.Warn("Could not shutdown instance %s on node %s. Proceeding"
458 " anyway. Please make sure node %s is down. Error details" %
459 (self.instance.name, source_node.name, source_node.name),
460 self.LogWarning)
461 else:
462 result.Raise("Could not shutdown instance %s on node %s" %
463 (self.instance.name, source_node.name))
464
465
466 try:
467 CreateDisks(self, self.instance, target_node_uuid=target_node.uuid)
468 except errors.OpExecError:
469 self.LogWarning("Device creation failed")
470 for disk_uuid in self.instance.disks:
471 self.cfg.ReleaseDRBDMinors(disk_uuid)
472 raise
473
474 errs = []
475 transfers = []
476
477 disks = self.cfg.GetInstanceDisks(self.instance.uuid)
478 for idx, disk in enumerate(disks):
479
480 dt = masterd.instance.DiskTransfer("disk/%s" % idx,
481 constants.IEIO_RAW_DISK,
482 (disk, self.instance),
483 constants.IEIO_RAW_DISK,
484 (disk, self.instance),
485 None)
486 transfers.append(dt)
487 self.cfg.Update(disk, feedback_fn)
488
489 import_result = \
490 masterd.instance.TransferInstanceData(self, feedback_fn,
491 source_node.uuid,
492 target_node.uuid,
493 target_node.secondary_ip,
494 self.op.compress,
495 self.instance, transfers)
496 if not compat.all(import_result):
497 errs.append("Failed to transfer instance data")
498
499 if errs:
500 self.LogWarning("Some disks failed to copy, aborting")
501 try:
502 RemoveDisks(self, self.instance, target_node_uuid=target_node.uuid)
503 finally:
504 for disk_uuid in self.instance.disks:
505 self.cfg.ReleaseDRBDMinors(disk_uuid)
506 raise errors.OpExecError("Errors during disk copy: %s" %
507 (",".join(errs),))
508
509 self.instance.primary_node = target_node.uuid
510 self.cfg.Update(self.instance, feedback_fn)
511 for disk in disks:
512 self.cfg.SetDiskNodes(disk.uuid, [target_node.uuid])
513
514 self.LogInfo("Removing the disks on the original node")
515 RemoveDisks(self, self.instance, target_node_uuid=source_node.uuid)
516
517
518 if self.instance.admin_state == constants.ADMINST_UP:
519 self.LogInfo("Starting instance %s on node %s",
520 self.instance.name, target_node.name)
521
522 disks_ok, _, _ = AssembleInstanceDisks(self, self.instance,
523 ignore_secondaries=True)
524 if not disks_ok:
525 ShutdownInstanceDisks(self, self.instance)
526 raise errors.OpExecError("Can't activate the instance's disks")
527
528 result = self.rpc.call_instance_start(target_node.uuid,
529 (self.instance, None, None), False,
530 self.op.reason)
531 msg = result.fail_msg
532 if msg:
533 ShutdownInstanceDisks(self, self.instance)
534 raise errors.OpExecError("Could not start instance %s on node %s: %s" %
535 (self.instance.name, target_node.name, msg))
536
537
539 """Allocates multiple instances at the same time.
540
541 """
542 REQ_BGL = False
543
581
583 """Calculate the locks.
584
585 """
586 self.share_locks = ShareAll()
587 self.needed_locks = {}
588
589 if self.op.iallocator:
590 self.needed_locks[locking.LEVEL_NODE] = locking.ALL_SET
591 self.needed_locks[locking.LEVEL_NODE_RES] = locking.ALL_SET
592
593 if self.op.opportunistic_locking:
594 self.opportunistic_locks[locking.LEVEL_NODE] = True
595 self.opportunistic_locks[locking.LEVEL_NODE_RES] = True
596 else:
597 nodeslist = []
598 for inst in self.op.instances:
599 (inst.pnode_uuid, inst.pnode) = \
600 ExpandNodeUuidAndName(self.cfg, inst.pnode_uuid, inst.pnode)
601 nodeslist.append(inst.pnode_uuid)
602 if inst.snode is not None:
603 (inst.snode_uuid, inst.snode) = \
604 ExpandNodeUuidAndName(self.cfg, inst.snode_uuid, inst.snode)
605 nodeslist.append(inst.snode_uuid)
606
607 self.needed_locks[locking.LEVEL_NODE] = nodeslist
608
609
610 self.needed_locks[locking.LEVEL_NODE_RES] = list(nodeslist)
611
613 """Check prerequisite.
614
615 """
616 if self.op.iallocator:
617 cluster = self.cfg.GetClusterInfo()
618 default_vg = self.cfg.GetVGName()
619 ec_id = self.proc.GetECId()
620
621 if self.op.opportunistic_locking:
622
623 node_whitelist = self.cfg.GetNodeNames(
624 set(self.owned_locks(locking.LEVEL_NODE)) &
625 set(self.owned_locks(locking.LEVEL_NODE_RES)))
626 else:
627 node_whitelist = None
628
629 insts = [CreateInstanceAllocRequest(op, ComputeDisks(op.disks,
630 op.disk_template,
631 default_vg),
632 ComputeNics(op, cluster, None,
633 self.cfg, ec_id),
634 ComputeFullBeParams(op, cluster),
635 node_whitelist)
636 for op in self.op.instances]
637
638 req = iallocator.IAReqMultiInstanceAlloc(instances=insts)
639 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
640
641 ial.Run(self.op.iallocator)
642
643 if not ial.success:
644 raise errors.OpPrereqError("Can't compute nodes using"
645 " iallocator '%s': %s" %
646 (self.op.iallocator, ial.info),
647 errors.ECODE_NORES)
648
649 self.ia_result = ial.result
650
651 if self.op.dry_run:
652 self.dry_run_result = objects.FillDict(self._ConstructPartialResult(), {
653 constants.JOB_IDS_KEY: [],
654 })
655
657 """Contructs the partial result.
658
659 """
660 if self.op.iallocator:
661 (allocatable, failed_insts) = self.ia_result
662 allocatable_insts = map(compat.fst, allocatable)
663 else:
664 allocatable_insts = [op.instance_name for op in self.op.instances]
665 failed_insts = []
666
667 return {
668 constants.ALLOCATABLE_KEY: allocatable_insts,
669 constants.FAILED_KEY: failed_insts,
670 }
671
672 - def Exec(self, feedback_fn):
673 """Executes the opcode.
674
675 """
676 jobs = []
677 if self.op.iallocator:
678 op2inst = dict((op.instance_name, op) for op in self.op.instances)
679 (allocatable, failed) = self.ia_result
680
681 for (name, node_names) in allocatable:
682 op = op2inst.pop(name)
683
684 (op.pnode_uuid, op.pnode) = \
685 ExpandNodeUuidAndName(self.cfg, None, node_names[0])
686 if len(node_names) > 1:
687 (op.snode_uuid, op.snode) = \
688 ExpandNodeUuidAndName(self.cfg, None, node_names[1])
689
690 jobs.append([op])
691
692 missing = set(op2inst.keys()) - set(failed)
693 assert not missing, \
694 "Iallocator did return incomplete result: %s" % \
695 utils.CommaJoin(missing)
696 else:
697 jobs.extend([op] for op in self.op.instances)
698
699 return ResultWithJobs(jobs, **self._ConstructPartialResult())
700
701
703 HPATH = "instance-change-group"
704 HTYPE = constants.HTYPE_INSTANCE
705 REQ_BGL = False
706
724
758
760 owned_instance_names = frozenset(self.owned_locks(locking.LEVEL_INSTANCE))
761 owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP))
762 owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE))
763
764 assert (self.req_target_uuids is None or
765 owned_groups.issuperset(self.req_target_uuids))
766 assert owned_instance_names == set([self.op.instance_name])
767
768
769 self.instance = self.cfg.GetInstanceInfo(self.op.instance_uuid)
770
771
772 instance_all_nodes = self.cfg.GetInstanceNodes(self.instance.uuid)
773 assert owned_nodes.issuperset(instance_all_nodes), \
774 ("Instance %s's nodes changed while we kept the lock" %
775 self.op.instance_name)
776
777 inst_groups = CheckInstanceNodeGroups(self.cfg, self.op.instance_uuid,
778 owned_groups)
779
780 if self.req_target_uuids:
781
782 self.target_uuids = frozenset(self.req_target_uuids)
783 else:
784
785 self.target_uuids = owned_groups - inst_groups
786
787 conflicting_groups = self.target_uuids & inst_groups
788 if conflicting_groups:
789 raise errors.OpPrereqError("Can't use group(s) '%s' as targets, they are"
790 " used by the instance '%s'" %
791 (utils.CommaJoin(conflicting_groups),
792 self.op.instance_name),
793 errors.ECODE_INVAL)
794
795 if not self.target_uuids:
796 raise errors.OpPrereqError("There are no possible target groups",
797 errors.ECODE_INVAL)
798
800 """Build hooks env.
801
802 """
803 assert self.target_uuids
804
805 env = {
806 "TARGET_GROUPS": " ".join(self.target_uuids),
807 }
808
809 env.update(BuildInstanceHookEnvByObject(self, self.instance))
810
811 return env
812
814 """Build hooks nodes.
815
816 """
817 mn = self.cfg.GetMasterNode()
818 return ([mn], [mn])
819
820 - def Exec(self, feedback_fn):
821 instances = list(self.owned_locks(locking.LEVEL_INSTANCE))
822
823 assert instances == [self.op.instance_name], "Instance not locked"
824
825 req = iallocator.IAReqGroupChange(instances=instances,
826 target_groups=list(self.target_uuids))
827 ial = iallocator.IAllocator(self.cfg, self.rpc, req)
828
829 ial.Run(self.op.iallocator)
830
831 if not ial.success:
832 raise errors.OpPrereqError("Can't compute solution for changing group of"
833 " instance '%s' using iallocator '%s': %s" %
834 (self.op.instance_name, self.op.iallocator,
835 ial.info), errors.ECODE_NORES)
836
837 jobs = LoadNodeEvacResult(self, ial.result, self.op.early_release, False)
838
839 self.LogInfo("Iallocator returned %s job(s) for changing group of"
840 " instance '%s'", len(jobs), self.op.instance_name)
841
842 return ResultWithJobs(jobs)
843