From c92b310aa3ec7a63b3acb0410f5b6a4fca7ec2de Mon Sep 17 00:00:00 2001
From: Michael Hanselmann <hansmi@google.com>
Date: Tue, 18 Mar 2008 13:02:53 +0000
Subject: [PATCH] Move SSH functions into a class

This renames some functions and does some minor codestyle cleanup.

Reviewed-by: ultrotter
---
 lib/backend.py |  14 ++--
 lib/cmdlib.py  |  24 ++++--
 lib/ssh.py     | 212 ++++++++++++++++++++++++-------------------------
 3 files changed, 131 insertions(+), 119 deletions(-)

diff --git a/lib/backend.py b/lib/backend.py
index f82674ed8..9df5ddac9 100644
--- a/lib/backend.py
+++ b/lib/backend.py
@@ -42,6 +42,10 @@ from ganeti import objects
 from ganeti import ssconf
 
 
+def _GetSshRunner():
+  return ssh.SshRunner()
+
+
 def StartMaster():
   """Activate local node as master node.
 
@@ -197,7 +201,7 @@ def VerifyNode(what):
   if 'nodelist' in what:
     result['nodelist'] = {}
     for node in what['nodelist']:
-      success, message = ssh.VerifyNodeHostname(node)
+      success, message = _GetSshRunner().VerifyNodeHostname(node)
       if not success:
         result['nodelist'][node] = message
   return result
@@ -1188,9 +1192,8 @@ def ExportSnapshot(disk, dest_node, instance):
 
   destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s/%s",
                                 destdir, destdir, destfile)
-  remotecmd = ssh.BuildSSHCmd(dest_node, constants.GANETI_RUNAS, destcmd)
-
-
+  remotecmd = _GetSshRunner().BuildCmd(dest_node, constants.GANETI_RUNAS,
+                                       destcmd)
 
   # all commands have been checked, so we're safe to combine them
   command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)])
@@ -1331,7 +1334,8 @@ def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
     os.mkdir(constants.LOG_OS_DIR, 0750)
 
   destcmd = utils.BuildShellCmd('cat %s', src_image)
-  remotecmd = ssh.BuildSSHCmd(src_node, constants.GANETI_RUNAS, destcmd)
+  remotecmd = _GetSshRunner().BuildCmd(src_node, constants.GANETI_RUNAS,
+                                       destcmd)
 
   comprcmd = "gunzip"
   impcmd = utils.BuildShellCmd("(cd %s; %s -i %s -b %s -s %s &>%s)",
diff --git a/lib/cmdlib.py b/lib/cmdlib.py
index 15c1e1478..62710f1bd 100644
--- a/lib/cmdlib.py
+++ b/lib/cmdlib.py
@@ -74,6 +74,8 @@ class LogicalUnit(object):
     self.op = op
     self.cfg = cfg
     self.sstore = sstore
+    self.__ssh = None
+
     for attr_name in self._OP_REQP:
       attr_val = getattr(op, attr_name, None)
       if attr_val is None:
@@ -89,6 +91,16 @@ class LogicalUnit(object):
           raise errors.OpPrereqError("Commands must be run on the master"
                                      " node %s" % master)
 
+  def __GetSSH(self):
+    """Returns the SshRunner object
+
+    """
+    if not self.__ssh:
+      self.__ssh = ssh.SshRunner()
+    return self.__ssh
+
+  ssh = property(fget=__GetSSH)
+
   def CheckPrereq(self):
     """Check prerequisites for this LU.
 
@@ -1229,7 +1241,7 @@ class LURemoveNode(LogicalUnit):
 
     rpc.call_node_leave_cluster(node.name)
 
-    ssh.SSHCall(node.name, 'root', "%s stop" % constants.NODE_INITD_SCRIPT)
+    self.ssh.Run(node.name, 'root', "%s stop" % constants.NODE_INITD_SCRIPT)
 
     logger.Info("Removing node %s from config" % node.name)
 
@@ -1539,7 +1551,7 @@ class LUAddNode(LogicalUnit):
                   constants.SSL_CERT_FILE, gntpem,
                   constants.NODE_INITD_SCRIPT))
 
-    result = ssh.SSHCall(node, 'root', mycommand, batch=False, ask_key=True)
+    result = self.ssh.Run(node, 'root', mycommand, batch=False, ask_key=True)
     if result.failed:
       raise errors.OpExecError("Remote command on node %s, error: %s,"
                                " output: %s" %
@@ -1597,7 +1609,7 @@ class LUAddNode(LogicalUnit):
                                  " you gave (%s). Please fix and re-run this"
                                  " command." % new_node.secondary_ip)
 
