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

Reply via email to