import socket
import tempfile
import shutil
+import itertools
from ganeti import ssh
from ganeti import utils
raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
-def _CheckNodeOnline(lu, node):
+def _CheckNodeOnline(lu, node, msg=None):
"""Ensure that a given node is online.
@param lu: the LU on behalf of which we make the check
@param node: the node to check
+ @param msg: if passed, should be a message to replace the default one
@raise errors.OpPrereqError: if the node is offline
"""
+ if msg is None:
+ msg = "Can't use offline node"
if lu.cfg.GetNodeInfo(node).offline:
- raise errors.OpPrereqError("Can't use offline node %s" % node,
- errors.ECODE_STATE)
+ raise errors.OpPrereqError("%s: %s" % (msg, node), errors.ECODE_STATE)
def _CheckNodeNotDrained(lu, node):
@param node_image: Node objects
@type instanceinfo: dict of (name, L{objects.Instance})
@param instanceinfo: Instance objects
+ @rtype: {instance: {node: [(succes, payload)]}}
+ @return: a dictionary of per-instance dictionaries with nodes as
+ keys and disk information as values; the disk information is a
+ list of tuples (success, payload)
"""
_ErrorIf = self._ErrorIf # pylint: disable-msg=C0103
node_disks = {}
node_disks_devonly = {}
+ diskless_instances = set()
+ diskless = constants.DT_DISKLESS
for nname in nodelist:
+ node_instances = list(itertools.chain(node_image[nname].pinst,
+ node_image[nname].sinst))
+ diskless_instances.update(inst for inst in node_instances
+ if instanceinfo[inst].disk_template == diskless)
disks = [(inst, disk)
- for instlist in [node_image[nname].pinst,
- node_image[nname].sinst]
- for inst in instlist
+ for inst in node_instances
for disk in instanceinfo[inst].disks]
if not disks:
instdisk = {}
for (nname, nres) in result.items():
- if nres.offline:
- # Ignore offline node
- continue
-
disks = node_disks[nname]
- msg = nres.fail_msg
- _ErrorIf(msg, self.ENODERPC, nname,
- "while getting disk information: %s", nres.fail_msg)
- if msg:
+ if nres.offline:
# No data from this node
- data = len(disks) * [None]
+ data = len(disks) * [(False, "node offline")]
else:
- data = nres.payload
+ msg = nres.fail_msg
+ _ErrorIf(msg, self.ENODERPC, nname,
+ "while getting disk information: %s", msg)
+ if msg:
+ # No data from this node
+ data = len(disks) * [(False, msg)]
+ else:
+ data = []
+ for idx, i in enumerate(nres.payload):
+ if isinstance(i, (tuple, list)) and len(i) == 2:
+ data.append(i)
+ else:
+ logging.warning("Invalid result from node %s, entry %d: %s",
+ nname, idx, i)
+ data.append((False, "Invalid result from the remote node"))
for ((inst, _), status) in zip(disks, data):
instdisk.setdefault(inst, {}).setdefault(nname, []).append(status)
+ # Add empty entries for diskless instances.
+ for inst in diskless_instances:
+ assert inst not in instdisk
+ instdisk[inst] = {}
+
assert compat.all(len(statuses) == len(instanceinfo[inst].disks) and
- len(nnames) <= len(instanceinfo[inst].all_nodes)
+ len(nnames) <= len(instanceinfo[inst].all_nodes) and
+ compat.all(isinstance(s, (tuple, list)) and
+ len(s) == 2 for s in statuses)
for inst, nnames in instdisk.items()
for nname, statuses in nnames.items())
+ assert set(instdisk) == set(instanceinfo), "instdisk consistency failure"
return instdisk
instance = self.cfg.GetInstanceInfo(self.op.instance_name)
assert instance is not None, \
"Cannot retrieve locked instance %s" % self.op.instance_name
- _CheckNodeOnline(self, instance.primary_node)
+ _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
+ " offline, cannot reinstall")
+ for node in instance.secondary_nodes:
+ _CheckNodeOnline(self, node, "Instance secondary node offline,"
+ " cannot reinstall")
if instance.disk_template == constants.DT_DISKLESS:
raise errors.OpPrereqError("Instance '%s' has no disks" %
("source_handshake", None, ht.TOr(ht.TList, ht.TNone)),
("source_x509_ca", None, ht.TMaybeString),
("source_instance_name", None, ht.TMaybeString),
+ ("source_shutdown_timeout", constants.DEFAULT_SHUTDOWN_TIMEOUT,
+ ht.TPositiveInt),
("src_node", None, ht.TMaybeString),
("src_path", None, ht.TMaybeString),
("pnode", None, ht.TMaybeString),
elif self.op.mode == constants.INSTANCE_REMOTE_IMPORT:
feedback_fn("* preparing remote import...")
- connect_timeout = constants.RIE_CONNECT_TIMEOUT
+ # The source cluster will stop the instance before attempting to make a
+ # connection. In some cases stopping an instance can take a long time,
+ # hence the shutdown timeout is added to the connection timeout.
+ connect_timeout = (constants.RIE_CONNECT_TIMEOUT +
+ self.op.source_shutdown_timeout)
timeouts = masterd.instance.ImportExportTimeouts(connect_timeout)
disk_results = masterd.instance.RemoteImport(self, feedback_fn, iobj,