Revision e311ed53
b/lib/cmdlib.py | ||
---|---|---|
45 | 45 |
from ganeti import serializer |
46 | 46 |
from ganeti import ssconf |
47 | 47 |
from ganeti import uidpool |
48 |
from ganeti import compat |
|
48 | 49 |
|
49 | 50 |
|
50 | 51 |
class LogicalUnit(object): |
... | ... | |
8871 | 8872 |
raise errors.OpPrereqError("Export not supported for instances with" |
8872 | 8873 |
" file-based disks", errors.ECODE_INVAL) |
8873 | 8874 |
|
8875 |
def _CreateSnapshots(self, feedback_fn): |
|
8876 |
"""Creates an LVM snapshot for every disk of the instance. |
|
8877 |
|
|
8878 |
@return: List of snapshots as L{objects.Disk} instances |
|
8879 |
|
|
8880 |
""" |
|
8881 |
instance = self.instance |
|
8882 |
src_node = instance.primary_node |
|
8883 |
|
|
8884 |
vgname = self.cfg.GetVGName() |
|
8885 |
|
|
8886 |
snap_disks = [] |
|
8887 |
|
|
8888 |
for idx, disk in enumerate(instance.disks): |
|
8889 |
feedback_fn("Creating a snapshot of disk/%s on node %s" % |
|
8890 |
(idx, src_node)) |
|
8891 |
|
|
8892 |
# result.payload will be a snapshot of an lvm leaf of the one we |
|
8893 |
# passed |
|
8894 |
result = self.rpc.call_blockdev_snapshot(src_node, disk) |
|
8895 |
msg = result.fail_msg |
|
8896 |
if msg: |
|
8897 |
self.LogWarning("Could not snapshot disk/%s on node %s: %s", |
|
8898 |
idx, src_node, msg) |
|
8899 |
snap_disks.append(False) |
|
8900 |
else: |
|
8901 |
disk_id = (vgname, result.payload) |
|
8902 |
new_dev = objects.Disk(dev_type=constants.LD_LV, size=disk.size, |
|
8903 |
logical_id=disk_id, physical_id=disk_id, |
|
8904 |
iv_name=disk.iv_name) |
|
8905 |
snap_disks.append(new_dev) |
|
8906 |
|
|
8907 |
return snap_disks |
|
8908 |
|
|
8909 |
def _RemoveSnapshot(self, feedback_fn, snap_disks, disk_index): |
|
8910 |
"""Removes an LVM snapshot. |
|
8911 |
|
|
8912 |
@type snap_disks: list |
|
8913 |
@param snap_disks: The list of all snapshots as returned by |
|
8914 |
L{_CreateSnapshots} |
|
8915 |
@type disk_index: number |
|
8916 |
@param disk_index: Index of the snapshot to be removed |
|
8917 |
@rtype: bool |
|
8918 |
@return: Whether removal was successful or not |
|
8919 |
|
|
8920 |
""" |
|
8921 |
disk = snap_disks[disk_index] |
|
8922 |
if disk: |
|
8923 |
src_node = self.instance.primary_node |
|
8924 |
|
|
8925 |
feedback_fn("Removing snapshot of disk/%s on node %s" % |
|
8926 |
(disk_index, src_node)) |
|
8927 |
|
|
8928 |
result = self.rpc.call_blockdev_remove(src_node, disk) |
|
8929 |
if not result.fail_msg: |
|
8930 |
return True |
|
8931 |
|
|
8932 |
self.LogWarning("Could not remove snapshot for disk/%d from node" |
|
8933 |
" %s: %s", disk_index, src_node, result.fail_msg) |
|
8934 |
|
|
8935 |
return False |
|
8936 |
|
|
8937 |
def _CleanupExports(self, feedback_fn): |
|
8938 |
"""Removes exports of current instance from all other nodes. |
|
8939 |
|
|
8940 |
If an instance in a cluster with nodes A..D was exported to node C, its |
|
8941 |
exports will be removed from the nodes A, B and D. |
|
8942 |
|
|
8943 |
""" |
|
8944 |
nodelist = self.cfg.GetNodeList() |
|
8945 |
nodelist.remove(self.dst_node.name) |
|
8946 |
|
|
8947 |
# on one-node clusters nodelist will be empty after the removal |
|
8948 |
# if we proceed the backup would be removed because OpQueryExports |
|
8949 |
# substitutes an empty list with the full cluster node list. |
|
8950 |
iname = self.instance.name |
|
8951 |
if nodelist: |
|
8952 |
feedback_fn("Removing old exports for instance %s" % iname) |
|
8953 |
exportlist = self.rpc.call_export_list(nodelist) |
|
8954 |
for node in exportlist: |
|
8955 |
if exportlist[node].fail_msg: |
|
8956 |
continue |
|
8957 |
if iname in exportlist[node].payload: |
|
8958 |
msg = self.rpc.call_export_remove(node, iname).fail_msg |
|
8959 |
if msg: |
|
8960 |
self.LogWarning("Could not remove older export for instance %s" |
|
8961 |
" on node %s: %s", iname, node, msg) |
|
8962 |
|
|
8874 | 8963 |
def Exec(self, feedback_fn): |
8875 | 8964 |
"""Export an instance to an image in the cluster. |
8876 | 8965 |
|
... | ... | |
8887 | 8976 |
result.Raise("Could not shutdown instance %s on" |
8888 | 8977 |
" node %s" % (instance.name, src_node)) |
8889 | 8978 |
|
8890 |
vgname = self.cfg.GetVGName() |
|
8891 |
|
|
8892 |
snap_disks = [] |
|
8893 |
|
|
8894 | 8979 |
# set the disks ID correctly since call_instance_start needs the |
8895 | 8980 |
# correct drbd minor to create the symlinks |
8896 | 8981 |
for disk in instance.disks: |
... | ... | |
8906 | 8991 |
try: |
8907 | 8992 |
# per-disk results |
8908 | 8993 |
dresults = [] |
8994 |
removed_snaps = [False] * len(instance.disks) |
|
8995 |
|
|
8996 |
snap_disks = None |
|
8909 | 8997 |
try: |
8910 |
for idx, disk in enumerate(instance.disks): |
|
8911 |
feedback_fn("Creating a snapshot of disk/%s on node %s" % |
|
8912 |
(idx, src_node)) |
|
8913 |
|
|
8914 |
# result.payload will be a snapshot of an lvm leaf of the one we |
|
8915 |
# passed |
|
8916 |
result = self.rpc.call_blockdev_snapshot(src_node, disk) |
|
8917 |
msg = result.fail_msg |
|
8918 |
if msg: |
|
8919 |
self.LogWarning("Could not snapshot disk/%s on node %s: %s", |
|
8920 |
idx, src_node, msg) |
|
8921 |
snap_disks.append(False) |
|
8922 |
else: |
|
8923 |
disk_id = (vgname, result.payload) |
|
8924 |
new_dev = objects.Disk(dev_type=constants.LD_LV, size=disk.size, |
|
8925 |
logical_id=disk_id, physical_id=disk_id, |
|
8926 |
iv_name=disk.iv_name) |
|
8927 |
snap_disks.append(new_dev) |
|
8998 |
try: |
|
8999 |
snap_disks = self._CreateSnapshots(feedback_fn) |
|
9000 |
finally: |
|
9001 |
if self.op.shutdown and instance.admin_up: |
|
9002 |
feedback_fn("Starting instance %s" % instance.name) |
|
9003 |
result = self.rpc.call_instance_start(src_node, instance, |
|
9004 |
None, None) |
|
9005 |
msg = result.fail_msg |
|
9006 |
if msg: |
|
9007 |
_ShutdownInstanceDisks(self, instance) |
|
9008 |
raise errors.OpExecError("Could not start instance: %s" % msg) |
|
8928 | 9009 |
|
8929 |
finally:
|
|
8930 |
if self.op.shutdown and instance.admin_up:
|
|
8931 |
feedback_fn("Starting instance %s" % instance.name) |
|
8932 |
result = self.rpc.call_instance_start(src_node, instance, None, None)
|
|
8933 |
msg = result.fail_msg |
|
8934 |
if msg:
|
|
8935 |
_ShutdownInstanceDisks(self, instance)
|
|
8936 |
raise errors.OpExecError("Could not start instance: %s" % msg)
|
|
8937 |
|
|
8938 |
# TODO: check for size
|
|
8939 |
|
|
8940 |
cluster_name = self.cfg.GetClusterName()
|
|
8941 |
for idx, dev in enumerate(snap_disks):
|
|
8942 |
feedback_fn("Exporting snapshot %s from %s to %s" %
|
|
8943 |
(idx, src_node, dst_node.name))
|
|
8944 |
if dev:
|
|
8945 |
# FIXME: pass debug from opcode to backend
|
|
8946 |
result = self.rpc.call_snapshot_export(src_node, dev, dst_node.name,
|
|
8947 |
instance, cluster_name,
|
|
8948 |
idx, self.op.debug_level)
|
|
8949 |
msg = result.fail_msg
|
|
8950 |
if msg: |
|
8951 |
self.LogWarning("Could not export disk/%s from node %s to"
|
|
8952 |
" node %s: %s", idx, src_node, dst_node.name, msg)
|
|
8953 |
dresults.append(False)
|
|
9010 |
assert len(snap_disks) == len(instance.disks)
|
|
9011 |
assert len(removed_snaps) == len(instance.disks)
|
|
9012 |
|
|
9013 |
# TODO: check for size
|
|
9014 |
|
|
9015 |
cluster_name = self.cfg.GetClusterName()
|
|
9016 |
for idx, dev in enumerate(snap_disks):
|
|
9017 |
feedback_fn("Exporting snapshot %s from %s to %s" %
|
|
9018 |
(idx, src_node, dst_node.name)) |
|
9019 |
if dev:
|
|
9020 |
# FIXME: pass debug from opcode to backend |
|
9021 |
result = self.rpc.call_snapshot_export(src_node, dev, dst_node.name,
|
|
9022 |
instance, cluster_name,
|
|
9023 |
idx, self.op.debug_level)
|
|
9024 |
msg = result.fail_msg
|
|
9025 |
if msg:
|
|
9026 |
self.LogWarning("Could not export disk/%s from node %s to"
|
|
9027 |
" node %s: %s", idx, src_node, dst_node.name, msg)
|
|
9028 |
dresults.append(False)
|
|
9029 |
else:
|
|
9030 |
dresults.append(True)
|
|
9031 |
|
|
9032 |
# Remove snapshot
|
|
9033 |
if self._RemoveSnapshot(feedback_fn, snap_disks, idx):
|
|
9034 |
removed_snaps[idx] = True
|
|
8954 | 9035 |
else: |
8955 |
dresults.append(True) |
|
8956 |
msg = self.rpc.call_blockdev_remove(src_node, dev).fail_msg |
|
8957 |
if msg: |
|
8958 |
self.LogWarning("Could not remove snapshot for disk/%d from node" |
|
8959 |
" %s: %s", idx, src_node, msg) |
|
8960 |
else: |
|
8961 |
dresults.append(False) |
|
9036 |
dresults.append(False) |
|
8962 | 9037 |
|
8963 |
feedback_fn("Finalizing export on %s" % dst_node.name) |
|
8964 |
result = self.rpc.call_finalize_export(dst_node.name, instance, |
|
8965 |
snap_disks) |
|
8966 |
fin_resu = True |
|
8967 |
msg = result.fail_msg |
|
8968 |
if msg: |
|
8969 |
self.LogWarning("Could not finalize export for instance %s" |
|
8970 |
" on node %s: %s", instance.name, dst_node.name, msg) |
|
8971 |
fin_resu = False |
|
9038 |
assert len(dresults) == len(instance.disks) |
|
9039 |
|
|
9040 |
# Check for backwards compatibility |
|
9041 |
assert compat.all(isinstance(i, bool) for i in dresults), \ |
|
9042 |
"Not all results are boolean: %r" % dresults |
|
9043 |
|
|
9044 |
feedback_fn("Finalizing export on %s" % dst_node.name) |
|
9045 |
result = self.rpc.call_finalize_export(dst_node.name, instance, |
|
9046 |
snap_disks) |
|
9047 |
msg = result.fail_msg |
|
9048 |
fin_resu = not msg |
|
9049 |
if msg: |
|
9050 |
self.LogWarning("Could not finalize export for instance %s" |
|
9051 |
" on node %s: %s", instance.name, dst_node.name, msg) |
|
9052 |
|
|
9053 |
finally: |
|
9054 |
# Remove all snapshots |
|
9055 |
assert len(removed_snaps) == len(instance.disks) |
|
9056 |
for idx, removed in enumerate(removed_snaps): |
|
9057 |
if not removed: |
|
9058 |
self._RemoveSnapshot(feedback_fn, snap_disks, idx) |
|
8972 | 9059 |
|
8973 | 9060 |
finally: |
8974 | 9061 |
if activate_disks: |
8975 | 9062 |
feedback_fn("Deactivating disks for %s" % instance.name) |
8976 | 9063 |
_ShutdownInstanceDisks(self, instance) |
8977 | 9064 |
|
8978 |
nodelist = self.cfg.GetNodeList() |
|
8979 |
nodelist.remove(dst_node.name) |
|
9065 |
self._CleanupExports(feedback_fn) |
|
8980 | 9066 |
|
8981 |
# on one-node clusters nodelist will be empty after the removal |
|
8982 |
# if we proceed the backup would be removed because OpQueryExports |
|
8983 |
# substitutes an empty list with the full cluster node list. |
|
8984 |
iname = instance.name |
|
8985 |
if nodelist: |
|
8986 |
feedback_fn("Removing old exports for instance %s" % iname) |
|
8987 |
exportlist = self.rpc.call_export_list(nodelist) |
|
8988 |
for node in exportlist: |
|
8989 |
if exportlist[node].fail_msg: |
|
8990 |
continue |
|
8991 |
if iname in exportlist[node].payload: |
|
8992 |
msg = self.rpc.call_export_remove(node, iname).fail_msg |
|
8993 |
if msg: |
|
8994 |
self.LogWarning("Could not remove older export for instance %s" |
|
8995 |
" on node %s: %s", iname, node, msg) |
|
8996 | 9067 |
return fin_resu, dresults |
8997 | 9068 |
|
8998 | 9069 |
|
Also available in: Unified diff