Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ a8083063

History | View | Annotate | Download (8.1 kB)

1
#!/usr/bin/python
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 dealing with command line parsing"""
23

    
24

    
25
import sys
26
import textwrap
27
import os.path
28
import copy
29

    
30
from ganeti import utils
31
from ganeti import logger
32
from ganeti import errors
33
from ganeti import mcpu
34
from ganeti import constants
35

    
36
from optparse import (OptionParser, make_option, TitledHelpFormatter,
37
                      Option, OptionValueError, SUPPRESS_HELP)
38

    
39
__all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain", "SubmitOpCode",
40
           "cli_option",
41
           "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
42
           "USEUNITS_OPT"]
43

    
44
DEBUG_OPT = make_option("-d", "--debug", default=False,
45
                        action="store_true",
46
                        help="Turn debugging on")
47

    
48
NOHDR_OPT = make_option("--no-headers", default=False,
49
                        action="store_true", dest="no_headers",
50
                        help="Don't display column headers")
51

    
52
SEP_OPT = make_option("--separator", default=" ",
53
                      action="store", dest="separator",
54
                      help="Separator between output fields"
55
                      " (defaults to one space)")
56

    
57
USEUNITS_OPT = make_option("--human-readable", default=False,
58
                           action="store_true", dest="human_readable",
59
                           help="Print sizes in human readable format")
60

    
61
_LOCK_OPT = make_option("--lock-retries", default=None,
62
                        type="int", help=SUPPRESS_HELP)
63

    
64

    
65
def ARGS_FIXED(val):
66
  """Macro-like function denoting a fixed number of arguments"""
67
  return -val
68

    
69

    
70
def ARGS_ATLEAST(val):
71
  """Macro-like function denoting a minimum number of arguments"""
72
  return val
73

    
74

    
75
ARGS_NONE = None
76
ARGS_ONE = ARGS_FIXED(1)
77
ARGS_ANY = ARGS_ATLEAST(0)
78

    
79

    
80
def check_unit(option, opt, value):
81
  try:
82
    return utils.ParseUnit(value)
83
  except errors.UnitParseError, err:
84
    raise OptionValueError, ("option %s: %s" % (opt, err))
85

    
86

    
87
class CliOption(Option):
88
  TYPES = Option.TYPES + ("unit",)
89
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
90
  TYPE_CHECKER["unit"] = check_unit
91

    
92

    
93
# optparse.py sets make_option, so we do it for our own option class, too
94
cli_option = CliOption
95

    
96

    
97
def _ParseArgs(argv, commands):
98
  """Parses the command line and return the function which must be
99
  executed together with its arguments
100

101
  Arguments:
102
    argv: the command line
103

104
    commands: dictionary with special contents, see the design doc for
105
    cmdline handling
106
  """
107
  if len(argv) == 0:
108
    binary = "<command>"
109
  else:
110
    binary = argv[0].split("/")[-1]
111

    
112
  if len(argv) > 1 and argv[1] == "--version":
113
    print "%s (ganeti) %s" % (binary, constants.RELEASE_VERSION)
114
    # Quit right away. That way we don't have to care about this special
115
    # argument. optparse.py does it the same.
116
    sys.exit(0)
117

    
118
  if len(argv) < 2 or argv[1] not in commands.keys():
119
    # let's do a nice thing
120
    sortedcmds = commands.keys()
121
    sortedcmds.sort()
122
    print ("Usage: %(bin)s {command} [options...] [argument...]"
123
           "\n%(bin)s <command> --help to see details, or"
124
           " man %(bin)s\n" % {"bin": binary})
125
    # compute the max line length for cmd + usage
126
    mlen = max([len(" %s %s" % (cmd, commands[cmd][3])) for cmd in commands])
127
    mlen = min(60, mlen) # should not get here...
128
    # and format a nice command list
129
    print "Commands:"
130
    for cmd in sortedcmds:
131
      cmdstr = " %s %s" % (cmd, commands[cmd][3])
132
      help_text = commands[cmd][4]
133
      help_lines = textwrap.wrap(help_text, 79-3-mlen)
134
      print "%-*s - %s" % (mlen, cmdstr,
135
                                          help_lines.pop(0))
136
      for line in help_lines:
137
        print "%-*s   %s" % (mlen, "", line)
138
    print
139
    return None, None, None
140
  cmd = argv.pop(1)
141
  func, nargs, parser_opts, usage, description = commands[cmd]
142
  parser_opts.append(_LOCK_OPT)
143
  parser = OptionParser(option_list=parser_opts,
144
                        description=description,
145
                        formatter=TitledHelpFormatter(),
146
                        usage="%%prog %s %s" % (cmd, usage))
147
  parser.disable_interspersed_args()
148
  options, args = parser.parse_args()
149
  if nargs is None:
150
    if len(args) != 0:
151
      print >> sys.stderr, ("Error: Command %s expects no arguments" % cmd)
152
      return None, None, None
153
  elif nargs < 0 and len(args) != -nargs:
154
    print >> sys.stderr, ("Error: Command %s expects %d argument(s)" %
155
                         (cmd, -nargs))
156
    return None, None, None
157
  elif nargs >= 0 and len(args) < nargs:
158
    print >> sys.stderr, ("Error: Command %s expects at least %d argument(s)" %
159
                         (cmd, nargs))
160
    return None, None, None
161

    
162
  return func, options, args
163

    
164

    
165
def _AskUser(text):
166
  """Ask the user a yes/no question.
