Statistics
| Branch: | Tag: | Revision:

root / tools / setup-ssh @ 5c654e95

History | View | Annotate | Download (7 kB)

1 05cd934d René Nussbaumer
#!/usr/bin/python
2 05cd934d René Nussbaumer
#
3 05cd934d René Nussbaumer
4 05cd934d René Nussbaumer
# Copyright (C) 2010 Google Inc.
5 05cd934d René Nussbaumer
#
6 05cd934d René Nussbaumer
# This program is free software; you can redistribute it and/or modify
7 05cd934d René Nussbaumer
# it under the terms of the GNU General Public License as published by
8 05cd934d René Nussbaumer
# the Free Software Foundation; either version 2 of the License, or
9 05cd934d René Nussbaumer
# (at your option) any later version.
10 05cd934d René Nussbaumer
#
11 05cd934d René Nussbaumer
# This program is distributed in the hope that it will be useful, but
12 05cd934d René Nussbaumer
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 05cd934d René Nussbaumer
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 05cd934d René Nussbaumer
# General Public License for more details.
15 05cd934d René Nussbaumer
#
16 05cd934d René Nussbaumer
# You should have received a copy of the GNU General Public License
17 05cd934d René Nussbaumer
# along with this program; if not, write to the Free Software
18 05cd934d René Nussbaumer
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 05cd934d René Nussbaumer
# 02110-1301, USA.
20 05cd934d René Nussbaumer
21 05cd934d René Nussbaumer
"""Tool to setup the SSH configuration on a remote node.
22 05cd934d René Nussbaumer
23 05cd934d René Nussbaumer
This is needed before we can join the node into the cluster.
24 05cd934d René Nussbaumer
25 05cd934d René Nussbaumer
"""
26 05cd934d René Nussbaumer
27 c9a4a662 Manuel Franceschini
# pylint: disable-msg=C0103
28 c9a4a662 Manuel Franceschini
# C0103: Invalid name setup-ssh
29 c9a4a662 Manuel Franceschini
30 05cd934d René Nussbaumer
import getpass
31 05cd934d René Nussbaumer
import logging
32 05cd934d René Nussbaumer
import paramiko
33 05cd934d René Nussbaumer
import os.path
34 05cd934d René Nussbaumer
import optparse
35 05cd934d René Nussbaumer
import sys
36 05cd934d René Nussbaumer
37 05cd934d René Nussbaumer
from ganeti import cli
38 05cd934d René Nussbaumer
from ganeti import constants
39 05cd934d René Nussbaumer
from ganeti import errors
40 05cd934d René Nussbaumer
from ganeti import netutils
41 05cd934d René Nussbaumer
from ganeti import ssh
42 05cd934d René Nussbaumer
from ganeti import utils
43 05cd934d René Nussbaumer
44 05cd934d René Nussbaumer
45 05cd934d René Nussbaumer
class RemoteCommandError(errors.GenericError):
46 05cd934d René Nussbaumer
  """Exception if remote command was not successful.
47 05cd934d René Nussbaumer
48 05cd934d René Nussbaumer
  """
49 05cd934d René Nussbaumer
50 05cd934d René Nussbaumer
51 05cd934d René Nussbaumer
def _RunRemoteCommand(transport, command):
52 05cd934d René Nussbaumer
  """Invokes and wait for the command over SSH.
53 05cd934d René Nussbaumer
54 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
55 05cd934d René Nussbaumer
  @param command: The command to be executed
56 05cd934d René Nussbaumer
57 05cd934d René Nussbaumer
  """
58 05cd934d René Nussbaumer
  chan = transport.open_session()
59 05cd934d René Nussbaumer
  chan.set_combine_stderr(True)
60 05cd934d René Nussbaumer
  output_handler = chan.makefile("r")
61 05cd934d René Nussbaumer
  chan.exec_command(command)
62 05cd934d René Nussbaumer
63 05cd934d René Nussbaumer
  result = chan.recv_exit_status()
64 05cd934d René Nussbaumer
  msg = output_handler.read()
65 05cd934d René Nussbaumer
66 05cd934d René Nussbaumer
  out_msg = "'%s' exited with status code %s, output %r" % (command, result,
67 05cd934d René Nussbaumer
                                                            msg)
68 05cd934d René Nussbaumer
69 05cd934d René Nussbaumer
  # If result is -1 (no exit status provided) we assume it was not successful
70 05cd934d René Nussbaumer
  if result:
71 05cd934d René Nussbaumer
    raise RemoteCommandError(out_msg)
72 05cd934d René Nussbaumer
73 05cd934d René Nussbaumer
  if msg:
74 05cd934d René Nussbaumer
    logging.info(out_msg)
75 05cd934d René Nussbaumer
76 05cd934d René Nussbaumer
77 05cd934d René Nussbaumer
def _InvokeDaemonUtil(transport, command):
78 05cd934d René Nussbaumer
  """Invokes daemon-util on the remote side.
79 05cd934d René Nussbaumer
80 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
81 05cd934d René Nussbaumer
  @param command: The daemon-util command to be run
82 05cd934d René Nussbaumer
83 05cd934d René Nussbaumer
  """
84 05cd934d René Nussbaumer
  _RunRemoteCommand(transport, "%s %s" % (constants.DAEMON_UTIL, command))
85 05cd934d René Nussbaumer
86 05cd934d René Nussbaumer
87 05cd934d René Nussbaumer
def _WriteSftpFile(sftp, name, perm, data):
88 05cd934d René Nussbaumer
  """SFTPs data to a remote file.
89 05cd934d René Nussbaumer
90 05cd934d René Nussbaumer
  @param sftp: A open paramiko SFTP client
91 05cd934d René Nussbaumer
  @param name: The remote file name
92 05cd934d René Nussbaumer
  @param perm: The remote file permission
93 05cd934d René Nussbaumer
  @param data: The data to write
94 05cd934d René Nussbaumer
95 05cd934d René Nussbaumer
  """
96 05cd934d René Nussbaumer
  remote_file = sftp.open(name, "w")
97 05cd934d René Nussbaumer
  try:
98 05cd934d René Nussbaumer
    sftp.chmod(name, perm)
99 05cd934d René Nussbaumer
    remote_file.write(data)
100 05cd934d René Nussbaumer
  finally:
101 05cd934d René Nussbaumer
    remote_file.close()
102 05cd934d René Nussbaumer
103 05cd934d René Nussbaumer
104 05cd934d René Nussbaumer
def SetupSSH(transport):
105 05cd934d René Nussbaumer
  """Sets the SSH up on the other side.
106 05cd934d René Nussbaumer
107 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
108 05cd934d René Nussbaumer
109 05cd934d René Nussbaumer
  """
110 05cd934d René Nussbaumer
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
111 05cd934d René Nussbaumer
  keyfiles = [
112 05cd934d René Nussbaumer
    (constants.SSH_HOST_DSA_PRIV, 0600),
113 05cd934d René Nussbaumer
    (constants.SSH_HOST_DSA_PUB, 0644),
114 05cd934d René Nussbaumer
    (constants.SSH_HOST_RSA_PRIV, 0600),
115 05cd934d René Nussbaumer
    (constants.SSH_HOST_RSA_PUB, 0644),
116 05cd934d René Nussbaumer
    (priv_key, 0600),
117 05cd934d René Nussbaumer
    (pub_key, 0644),
118 05cd934d René Nussbaumer
    ]
119 05cd934d René Nussbaumer
120 05cd934d René Nussbaumer
  sftp = transport.open_sftp_client()
121 05cd934d René Nussbaumer
122 05cd934d René Nussbaumer
  filemap = dict((name, (utils.ReadFile(name), perm))
123 05cd934d René Nussbaumer
                 for (name, perm) in keyfiles)
124 05cd934d René Nussbaumer
125 05cd934d René Nussbaumer
  auth_path = os.path.dirname(auth_keys)
126 05cd934d René Nussbaumer
127 05cd934d René Nussbaumer
  try:
128 05cd934d René Nussbaumer
    sftp.mkdir(auth_path, 0700)
129 05cd934d René Nussbaumer
  except IOError:
130 05cd934d René Nussbaumer
    # Sadly paramiko doesn't provide errno or similiar
131 05cd934d René Nussbaumer
    # so we can just assume that the path already exists
132 5c654e95 Iustin Pop
    logging.info("Path %s seems already to exist on remote node. Ignoring.",
133 05cd934d René Nussbaumer
                 auth_path)
134 05cd934d René Nussbaumer
135 05cd934d René Nussbaumer
  for name, (data, perm) in filemap.iteritems():
136 05cd934d René Nussbaumer
    _WriteSftpFile(sftp, name, perm, data)
