Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ a1578d63

History | View | Annotate | Download (20.7 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 e9d741b6 Iustin Pop
           "JobSubmittedException",
51 846baef9 Iustin Pop
           ]
52 846baef9 Iustin Pop
53 846baef9 Iustin Pop
54 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
55 846baef9 Iustin Pop
  """Extract the tag type object.
56 846baef9 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

381 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
382 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
383 e9d741b6 Iustin Pop

384 e9d741b6 Iustin Pop
  This is not an error, just a structured way to exit from clients.
385 e9d741b6 Iustin Pop

386 e9d741b6 Iustin Pop
  """
387 e9d741b6 Iustin Pop
388 e9d741b6 Iustin Pop
389 0a1e74d9 Iustin Pop
def SendJob(ops, cl=None):
390 0a1e74d9 Iustin Pop
  """Function to submit an opcode without waiting for the results.
391 a8083063 Iustin Pop

392 0a1e74d9 Iustin Pop
  @type ops: list
393 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
394 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
395 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
396 0a1e74d9 Iustin Pop
             if None, a new client will be created
397 a8083063 Iustin Pop

398 a8083063 Iustin Pop
  """
399 e2212007 Iustin Pop
  if cl is None:
400 b33e986b Iustin Pop
    cl = GetClient()
401 685ee993 Iustin Pop
402 0a1e74d9 Iustin Pop
  job_id = cl.SubmitJob(ops)
403 0a1e74d9 Iustin Pop
404 0a1e74d9 Iustin Pop
  return job_id
405 0a1e74d9 Iustin Pop
406 0a1e74d9 Iustin Pop
407 281606c1 Michael Hanselmann
def PollJob(job_id, cl=None, feedback_fn=None):
408 0a1e74d9 Iustin Pop
  """Function to poll for the result of a job.
409 0a1e74d9 Iustin Pop

410 0a1e74d9 Iustin Pop
  @type job_id: job identified
411 0a1e74d9 Iustin Pop
  @param job_id: the job to poll for results
412 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
413 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
414 0a1e74d9 Iustin Pop
             if None, a new client will be created
415 0a1e74d9 Iustin Pop

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

463 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
464 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
465 0a1e74d9 Iustin Pop
  interaction functions.
466 0a1e74d9 Iustin Pop

467 0a1e74d9 Iustin Pop
  """
468 0a1e74d9 Iustin Pop
  if cl is None:
469 0a1e74d9 Iustin Pop
    cl = GetClient()
470 0a1e74d9 Iustin Pop
471 0a1e74d9 Iustin Pop
  job_id = SendJob([op], cl)
472 0a1e74d9 Iustin Pop
473 281606c1 Michael Hanselmann
  return PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
474 0a1e74d9 Iustin Pop
475 0a1e74d9 Iustin Pop
476 94428652 Iustin Pop
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
477 94428652 Iustin Pop
  """Wrapper around SubmitOpCode or SendJob.
478 94428652 Iustin Pop

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

484 94428652 Iustin Pop
  """
485 94428652 Iustin Pop
  if opts and opts.submit_only:
486 e9d741b6 Iustin Pop
    job_id = SendJob([op], cl=cl)
487 e9d741b6 Iustin Pop
    raise JobSubmittedException(job_id)
488 94428652 Iustin Pop
  else:
489 94428652 Iustin Pop
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
490 94428652 Iustin Pop
491 94428652 Iustin Pop
492 af30b2fd Michael Hanselmann
def GetClient():
493 af30b2fd Michael Hanselmann
  # TODO: Cache object?
494 b33e986b Iustin Pop
  try:
495 b33e986b Iustin Pop
    client = luxi.Client()
496 b33e986b Iustin Pop
  except luxi.NoMasterError:
497 b33e986b Iustin Pop
    master, myself = ssconf.GetMasterAndMyself()
498 b33e986b Iustin Pop
    if master != myself:
499 b33e986b Iustin Pop
      raise errors.OpPrereqError("This is not the master node, please connect"
500 b33e986b Iustin Pop
                                 " to node '%s' and rerun the command" %
501 b33e986b Iustin Pop
                                 master)
502 b33e986b Iustin Pop
    else:
503 b33e986b Iustin Pop
      raise
504 b33e986b Iustin Pop
  return client
