p2v_transfer.py now shows the user a summary of what it is doing. Additionally, there is now a check to see if a remote command is taking longer than a minute. All it does is ask the user to try pinging the machine and if it works to be patient, but it's better than no message at all.
Signed-off-by: Ben Lipton <[email protected]> --- p2v-transfer/p2v_transfer.py | 71 +++++++++++++++++++++++++++++-- p2v-transfer/test/p2v_transfer_test.py | 4 ++ 2 files changed, 70 insertions(+), 5 deletions(-) diff --git a/p2v-transfer/p2v_transfer.py b/p2v-transfer/p2v_transfer.py index 54ff12e..135d71f 100755 --- a/p2v-transfer/p2v_transfer.py +++ b/p2v-transfer/p2v_transfer.py @@ -71,14 +71,18 @@ def LoadSSHKey(keyfile): @raise P2VError: Keyfile is missing, invalid, or encrypted. """ + DisplayCommandStart("Loading SSH keys...") + try: key = paramiko.DSSKey.from_private_key_file(keyfile) - return key except paramiko.PasswordRequiredException: raise P2VError("Why is the private key file encrypted?") except (IOError, paramiko.SSHException): raise P2VError("Key file is missing or invalid") + DisplayCommandEnd("done") + return key + def EstablishConnection(user, host, key): """Creates a connection to the specified host. @@ -95,13 +99,17 @@ def EstablishConnection(user, host, key): @rtype: paramiko.SSHClient @returns: SSHClient object connected to a root shell on the target instance. + """ + DisplayCommandStart("Connecting to instance...") client = paramiko.SSHClient() client.set_missing_host_key_policy(paramiko.WarningPolicy()) client.load_system_host_keys() client.connect(host, username=user, pkey=key, allow_agent=False, look_for_keys=False) + + DisplayCommandEnd("done") return client @@ -121,9 +129,17 @@ def VerifyKernelMatches(client): @returns: True if the proper kernel is installed, else False. """ + DisplayCommandStart("Checking kernel compatibility...") + stdin, stdout, stderr = client.exec_command("uname -r") kernel = stdout.read().strip() - return os.path.exists(os.path.join(SOURCE_MOUNT, "lib", "modules", kernel)) + + if os.path.exists(os.path.join(SOURCE_MOUNT, "lib", "modules", kernel)): + DisplayCommandEnd("Kernel matches") + return True + else: + DisplayCommandEnd("Kernel does not match") + return False def MountSourceFilesystems(root_dev): @@ -140,6 +156,8 @@ def MountSourceFilesystems(root_dev): source OS """ + DisplayCommandStart("Mounting filesystems to copy...") + if not os.path.isdir(SOURCE_MOUNT): os.mkdir(SOURCE_MOUNT) errcode = subprocess.call(["mount", root_dev, SOURCE_MOUNT]) @@ -148,6 +166,8 @@ def MountSourceFilesystems(root_dev): print "Error mounting %s" % root_dev sys.exit(1) + DisplayCommandEnd("done") + def ShutDownTarget(client): """Shut down the target instance. @@ -158,7 +178,9 @@ def ShutDownTarget(client): @param client: SSH client object used to connect to the instance. """ + DisplayCommandStart("Transfer complete! Shutting down the instance...") _RunCommandAndWait(client, "poweroff") + DisplayCommandEnd("done") def _RunCommandAndWait(client, command): @@ -172,6 +194,9 @@ def _RunCommandAndWait(client, command): """ stdin, stdout, stderr = client.exec_command(command) + + _WaitForCompletion(stdout.channel) + if stdout.channel.recv_exit_status() != 0: raise P2VError("Remote command returned nonzero exit status: %s" % command) @@ -187,8 +212,19 @@ def _WaitForCompletion(channel): stdin, stdout, stderr = exec_command(), use stdout.channel. """ + start = time.time() + gave_warning = False + while not channel.exit_status_ready(): time.sleep(.01) + if time.time() - start > 60 and not gave_warning: + gave_warning = True + print ("\nThe current command is taking a while to complete. Please make" + " sure the instance is still pingable. If so, try waiting another" + " few minutes.") + + if gave_warning: + print "The command has completed." def GetDiskSize(client): @@ -204,6 +240,8 @@ def GetDiskSize(client): @return: Total size in megabytes, swap size in megabytes """ + DisplayCommandStart("Determining partition sizes...") + # Find out how many MB are available on target stdin, stdout, stderr = client.exec_command("blockdev --getsize64 /dev/xvda") for line in stdout: @@ -224,7 +262,11 @@ def GetDiskSize(client): except ValueError: pass # This line doesn't have data on it - return total_megs, min(swap_megs, int(total_megs * 0.1)) + swap_size = min(swap_megs, int(total_megs * 0.1)) + + DisplayCommandEnd("%d MB disk, %d MB reserved for swap" % (total_megs, + swap_size)) + return total_megs, swap_size def PartitionTargetDisks(client, total_megs, swap_megs): @@ -241,6 +283,8 @@ def PartitionTargetDisks(client, total_megs, swap_megs): @param swap_megs: Desired size of swap space, in megabytes """ + DisplayCommandStart("Partitioning disks...") + nonswap_megs = total_megs - swap_megs sfdisk_command = """sfdisk -uM /dev/xvda <<EOF 0,%d,83 @@ -248,8 +292,6 @@ def PartitionTargetDisks(client, total_megs, swap_megs): EOF """ % nonswap_megs - _RunCommandAndWait(client, sfdisk_command) - other_commands = [ "mkfs.ext3 /dev/xvda1", "mkswap /dev/xvda2", @@ -258,12 +300,15 @@ EOF ] try: + _RunCommandAndWait(client, sfdisk_command) _RunCommandAndWait(client, " && ".join(other_commands)) except P2VError: # Make sure target is unmounted, then try again CleanUpTarget(client) _RunCommandAndWait(client, " && ".join(other_commands)) + DisplayCommandEnd("done") + def TransferFiles(user, host, keyfile): """Transfer files to the bootstrap OS. @@ -277,6 +322,8 @@ def TransferFiles(user, host, keyfile): @param host: Hostname of instance to connect to. """ + DisplayCommandStart("Transferring files. This will take a while...") + errcode = subprocess.call(["rsync", "-aHAXz", "-e", "ssh -i %s" % keyfile, "%s/" % SOURCE_MOUNT, "%s@%s:%s" % (user, host, TARGET_MOUNT)]) @@ -284,6 +331,8 @@ def TransferFiles(user, host, keyfile): print "Error using rsync to transfer files" sys.exit(1) + DisplayCommandEnd("done") + def RunFixScripts(client): """Runs the post-transfer scripts on the bootstrap OS. @@ -295,7 +344,9 @@ def RunFixScripts(client): @param client: SSH client object used to connect to the instance. """ + DisplayCommandStart("Running fix scripts...") _RunCommandAndWait(client, "run-parts /usr/lib/ganeti/fixes") + DisplayCommandEnd("done") def UnmountSourceFilesystems(): @@ -321,11 +372,21 @@ def CleanUpTarget(client): @type client: paramiko.SSHClient @param client: SSH client object used to connect to the instance. + """ _RunCommandAndWait(client, "umount %s ; rmdir %s" % (TARGET_MOUNT, TARGET_MOUNT)) +def DisplayCommandStart(message): + """Display a message that an action is beginning.""" + print message, + sys.stdout.flush() + +def DisplayCommandEnd(message): + """Display a message that an action has completed.""" + print message + def main(argv): options, args = ParseOptions(argv) diff --git a/p2v-transfer/test/p2v_transfer_test.py b/p2v-transfer/test/p2v_transfer_test.py index bd1fdb5..7ba73cc 100755 --- a/p2v-transfer/test/p2v_transfer_test.py +++ b/p2v-transfer/test/p2v_transfer_test.py @@ -70,6 +70,10 @@ class P2vtransferTest(unittest.TestCase): stderr = _MockChannelFile(self.mox) self.client.exec_command(command).AndReturn((stdin, stdout, stderr)) + # pretend the command is taking a few cycles + stdout.channel.exit_status_ready().AndReturn(False) + stdout.channel.exit_status_ready().AndReturn(False) + stdout.channel.exit_status_ready().AndReturn(True) stdout.channel.recv_exit_status().AndReturn(exit_status) # return stdout in case we want to do something else with it -- 1.7.3.1