167

168
  Args:
169
    questionstring - the question to ask.
170

171
  Returns:
172
    True or False depending on answer (No for False is default).
173

174
  """
175
  try:
176
    f = file("/dev/tty", "r+")
177
  except IOError:
178
    return False
179
  answer = False
180
  try:
181
    f.write(textwrap.fill(text))
182
    f.write('\n')
183
    f.write("y/[n]: ")
184
    line = f.readline(16).strip().lower()
185
    answer = line in ('y', 'yes')
186
  finally:
187
    f.close()
188
  return answer
189

    
190

    
191
def SubmitOpCode(op):
192
  """Function to submit an opcode.
193

194
  This is just a simple wrapper over the construction of the processor
195
  instance. It should be extended to better handle feedback and
196
  interaction functions.
197

198
  """
199
  proc = mcpu.Processor()
200
  return proc.ExecOpCode(op, logger.ToStdout)
201

    
202

    
203
def GenericMain(commands):
204
  """Generic main function for all the gnt-* commands.
205

206
  Argument: a dictionary with a special structure, see the design doc
207
  for command line handling.
208

209
  """
210
  # save the program name and the entire command line for later logging
211
  if sys.argv:
212
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
213
    if len(sys.argv) >= 2:
214
      binary += " " + sys.argv[1]
215
      old_cmdline = " ".join(sys.argv[2:])
216
    else:
217
      old_cmdline = ""
218
  else:
219
    binary = "<unknown program>"
220
    old_cmdline = ""
221

    
222
  func, options, args = _ParseArgs(sys.argv, commands)
223
  if func is None: # parse error
224
    return 1
225

    
226
  options._ask_user = _AskUser
227

    
228
  logger.SetupLogging(debug=options.debug, program=binary)
229

    
230
  try:
231
    utils.Lock('cmd', max_retries=options.lock_retries, debug=options.debug)
232
  except errors.LockError, err:
233
    logger.ToStderr(str(err))
234
    return 1
235

    
236
  if old_cmdline:
237
    logger.Info("run with arguments '%s'" % old_cmdline)
238
  else:
239
    logger.Info("run with no arguments")
240

    
241
  try:
242
    try:
243
      result = func(options, args)
244
    except errors.ConfigurationError, err:
245
      logger.Error("Corrupt configuration file: %s" % err)
246
      logger.ToStderr("Aborting.")
247
      result = 2
248
    except errors.HooksAbort, err:
249
      logger.ToStderr("Failure: hooks execution failed:")
250
      for node, script, out in err.args[0]:
251
        if out:
252
          logger.ToStderr("  node: %s, script: %s, output: %s" %
253
                          (node, script, out))
254
        else:
255
          logger.ToStderr("  node: %s, script: %s (no output)" %
256
                          (node, script))
257
      result = 1
258
    except errors.HooksFailure, err:
259
      logger.ToStderr("Failure: hooks general failure: %s" % str(err))
260
      result = 1
261
    except errors.OpPrereqError, err:
262
      logger.ToStderr("Failure: prerequisites not met for this"
263
                      " operation:\n%s" % str(err))
264
      result = 1
265
    except errors.OpExecError, err:
266
      logger.ToStderr("Failure: command execution error:\n%s" % str(err))
267
      result = 1
268
  finally:
269
    utils.Unlock('cmd')
270
    utils.LockCleanup()
271

    
272
  return result