Statistics
| Branch: | Tag: | Revision:

root / tools / setup-ssh @ c9a4a662

History | View | Annotate | Download (6.2 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2010 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
"""Tool to setup the SSH configuration on a remote node.
22

    
23
This is needed before we can join the node into the cluster.
24

    
25
"""
26

    
27
# pylint: disable-msg=C0103
28
# C0103: Invalid name setup-ssh
29

    
30
import getpass
31
import logging
32
import paramiko
33
import os.path
34
import optparse
35
import sys
36

    
37
from ganeti import cli
38
from ganeti import constants
39
from ganeti import errors
40
from ganeti import netutils
41
from ganeti import ssh
42
from ganeti import utils
43

    
44

    
45
class RemoteCommandError(errors.GenericError):
46
  """Exception if remote command was not successful.
47

    
48
  """
49

    
50

    
51
def _RunRemoteCommand(transport, command):
52
  """Invokes and wait for the command over SSH.
53

    
54
  @param transport: The paramiko transport instance
55
  @param command: The command to be executed
56

    
57
  """
58
  chan = transport.open_session()
59
  chan.set_combine_stderr(True)
60
  output_handler = chan.makefile("r")
61
  chan.exec_command(command)
62

    
63
  result = chan.recv_exit_status()
64
  msg = output_handler.read()
65

    
66
  out_msg = "'%s' exited with status code %s, output %r" % (command, result,
67
                                                            msg)
68

    
69
  # If result is -1 (no exit status provided) we assume it was not successful
70
  if result:
71
    raise RemoteCommandError(out_msg)
72

    
73
  if msg:
74
    logging.info(out_msg)
75

    
76

    
77
def _InvokeDaemonUtil(transport, command):
78
  """Invokes daemon-util on the remote side.
79

    
80
  @param transport: The paramiko transport instance
81
  @param command: The daemon-util command to be run
82

    
83
  """
84
  _RunRemoteCommand(transport, "%s %s" % (constants.DAEMON_UTIL, command))
85

    
86

    
87
def _WriteSftpFile(sftp, name, perm, data):
88
  """SFTPs data to a remote file.
89

    
90
  @param sftp: A open paramiko SFTP client
91
  @param name: The remote file name
92
  @param perm: The remote file permission
93
  @param data: The data to write
94

    
95
  """
96
  remote_file = sftp.open(name, "w")
97
  try:
98
    sftp.chmod(name, perm)
99
    remote_file.write(data)
100
  finally:
101
    remote_file.close()
102

    
103

    
104
def SetupSSH(transport):
105
  """Sets the SSH up on the other side.
106

    
107
  @param transport: The paramiko transport instance
108

    
109
  """
110
  priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.GANETI_RUNAS)
111
  keyfiles = [
112
    (constants.SSH_HOST_DSA_PRIV, 0600),
113
    (constants.SSH_HOST_DSA_PUB, 0644),
114
    (constants.SSH_HOST_RSA_PRIV, 0600),
115
    (constants.SSH_HOST_RSA_PUB, 0644),
116
    (priv_key, 0600),
117
    (pub_key, 0644),
118
    ]
119

    
120
  sftp = transport.open_sftp_client()
121

    
122
  filemap = dict((name, (utils.ReadFile(name), perm))
123
                 for (name, perm) in keyfiles)
124

    
125
  auth_path = os.path.dirname(auth_keys)
126

    
127
  try:
128
    sftp.mkdir(auth_path, 0700)
129
  except IOError:
130
    # Sadly paramiko doesn't provide errno or similiar
131
    # so we can just assume that the path already exists
132
    logging.info("Path %s seems already to exist on remote node. Ignore.",
133
                 auth_path)
134

    
135
  for name, (data, perm) in filemap.iteritems():
136
    _WriteSftpFile(sftp, name, perm, data)
137

    
138
  authorized_keys = sftp.open(auth_keys, "a+")
139
  try:
140
    # We don't have to close, as the close happened already in AddAuthorizedKey
141
    utils.AddAuthorizedKey(authorized_keys, filemap[pub_key][0])
142
  finally:
143
    authorized_keys.close()
144

    
145
  _InvokeDaemonUtil(transport, "reload-ssh-keys")
146

    
147

    
148
def SetupNodeDaemon(transport):
149
  """Sets the node daemon up on the other side.
150

    
151
  @param transport: The paramiko transport instance
152

    
153
  """
154
  noded_cert = utils.ReadFile(constants.NODED_CERT_FILE)
155

    
156
  sftp = transport.open_sftp_client()
157
  _WriteSftpFile(sftp, constants.NODED_CERT_FILE, 0400, noded_cert)
158

    
159
  _InvokeDaemonUtil(transport, "start %s" % constants.NODED)
160

    
161

    
162
def ParseOptions():
163
  """Parses options passed to program.
164

    
165
  """
166
  program = os.path.basename(sys.argv[0])
167

    
168
  parser = optparse.OptionParser(usage=("%prog [--debug|--verbose] <node>"
169
                                        " <node...>"), prog=program)
170
  parser.add_option(cli.DEBUG_OPT)
171
  parser.add_option(cli.VERBOSE_OPT)
172

    
173
  (options, args) = parser.parse_args()
174

    
175
  return (options, args)
176

    
177

    
178
def SetupLogging(options):
179
  """Sets up the logging.
180

    
181
  @param options: Parsed options
182

    
183
  """
184
  fmt = "%(asctime)s: %(threadName)s "
185
  if options.debug or options.verbose:
186
    fmt += "%(levelname)s "
187
  fmt += "%(message)s"
188

    
189
  formatter = logging.Formatter(fmt)
190

    
191
  file_handler = logging.FileHandler(constants.LOG_SETUP_SSH)
192
  stderr_handler = logging.StreamHandler()
193
  stderr_handler.setFormatter(formatter)
194
  file_handler.setFormatter(formatter)
195
  file_handler.setLevel(logging.DEBUG)
196

    
197
  if options.debug:
198
    stderr_handler.setLevel(logging.NOTSET)
199
  elif options.verbose:
200
    stderr_handler.setLevel(logging.INFO)
201
  else:
202
    stderr_handler.setLevel(logging.ERROR)
203

    
204
  # This is the paramiko logger instance
205
  paramiko_logger = logging.getLogger("paramiko")
206
  root_logger = logging.getLogger("")
207
  root_logger.setLevel(logging.NOTSET)
208
  root_logger.addHandler(stderr_handler)
209
  root_logger.addHandler(file_handler)
210
  paramiko_logger.addHandler(file_handler)
211

    
212

    
213
def main():
214
  """Main routine.
215

    
216
  """
217
  (options, args) = ParseOptions()
218

    
219
  SetupLogging(options)
220

    
221
  passwd = getpass.getpass(prompt="%s password:" % constants.GANETI_RUNAS)
222

    
223
  for host in args:
224
    transport = paramiko.Transport((host, netutils.GetDaemonPort("ssh")))
225
    transport.connect(username=constants.GANETI_RUNAS, password=passwd)
226
    try:
227
      try:
228
        SetupSSH(transport)
229
        SetupNodeDaemon(transport)
230
      except errors.GenericError, err:
231
        logging.fatal("While doing setup on host %s an error occured: %s", host,
232
                      err)
233
    finally:
234
      transport.close()
235

    
236

    
237
if __name__ == "__main__":
238
  main()