Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ b91a34a5

History | View | Annotate | Download (20.3 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 94428652 Iustin Pop
           "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT", "SUBMIT_OPT",
48 810c50b7 Iustin Pop
           "ListTags", "AddTags", "RemoveTags", "TAG_SRC_OPT",
49 94428652 Iustin Pop
           "FormatError", "SplitNodeOption", "SubmitOrSend",
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 94428652 Iustin Pop
SUBMIT_OPT = make_option("--submit", dest="submit_only",
184 94428652 Iustin Pop
                         default=False, action="store_true",
185 94428652 Iustin Pop
                         help="Submit the job and return the job ID, but"
186 94428652 Iustin Pop
                         " don't wait for the job to finish")
187 94428652 Iustin Pop
188 60d49723 Michael Hanselmann
189 a8083063 Iustin Pop
def ARGS_FIXED(val):
190 a8083063 Iustin Pop
  """Macro-like function denoting a fixed number of arguments"""
191 a8083063 Iustin Pop
  return -val
192 a8083063 Iustin Pop
193 a8083063 Iustin Pop
194 a8083063 Iustin Pop
def ARGS_ATLEAST(val):
195 a8083063 Iustin Pop
  """Macro-like function denoting a minimum number of arguments"""
196 a8083063 Iustin Pop
  return val
197 a8083063 Iustin Pop
198 a8083063 Iustin Pop
199 a8083063 Iustin Pop
ARGS_NONE = None
200 a8083063 Iustin Pop
ARGS_ONE = ARGS_FIXED(1)
201 a8083063 Iustin Pop
ARGS_ANY = ARGS_ATLEAST(0)
202 a8083063 Iustin Pop
203 a8083063 Iustin Pop
204 a8083063 Iustin Pop
def check_unit(option, opt, value):
205 65fe4693 Iustin Pop
  """OptParsers custom converter for units.
206 65fe4693 Iustin Pop

207 65fe4693 Iustin Pop
  """
208 a8083063 Iustin Pop
  try:
209 a8083063 Iustin Pop
    return utils.ParseUnit(value)
210 a8083063 Iustin Pop
  except errors.UnitParseError, err:
211 3ecf6786 Iustin Pop
    raise OptionValueError("option %s: %s" % (opt, err))
212 a8083063 Iustin Pop
213 a8083063 Iustin Pop
214 a8083063 Iustin Pop
class CliOption(Option):
215 65fe4693 Iustin Pop
  """Custom option class for optparse.
216 65fe4693 Iustin Pop

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

231 a8083063 Iustin Pop
  Arguments:
232 a8083063 Iustin Pop
    argv: the command line
233 a8083063 Iustin Pop

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

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

312 60d49723 Michael Hanselmann
  """
313 60d49723 Michael Hanselmann
  if value and ':' in value:
314 60d49723 Michael Hanselmann
    return value.split(':', 1)
315 60d49723 Michael Hanselmann
  else:
316 60d49723 Michael Hanselmann
    return (value, None)
317 60d49723 Michael Hanselmann
318 60d49723 Michael Hanselmann
319 47988778 Iustin Pop
def AskUser(text, choices=None):
320 47988778 Iustin Pop
  """Ask the user a question.
321 a8083063 Iustin Pop

322 a8083063 Iustin Pop
  Args:
323 47988778 Iustin Pop
    text - the question to ask.
324 a8083063 Iustin Pop

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

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

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

380 0a1e74d9 Iustin Pop
  @type ops: list
381 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
382 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
383 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
384 0a1e74d9 Iustin Pop
             if None, a new client will be created
385 a8083063 Iustin Pop

386 a8083063 Iustin Pop
  """
387 e2212007 Iustin Pop
  if cl is None:
388 b33e986b Iustin Pop
    cl = GetClient()
389 685ee993 Iustin Pop
390 0a1e74d9 Iustin Pop
  job_id = cl.SubmitJob(ops)
391 0a1e74d9 Iustin Pop
392 0a1e74d9 Iustin Pop
  return job_id
393 0a1e74d9 Iustin Pop
394 0a1e74d9 Iustin Pop
395 281606c1 Michael Hanselmann
def PollJob(job_id, cl=None, feedback_fn=None):
396 0a1e74d9 Iustin Pop
  """Function to poll for the result of a job.
397 0a1e74d9 Iustin Pop

398 0a1e74d9 Iustin Pop
  @type job_id: job identified
399 0a1e74d9 Iustin Pop
  @param job_id: the job to poll for results
400 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
401 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
402 0a1e74d9 Iustin Pop
             if None, a new client will be created
403 0a1e74d9 Iustin Pop

404 0a1e74d9 Iustin Pop
  """
405 0a1e74d9 Iustin Pop
  if cl is None:
406 0a1e74d9 Iustin Pop
    cl = GetClient()
407 685ee993 Iustin Pop
408 6c5a7090 Michael Hanselmann
  prev_job_info = None
409 6c5a7090 Michael Hanselmann
  prev_logmsg_serial = None
410 6c5a7090 Michael Hanselmann
411 685ee993 Iustin Pop
  while True:
412 6c5a7090 Michael Hanselmann
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
413 6c5a7090 Michael Hanselmann
                                 prev_logmsg_serial)
414 6c5a7090 Michael Hanselmann
    if not result:
415 685ee993 Iustin Pop
      # job not found, go away!
416 0bbe448c Michael Hanselmann
      raise errors.JobLost("Job with id %s lost" % job_id)
417 685ee993 Iustin Pop
418 6c5a7090 Michael Hanselmann
    # Split result, a tuple of (field values, log entries)
419 6c5a7090 Michael Hanselmann
    (job_info, log_entries) = result
420 6c5a7090 Michael Hanselmann
    (status, ) = job_info
421 6c5a7090 Michael Hanselmann
422 6c5a7090 Michael Hanselmann
    if log_entries:
423 6c5a7090 Michael Hanselmann
      for log_entry in log_entries:
424 6c5a7090 Michael Hanselmann
        (serial, timestamp, _, message) = log_entry
425 6c5a7090 Michael Hanselmann
        if callable(feedback_fn):
426 6c5a7090 Michael Hanselmann
          feedback_fn(log_entry[1:])
427 6c5a7090 Michael Hanselmann
        else:
428 6c5a7090 Michael Hanselmann
          print "%s %s" % (time.ctime(utils.MergeTime(timestamp)), message)
429 6c5a7090 Michael Hanselmann
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
430 6c5a7090 Michael Hanselmann
431 0bbe448c Michael Hanselmann
    # TODO: Handle canceled and archived jobs
432 6c5a7090 Michael Hanselmann
    elif status in (constants.JOB_STATUS_SUCCESS, constants.JOB_STATUS_ERROR):
433 685ee993 Iustin Pop
      break
434 6c5a7090 Michael Hanselmann
435 6c5a7090 Michael Hanselmann
    prev_job_info = job_info
436 685ee993 Iustin Pop
437 af30b2fd Michael Hanselmann
  jobs = cl.QueryJobs([job_id], ["status", "opresult"])
438 0bbe448c Michael Hanselmann
  if not jobs:
439 0bbe448c Michael Hanselmann
    raise errors.JobLost("Job with id %s lost" % job_id)
440 685ee993 Iustin Pop
441 0bbe448c Michael Hanselmann
  status, result = jobs[0]
442 0bbe448c Michael Hanselmann
  if status == constants.JOB_STATUS_SUCCESS:
443 0bbe448c Michael Hanselmann
    return result[0]
444 0bbe448c Michael Hanselmann
  else:
445 0bbe448c Michael Hanselmann
    raise errors.OpExecError(result)
446 ceab32dd Iustin Pop
447 ceab32dd Iustin Pop
448 0a1e74d9 Iustin Pop
def SubmitOpCode(op, cl=None, feedback_fn=None):
449 0a1e74d9 Iustin Pop
  """Legacy function to submit an opcode.
450 0a1e74d9 Iustin Pop

451 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
452 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
453 0a1e74d9 Iustin Pop
  interaction functions.
454 0a1e74d9 Iustin Pop

455 0a1e74d9 Iustin Pop
  """
456 0a1e74d9 Iustin Pop
  if cl is None:
457 0a1e74d9 Iustin Pop
    cl = GetClient()
458 0a1e74d9 Iustin Pop
459 0a1e74d9 Iustin Pop
  job_id = SendJob([op], cl)
460 0a1e74d9 Iustin Pop
461 281606c1 Michael Hanselmann
  return PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
462 0a1e74d9 Iustin Pop
463 0a1e74d9 Iustin Pop
464 94428652 Iustin Pop
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
465 94428652 Iustin Pop
  """Wrapper around SubmitOpCode or SendJob.
466 94428652 Iustin Pop

467 94428652 Iustin Pop
  This function will decide, based on the 'opts' parameter, whether to
468 94428652 Iustin Pop
  submit and wait for the result of the opcode (and return it), or
469 94428652 Iustin Pop
  whether to just send the job and print its identifier. It is used in
470 94428652 Iustin Pop
  order to simplify the implementation of the '--submit' option.
471 94428652 Iustin Pop

472 94428652 Iustin Pop
  """
473 94428652 Iustin Pop
  if opts and opts.submit_only:
474 94428652 Iustin Pop
    print SendJob([op], cl=cl)
475 94428652 Iustin Pop
    sys.exit(0)
476 94428652 Iustin Pop
  else:
477 94428652 Iustin Pop
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
478 94428652 Iustin Pop
479 94428652 Iustin Pop
480 af30b2fd Michael Hanselmann
def GetClient():
481 af30b2fd Michael Hanselmann
  # TODO: Cache object?
482 b33e986b Iustin Pop
  try:
483 b33e986b Iustin Pop
    client = luxi.Client()
484 b33e986b Iustin Pop
  except luxi.NoMasterError:
485 b33e986b Iustin Pop
    master, myself = ssconf.GetMasterAndMyself()
486 b33e986b Iustin Pop
    if master != myself:
487 b33e986b Iustin Pop
      raise errors.OpPrereqError("This is not the master node, please connect"
488 b33e986b Iustin Pop
                                 " to node '%s' and rerun the command" %
489 b33e986b Iustin Pop
                                 master)
490 b33e986b Iustin Pop
    else:
491 b33e986b Iustin Pop
      raise
492 b33e986b Iustin Pop
  return client
493 af30b2fd Michael Hanselmann
494 af30b2fd Michael Hanselmann
495 73702ee7 Iustin Pop
def FormatError(err):
496 73702ee7 Iustin Pop
  """Return a formatted error message for a given error.
497 73702ee7 Iustin Pop

498 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
499 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
500 73702ee7 Iustin Pop
  second, a string describing the error message (not
501 73702ee7 Iustin Pop
  newline-terminated).
502 73702ee7 Iustin Pop

503 73702ee7 Iustin Pop
  """
504 73702ee7 Iustin Pop
  retcode = 1
505 73702ee7 Iustin Pop
  obuf = StringIO()
506 e2e521d0 Iustin Pop
  msg = str(err)
507 73702ee7 Iustin Pop
  if isinstance(err, errors.ConfigurationError):
508 e2e521d0 Iustin Pop
    txt = "Corrupt configuration file: %s" % msg
509 e2e521d0 Iustin Pop
    logger.Error(txt)
510 e2e521d0 Iustin Pop
    obuf.write(txt + "\n")
511 73702ee7 Iustin Pop
    obuf.write("Aborting.")
512 73702ee7 Iustin Pop
    retcode = 2
513 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksAbort):
514 73702ee7 Iustin Pop
    obuf.write("Failure: hooks execution failed:\n")
515 73702ee7 Iustin Pop
    for node, script, out in err.args[0]:
516 73702ee7 Iustin Pop
      if out:
517 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s, output: %s\n" %
518 73702ee7 Iustin Pop
                   (node, script, out))
519 73702ee7 Iustin Pop
      else:
520 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s (no output)\n" %
521 73702ee7 Iustin Pop
                   (node, script))
522 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksFailure):
523 e2e521d0 Iustin Pop
    obuf.write("Failure: hooks general failure: %s" % msg)
524 73702ee7 Iustin Pop
  elif isinstance(err, errors.ResolverError):
525 73702ee7 Iustin Pop
    this_host = utils.HostInfo.SysName()
526 73702ee7 Iustin Pop
    if err.args[0] == this_host:
527 73702ee7 Iustin Pop
      msg = "Failure: can't resolve my own hostname ('%s')"
528 73702ee7 Iustin Pop
    else:
529 73702ee7 Iustin Pop
      msg = "Failure: can't resolve hostname '%s'"
530 73702ee7 Iustin Pop
    obuf.write(msg % err.args[0])
531 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpPrereqError):
532 73702ee7 Iustin Pop
    obuf.write("Failure: prerequisites not met for this"
533 e2e521d0 Iustin Pop
               " operation:\n%s" % msg)
534 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpExecError):
535 e2e521d0 Iustin Pop
    obuf.write("Failure: command execution error:\n%s" % msg)
536 73702ee7 Iustin Pop
  elif isinstance(err, errors.TagError):
537 e2e521d0 Iustin Pop
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
538 73702ee7 Iustin Pop
  elif isinstance(err, errors.GenericError):
539 e2e521d0 Iustin Pop
    obuf.write("Unhandled Ganeti error: %s" % msg)
540 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.NoMasterError):
541 03a8dbdc Iustin Pop
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
542 082c5adb Michael Hanselmann
               " and listening for connections?")
543 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.TimeoutError):
544 03a8dbdc Iustin Pop
    obuf.write("Timeout while talking to the master daemon. Error:\n"
545 03a8dbdc Iustin Pop
               "%s" % msg)
546 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.ProtocolError):
547 03a8dbdc Iustin Pop
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
548 03a8dbdc Iustin Pop
               "%s" % msg)
549 73702ee7 Iustin Pop
  else:
550 e2e521d0 Iustin Pop
    obuf.write("Unhandled exception: %s" % msg)
551 73702ee7 Iustin Pop
  return retcode, obuf.getvalue().rstrip('\n')
552 73702ee7 Iustin Pop
553 73702ee7 Iustin Pop
554 de47cf8f Guido Trotter
def GenericMain(commands, override=None, aliases=None):
555 a8083063 Iustin Pop
  """Generic main function for all the gnt-* commands.
556 a8083063 Iustin Pop

557 334d1483 Iustin Pop
  Arguments:
558 334d1483 Iustin Pop
    - commands: a dictionary with a special structure, see the design doc
559 334d1483 Iustin Pop
                for command line handling.
560 334d1483 Iustin Pop
    - override: if not None, we expect a dictionary with keys that will
561 334d1483 Iustin Pop
                override command line options; this can be used to pass
562 334d1483 Iustin Pop
                options from the scripts to generic functions
563 de47cf8f Guido Trotter
    - aliases: dictionary with command aliases {'alias': 'target, ...}
564 a8083063 Iustin Pop

565 a8083063 Iustin Pop
  """
566 a8083063 Iustin Pop
  # save the program name and the entire command line for later logging
567 a8083063 Iustin Pop
  if sys.argv:
568 a8083063 Iustin Pop
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
569 a8083063 Iustin Pop
    if len(sys.argv) >= 2:
570 a8083063 Iustin Pop
      binary += " " + sys.argv[1]
571 a8083063 Iustin Pop
      old_cmdline = " ".join(sys.argv[2:])
572 a8083063 Iustin Pop
    else:
573 a8083063 Iustin Pop
      old_cmdline = ""
574 a8083063 Iustin Pop
  else:
575 a8083063 Iustin Pop
    binary = "<unknown program>"
576 a8083063 Iustin Pop
    old_cmdline = ""
577 a8083063 Iustin Pop
578 de47cf8f Guido Trotter
  if aliases is None:
579 de47cf8f Guido Trotter
    aliases = {}
580 de47cf8f Guido Trotter
581 de47cf8f Guido Trotter
  func, options, args = _ParseArgs(sys.argv, commands, aliases)
582 a8083063 Iustin Pop
  if func is None: # parse error
583 a8083063 Iustin Pop
    return 1
584 a8083063 Iustin Pop
585 334d1483 Iustin Pop
  if override is not None:
586 334d1483 Iustin Pop
    for key, val in override.iteritems():
587 334d1483 Iustin Pop
      setattr(options, key, val)
588 334d1483 Iustin Pop
589 59f187eb Iustin Pop
  logger.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
590 59f187eb Iustin Pop
                      stderr_logging=True, program=binary)
591 a8083063 Iustin Pop
592 f362096f Iustin Pop
  utils.debug = options.debug
593 a8083063 Iustin Pop
594 a8083063 Iustin Pop
  if old_cmdline:
595 a8083063 Iustin Pop
    logger.Info("run with arguments '%s'" % old_cmdline)
596 a8083063 Iustin Pop
  else:
597 a8083063 Iustin Pop
    logger.Info("run with no arguments")
598 a8083063 Iustin Pop
599 a8083063 Iustin Pop
  try:
600 a4af651e Iustin Pop
    result = func(options, args)
601 03a8dbdc Iustin Pop
  except (errors.GenericError, luxi.ProtocolError), err:
602 a4af651e Iustin Pop
    result, err_msg = FormatError(err)
603 a4af651e Iustin Pop
    logger.ToStderr(err_msg)
604 a8083063 Iustin Pop
605 a8083063 Iustin Pop
  return result