137 05cd934d René Nussbaumer
138 05cd934d René Nussbaumer
  authorized_keys = sftp.open(auth_keys, "a+")
139 05cd934d René Nussbaumer
  try:
140 05cd934d René Nussbaumer
    # We don't have to close, as the close happened already in AddAuthorizedKey
141 05cd934d René Nussbaumer
    utils.AddAuthorizedKey(authorized_keys, filemap[pub_key][0])
142 05cd934d René Nussbaumer
  finally:
143 05cd934d René Nussbaumer
    authorized_keys.close()
144 05cd934d René Nussbaumer
145 05cd934d René Nussbaumer
  _InvokeDaemonUtil(transport, "reload-ssh-keys")
146 05cd934d René Nussbaumer
147 05cd934d René Nussbaumer
148 05cd934d René Nussbaumer
def SetupNodeDaemon(transport):
149 05cd934d René Nussbaumer
  """Sets the node daemon up on the other side.
150 05cd934d René Nussbaumer
151 05cd934d René Nussbaumer
  @param transport: The paramiko transport instance
152 05cd934d René Nussbaumer
153 05cd934d René Nussbaumer
  """
154 05cd934d René Nussbaumer
  noded_cert = utils.ReadFile(constants.NODED_CERT_FILE)
155 05cd934d René Nussbaumer
156 05cd934d René Nussbaumer
  sftp = transport.open_sftp_client()
157 05cd934d René Nussbaumer
  _WriteSftpFile(sftp, constants.NODED_CERT_FILE, 0400, noded_cert)
158 05cd934d René Nussbaumer
159 05cd934d René Nussbaumer
  _InvokeDaemonUtil(transport, "start %s" % constants.NODED)
160 05cd934d René Nussbaumer
161 05cd934d René Nussbaumer
162 05cd934d René Nussbaumer
def ParseOptions():
163 05cd934d René Nussbaumer
  """Parses options passed to program.
164 05cd934d René Nussbaumer
165 05cd934d René Nussbaumer
  """
166 05cd934d René Nussbaumer
  program = os.path.basename(sys.argv[0])
167 05cd934d René Nussbaumer
168 05cd934d René Nussbaumer
  parser = optparse.OptionParser(usage=("%prog [--debug|--verbose] <node>"
169 05cd934d René Nussbaumer
                                        " <node...>"), prog=program)
170 05cd934d René Nussbaumer
  parser.add_option(cli.DEBUG_OPT)
171 05cd934d René Nussbaumer
  parser.add_option(cli.VERBOSE_OPT)
172 05cd934d René Nussbaumer
173 05cd934d René Nussbaumer
  (options, args) = parser.parse_args()
174 05cd934d René Nussbaumer
175 05cd934d René Nussbaumer
  return (options, args)
176 05cd934d René Nussbaumer
177 05cd934d René Nussbaumer
178 05cd934d René Nussbaumer
def SetupLogging(options):
179 05cd934d René Nussbaumer
  """Sets up the logging.
180 05cd934d René Nussbaumer
181 05cd934d René Nussbaumer
  @param options: Parsed options
182 05cd934d René Nussbaumer
183 05cd934d René Nussbaumer
  """
184 05cd934d René Nussbaumer
  fmt = "%(asctime)s: %(threadName)s "
185 05cd934d René Nussbaumer
  if options.debug or options.verbose:
186 05cd934d René Nussbaumer
    fmt += "%(levelname)s "
187 05cd934d René Nussbaumer
  fmt += "%(message)s"
188 05cd934d René Nussbaumer
189 05cd934d René Nussbaumer
  formatter = logging.Formatter(fmt)
190 05cd934d René Nussbaumer
191 05cd934d René Nussbaumer
  file_handler = logging.FileHandler(constants.LOG_SETUP_SSH)
192 05cd934d René Nussbaumer
  stderr_handler = logging.StreamHandler()
193 05cd934d René Nussbaumer
  stderr_handler.setFormatter(formatter)
194 05cd934d René Nussbaumer
  file_handler.setFormatter(formatter)
195 5c654e95 Iustin Pop
  file_handler.setLevel(logging.INFO)
196 05cd934d René Nussbaumer
197 05cd934d René Nussbaumer
  if options.debug:
198 5c654e95 Iustin Pop
    stderr_handler.setLevel(logging.DEBUG)
199 05cd934d René Nussbaumer
  elif options.verbose:
200 05cd934d René Nussbaumer
    stderr_handler.setLevel(logging.INFO)
201 05cd934d René Nussbaumer
  else:
202 5c654e95 Iustin Pop
    stderr_handler.setLevel(logging.WARNING)
203 05cd934d René Nussbaumer
204 05cd934d René Nussbaumer
  root_logger = logging.getLogger("")
205 5c654e95 Iustin Pop
  root_logger.setLevel(logging.INFO)
206 05cd934d René Nussbaumer
  root_logger.addHandler(stderr_handler)
207 05cd934d René Nussbaumer
  root_logger.addHandler(file_handler)
208 8647a52c Iustin Pop
209 8647a52c Iustin Pop
  # This is the paramiko logger instance
210 8647a52c Iustin Pop
  paramiko_logger = logging.getLogger("paramiko")
211 05cd934d René Nussbaumer
  paramiko_logger.addHandler(file_handler)
212 5c654e95 Iustin Pop
  # We don't want to debug Paramiko, so filter anything below warning
213 5c654e95 Iustin Pop
  paramiko_logger.setLevel(logging.WARNING)
214 05cd934d René Nussbaumer
215 05cd934d René Nussbaumer
216 05cd934d René Nussbaumer
def main():
217 05cd934d René Nussbaumer
  """Main routine.
218 05cd934d René Nussbaumer
219 05cd934d René Nussbaumer
  """
220 05cd934d René Nussbaumer
  (options, args) = ParseOptions()
221 05cd934d René Nussbaumer
222 05cd934d René Nussbaumer
  SetupLogging(options)
223 05cd934d René Nussbaumer
224 05cd934d René Nussbaumer
  passwd = getpass.getpass(prompt="%s password:" % constants.GANETI_RUNAS)
225 7bff16bd Iustin Pop
  ssh_port = netutils.GetDaemonPort("ssh")
226 05cd934d René Nussbaumer
227 8647a52c Iustin Pop
  # Below, we need to join() the transport objects, as otherwise the
228 8647a52c Iustin Pop
  # following happens:
229 8647a52c Iustin Pop
  # - the main thread finishes
230 8647a52c Iustin Pop
  # - the atexit functions run (in the main thread), and cause the
231 8647a52c Iustin Pop
  #   logging file to be closed
232 8647a52c Iustin Pop
  # - a tiny bit later, the transport thread is finally ending, and
233 8647a52c Iustin Pop
  #   wants to log one more message, which fails as the file is closed
234 8647a52c Iustin Pop
  #   now
235 8647a52c Iustin Pop
236 05cd934d René Nussbaumer
  for host in args:
237 7bff16bd Iustin Pop
    transport = paramiko.Transport((host, ssh_port))
238 8647a52c Iustin Pop
    try:
239 8647a52c Iustin Pop
      transport.connect(username=constants.GANETI_RUNAS, password=passwd)
240 8647a52c Iustin Pop
    except Exception, err:
241 8647a52c Iustin Pop
      logging.error("Connection or authentication failed to host %s: %s",
242 8647a52c Iustin Pop
                    host, err)
243 8647a52c Iustin Pop
      transport.close()
244 8647a52c Iustin Pop
      # this is needed for compatibility with older Paramiko or Python
245 8647a52c Iustin Pop
      # versions
246 8647a52c Iustin Pop
      transport.join()
247 8647a52c Iustin Pop
      continue
248 05cd934d René Nussbaumer
    try:
249 05cd934d René Nussbaumer
      try:
250 05cd934d René Nussbaumer
        SetupSSH(transport)
251 05cd934d René Nussbaumer
        SetupNodeDaemon(transport)
252 05cd934d René Nussbaumer
      except errors.GenericError, err:
253 8647a52c Iustin Pop
        logging.error("While doing setup on host %s an error occured: %s",
254 8647a52c Iustin Pop
                      host, err)
255 05cd934d René Nussbaumer
    finally:
256 05cd934d René Nussbaumer
      transport.close()
257 8647a52c Iustin Pop
      # this is needed for compatibility with older Paramiko or Python
258 8647a52c Iustin Pop
      # versions
259 8647a52c Iustin Pop
      transport.join()
260 05cd934d René Nussbaumer
261 05cd934d René Nussbaumer
262 05cd934d René Nussbaumer
if __name__ == "__main__":
263 05cd934d René Nussbaumer
  main()