Commit c92b310a authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Move SSH functions into a class

This renames some functions and does some minor codestyle cleanup.

Reviewed-by: ultrotter
parent 75a5f456
...@@ -42,6 +42,10 @@ from ganeti import objects ...@@ -42,6 +42,10 @@ from ganeti import objects
from ganeti import ssconf from ganeti import ssconf
def _GetSshRunner():
return ssh.SshRunner()
def StartMaster(): def StartMaster():
"""Activate local node as master node. """Activate local node as master node.
...@@ -197,7 +201,7 @@ def VerifyNode(what): ...@@ -197,7 +201,7 @@ def VerifyNode(what):
if 'nodelist' in what: if 'nodelist' in what:
result['nodelist'] = {} result['nodelist'] = {}
for node in what['nodelist']: for node in what['nodelist']:
success, message = ssh.VerifyNodeHostname(node) success, message = _GetSshRunner().VerifyNodeHostname(node)
if not success: if not success:
result['nodelist'][node] = message result['nodelist'][node] = message
return result return result
...@@ -1188,9 +1192,8 @@ def ExportSnapshot(disk, dest_node, instance): ...@@ -1188,9 +1192,8 @@ def ExportSnapshot(disk, dest_node, instance):
destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s/%s", destcmd = utils.BuildShellCmd("mkdir -p %s && cat > %s/%s",
destdir, destdir, destfile) 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 # all commands have been checked, so we're safe to combine them
command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)]) command = '|'.join([expcmd, comprcmd, utils.ShellQuoteArgs(remotecmd)])
...@@ -1331,7 +1334,8 @@ def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image): ...@@ -1331,7 +1334,8 @@ def ImportOSIntoInstance(instance, os_disk, swap_disk, src_node, src_image):
os.mkdir(constants.LOG_OS_DIR, 0750) os.mkdir(constants.LOG_OS_DIR, 0750)
destcmd = utils.BuildShellCmd('cat %s', src_image) 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" comprcmd = "gunzip"
impcmd = utils.BuildShellCmd("(cd %s; %s -i %s -b %s -s %s &>%s)", impcmd = utils.BuildShellCmd("(cd %s; %s -i %s -b %s -s %s &>%s)",
......
...@@ -74,6 +74,8 @@ class LogicalUnit(object): ...@@ -74,6 +74,8 @@ class LogicalUnit(object):
self.op = op self.op = op
self.cfg = cfg self.cfg = cfg
self.sstore = sstore self.sstore = sstore
self.__ssh = None
for attr_name in self._OP_REQP: for attr_name in self._OP_REQP:
attr_val = getattr(op, attr_name, None) attr_val = getattr(op, attr_name, None)
if attr_val is None: if attr_val is None:
...@@ -89,6 +91,16 @@ class LogicalUnit(object): ...@@ -89,6 +91,16 @@ class LogicalUnit(object):
raise errors.OpPrereqError("Commands must be run on the master" raise errors.OpPrereqError("Commands must be run on the master"
" node %s" % 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): def CheckPrereq(self):
"""Check prerequisites for this LU. """Check prerequisites for this LU.
...@@ -1229,7 +1241,7 @@ class LURemoveNode(LogicalUnit): ...@@ -1229,7 +1241,7 @@ class LURemoveNode(LogicalUnit):
rpc.call_node_leave_cluster(node.name) 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) logger.Info("Removing node %s from config" % node.name)
...@@ -1539,7 +1551,7 @@ class LUAddNode(LogicalUnit): ...@@ -1539,7 +1551,7 @@ class LUAddNode(LogicalUnit):
constants.SSL_CERT_FILE, gntpem, constants.SSL_CERT_FILE, gntpem,
constants.NODE_INITD_SCRIPT)) 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: if result.failed:
raise errors.OpExecError("Remote command on node %s, error: %s," raise errors.OpExecError("Remote command on node %s, error: %s,"
" output: %s" % " output: %s" %
...@@ -1597,7 +1609,7 @@ class LUAddNode(LogicalUnit): ...@@ -1597,7 +1609,7 @@ class LUAddNode(LogicalUnit):
" you gave (%s). Please fix and re-run this" " you gave (%s). Please fix and re-run this"
" command." % new_node.secondary_ip) " command." % new_node.secondary_ip)
success, msg = ssh.VerifyNodeHostname(node) success, msg = self.ssh.VerifyNodeHostname(node)
if not success: if not success:
raise errors.OpExecError("Node '%s' claims it has a different hostname" raise errors.OpExecError("Node '%s' claims it has a different hostname"
" than the one the resolver gives: %s." " than the one the resolver gives: %s."
...@@ -1623,7 +1635,7 @@ class LUAddNode(LogicalUnit): ...@@ -1623,7 +1635,7 @@ class LUAddNode(LogicalUnit):
if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31: if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31:
to_copy.append(constants.VNC_PASSWORD_FILE) to_copy.append(constants.VNC_PASSWORD_FILE)
for fname in to_copy: 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.Error("could not copy file %s to node %s" % (fname, node))
logger.Info("adding node %s to cluster.conf" % node) logger.Info("adding node %s to cluster.conf" % node)
...@@ -1767,7 +1779,7 @@ class LUClusterCopyFile(NoHooksLU): ...@@ -1767,7 +1779,7 @@ class LUClusterCopyFile(NoHooksLU):
for node in self.nodes: for node in self.nodes:
if node == myname: if node == myname:
continue 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)) logger.Error("Copy of file %s to node %s failed" % (filename, node))
...@@ -1810,7 +1822,7 @@ class LURunClusterCommand(NoHooksLU): ...@@ -1810,7 +1822,7 @@ class LURunClusterCommand(NoHooksLU):
""" """
data = [] data = []
for node in self.nodes: 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)) data.append((node, result.output, result.exit_code))
return data return data
......
...@@ -32,10 +32,6 @@ from ganeti import errors ...@@ -32,10 +32,6 @@ from ganeti import errors
from ganeti import constants from ganeti import constants
__all__ = ["SSHCall", "CopyFileToNode", "VerifyNodeHostname",
"KNOWN_HOSTS_OPTS", "BATCH_MODE_OPTS", "ASK_KEY_OPTS"]
KNOWN_HOSTS_OPTS = [ KNOWN_HOSTS_OPTS = [
"-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE, "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
"-oUserKnownHostsFile=/dev/null", "-oUserKnownHostsFile=/dev/null",
...@@ -90,128 +86,128 @@ def GetUserFiles(user, mkdir=False): ...@@ -90,128 +86,128 @@ def GetUserFiles(user, mkdir=False):
for base in ["id_dsa", "id_dsa.pub", "authorized_keys"]] for base in ["id_dsa", "id_dsa.pub", "authorized_keys"]]
def BuildSSHCmd(hostname, user, command, batch=True, ask_key=False): class SshRunner:
"""Build an ssh string to execute a command on a remote node. """Wrapper for SSH commands.
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
""" """
if not os.path.isfile(filename): def BuildCmd(self, hostname, user, command, batch=True, ask_key=False):
logger.Error("file %s does not exist" % (filename)) """Build an ssh command to execute a command on a remote node.
return False
Args:
if not os.path.isabs(filename): hostname: the target host, string
logger.Error("file %s must be an absolute path" % (filename)) user: user to auth as
return False 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) if not os.path.isfile(filename):
command.extend(BATCH_MODE_OPTS) logger.Error("file %s does not exist" % (filename))
command.append(filename) return False
command.append("%s:%s" % (node, filename))
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: command = ["scp", "-q", "-p"]
logger.Error("copy to node %s failed (%s) error %s," command.extend(KNOWN_HOSTS_OPTS)
" command was %s" % command.extend(BATCH_MODE_OPTS)
(node, result.fail_reason, result.output, result.cmd)) 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): return not result.failed
"""Verify hostname consistency via SSH.
def VerifyNodeHostname(self, node):
"""Verify hostname consistency via SSH.
This functions connects via ssh to a node and compares the hostname 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 reported by the node to the name with have (the one that we
connected to). connected to).
This is used to detect problems in ssh known_hosts files This is used to detect problems in ssh known_hosts files
(conflicting known hosts) and incosistencies between dns/hosts (conflicting known hosts) and incosistencies between dns/hosts
entries and local machine names entries and local machine names
Args: Args:
node: nodename of a host to check. can be short or full qualified hostname node: nodename of a host to check. can be short or full qualified hostname
Returns: Returns:
(success, detail) (success, detail)
where where
success: True/False success: True/False
detail: String with details detail: String with details
""" """
retval = SSHCall(node, 'root', 'hostname') retval = self.Run(node, 'root', 'hostname')
if retval.failed: if retval.failed:
msg = "ssh problem" msg = "ssh problem"
output = retval.output output = retval.output
if output: if output:
msg += ": %s" % output msg += ": %s" % output
return False, msg return False, msg
remotehostname = retval.stdout.strip() remotehostname = retval.stdout.strip()
if not remotehostname or remotehostname != node: if not remotehostname or remotehostname != node:
return False, "hostname mismatch, got %s" % remotehostname return False, "hostname mismatch, got %s" % remotehostname
return True, "host matches" return True, "host matches"
def WriteKnownHostsFile(cfg, sstore, file_name): def WriteKnownHostsFile(cfg, sstore, file_name):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment