Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ b33e986b

History | View | Annotate | Download (18.4 kB)

1 2f31098c Iustin Pop
#
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 a8083063 Iustin Pop
# Copyright (C) 2006, 2007 Google Inc.
5 a8083063 Iustin Pop
#
6 a8083063 Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 a8083063 Iustin Pop
# it under the terms of the GNU General Public License as published by
8 a8083063 Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 a8083063 Iustin Pop
# (at your option) any later version.
10 a8083063 Iustin Pop
#
11 a8083063 Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 a8083063 Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a8083063 Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a8083063 Iustin Pop
# General Public License for more details.
15 a8083063 Iustin Pop
#
16 a8083063 Iustin Pop
# You should have received a copy of the GNU General Public License
17 a8083063 Iustin Pop
# along with this program; if not, write to the Free Software
18 a8083063 Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a8083063 Iustin Pop
# 02110-1301, USA.
20 a8083063 Iustin Pop
21 a8083063 Iustin Pop
22 a8083063 Iustin Pop
"""Module dealing with command line parsing"""
23 a8083063 Iustin Pop
24 a8083063 Iustin Pop
25 a8083063 Iustin Pop
import sys
26 a8083063 Iustin Pop
import textwrap
27 a8083063 Iustin Pop
import os.path
28 a8083063 Iustin Pop
import copy
29 685ee993 Iustin Pop
import time
30 73702ee7 Iustin Pop
from cStringIO import StringIO
31 a8083063 Iustin Pop
32 a8083063 Iustin Pop
from ganeti import utils
33 a8083063 Iustin Pop
from ganeti import logger
34 a8083063 Iustin Pop
from ganeti import errors
35 a8083063 Iustin Pop
from ganeti import constants
36 846baef9 Iustin Pop
from ganeti import opcodes
37 ceab32dd Iustin Pop
from ganeti import luxi
38 b33e986b Iustin Pop
from ganeti import ssconf
39 a8083063 Iustin Pop
40 a8083063 Iustin Pop
from optparse import (OptionParser, make_option, TitledHelpFormatter,
41 38206f3c Iustin Pop
                      Option, OptionValueError)
42 a8083063 Iustin Pop
43 ceab32dd Iustin Pop
__all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain",
44 af30b2fd Michael Hanselmann
           "SubmitOpCode", "GetClient",
45 47988778 Iustin Pop
           "cli_option", "GenerateTable", "AskUser",
46 a8083063 Iustin Pop
           "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
47 846baef9 Iustin Pop
           "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT",
48 810c50b7 Iustin Pop
           "ListTags", "AddTags", "RemoveTags", "TAG_SRC_OPT",
49 60d49723 Michael Hanselmann
           "FormatError", "SplitNodeOption"
50 846baef9 Iustin Pop
           ]
51 846baef9 Iustin Pop
52 846baef9 Iustin Pop
53 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
54 846baef9 Iustin Pop
  """Extract the tag type object.
55 846baef9 Iustin Pop

56 846baef9 Iustin Pop
  Note that this function will modify its args parameter.
57 846baef9 Iustin Pop

58 846baef9 Iustin Pop
  """
59 846baef9 Iustin Pop
  if not hasattr(opts, "tag_type"):
60 846baef9 Iustin Pop
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
61 846baef9 Iustin Pop
  kind = opts.tag_type
62 846baef9 Iustin Pop
  if kind == constants.TAG_CLUSTER:
63 846baef9 Iustin Pop
    retval = kind, kind
64 846baef9 Iustin Pop
  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
65 846baef9 Iustin Pop
    if not args:
66 0c434948 Iustin Pop
      raise errors.OpPrereqError("no arguments passed to the command")
67 846baef9 Iustin Pop
    name = args.pop(0)
68 846baef9 Iustin Pop
    retval = kind, name
69 846baef9 Iustin Pop
  else:
70 846baef9 Iustin Pop
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
71 846baef9 Iustin Pop
  return retval
72 846baef9 Iustin Pop
73 846baef9 Iustin Pop
74 810c50b7 Iustin Pop
def _ExtendTags(opts, args):
75 810c50b7 Iustin Pop
  """Extend the args if a source file has been given.
76 810c50b7 Iustin Pop

77 810c50b7 Iustin Pop
  This function will extend the tags with the contents of the file
78 810c50b7 Iustin Pop
  passed in the 'tags_source' attribute of the opts parameter. A file
79 810c50b7 Iustin Pop
  named '-' will be replaced by stdin.
80 810c50b7 Iustin Pop

81 810c50b7 Iustin Pop
  """
82 810c50b7 Iustin Pop
  fname = opts.tags_source
83 810c50b7 Iustin Pop
  if fname is None:
84 810c50b7 Iustin Pop
    return
85 810c50b7 Iustin Pop
  if fname == "-":
86 810c50b7 Iustin Pop
    new_fh = sys.stdin
87 810c50b7 Iustin Pop
  else:
88 810c50b7 Iustin Pop
    new_fh = open(fname, "r")
89 810c50b7 Iustin Pop
  new_data = []
90 810c50b7 Iustin Pop
  try:
91 810c50b7 Iustin Pop
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
92 810c50b7 Iustin Pop
    # because of python bug 1633941
93 810c50b7 Iustin Pop
    while True:
94 810c50b7 Iustin Pop
      line = new_fh.readline()
95 810c50b7 Iustin Pop
      if not line:
96 810c50b7 Iustin Pop
        break
97 810c50b7 Iustin Pop
      new_data.append(line.strip())
98 810c50b7 Iustin Pop
  finally:
99 810c50b7 Iustin Pop
    new_fh.close()
100 810c50b7 Iustin Pop
  args.extend(new_data)
101 810c50b7 Iustin Pop
102 810c50b7 Iustin Pop
103 846baef9 Iustin Pop
def ListTags(opts, args):
104 846baef9 Iustin Pop
  """List the tags on a given object.
105 846baef9 Iustin Pop

106 846baef9 Iustin Pop
  This is a generic implementation that knows how to deal with all
107 846baef9 Iustin Pop
  three cases of tag objects (cluster, node, instance). The opts
108 846baef9 Iustin Pop
  argument is expected to contain a tag_type field denoting what
109 846baef9 Iustin Pop
  object type we work on.
110 846baef9 Iustin Pop

111 846baef9 Iustin Pop
  """
112 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
113 846baef9 Iustin Pop
  op = opcodes.OpGetTags(kind=kind, name=name)
114 846baef9 Iustin Pop
  result = SubmitOpCode(op)
115 846baef9 Iustin Pop
  result = list(result)
116 846baef9 Iustin Pop
  result.sort()
117 846baef9 Iustin Pop
  for tag in result:
118 846baef9 Iustin Pop
    print tag
119 846baef9 Iustin Pop
120 846baef9 Iustin Pop
121 846baef9 Iustin Pop
def AddTags(opts, args):
122 846baef9 Iustin Pop
  """Add tags on a given object.
123 846baef9 Iustin Pop

124 846baef9 Iustin Pop
  This is a generic implementation that knows how to deal with all
125 846baef9 Iustin Pop
  three cases of tag objects (cluster, node, instance). The opts
126 846baef9 Iustin Pop
  argument is expected to contain a tag_type field denoting what
127 846baef9 Iustin Pop
  object type we work on.
128 846baef9 Iustin Pop

129 846baef9 Iustin Pop
  """
130 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
131 810c50b7 Iustin Pop
  _ExtendTags(opts, args)
132 846baef9 Iustin Pop
  if not args:
133 846baef9 Iustin Pop
    raise errors.OpPrereqError("No tags to be added")
134 846baef9 Iustin Pop
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
135 846baef9 Iustin Pop
  SubmitOpCode(op)
136 846baef9 Iustin Pop
137 846baef9 Iustin Pop
138 846baef9 Iustin Pop
def RemoveTags(opts, args):
139 846baef9 Iustin Pop
  """Remove tags from a given object.
140 846baef9 Iustin Pop

141 846baef9 Iustin Pop
  This is a generic implementation that knows how to deal with all
142 846baef9 Iustin Pop
  three cases of tag objects (cluster, node, instance). The opts
143 846baef9 Iustin Pop
  argument is expected to contain a tag_type field denoting what
144 846baef9 Iustin Pop
  object type we work on.
145 846baef9 Iustin Pop

146 846baef9 Iustin Pop
  """
147 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
148 810c50b7 Iustin Pop
  _ExtendTags(opts, args)
149 846baef9 Iustin Pop
  if not args:
150 846baef9 Iustin Pop
    raise errors.OpPrereqError("No tags to be removed")
151 846baef9 Iustin Pop
  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
152 846baef9 Iustin Pop
  SubmitOpCode(op)
153 846baef9 Iustin Pop
154 a8083063 Iustin Pop
155 a8083063 Iustin Pop
DEBUG_OPT = make_option("-d", "--debug", default=False,
156 a8083063 Iustin Pop
                        action="store_true",
157 a8083063 Iustin Pop
                        help="Turn debugging on")
158 a8083063 Iustin Pop
159 a8083063 Iustin Pop
NOHDR_OPT = make_option("--no-headers", default=False,
160 a8083063 Iustin Pop
                        action="store_true", dest="no_headers",
161 a8083063 Iustin Pop
                        help="Don't display column headers")
162 a8083063 Iustin Pop
163 137161c9 Michael Hanselmann
SEP_OPT = make_option("--separator", default=None,
164 a8083063 Iustin Pop
                      action="store", dest="separator",
165 a8083063 Iustin Pop
                      help="Separator between output fields"
166 a8083063 Iustin Pop
                      " (defaults to one space)")
167 a8083063 Iustin Pop
168 a8083063 Iustin Pop
USEUNITS_OPT = make_option("--human-readable", default=False,
169 a8083063 Iustin Pop
                           action="store_true", dest="human_readable",
170 a8083063 Iustin Pop
                           help="Print sizes in human readable format")
171 a8083063 Iustin Pop
172 dcb93971 Michael Hanselmann
FIELDS_OPT = make_option("-o", "--output", dest="output", action="store",
173 d8a4b51d Iustin Pop
                         type="string", help="Comma separated list of"
174 d8a4b51d Iustin Pop
                         " output fields",
175 dcb93971 Michael Hanselmann
                         metavar="FIELDS")
176 dcb93971 Michael Hanselmann
177 fe7b0351 Michael Hanselmann
FORCE_OPT = make_option("-f", "--force", dest="force", action="store_true",
178 fe7b0351 Michael Hanselmann
                        default=False, help="Force the operation")
179 fe7b0351 Michael Hanselmann
180 810c50b7 Iustin Pop
TAG_SRC_OPT = make_option("--from", dest="tags_source",
181 810c50b7 Iustin Pop
                          default=None, help="File with tag names")
182 810c50b7 Iustin Pop
183 60d49723 Michael Hanselmann
184 a8083063 Iustin Pop
def ARGS_FIXED(val):
185 a8083063 Iustin Pop
  """Macro-like function denoting a fixed number of arguments"""
186 a8083063 Iustin Pop
  return -val
187 a8083063 Iustin Pop
188 a8083063 Iustin Pop
189 a8083063 Iustin Pop
def ARGS_ATLEAST(val):
190 a8083063 Iustin Pop
  """Macro-like function denoting a minimum number of arguments"""
191 a8083063 Iustin Pop
  return val
192 a8083063 Iustin Pop
193 a8083063 Iustin Pop
194 a8083063 Iustin Pop
ARGS_NONE = None
195 a8083063 Iustin Pop
ARGS_ONE = ARGS_FIXED(1)
196 a8083063 Iustin Pop
ARGS_ANY = ARGS_ATLEAST(0)
197 a8083063 Iustin Pop
198 a8083063 Iustin Pop
199 a8083063 Iustin Pop
def check_unit(option, opt, value):
200 65fe4693 Iustin Pop
  """OptParsers custom converter for units.
201 65fe4693 Iustin Pop

202 65fe4693 Iustin Pop
  """
203 a8083063 Iustin Pop
  try:
204 a8083063 Iustin Pop
    return utils.ParseUnit(value)
205 a8083063 Iustin Pop
  except errors.UnitParseError, err:
206 3ecf6786 Iustin Pop
    raise OptionValueError("option %s: %s" % (opt, err))
207 a8083063 Iustin Pop
208 a8083063 Iustin Pop
209 a8083063 Iustin Pop
class CliOption(Option):
210 65fe4693 Iustin Pop
  """Custom option class for optparse.
211 65fe4693 Iustin Pop

212 65fe4693 Iustin Pop
  """
213 a8083063 Iustin Pop
  TYPES = Option.TYPES + ("unit",)
214 a8083063 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
215 a8083063 Iustin Pop
  TYPE_CHECKER["unit"] = check_unit
216 a8083063 Iustin Pop
217 a8083063 Iustin Pop
218 a8083063 Iustin Pop
# optparse.py sets make_option, so we do it for our own option class, too
219 a8083063 Iustin Pop
cli_option = CliOption
220 a8083063 Iustin Pop
221 a8083063 Iustin Pop
222 de47cf8f Guido Trotter
def _ParseArgs(argv, commands, aliases):
223 a8083063 Iustin Pop
  """Parses the command line and return the function which must be
224 a8083063 Iustin Pop
  executed together with its arguments
225 a8083063 Iustin Pop

226 a8083063 Iustin Pop
  Arguments:
227 a8083063 Iustin Pop
    argv: the command line
228 a8083063 Iustin Pop

229 a8083063 Iustin Pop
    commands: dictionary with special contents, see the design doc for
230 a8083063 Iustin Pop
    cmdline handling
231 de47cf8f Guido Trotter
    aliases: dictionary with command aliases {'alias': 'target, ...}
232 098c0958 Michael Hanselmann

233 a8083063 Iustin Pop
  """
234 a8083063 Iustin Pop
  if len(argv) == 0:
235 a8083063 Iustin Pop
    binary = "<command>"
236 a8083063 Iustin Pop
  else:
237 a8083063 Iustin Pop
    binary = argv[0].split("/")[-1]
238 a8083063 Iustin Pop
239 a8083063 Iustin Pop
  if len(argv) > 1 and argv[1] == "--version":
240 a8083063 Iustin Pop
    print "%s (ganeti) %s" % (binary, constants.RELEASE_VERSION)
241 a8083063 Iustin Pop
    # Quit right away. That way we don't have to care about this special
242 a8083063 Iustin Pop
    # argument. optparse.py does it the same.
243 a8083063 Iustin Pop
    sys.exit(0)
244 a8083063 Iustin Pop
245 de47cf8f Guido Trotter
  if len(argv) < 2 or not (argv[1] in commands or
246 70a35b6f Guido Trotter
                           argv[1] in aliases):
247 a8083063 Iustin Pop
    # let's do a nice thing
248 a8083063 Iustin Pop
    sortedcmds = commands.keys()
249 a8083063 Iustin Pop
    sortedcmds.sort()
250 a8083063 Iustin Pop
    print ("Usage: %(bin)s {command} [options...] [argument...]"
251 a8083063 Iustin Pop
           "\n%(bin)s <command> --help to see details, or"
252 a8083063 Iustin Pop
           " man %(bin)s\n" % {"bin": binary})
253 a8083063 Iustin Pop
    # compute the max line length for cmd + usage
254 4e713df6 Iustin Pop
    mlen = max([len(" %s" % cmd) for cmd in commands])
255 a8083063 Iustin Pop
    mlen = min(60, mlen) # should not get here...
256 a8083063 Iustin Pop
    # and format a nice command list
257 a8083063 Iustin Pop
    print "Commands:"
258 a8083063 Iustin Pop
    for cmd in sortedcmds:
259 4e713df6 Iustin Pop
      cmdstr = " %s" % (cmd,)
260 9a033156 Iustin Pop
      help_text = commands[cmd][4]
261 a8083063 Iustin Pop
      help_lines = textwrap.wrap(help_text, 79-3-mlen)
262 4e713df6 Iustin Pop
      print "%-*s - %s" % (mlen, cmdstr, help_lines.pop(0))
263 a8083063 Iustin Pop
      for line in help_lines:
264 a8083063 Iustin Pop
        print "%-*s   %s" % (mlen, "", line)
265 a8083063 Iustin Pop
    print
266 a8083063 Iustin Pop
    return None, None, None
267 de47cf8f Guido Trotter
268 de47cf8f Guido Trotter
  # get command, unalias it, and look it up in commands
269 a8083063 Iustin Pop
  cmd = argv.pop(1)
270 de47cf8f Guido Trotter
  if cmd in aliases:
271 de47cf8f Guido Trotter
    if cmd in commands:
272 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
273 de47cf8f Guido Trotter
                                   " command" % cmd)
274 de47cf8f Guido Trotter
275 de47cf8f Guido Trotter
    if aliases[cmd] not in commands:
276 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
277 de47cf8f Guido Trotter
                                   " command '%s'" % (cmd, aliases[cmd]))
278 de47cf8f Guido Trotter
279 de47cf8f Guido Trotter
    cmd = aliases[cmd]
280 de47cf8f Guido Trotter
281 a8083063 Iustin Pop
  func, nargs, parser_opts, usage, description = commands[cmd]
282 a8083063 Iustin Pop
  parser = OptionParser(option_list=parser_opts,
283 a8083063 Iustin Pop
                        description=description,
284 a8083063 Iustin Pop
                        formatter=TitledHelpFormatter(),
285 a8083063 Iustin Pop
                        usage="%%prog %s %s" % (cmd, usage))
286 a8083063 Iustin Pop
  parser.disable_interspersed_args()
287 a8083063 Iustin Pop
  options, args = parser.parse_args()
288 a8083063 Iustin Pop
  if nargs is None:
289 a8083063 Iustin Pop
    if len(args) != 0:
290 a8083063 Iustin Pop
      print >> sys.stderr, ("Error: Command %s expects no arguments" % cmd)
291 a8083063 Iustin Pop
      return None, None, None
292 a8083063 Iustin Pop
  elif nargs < 0 and len(args) != -nargs:
293 a8083063 Iustin Pop
    print >> sys.stderr, ("Error: Command %s expects %d argument(s)" %
294 a8083063 Iustin Pop
                         (cmd, -nargs))
295 a8083063 Iustin Pop
    return None, None, None
296 a8083063 Iustin Pop
  elif nargs >= 0 and len(args) < nargs:
297 a8083063 Iustin Pop
    print >> sys.stderr, ("Error: Command %s expects at least %d argument(s)" %
298 a8083063 Iustin Pop
                         (cmd, nargs))
299 a8083063 Iustin Pop
    return None, None, None
300 a8083063 Iustin Pop
301 a8083063 Iustin Pop
  return func, options, args
302 a8083063 Iustin Pop
303 a8083063 Iustin Pop
304 60d49723 Michael Hanselmann
def SplitNodeOption(value):
305 60d49723 Michael Hanselmann
  """Splits the value of a --node option.
306 60d49723 Michael Hanselmann

307 60d49723 Michael Hanselmann
  """
308 60d49723 Michael Hanselmann
  if value and ':' in value:
309 60d49723 Michael Hanselmann
    return value.split(':', 1)