-    success, msg = ssh.VerifyNodeHostname(node)
+    success, msg = self.ssh.VerifyNodeHostname(node)
     if not success:
       raise errors.OpExecError("Node '%s' claims it has a different hostname"
                                " than the one the resolver gives: %s."
@@ -1623,7 +1635,7 @@ class LUAddNode(LogicalUnit):
     if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31:
       to_copy.append(constants.VNC_PASSWORD_FILE)
     for fname in to_copy:
-      if not ssh.CopyFileToNode(node, fname):
+      if not self.ssh.CopyFileToNode(node, fname):
         logger.Error("could not copy file %s to node %s" % (fname, node))
 
     logger.Info("adding node %s to cluster.conf" % node)
@@ -1767,7 +1779,7 @@ class LUClusterCopyFile(NoHooksLU):
     for node in self.nodes:
       if node == myname:
         continue
-      if not ssh.CopyFileToNode(node, filename):
+      if not self.ssh.CopyFileToNode(node, filename):
         logger.Error("Copy of file %s to node %s failed" % (filename, node))
 
 
@@ -1810,7 +1822,7 @@ class LURunClusterCommand(NoHooksLU):
     """
     data = []
     for node in self.nodes:
-      result = ssh.SSHCall(node, "root", self.op.command)
+      result = self.ssh.Run(node, "root", self.op.command)
       data.append((node, result.output, result.exit_code))
 
     return data
diff --git a/lib/ssh.py b/lib/ssh.py
index 7cc055b75..1288c7ff2 100644
--- a/lib/ssh.py
+++ b/lib/ssh.py
@@ -32,10 +32,6 @@ from ganeti import errors
 from ganeti import constants
 
 
-__all__ = ["SSHCall", "CopyFileToNode", "VerifyNodeHostname",
-           "KNOWN_HOSTS_OPTS", "BATCH_MODE_OPTS", "ASK_KEY_OPTS"]
-
-
 KNOWN_HOSTS_OPTS = [
   "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
   "-oUserKnownHostsFile=/dev/null",
@@ -90,128 +86,128 @@ def GetUserFiles(user, mkdir=False):
           for base in ["id_dsa", "id_dsa.pub", "authorized_keys"]]
 
 
-def BuildSSHCmd(hostname, user, command, batch=True, ask_key=False):
-  """Build an ssh string to execute a command on a remote node.
-
-  Args:
-    hostname: the target host, string
-    user: user to auth as
-    command: the command
-    batch: if true, ssh will run in batch mode with no prompting
-    ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
-             we can connect to an unknown host (not valid in batch mode)
-
-  Returns:
-    The ssh call to run 'command' on the remote host.
-
-  """
-  argv = ["ssh", "-q"]
-  argv.extend(KNOWN_HOSTS_OPTS)
-  if batch:
-    # if we are in batch mode, we can't ask the key
-    if ask_key:
-      raise errors.ProgrammerError("SSH call requested conflicting options")
-    argv.extend(BATCH_MODE_OPTS)
-  elif ask_key:
-    argv.extend(ASK_KEY_OPTS)
-  argv.extend(["%s@%s" % (user, hostname), command])
-  return argv
-
-
-def SSHCall(hostname, user, command, batch=True, ask_key=False):
-  """Execute a command on a remote node.
-
-  This method has the same return value as `utils.RunCmd()`, which it
-  uses to launch ssh.
-
-  Args:
-    hostname: the target host, string
-    user: user to auth as
-    command: the command
-    batch: if true, ssh will run in batch mode with no prompting
-    ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
-             we can connect to an unknown host (not valid in batch mode)
-
-  Returns:
-    `utils.RunResult` as for `utils.RunCmd()`
-
-  """
-  return utils.RunCmd(BuildSSHCmd(hostname, user, command,
-                                  batch=batch, ask_key=ask_key))
-
-
-def CopyFileToNode(node, filename):
-  """Copy a file to another node with scp.
-
-  Args:
-    node: node in the cluster
-    filename: absolute pathname of a local file
-
-  Returns:
-    success: True/False
+class SshRunner:
+  """Wrapper for SSH commands.
 
   """
-  if not os.path.isfile(filename):
-    logger.Error("file %s does not exist" % (filename))
-    return False
-
-  if not os.path.isabs(filename):
-    logger.Error("file %s must be an absolute path" % (filename))
-    return False
+  def BuildCmd(self, hostname, user, command, batch=True, ask_key=False):
+    """Build an ssh command to execute a command on a remote node.
+
+    Args:
+      hostname: the target host, string
+      user: user to auth as
+      command: the command
+      batch: if true, ssh will run in batch mode with no prompting
+      ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
+               we can connect to an unknown host (not valid in batch mode)
+
+    Returns:
+      The ssh call to run 'command' on the remote host.
+
+    """
+    argv = ["ssh", "-q"]
+    argv.extend(KNOWN_HOSTS_OPTS)
+    if batch:
+      # if we are in batch mode, we can't ask the key
+      if ask_key:
+        raise errors.ProgrammerError("SSH call requested conflicting options")
+      argv.extend(BATCH_MODE_OPTS)
+    elif ask_key:
+      argv.extend(ASK_KEY_OPTS)
+    argv.extend(["%s@%s" % (user, hostname), command])
+    return argv
+
+  def Run(self, hostname, user, command, batch=True, ask_key=False):
+    """Runs a command on a remote node.
+
+    This method has the same return value as `utils.RunCmd()`, which it
+    uses to launch ssh.
+
+    Args:
+      hostname: the target host, string
+      user: user to auth as
+      command: the command
+      batch: if true, ssh will run in batch mode with no prompting
+      ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
+               we can connect to an unknown host (not valid in batch mode)
+
+    Returns:
+      `utils.RunResult` like `utils.RunCmd()`
+
+    """
+    return utils.RunCmd(self.BuildCmd(hostname, user, command, batch=batch,
+                                      ask_key=ask_key))
+
+  def CopyFileToNode(self, node, filename):
+    """Copy a file to another node with scp.
+
+    Args:
+      node: node in the cluster
+      filename: absolute pathname of a local file
+
+    Returns:
+      success: True/False
 
-  command = ["scp", "-q", "-p"]
-  command.extend(KNOWN_HOSTS_OPTS)
-  command.extend(BATCH_MODE_OPTS)
-  command.append(filename)
-  command.append("%s:%s" % (node, filename))
+    """
+    if not os.path.isfile(filename):
+      logger.Error("file %s does not exist" % (filename))
+      return False
 
-  result = utils.RunCmd(command)
+    if not os.path.isabs(filename):
+      logger.Error("file %s must be an absolute path" % (filename))
+      return False
 
-  if result.failed:
-    logger.Error("copy to node %s failed (%s) error %s,"
-                 " command was %s" %
-                 (node, result.fail_reason, result.output, result.cmd))
+    command = ["scp", "-q", "-p"]
+    command.extend(KNOWN_HOSTS_OPTS)
+    command.extend(BATCH_MODE_OPTS)
+    command.append(filename)
+    command.append("%s:%s" % (node, filename))
 
-  return not result.failed
+    result = utils.RunCmd(command)
 
+    if result.failed:
+      logger.Error("copy to node %s failed (%s) error %s,"
+                   " command was %s" %
+                   (node, result.fail_reason, result.output, result.cmd))
 
-def VerifyNodeHostname(node):
-  """Verify hostname consistency via SSH.
+    return not result.failed
 
+  def VerifyNodeHostname(self, node):
+    """Verify hostname consistency via SSH.
 
-  This functions connects via ssh to a node and compares the hostname
-  reported by the node to the name with have (the one that we
-  connected to).
+    This functions connects via ssh to a node and compares the hostname
+    reported by the node to the name with have (the one that we
+    connected to).
 
-  This is used to detect problems in ssh known_hosts files
-  (conflicting known hosts) and incosistencies between dns/hosts
-  entries and local machine names
+    This is used to detect problems in ssh known_hosts files
+    (conflicting known hosts) and incosistencies between dns/hosts
+    entries and local machine names
 
-  Args:
-    node: nodename of a host to check. can be short or full qualified hostname
+    Args:
+      node: nodename of a host to check. can be short or full qualified hostname
 
-  Returns:
-    (success, detail)
-    where
-      success: True/False
-      detail: String with details
+    Returns:
+      (success, detail)
+      where
+        success: True/False
+        detail: String with details
 
-  """
-  retval = SSHCall(node, 'root', 'hostname')
+    """
+    retval = self.Run(node, 'root', 'hostname')
 
-  if retval.failed:
-    msg = "ssh problem"
-    output = retval.output
-    if output:
-      msg += ": %s" % output
-    return False, msg
+    if retval.failed:
+      msg = "ssh problem"
+      output = retval.output
+      if output:
+        msg += ": %s" % output
+      return False, msg
 
-  remotehostname = retval.stdout.strip()
+    remotehostname = retval.stdout.strip()
 
-  if not remotehostname or remotehostname != node:
-    return False, "hostname mismatch, got %s" % remotehostname
+    if not remotehostname or remotehostname != node:
+      return False, "hostname mismatch, got %s" % remotehostname
 
-  return True, "host matches"
+    return True, "host matches"
 
 
 def WriteKnownHostsFile(cfg, sstore, file_name):
-- 
GitLab