505 af30b2fd Michael Hanselmann
506 af30b2fd Michael Hanselmann
507 73702ee7 Iustin Pop
def FormatError(err):
508 73702ee7 Iustin Pop
  """Return a formatted error message for a given error.
509 73702ee7 Iustin Pop

510 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
511 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
512 73702ee7 Iustin Pop
  second, a string describing the error message (not
513 73702ee7 Iustin Pop
  newline-terminated).
514 73702ee7 Iustin Pop

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

572 334d1483 Iustin Pop
  Arguments:
573 334d1483 Iustin Pop
    - commands: a dictionary with a special structure, see the design doc
574 334d1483 Iustin Pop
                for command line handling.
575 334d1483 Iustin Pop
    - override: if not None, we expect a dictionary with keys that will
576 334d1483 Iustin Pop
                override command line options; this can be used to pass
577 334d1483 Iustin Pop
                options from the scripts to generic functions
578 de47cf8f Guido Trotter
    - aliases: dictionary with command aliases {'alias': 'target, ...}
579 a8083063 Iustin Pop

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

627 137161c9 Michael Hanselmann
  Args:
628 137161c9 Michael Hanselmann
    headers: Dict of header titles or None if no headers should be shown
629 137161c9 Michael Hanselmann
    fields: List of fields to show
630 137161c9 Michael Hanselmann
    separator: String used to separate fields or None for spaces
631 137161c9 Michael Hanselmann
    data: Data to be printed
632 137161c9 Michael Hanselmann
    numfields: List of fields to be aligned to right
633 137161c9 Michael Hanselmann
    unitfields: List of fields to be formatted as units
634 137161c9 Michael Hanselmann

635 137161c9 Michael Hanselmann
  """
636 137161c9 Michael Hanselmann
  if numfields is None:
637 137161c9 Michael Hanselmann
    numfields = []
638 137161c9 Michael Hanselmann
  if unitfields is None:
639 137161c9 Michael Hanselmann
    unitfields = []
640 137161c9 Michael Hanselmann
641 137161c9 Michael Hanselmann
  format_fields = []
642 137161c9 Michael Hanselmann
  for field in fields:
643 01ca31ae Iustin Pop
    if headers and field not in headers:
644 01ca31ae Iustin Pop
      raise errors.ProgrammerError("Missing header description for field '%s'"
645 01ca31ae Iustin Pop
                                   % field)
646 137161c9 Michael Hanselmann
    if separator is not None:
647 137161c9 Michael Hanselmann
      format_fields.append("%s")
648 137161c9 Michael Hanselmann
    elif field in numfields:
649 137161c9 Michael Hanselmann
      format_fields.append("%*s")
650 137161c9 Michael Hanselmann
    else:
651 137161c9 Michael Hanselmann
      format_fields.append("%-*s")
652 137161c9 Michael Hanselmann
653 137161c9 Michael Hanselmann
  if separator is None:
654 137161c9 Michael Hanselmann
    mlens = [0 for name in fields]
655 137161c9 Michael Hanselmann
    format = ' '.join(format_fields)
656 137161c9 Michael Hanselmann
  else:
657 137161c9 Michael Hanselmann
    format = separator.replace("%", "%%").join(format_fields)
658 137161c9 Michael Hanselmann
659 137161c9 Michael Hanselmann
  for row in data:
660 137161c9 Michael Hanselmann
    for idx, val in enumerate(row):
661 137161c9 Michael Hanselmann
      if fields[idx] in unitfields:
662 137161c9 Michael Hanselmann
        try:
663 137161c9 Michael Hanselmann
          val = int(val)
664 137161c9 Michael Hanselmann
        except ValueError:
665 137161c9 Michael Hanselmann
          pass
666 137161c9 Michael Hanselmann
        else:
667 137161c9 Michael Hanselmann
          val = row[idx] = utils.FormatUnit(val)
668 01ca31ae Iustin Pop
      val = row[idx] = str(val)
669 137161c9 Michael Hanselmann
      if separator is None:
670 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(val))
671 137161c9 Michael Hanselmann
672 16be8703 Iustin Pop
  result = []
673 137161c9 Michael Hanselmann
  if headers:
674 137161c9 Michael Hanselmann
    args = []
675 137161c9 Michael Hanselmann
    for idx, name in enumerate(fields):
676 137161c9 Michael Hanselmann
      hdr = headers[name]
677 137161c9 Michael Hanselmann
      if separator is None:
678 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(hdr))
679 137161c9 Michael Hanselmann
        args.append(mlens[idx])
680 137161c9 Michael Hanselmann
      args.append(hdr)
681 16be8703 Iustin Pop
    result.append(format % tuple(args))
682 137161c9 Michael Hanselmann
683 137161c9 Michael Hanselmann
  for line in data:
684 137161c9 Michael Hanselmann
    args = []
685 137161c9 Michael Hanselmann
    for idx in xrange(len(fields)):
686 137161c9 Michael Hanselmann
      if separator is None:
687 137161c9 Michael Hanselmann
        args.append(mlens[idx])
688 137161c9 Michael Hanselmann
      args.append(line[idx])
689 16be8703 Iustin Pop
    result.append(format % tuple(args))
690 16be8703 Iustin Pop
691 16be8703 Iustin Pop
  return result