310 60d49723 Michael Hanselmann
  else:
311 60d49723 Michael Hanselmann
    return (value, None)
312 60d49723 Michael Hanselmann
313 60d49723 Michael Hanselmann
314 47988778 Iustin Pop
def AskUser(text, choices=None):
315 47988778 Iustin Pop
  """Ask the user a question.
316 a8083063 Iustin Pop

317 a8083063 Iustin Pop
  Args:
318 47988778 Iustin Pop
    text - the question to ask.
319 a8083063 Iustin Pop

320 47988778 Iustin Pop
    choices - list with elements tuples (input_char, return_value,
321 47988778 Iustin Pop
    description); if not given, it will default to: [('y', True,
322 47988778 Iustin Pop
    'Perform the operation'), ('n', False, 'Do no do the operation')];
323 47988778 Iustin Pop
    note that the '?' char is reserved for help
324 47988778 Iustin Pop

325 47988778 Iustin Pop
  Returns: one of the return values from the choices list; if input is
326 47988778 Iustin Pop
  not possible (i.e. not running with a tty, we return the last entry
327 47988778 Iustin Pop
  from the list
328 a8083063 Iustin Pop

329 a8083063 Iustin Pop
  """
330 47988778 Iustin Pop
  if choices is None:
331 47988778 Iustin Pop
    choices = [('y', True, 'Perform the operation'),
332 47988778 Iustin Pop
               ('n', False, 'Do not perform the operation')]
333 47988778 Iustin Pop
  if not choices or not isinstance(choices, list):
334 47988778 Iustin Pop
    raise errors.ProgrammerError("Invalid choiches argument to AskUser")
335 47988778 Iustin Pop
  for entry in choices:
336 47988778 Iustin Pop
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
337 47988778 Iustin Pop
      raise errors.ProgrammerError("Invalid choiches element to AskUser")
338 47988778 Iustin Pop
339 47988778 Iustin Pop
  answer = choices[-1][1]
340 47988778 Iustin Pop
  new_text = []
341 47988778 Iustin Pop
  for line in text.splitlines():
342 47988778 Iustin Pop
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
343 47988778 Iustin Pop
  text = "\n".join(new_text)
344 a8083063 Iustin Pop
  try:
345 3023170f Iustin Pop
    f = file("/dev/tty", "a+")
346 a8083063 Iustin Pop
  except IOError:
347 47988778 Iustin Pop
    return answer
348 a8083063 Iustin Pop
  try:
349 47988778 Iustin Pop
    chars = [entry[0] for entry in choices]
350 47988778 Iustin Pop
    chars[-1] = "[%s]" % chars[-1]
351 47988778 Iustin Pop
    chars.append('?')
352 47988778 Iustin Pop
    maps = dict([(entry[0], entry[1]) for entry in choices])
353 47988778 Iustin Pop
    while True:
354 47988778 Iustin Pop
      f.write(text)
355 47988778 Iustin Pop
      f.write('\n')
356 47988778 Iustin Pop
      f.write("/".join(chars))
357 47988778 Iustin Pop
      f.write(": ")
358 47988778 Iustin Pop
      line = f.readline(2).strip().lower()
359 47988778 Iustin Pop
      if line in maps:
360 47988778 Iustin Pop
        answer = maps[line]
361 47988778 Iustin Pop
        break
362 47988778 Iustin Pop
      elif line == '?':
363 47988778 Iustin Pop
        for entry in choices:
364 47988778 Iustin Pop
          f.write(" %s - %s\n" % (entry[0], entry[2]))
365 47988778 Iustin Pop
        f.write("\n")
366 47988778 Iustin Pop
        continue
367 a8083063 Iustin Pop
  finally:
368 a8083063 Iustin Pop
    f.close()
369 a8083063 Iustin Pop
  return answer
370 a8083063 Iustin Pop
371 a8083063 Iustin Pop
372 e2212007 Iustin Pop
def SubmitOpCode(op, cl=None, feedback_fn=None):
373 0bbe448c Michael Hanselmann
  """Legacy function to submit an opcode.
374 a8083063 Iustin Pop

375 a8083063 Iustin Pop
  This is just a simple wrapper over the construction of the processor
376 a8083063 Iustin Pop
  instance. It should be extended to better handle feedback and
377 a8083063 Iustin Pop
  interaction functions.
378 a8083063 Iustin Pop

379 a8083063 Iustin Pop
  """
380 e2212007 Iustin Pop
  if cl is None:
381 b33e986b Iustin Pop
    cl = GetClient()
382 685ee993 Iustin Pop
383 0bbe448c Michael Hanselmann
  job_id = cl.SubmitJob([op])
384 685ee993 Iustin Pop
385 f1048938 Iustin Pop
  lastmsg = None
386 685ee993 Iustin Pop
  while True:
387 f1048938 Iustin Pop
    jobs = cl.QueryJobs([job_id], ["status", "ticker"])
