Remove the shebang from modules
[ganeti-local] / lib / ssh.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007 Google Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful, but
12 # WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 # General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 # 02110-1301, USA.
20
21
22 """Module encapsulating ssh functionality.
23
24 """
25
26
27 import os
28
29 from ganeti import logger
30 from ganeti import utils
31 from ganeti import errors
32 from ganeti import constants
33
34
35 __all__ = ["SSHCall", "CopyFileToNode", "VerifyNodeHostname",
36            "KNOWN_HOSTS_OPTS", "BATCH_MODE_OPTS", "ASK_KEY_OPTS"]
37
38
39 KNOWN_HOSTS_OPTS = [
40   "-oGlobalKnownHostsFile=%s" % constants.SSH_KNOWN_HOSTS_FILE,
41   "-oUserKnownHostsFile=/dev/null",
42   ]
43
44 # Note: BATCH_MODE conflicts with ASK_KEY
45 BATCH_MODE_OPTS = [
46   "-oEscapeChar=none",
47   "-oBatchMode=yes",
48   "-oStrictHostKeyChecking=yes",
49   ]
50
51 ASK_KEY_OPTS = [
52   "-oStrictHostKeyChecking=ask",
53   "-oEscapeChar=none",
54   "-oHashKnownHosts=no",
55   ]
56
57
58 def BuildSSHCmd(hostname, user, command, batch=True, ask_key=False):
59   """Build an ssh string to execute a command on a remote node.
60
61   Args:
62     hostname: the target host, string
63     user: user to auth as
64     command: the command
65     batch: if true, ssh will run in batch mode with no prompting
66     ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
67              we can connect to an unknown host (not valid in batch mode)
68
69   Returns:
70     The ssh call to run 'command' on the remote host.
71
72   """
73   argv = ["ssh", "-q"]
74   argv.extend(KNOWN_HOSTS_OPTS)
75   if batch:
76     # if we are in batch mode, we can't ask the key
77     if ask_key:
78       raise errors.ProgrammerError("SSH call requested conflicting options")
79     argv.extend(BATCH_MODE_OPTS)
80   elif ask_key:
81     argv.extend(ASK_KEY_OPTS)
82   argv.extend(["%s@%s" % (user, hostname), command])
83   return argv
84
85
86 def SSHCall(hostname, user, command, batch=True, ask_key=False):
87   """Execute a command on a remote node.
88
89   This method has the same return value as `utils.RunCmd()`, which it
90   uses to launch ssh.
91
92   Args:
93     hostname: the target host, string
94     user: user to auth as
95     command: the command
96     batch: if true, ssh will run in batch mode with no prompting
97     ask_key: if true, ssh will run with StrictHostKeyChecking=ask, so that
98              we can connect to an unknown host (not valid in batch mode)
99
100   Returns:
101     `utils.RunResult` as for `utils.RunCmd()`
102
103   """
104   return utils.RunCmd(BuildSSHCmd(hostname, user, command, batch=batch, ask_key=ask_key))
105
106
107 def CopyFileToNode(node, filename):
108   """Copy a file to another node with scp.
109
110   Args:
111     node: node in the cluster
112     filename: absolute pathname of a local file
113
114   Returns:
115     success: True/False
116
117   """
118   if not os.path.isfile(filename):
119     logger.Error("file %s does not exist" % (filename))
120     return False
121
122   if not os.path.isabs(filename):
123     logger.Error("file %s must be an absolute path" % (filename))
124     return False
125
126   command = ["scp", "-q", "-p"]
127   command.extend(KNOWN_HOSTS_OPTS)
128   command.extend(BATCH_MODE_OPTS)
129   command.append(filename)
130   command.append("%s:%s" % (node, filename))
131
132   result = utils.RunCmd(command)
133
134   if result.failed:
135     logger.Error("copy to node %s failed (%s) error %s,"
136                  " command was %s" %
137                  (node, result.fail_reason, result.output, result.cmd))
138
139   return not result.failed
140
141
142 def VerifyNodeHostname(node):
143   """Verify hostname consistency via SSH.
144
145
146   This functions connects via ssh to a node and compares the hostname
147   reported by the node to the name with have (the one that we
148   connected to).
149
150   This is used to detect problems in ssh known_hosts files
151   (conflicting known hosts) and incosistencies between dns/hosts
152   entries and local machine names
153
154   Args:
155     node: nodename of a host to check. can be short or full qualified hostname
156
157   Returns:
158     (success, detail)
159     where
160       success: True/False
161       detail: String with details
162
163   """
164   retval = SSHCall(node, 'root', 'hostname')
165
166   if retval.failed:
167     msg = "ssh problem"
168     output = retval.output
169     if output:
170       msg += ": %s" % output
171     return False, msg
172
173   remotehostname = retval.stdout.strip()
174
175   if not remotehostname or remotehostname != node:
176     return False, "hostname mismatch, got %s" % remotehostname
177
178   return True, "host matches"