606 137161c9 Michael Hanselmann
607 137161c9 Michael Hanselmann
608 16be8703 Iustin Pop
def GenerateTable(headers, fields, separator, data,
609 16be8703 Iustin Pop
                  numfields=None, unitfields=None):
610 137161c9 Michael Hanselmann
  """Prints a table with headers and different fields.
611 137161c9 Michael Hanselmann

612 137161c9 Michael Hanselmann
  Args:
613 137161c9 Michael Hanselmann
    headers: Dict of header titles or None if no headers should be shown
614 137161c9 Michael Hanselmann
    fields: List of fields to show
615 137161c9 Michael Hanselmann
    separator: String used to separate fields or None for spaces
616 137161c9 Michael Hanselmann
    data: Data to be printed
617 137161c9 Michael Hanselmann
    numfields: List of fields to be aligned to right
618 137161c9 Michael Hanselmann
    unitfields: List of fields to be formatted as units
619 137161c9 Michael Hanselmann

620 137161c9 Michael Hanselmann
  """
621 137161c9 Michael Hanselmann
  if numfields is None:
622 137161c9 Michael Hanselmann
    numfields = []
623 137161c9 Michael Hanselmann
  if unitfields is None:
624 137161c9 Michael Hanselmann
    unitfields = []
625 137161c9 Michael Hanselmann
626 137161c9 Michael Hanselmann
  format_fields = []
627 137161c9 Michael Hanselmann
  for field in fields:
628 01ca31ae Iustin Pop
    if headers and field not in headers:
629 01ca31ae Iustin Pop
      raise errors.ProgrammerError("Missing header description for field '%s'"
630 01ca31ae Iustin Pop
                                   % field)
631 137161c9 Michael Hanselmann
    if separator is not None:
632 137161c9 Michael Hanselmann
      format_fields.append("%s")
633 137161c9 Michael Hanselmann
    elif field in numfields:
634 137161c9 Michael Hanselmann
      format_fields.append("%*s")
635 137161c9 Michael Hanselmann
    else:
636 137161c9 Michael Hanselmann
      format_fields.append("%-*s")
637 137161c9 Michael Hanselmann
638 137161c9 Michael Hanselmann
  if separator is None:
639 137161c9 Michael Hanselmann
    mlens = [0 for name in fields]
640 137161c9 Michael Hanselmann
    format = ' '.join(format_fields)
641 137161c9 Michael Hanselmann
  else:
642 137161c9 Michael Hanselmann
    format = separator.replace("%", "%%").join(format_fields)
643 137161c9 Michael Hanselmann
644 137161c9 Michael Hanselmann
  for row in data:
645 137161c9 Michael Hanselmann
    for idx, val in enumerate(row):
646 137161c9 Michael Hanselmann
      if fields[idx] in unitfields:
647 137161c9 Michael Hanselmann
        try:
648 137161c9 Michael Hanselmann
          val = int(val)
649 137161c9 Michael Hanselmann
        except ValueError:
650 137161c9 Michael Hanselmann
          pass
651 137161c9 Michael Hanselmann
        else:
652 137161c9 Michael Hanselmann
          val = row[idx] = utils.FormatUnit(val)
653 01ca31ae Iustin Pop
      val = row[idx] = str(val)
654 137161c9 Michael Hanselmann
      if separator is None:
655 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(val))
656 137161c9 Michael Hanselmann
657 16be8703 Iustin Pop
  result = []
658 137161c9 Michael Hanselmann
  if headers:
659 137161c9 Michael Hanselmann
    args = []
660 137161c9 Michael Hanselmann
    for idx, name in enumerate(fields):
661 137161c9 Michael Hanselmann
      hdr = headers[name]
662 137161c9 Michael Hanselmann
      if separator is None:
663 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(hdr))
664 137161c9 Michael Hanselmann
        args.append(mlens[idx])
665 137161c9 Michael Hanselmann
      args.append(hdr)
666 16be8703 Iustin Pop
    result.append(format % tuple(args))
667 137161c9 Michael Hanselmann
668 137161c9 Michael Hanselmann
  for line in data:
669 137161c9 Michael Hanselmann
    args = []
670 137161c9 Michael Hanselmann
    for idx in xrange(len(fields)):
671 137161c9 Michael Hanselmann
      if separator is None:
672 137161c9 Michael Hanselmann
        args.append(mlens[idx])
673 137161c9 Michael Hanselmann
      args.append(line[idx])
674 16be8703 Iustin Pop
    result.append(format % tuple(args))
675 16be8703 Iustin Pop
676 16be8703 Iustin Pop
  return result