388 0bbe448c Michael Hanselmann
    if not jobs:
389 685ee993 Iustin Pop
      # job not found, go away!
390 0bbe448c Michael Hanselmann
      raise errors.JobLost("Job with id %s lost" % job_id)
391 685ee993 Iustin Pop
392 0bbe448c Michael Hanselmann
    # TODO: Handle canceled and archived jobs
393 0bbe448c Michael Hanselmann
    status = jobs[0][0]
394 0bbe448c Michael Hanselmann
    if status in (constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR):
395 685ee993 Iustin Pop
      break
396 f1048938 Iustin Pop
    msg = jobs[0][1]
397 f1048938 Iustin Pop
    if msg is not None and msg != lastmsg:
398 e2212007 Iustin Pop
      if callable(feedback_fn):
399 e2212007 Iustin Pop
        feedback_fn(msg)
400 e2212007 Iustin Pop
      else:
401 e2212007 Iustin Pop
        print "%s %s" % (time.ctime(msg[0]), msg[2])
402 f1048938 Iustin Pop
    lastmsg = msg
403 685ee993 Iustin Pop
    time.sleep(1)
404 685ee993 Iustin Pop
405 af30b2fd Michael Hanselmann
  jobs = cl.QueryJobs([job_id], ["status", "opresult"])
406 0bbe448c Michael Hanselmann
  if not jobs:
407 0bbe448c Michael Hanselmann
    raise errors.JobLost("Job with id %s lost" % job_id)
408 685ee993 Iustin Pop
409 0bbe448c Michael Hanselmann
  status, result = jobs[0]
410 0bbe448c Michael Hanselmann
  if status == constants.JOB_STATUS_SUCCESS:
411 0bbe448c Michael Hanselmann
    return result[0]
412 0bbe448c Michael Hanselmann
  else:
413 0bbe448c Michael Hanselmann
    raise errors.OpExecError(result)
414 ceab32dd Iustin Pop
415 ceab32dd Iustin Pop
416 af30b2fd Michael Hanselmann
def GetClient():
417 af30b2fd Michael Hanselmann
  # TODO: Cache object?
418 b33e986b Iustin Pop
  try:
419 b33e986b Iustin Pop
    client = luxi.Client()
420 b33e986b Iustin Pop
  except luxi.NoMasterError:
421 b33e986b Iustin Pop
    master, myself = ssconf.GetMasterAndMyself()
422 b33e986b Iustin Pop
    if master != myself:
423 b33e986b Iustin Pop
      raise errors.OpPrereqError("This is not the master node, please connect"
424 b33e986b Iustin Pop
                                 " to node '%s' and rerun the command" %
425 b33e986b Iustin Pop
                                 master)
426 b33e986b Iustin Pop
    else:
427 b33e986b Iustin Pop
      raise
428 b33e986b Iustin Pop
  return client
429 af30b2fd Michael Hanselmann
430 af30b2fd Michael Hanselmann
431 73702ee7 Iustin Pop
def FormatError(err):
432 73702ee7 Iustin Pop
  """Return a formatted error message for a given error.
433 73702ee7 Iustin Pop

434 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
435 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
436 73702ee7 Iustin Pop
  second, a string describing the error message (not
437 73702ee7 Iustin Pop
  newline-terminated).
438 73702ee7 Iustin Pop

439 73702ee7 Iustin Pop
  """
440 73702ee7 Iustin Pop
  retcode = 1
441 73702ee7 Iustin Pop
  obuf = StringIO()
442 e2e521d0 Iustin Pop
  msg = str(err)
443 73702ee7 Iustin Pop
  if isinstance(err, errors.ConfigurationError):
444 e2e521d0 Iustin Pop
    txt = "Corrupt configuration file: %s" % msg
445 e2e521d0 Iustin Pop
    logger.Error(txt)
446 e2e521d0 Iustin Pop
    obuf.write(txt + "\n")
447 73702ee7 Iustin Pop
    obuf.write("Aborting.")
448 73702ee7 Iustin Pop
    retcode = 2
449 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksAbort):
450 73702ee7 Iustin Pop
    obuf.write("Failure: hooks execution failed:\n")
451 73702ee7 Iustin Pop
    for node, script, out in err.args[0]:
452 73702ee7 Iustin Pop
      if out:
453 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s, output: %s\n" %
454 73702ee7 Iustin Pop
                   (node, script, out))
455 73702ee7 Iustin Pop
      else:
456 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s (no output)\n" %
457 73702ee7 Iustin Pop
                   (node, script))
458 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksFailure):
459 e2e521d0 Iustin Pop
    obuf.write("Failure: hooks general failure: %s" % msg)
460 73702ee7 Iustin Pop
  elif isinstance(err, errors.ResolverError):
461 73702ee7 Iustin Pop
    this_host = utils.HostInfo.SysName()
462 73702ee7 Iustin Pop
    if err.args[0] == this_host:
463 73702ee7 Iustin Pop
      msg = "Failure: can't resolve my own hostname ('%s')"
464 73702ee7 Iustin Pop
    else:
465 73702ee7 Iustin Pop
      msg = "Failure: can't resolve hostname '%s'"
466 73702ee7 Iustin Pop
    obuf.write(msg % err.args[0])
467 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpPrereqError):
468 73702ee7 Iustin Pop
    obuf.write("Failure: prerequisites not met for this"
469 e2e521d0 Iustin Pop
               " operation:\n%s" % msg)
470 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpExecError):
471 e2e521d0 Iustin Pop
    obuf.write("Failure: command execution error:\n%s" % msg)
472 73702ee7 Iustin Pop
  elif isinstance(err, errors.TagError):
473 e2e521d0 Iustin Pop
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
474 73702ee7 Iustin Pop
  elif isinstance(err, errors.GenericError):
475 e2e521d0 Iustin Pop
    obuf.write("Unhandled Ganeti error: %s" % msg)
476 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.NoMasterError):
477 03a8dbdc Iustin Pop
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
478 03a8dbdc Iustin Pop
               " and listening on '%s'?" % err.args[0])
479 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.TimeoutError):
480 03a8dbdc Iustin Pop
    obuf.write("Timeout while talking to the master daemon. Error:\n"
481 03a8dbdc Iustin Pop
               "%s" % msg)
482 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.ProtocolError):
483 03a8dbdc Iustin Pop
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
484 03a8dbdc Iustin Pop
               "%s" % msg)
485 73702ee7 Iustin Pop
  else:
486 e2e521d0 Iustin Pop
    obuf.write("Unhandled exception: %s" % msg)
487 73702ee7 Iustin Pop
  return retcode, obuf.getvalue().rstrip('\n')
488 73702ee7 Iustin Pop
489 73702ee7 Iustin Pop
490 de47cf8f Guido Trotter
def GenericMain(commands, override=None, aliases=None):
491 a8083063 Iustin Pop
  """Generic main function for all the gnt-* commands.
492 a8083063 Iustin Pop

493 334d1483 Iustin Pop
  Arguments:
494 334d1483 Iustin Pop
    - commands: a dictionary with a special structure, see the design doc
495 334d1483 Iustin Pop
                for command line handling.
496 334d1483 Iustin Pop
    - override: if not None, we expect a dictionary with keys that will
497 334d1483 Iustin Pop
                override command line options; this can be used to pass
498 334d1483 Iustin Pop
                options from the scripts to generic functions
499 de47cf8f Guido Trotter
    - aliases: dictionary with command aliases {'alias': 'target, ...}
500 a8083063 Iustin Pop

501 a8083063 Iustin Pop
  """
502 a8083063 Iustin Pop
  # save the program name and the entire command line for later logging
503 a8083063 Iustin Pop
  if sys.argv:
504 a8083063 Iustin Pop
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
505 a8083063 Iustin Pop
    if len(sys.argv) >= 2:
506 a8083063 Iustin Pop
      binary += " " + sys.argv[1]
507 a8083063 Iustin Pop
      old_cmdline = " ".join(sys.argv[2:])
508 a8083063 Iustin Pop
    else:
509 a8083063 Iustin Pop
      old_cmdline = ""
510 a8083063 Iustin Pop
  else:
511 a8083063 Iustin Pop
    binary = "<unknown program>"
512 a8083063 Iustin Pop
    old_cmdline = ""
513 a8083063 Iustin Pop
514 de47cf8f Guido Trotter
  if aliases is None:
515 de47cf8f Guido Trotter
    aliases = {}
516 de47cf8f Guido Trotter
517 de47cf8f Guido Trotter
  func, options, args = _ParseArgs(sys.argv, commands, aliases)
518 a8083063 Iustin Pop
  if func is None: # parse error
519 a8083063 Iustin Pop
    return 1
520 a8083063 Iustin Pop
521 334d1483 Iustin Pop
  if override is not None:
522 334d1483 Iustin Pop
    for key, val in override.iteritems():
523 334d1483 Iustin Pop
      setattr(options, key, val)
524 334d1483 Iustin Pop
525 59f187eb Iustin Pop
  logger.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
526 59f187eb Iustin Pop
                      stderr_logging=True, program=binary)
527 a8083063 Iustin Pop
528 f362096f Iustin Pop
  utils.debug = options.debug
529 a8083063 Iustin Pop
530 a8083063 Iustin Pop
  if old_cmdline:
531 a8083063 Iustin Pop
    logger.Info("run with arguments '%s'" % old_cmdline)
532 a8083063 Iustin Pop
  else:
533 a8083063 Iustin Pop
    logger.Info("run with no arguments")
534 a8083063 Iustin Pop
535 a8083063 Iustin Pop
  try:
536 a4af651e Iustin Pop
    result = func(options, args)
537 03a8dbdc Iustin Pop
  except (errors.GenericError, luxi.ProtocolError), err:
538 a4af651e Iustin Pop
    result, err_msg = FormatError(err)
539 a4af651e Iustin Pop
    logger.ToStderr(err_msg)
540 a8083063 Iustin Pop
541 a8083063 Iustin Pop
  return result
542 137161c9 Michael Hanselmann
543 137161c9 Michael Hanselmann
544 16be8703 Iustin Pop
def GenerateTable(headers, fields, separator, data,
545 16be8703 Iustin Pop
                  numfields=None, unitfields=None):
546 137161c9 Michael Hanselmann
  """Prints a table with headers and different fields.
547 137161c9 Michael Hanselmann

548 137161c9 Michael Hanselmann
  Args:
549 137161c9 Michael Hanselmann
    headers: Dict of header titles or None if no headers should be shown
550 137161c9 Michael Hanselmann
    fields: List of fields to show
551 137161c9 Michael Hanselmann
    separator: String used to separate fields or None for spaces
552 137161c9 Michael Hanselmann
    data: Data to be printed
553 137161c9 Michael Hanselmann
    numfields: List of fields to be aligned to right
554 137161c9 Michael Hanselmann
    unitfields: List of fields to be formatted as units
555 137161c9 Michael Hanselmann

556 137161c9 Michael Hanselmann
  """
557 137161c9 Michael Hanselmann
  if numfields is None:
558 137161c9 Michael Hanselmann
    numfields = []
559 137161c9 Michael Hanselmann
  if unitfields is None:
560 137161c9 Michael Hanselmann
    unitfields = []
561 137161c9 Michael Hanselmann
562 137161c9 Michael Hanselmann
  format_fields = []
563 137161c9 Michael Hanselmann
  for field in fields:
564 01ca31ae Iustin Pop
    if headers and field not in headers:
565 01ca31ae Iustin Pop
      raise errors.ProgrammerError("Missing header description for field '%s'"
566 01ca31ae Iustin Pop
                                   % field)
567 137161c9 Michael Hanselmann
    if separator is not None:
568 137161c9 Michael Hanselmann
      format_fields.append("%s")
569 137161c9 Michael Hanselmann
    elif field in numfields:
570 137161c9 Michael Hanselmann
      format_fields.append("%*s")
571 137161c9 Michael Hanselmann
    else:
572 137161c9 Michael Hanselmann
      format_fields.append("%-*s")
573 137161c9 Michael Hanselmann
574 137161c9 Michael Hanselmann
  if separator is None:
575 137161c9 Michael Hanselmann
    mlens = [0 for name in fields]
576 137161c9 Michael Hanselmann
    format = ' '.join(format_fields)
577 137161c9 Michael Hanselmann
  else:
578 137161c9 Michael Hanselmann
    format = separator.replace("%", "%%").join(format_fields)
579 137161c9 Michael Hanselmann
580 137161c9 Michael Hanselmann
  for row in data:
581 137161c9 Michael Hanselmann
    for idx, val in enumerate(row):
582 137161c9 Michael Hanselmann
      if fields[idx] in unitfields:
583 137161c9 Michael Hanselmann
        try:
584 137161c9 Michael Hanselmann
          val = int(val)
585 137161c9 Michael Hanselmann
        except ValueError:
586 137161c9 Michael Hanselmann
          pass
587 137161c9 Michael Hanselmann
        else:
588 137161c9 Michael Hanselmann
          val = row[idx] = utils.FormatUnit(val)
589 01ca31ae Iustin Pop
      val = row[idx] = str(val)
590 137161c9 Michael Hanselmann
      if separator is None:
591 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(val))
592 137161c9 Michael Hanselmann
593 16be8703 Iustin Pop
  result = []
594 137161c9 Michael Hanselmann
  if headers:
595 137161c9 Michael Hanselmann
    args = []
596 137161c9 Michael Hanselmann
    for idx, name in enumerate(fields):
597 137161c9 Michael Hanselmann
      hdr = headers[name]
598 137161c9 Michael Hanselmann
      if separator is None:
599 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(hdr))
600 137161c9 Michael Hanselmann
        args.append(mlens[idx])
601 137161c9 Michael Hanselmann
      args.append(hdr)
602 16be8703 Iustin Pop
    result.append(format % tuple(args))
603 137161c9 Michael Hanselmann
604 137161c9 Michael Hanselmann
  for line in data:
605 137161c9 Michael Hanselmann
    args = []
606 137161c9 Michael Hanselmann
    for idx in xrange(len(fields)):
607 137161c9 Michael Hanselmann
      if separator is None:
608 137161c9 Michael Hanselmann
        args.append(mlens[idx])
609 137161c9 Michael Hanselmann
      args.append(line[idx])
610 16be8703 Iustin Pop
    result.append(format % tuple(args))
611 16be8703 Iustin Pop
612 16be8703 Iustin Pop
  return result