Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ e6345c35

History | View | Annotate | Download (31.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 46fbdd04 Iustin Pop
import logging
31 73702ee7 Iustin Pop
from cStringIO import StringIO
32 a8083063 Iustin Pop
33 a8083063 Iustin Pop
from ganeti import utils
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 4331f6cd Michael Hanselmann
from ganeti import rpc
40 a8083063 Iustin Pop
41 a8083063 Iustin Pop
from optparse import (OptionParser, make_option, TitledHelpFormatter,
42 38206f3c Iustin Pop
                      Option, OptionValueError)
43 a8083063 Iustin Pop
44 73b90123 Michael Hanselmann
45 ceab32dd Iustin Pop
__all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain",
46 af30b2fd Michael Hanselmann
           "SubmitOpCode", "GetClient",
47 a8469393 Iustin Pop
           "cli_option", "ikv_option", "keyval_option",
48 a8469393 Iustin Pop
           "GenerateTable", "AskUser",
49 a8083063 Iustin Pop
           "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
50 94428652 Iustin Pop
           "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT", "SUBMIT_OPT",
51 810c50b7 Iustin Pop
           "ListTags", "AddTags", "RemoveTags", "TAG_SRC_OPT",
52 94428652 Iustin Pop
           "FormatError", "SplitNodeOption", "SubmitOrSend",
53 07cd723a Iustin Pop
           "JobSubmittedException", "FormatTimestamp", "ParseTimespec",
54 a5728081 Guido Trotter
           "ToStderr", "ToStdout", "UsesRPC",
55 ec79568d Iustin Pop
           "GetOnlineNodes", "JobExecutor", "SYNC_OPT",
56 846baef9 Iustin Pop
           ]
57 846baef9 Iustin Pop
58 846baef9 Iustin Pop
59 73b90123 Michael Hanselmann
60 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
61 846baef9 Iustin Pop
  """Extract the tag type object.
62 846baef9 Iustin Pop

63 846baef9 Iustin Pop
  Note that this function will modify its args parameter.
64 846baef9 Iustin Pop

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

84 810c50b7 Iustin Pop
  This function will extend the tags with the contents of the file
85 810c50b7 Iustin Pop
  passed in the 'tags_source' attribute of the opts parameter. A file
86 810c50b7 Iustin Pop
  named '-' will be replaced by stdin.
87 810c50b7 Iustin Pop

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

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

118 846baef9 Iustin Pop
  """
119 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
120 846baef9 Iustin Pop
  op = opcodes.OpGetTags(kind=kind, name=name)
121 846baef9 Iustin Pop
  result = SubmitOpCode(op)
122 846baef9 Iustin Pop
  result = list(result)
123 846baef9 Iustin Pop
  result.sort()
124 846baef9 Iustin Pop
  for tag in result:
125 73b90123 Michael Hanselmann
    ToStdout(tag)
126 846baef9 Iustin Pop
127 846baef9 Iustin Pop
128 846baef9 Iustin Pop
def AddTags(opts, args):
129 846baef9 Iustin Pop
  """Add tags on a given object.
130 846baef9 Iustin Pop

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

136 846baef9 Iustin Pop
  """
137 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
138 810c50b7 Iustin Pop
  _ExtendTags(opts, args)
139 846baef9 Iustin Pop
  if not args:
140 846baef9 Iustin Pop
    raise errors.OpPrereqError("No tags to be added")
141 846baef9 Iustin Pop
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
142 846baef9 Iustin Pop
  SubmitOpCode(op)
143 846baef9 Iustin Pop
144 846baef9 Iustin Pop
145 846baef9 Iustin Pop
def RemoveTags(opts, args):
146 846baef9 Iustin Pop
  """Remove tags from a given object.
147 846baef9 Iustin Pop

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

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

219 65fe4693 Iustin Pop
  """
220 a8083063 Iustin Pop
  try:
221 a8083063 Iustin Pop
    return utils.ParseUnit(value)
222 a8083063 Iustin Pop
  except errors.UnitParseError, err:
223 3ecf6786 Iustin Pop
    raise OptionValueError("option %s: %s" % (opt, err))
224 a8083063 Iustin Pop
225 a8083063 Iustin Pop
226 a8083063 Iustin Pop
class CliOption(Option):
227 65fe4693 Iustin Pop
  """Custom option class for optparse.
228 65fe4693 Iustin Pop

229 65fe4693 Iustin Pop
  """
230 a8083063 Iustin Pop
  TYPES = Option.TYPES + ("unit",)
231 a8083063 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
232 a8083063 Iustin Pop
  TYPE_CHECKER["unit"] = check_unit
233 a8083063 Iustin Pop
234 a8083063 Iustin Pop
235 a8469393 Iustin Pop
def _SplitKeyVal(opt, data):
236 a8469393 Iustin Pop
  """Convert a KeyVal string into a dict.
237 a8469393 Iustin Pop

238 a8469393 Iustin Pop
  This function will convert a key=val[,...] string into a dict. Empty
239 a8469393 Iustin Pop
  values will be converted specially: keys which have the prefix 'no_'
240 a8469393 Iustin Pop
  will have the value=False and the prefix stripped, the others will
241 a8469393 Iustin Pop
  have value=True.
242 a8469393 Iustin Pop

243 a8469393 Iustin Pop
  @type opt: string
244 a8469393 Iustin Pop
  @param opt: a string holding the option name for which we process the
245 a8469393 Iustin Pop
      data, used in building error messages
246 a8469393 Iustin Pop
  @type data: string
247 a8469393 Iustin Pop
  @param data: a string of the format key=val,key=val,...
248 a8469393 Iustin Pop
  @rtype: dict
249 a8469393 Iustin Pop
  @return: {key=val, key=val}
250 a8469393 Iustin Pop
  @raises errors.ParameterError: if there are duplicate keys
251 a8469393 Iustin Pop

252 a8469393 Iustin Pop
  """
253 a8469393 Iustin Pop
  NO_PREFIX = "no_"
254 fcd62d84 Iustin Pop
  UN_PREFIX = "-"
255 a8469393 Iustin Pop
  kv_dict = {}
256 a8469393 Iustin Pop
  for elem in data.split(","):
257 a8469393 Iustin Pop
    if "=" in elem:
258 a8469393 Iustin Pop
      key, val = elem.split("=", 1)
259 a8469393 Iustin Pop
    else:
260 a8469393 Iustin Pop
      if elem.startswith(NO_PREFIX):
261 a8469393 Iustin Pop
        key, val = elem[len(NO_PREFIX):], False
262 fcd62d84 Iustin Pop
      elif elem.startswith(UN_PREFIX):
263 fcd62d84 Iustin Pop
        key, val = elem[len(UN_PREFIX):], None
264 a8469393 Iustin Pop
      else:
265 a8469393 Iustin Pop
        key, val = elem, True
266 a8469393 Iustin Pop
    if key in kv_dict:
267 a8469393 Iustin Pop
      raise errors.ParameterError("Duplicate key '%s' in option %s" %
268 a8469393 Iustin Pop
                                  (key, opt))
269 a8469393 Iustin Pop
    kv_dict[key] = val
270 a8469393 Iustin Pop
  return kv_dict
271 a8469393 Iustin Pop
272 a8469393 Iustin Pop
273 a8469393 Iustin Pop
def check_ident_key_val(option, opt, value):
274 a8469393 Iustin Pop
  """Custom parser for the IdentKeyVal option type.
275 a8469393 Iustin Pop

276 a8469393 Iustin Pop
  """
277 a8469393 Iustin Pop
  if ":" not in value:
278 a8469393 Iustin Pop
    retval =  (value, {})
279 a8469393 Iustin Pop
  else:
280 a8469393 Iustin Pop
    ident, rest = value.split(":", 1)
281 a8469393 Iustin Pop
    kv_dict = _SplitKeyVal(opt, rest)
282 a8469393 Iustin Pop
    retval = (ident, kv_dict)
283 a8469393 Iustin Pop
  return retval
284 a8469393 Iustin Pop
285 a8469393 Iustin Pop
286 a8469393 Iustin Pop
class IdentKeyValOption(Option):
287 a8469393 Iustin Pop
  """Custom option class for ident:key=val,key=val options.
288 a8469393 Iustin Pop

289 a8469393 Iustin Pop
  This will store the parsed values as a tuple (ident, {key: val}). As
290 a8469393 Iustin Pop
  such, multiple uses of this option via action=append is possible.
291 a8469393 Iustin Pop

292 a8469393 Iustin Pop
  """
293 a8469393 Iustin Pop
  TYPES = Option.TYPES + ("identkeyval",)
294 a8469393 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
295 a8469393 Iustin Pop
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
296 a8469393 Iustin Pop
297 a8469393 Iustin Pop
298 a8469393 Iustin Pop
def check_key_val(option, opt, value):
299 a8469393 Iustin Pop
  """Custom parser for the KeyVal option type.
300 a8469393 Iustin Pop

301 a8469393 Iustin Pop
  """
302 a8469393 Iustin Pop
  return _SplitKeyVal(opt, value)
303 a8469393 Iustin Pop
304 a8469393 Iustin Pop
305 a8469393 Iustin Pop
class KeyValOption(Option):
306 a8469393 Iustin Pop
  """Custom option class for key=val,key=val options.
307 a8469393 Iustin Pop

308 a8469393 Iustin Pop
  This will store the parsed values as a dict {key: val}.
309 a8469393 Iustin Pop

310 a8469393 Iustin Pop
  """
311 a8469393 Iustin Pop
  TYPES = Option.TYPES + ("keyval",)
312 a8469393 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
313 a8469393 Iustin Pop
  TYPE_CHECKER["keyval"] = check_key_val
314 a8469393 Iustin Pop
315 a8469393 Iustin Pop
316 a8083063 Iustin Pop
# optparse.py sets make_option, so we do it for our own option class, too
317 a8083063 Iustin Pop
cli_option = CliOption
318 a8469393 Iustin Pop
ikv_option = IdentKeyValOption
319 a8469393 Iustin Pop
keyval_option = KeyValOption
320 a8083063 Iustin Pop
321 a8083063 Iustin Pop
322 de47cf8f Guido Trotter
def _ParseArgs(argv, commands, aliases):
323 c41eea6e Iustin Pop
  """Parser for the command line arguments.
324 a8083063 Iustin Pop

325 5bbd3f7f Michael Hanselmann
  This function parses the arguments and returns the function which
326 c41eea6e Iustin Pop
  must be executed together with its (modified) arguments.
327 a8083063 Iustin Pop

328 c41eea6e Iustin Pop
  @param argv: the command line
329 c41eea6e Iustin Pop
  @param commands: dictionary with special contents, see the design
330 c41eea6e Iustin Pop
      doc for cmdline handling
331 c41eea6e Iustin Pop
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
332 098c0958 Michael Hanselmann

333 a8083063 Iustin Pop
  """
334 a8083063 Iustin Pop
  if len(argv) == 0:
335 a8083063 Iustin Pop
    binary = "<command>"
336 a8083063 Iustin Pop
  else:
337 a8083063 Iustin Pop
    binary = argv[0].split("/")[-1]
338 a8083063 Iustin Pop
339 a8083063 Iustin Pop
  if len(argv) > 1 and argv[1] == "--version":
340 73b90123 Michael Hanselmann
    ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
341 a8083063 Iustin Pop
    # Quit right away. That way we don't have to care about this special
342 a8083063 Iustin Pop
    # argument. optparse.py does it the same.
343 a8083063 Iustin Pop
    sys.exit(0)
344 a8083063 Iustin Pop
345 de47cf8f Guido Trotter
  if len(argv) < 2 or not (argv[1] in commands or
346 70a35b6f Guido Trotter
                           argv[1] in aliases):
347 a8083063 Iustin Pop
    # let's do a nice thing
348 a8083063 Iustin Pop
    sortedcmds = commands.keys()
349 a8083063 Iustin Pop
    sortedcmds.sort()
350 73b90123 Michael Hanselmann
351 73b90123 Michael Hanselmann
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
352 73b90123 Michael Hanselmann
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
353 73b90123 Michael Hanselmann
    ToStdout("")
354 73b90123 Michael Hanselmann
355 a8083063 Iustin Pop
    # compute the max line length for cmd + usage
356 4e713df6 Iustin Pop
    mlen = max([len(" %s" % cmd) for cmd in commands])
357 a8083063 Iustin Pop
    mlen = min(60, mlen) # should not get here...
358 73b90123 Michael Hanselmann
359 a8083063 Iustin Pop
    # and format a nice command list
360 73b90123 Michael Hanselmann
    ToStdout("Commands:")
361 a8083063 Iustin Pop
    for cmd in sortedcmds:
362 4e713df6 Iustin Pop
      cmdstr = " %s" % (cmd,)
363 9a033156 Iustin Pop
      help_text = commands[cmd][4]
364 73b90123 Michael Hanselmann
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
365 73b90123 Michael Hanselmann
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
366 a8083063 Iustin Pop
      for line in help_lines:
367 73b90123 Michael Hanselmann
        ToStdout("%-*s   %s", mlen, "", line)
368 73b90123 Michael Hanselmann
369 73b90123 Michael Hanselmann
    ToStdout("")
370 73b90123 Michael Hanselmann
371 a8083063 Iustin Pop
    return None, None, None
372 de47cf8f Guido Trotter
373 de47cf8f Guido Trotter
  # get command, unalias it, and look it up in commands
374 a8083063 Iustin Pop
  cmd = argv.pop(1)
375 de47cf8f Guido Trotter
  if cmd in aliases:
376 de47cf8f Guido Trotter
    if cmd in commands:
377 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
378 de47cf8f Guido Trotter
                                   " command" % cmd)
379 de47cf8f Guido Trotter
380 de47cf8f Guido Trotter
    if aliases[cmd] not in commands:
381 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
382 de47cf8f Guido Trotter
                                   " command '%s'" % (cmd, aliases[cmd]))
383 de47cf8f Guido Trotter
384 de47cf8f Guido Trotter
    cmd = aliases[cmd]
385 de47cf8f Guido Trotter
386 a8083063 Iustin Pop
  func, nargs, parser_opts, usage, description = commands[cmd]
387 a8083063 Iustin Pop
  parser = OptionParser(option_list=parser_opts,
388 a8083063 Iustin Pop
                        description=description,
389 a8083063 Iustin Pop
                        formatter=TitledHelpFormatter(),
390 a8083063 Iustin Pop
                        usage="%%prog %s %s" % (cmd, usage))
391 a8083063 Iustin Pop
  parser.disable_interspersed_args()
392 a8083063 Iustin Pop
  options, args = parser.parse_args()
393 a8083063 Iustin Pop
  if nargs is None:
394 a8083063 Iustin Pop
    if len(args) != 0:
395 73b90123 Michael Hanselmann
      ToStderr("Error: Command %s expects no arguments", cmd)
396 a8083063 Iustin Pop
      return None, None, None
397 a8083063 Iustin Pop
  elif nargs < 0 and len(args) != -nargs:
398 73b90123 Michael Hanselmann
    ToStderr("Error: Command %s expects %d argument(s)", cmd, -nargs)
399 a8083063 Iustin Pop
    return None, None, None
400 a8083063 Iustin Pop
  elif nargs >= 0 and len(args) < nargs:
401 73b90123 Michael Hanselmann
    ToStderr("Error: Command %s expects at least %d argument(s)", cmd, nargs)
402 a8083063 Iustin Pop
    return None, None, None
403 a8083063 Iustin Pop
404 a8083063 Iustin Pop
  return func, options, args
405 a8083063 Iustin Pop
406 a8083063 Iustin Pop
407 60d49723 Michael Hanselmann
def SplitNodeOption(value):
408 60d49723 Michael Hanselmann
  """Splits the value of a --node option.
409 60d49723 Michael Hanselmann

410 60d49723 Michael Hanselmann
  """
411 60d49723 Michael Hanselmann
  if value and ':' in value:
412 60d49723 Michael Hanselmann
    return value.split(':', 1)
413 60d49723 Michael Hanselmann
  else:
414 60d49723 Michael Hanselmann
    return (value, None)
415 60d49723 Michael Hanselmann
416 60d49723 Michael Hanselmann
417 4331f6cd Michael Hanselmann
def UsesRPC(fn):
418 4331f6cd Michael Hanselmann
  def wrapper(*args, **kwargs):
419 4331f6cd Michael Hanselmann
    rpc.Init()
420 4331f6cd Michael Hanselmann
    try:
421 4331f6cd Michael Hanselmann
      return fn(*args, **kwargs)
422 4331f6cd Michael Hanselmann
    finally:
423 4331f6cd Michael Hanselmann
      rpc.Shutdown()
424 4331f6cd Michael Hanselmann
  return wrapper
425 4331f6cd Michael Hanselmann
426 4331f6cd Michael Hanselmann
427 47988778 Iustin Pop
def AskUser(text, choices=None):
428 47988778 Iustin Pop
  """Ask the user a question.
429 a8083063 Iustin Pop

430 c41eea6e Iustin Pop
  @param text: the question to ask
431 a8083063 Iustin Pop

432 c41eea6e Iustin Pop
  @param choices: list with elements tuples (input_char, return_value,
433 c41eea6e Iustin Pop
      description); if not given, it will default to: [('y', True,
434 c41eea6e Iustin Pop
      'Perform the operation'), ('n', False, 'Do no do the operation')];
435 c41eea6e Iustin Pop
      note that the '?' char is reserved for help
436 47988778 Iustin Pop

437 c41eea6e Iustin Pop
  @return: one of the return values from the choices list; if input is
438 c41eea6e Iustin Pop
      not possible (i.e. not running with a tty, we return the last
439 c41eea6e Iustin Pop
      entry from the list
440 a8083063 Iustin Pop

441 a8083063 Iustin Pop
  """
442 47988778 Iustin Pop
  if choices is None:
443 47988778 Iustin Pop
    choices = [('y', True, 'Perform the operation'),
444 47988778 Iustin Pop
               ('n', False, 'Do not perform the operation')]
445 47988778 Iustin Pop
  if not choices or not isinstance(choices, list):
446 5bbd3f7f Michael Hanselmann
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
447 47988778 Iustin Pop
  for entry in choices:
448 47988778 Iustin Pop
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
449 5bbd3f7f Michael Hanselmann
      raise errors.ProgrammerError("Invalid choices element to AskUser")
450 47988778 Iustin Pop
451 47988778 Iustin Pop
  answer = choices[-1][1]
452 47988778 Iustin Pop
  new_text = []
453 47988778 Iustin Pop
  for line in text.splitlines():
454 47988778 Iustin Pop
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
455 47988778 Iustin Pop
  text = "\n".join(new_text)
456 a8083063 Iustin Pop
  try:
457 3023170f Iustin Pop
    f = file("/dev/tty", "a+")
458 a8083063 Iustin Pop
  except IOError:
459 47988778 Iustin Pop
    return answer
460 a8083063 Iustin Pop
  try:
461 47988778 Iustin Pop
    chars = [entry[0] for entry in choices]
462 47988778 Iustin Pop
    chars[-1] = "[%s]" % chars[-1]
463 47988778 Iustin Pop
    chars.append('?')
464 47988778 Iustin Pop
    maps = dict([(entry[0], entry[1]) for entry in choices])
465 47988778 Iustin Pop
    while True:
466 47988778 Iustin Pop
      f.write(text)
467 47988778 Iustin Pop
      f.write('\n')
468 47988778 Iustin Pop
      f.write("/".join(chars))
469 47988778 Iustin Pop
      f.write(": ")
470 47988778 Iustin Pop
      line = f.readline(2).strip().lower()
471 47988778 Iustin Pop
      if line in maps:
472 47988778 Iustin Pop
        answer = maps[line]
473 47988778 Iustin Pop
        break
474 47988778 Iustin Pop
      elif line == '?':
475 47988778 Iustin Pop
        for entry in choices:
476 47988778 Iustin Pop
          f.write(" %s - %s\n" % (entry[0], entry[2]))
477 47988778 Iustin Pop
        f.write("\n")
478 47988778 Iustin Pop
        continue
479 a8083063 Iustin Pop
  finally:
480 a8083063 Iustin Pop
    f.close()
481 a8083063 Iustin Pop
  return answer
482 a8083063 Iustin Pop
483 a8083063 Iustin Pop
484 e9d741b6 Iustin Pop
class JobSubmittedException(Exception):
485 e9d741b6 Iustin Pop
  """Job was submitted, client should exit.
486 e9d741b6 Iustin Pop

487 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
488 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
489 e9d741b6 Iustin Pop

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

492 e9d741b6 Iustin Pop
  """
493 e9d741b6 Iustin Pop
494 e9d741b6 Iustin Pop
495 0a1e74d9 Iustin Pop
def SendJob(ops, cl=None):
496 0a1e74d9 Iustin Pop
  """Function to submit an opcode without waiting for the results.
497 a8083063 Iustin Pop

498 0a1e74d9 Iustin Pop
  @type ops: list
499 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
500 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
501 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
502 0a1e74d9 Iustin Pop
             if None, a new client will be created
503 a8083063 Iustin Pop

504 a8083063 Iustin Pop
  """
505 e2212007 Iustin Pop
  if cl is None:
506 b33e986b Iustin Pop
    cl = GetClient()
507 685ee993 Iustin Pop
508 0a1e74d9 Iustin Pop
  job_id = cl.SubmitJob(ops)
509 0a1e74d9 Iustin Pop
510 0a1e74d9 Iustin Pop
  return job_id
511 0a1e74d9 Iustin Pop
512 0a1e74d9 Iustin Pop
513 281606c1 Michael Hanselmann
def PollJob(job_id, cl=None, feedback_fn=None):
514 0a1e74d9 Iustin Pop
  """Function to poll for the result of a job.
515 0a1e74d9 Iustin Pop

516 0a1e74d9 Iustin Pop
  @type job_id: job identified
517 0a1e74d9 Iustin Pop
  @param job_id: the job to poll for results
518 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
519 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
520 0a1e74d9 Iustin Pop
             if None, a new client will be created
521 0a1e74d9 Iustin Pop

522 0a1e74d9 Iustin Pop
  """
523 0a1e74d9 Iustin Pop
  if cl is None:
524 0a1e74d9 Iustin Pop
    cl = GetClient()
525 685ee993 Iustin Pop
526 6c5a7090 Michael Hanselmann
  prev_job_info = None
527 6c5a7090 Michael Hanselmann
  prev_logmsg_serial = None
528 6c5a7090 Michael Hanselmann
529 685ee993 Iustin Pop
  while True:
530 6c5a7090 Michael Hanselmann
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
531 6c5a7090 Michael Hanselmann
                                 prev_logmsg_serial)
532 6c5a7090 Michael Hanselmann
    if not result:
533 685ee993 Iustin Pop
      # job not found, go away!
534 0bbe448c Michael Hanselmann
      raise errors.JobLost("Job with id %s lost" % job_id)
535 685ee993 Iustin Pop
536 6c5a7090 Michael Hanselmann
    # Split result, a tuple of (field values, log entries)
537 6c5a7090 Michael Hanselmann
    (job_info, log_entries) = result
538 6c5a7090 Michael Hanselmann
    (status, ) = job_info
539 6c5a7090 Michael Hanselmann
540 6c5a7090 Michael Hanselmann
    if log_entries:
541 6c5a7090 Michael Hanselmann
      for log_entry in log_entries:
542 6c5a7090 Michael Hanselmann
        (serial, timestamp, _, message) = log_entry
543 6c5a7090 Michael Hanselmann
        if callable(feedback_fn):
544 6c5a7090 Michael Hanselmann
          feedback_fn(log_entry[1:])
545 6c5a7090 Michael Hanselmann
        else:
546 26f15862 Iustin Pop
          encoded = utils.SafeEncode(message)
547 73b90123 Michael Hanselmann
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
548 6c5a7090 Michael Hanselmann
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
549 6c5a7090 Michael Hanselmann
550 0bbe448c Michael Hanselmann
    # TODO: Handle canceled and archived jobs
551 fbf0262f Michael Hanselmann
    elif status in (constants.JOB_STATUS_SUCCESS,
552 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_ERROR,
553 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_CANCELING,
554 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_CANCELED):
555 685ee993 Iustin Pop
      break
556 6c5a7090 Michael Hanselmann
557 6c5a7090 Michael Hanselmann
    prev_job_info = job_info
558 685ee993 Iustin Pop
559 0e050889 Iustin Pop
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
560 0bbe448c Michael Hanselmann
  if not jobs:
561 0bbe448c Michael Hanselmann
    raise errors.JobLost("Job with id %s lost" % job_id)
562 685ee993 Iustin Pop
563 0e050889 Iustin Pop
  status, opstatus, result = jobs[0]
564 0bbe448c Michael Hanselmann
  if status == constants.JOB_STATUS_SUCCESS:
565 53c04d04 Iustin Pop
    return result
566 fbf0262f Michael Hanselmann
  elif status in (constants.JOB_STATUS_CANCELING,
567 fbf0262f Michael Hanselmann
                  constants.JOB_STATUS_CANCELED):
568 fbf0262f Michael Hanselmann
    raise errors.OpExecError("Job was canceled")
569 0bbe448c Michael Hanselmann
  else:
570 0e050889 Iustin Pop
    has_ok = False
571 0e050889 Iustin Pop
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
572 0e050889 Iustin Pop
      if status == constants.OP_STATUS_SUCCESS:
573 0e050889 Iustin Pop
        has_ok = True
574 0e050889 Iustin Pop
      elif status == constants.OP_STATUS_ERROR:
575 e6345c35 Iustin Pop
        errors.MaybeRaise(msg)
576 0e050889 Iustin Pop
        if has_ok:
577 0e050889 Iustin Pop
          raise errors.OpExecError("partial failure (opcode %d): %s" %
578 0e050889 Iustin Pop
                                   (idx, msg))
579 0e050889 Iustin Pop
        else:
580 0e050889 Iustin Pop
          raise errors.OpExecError(str(msg))
581 0e050889 Iustin Pop
    # default failure mode
582 0bbe448c Michael Hanselmann
    raise errors.OpExecError(result)
583 ceab32dd Iustin Pop
584 ceab32dd Iustin Pop
585 0a1e74d9 Iustin Pop
def SubmitOpCode(op, cl=None, feedback_fn=None):
586 0a1e74d9 Iustin Pop
  """Legacy function to submit an opcode.
587 0a1e74d9 Iustin Pop

588 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
589 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
590 0a1e74d9 Iustin Pop
  interaction functions.
591 0a1e74d9 Iustin Pop

592 0a1e74d9 Iustin Pop
  """
593 0a1e74d9 Iustin Pop
  if cl is None:
594 0a1e74d9 Iustin Pop
    cl = GetClient()
595 0a1e74d9 Iustin Pop
596 0a1e74d9 Iustin Pop
  job_id = SendJob([op], cl)
597 0a1e74d9 Iustin Pop
598 53c04d04 Iustin Pop
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
599 53c04d04 Iustin Pop
600 53c04d04 Iustin Pop
  return op_results[0]
601 0a1e74d9 Iustin Pop
602 0a1e74d9 Iustin Pop
603 94428652 Iustin Pop
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
604 94428652 Iustin Pop
  """Wrapper around SubmitOpCode or SendJob.
605 94428652 Iustin Pop

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

611 94428652 Iustin Pop
  """
612 94428652 Iustin Pop
  if opts and opts.submit_only:
613 e9d741b6 Iustin Pop
    job_id = SendJob([op], cl=cl)
614 e9d741b6 Iustin Pop
    raise JobSubmittedException(job_id)
615 94428652 Iustin Pop
  else:
616 94428652 Iustin Pop
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
617 94428652 Iustin Pop
618 94428652 Iustin Pop
619 af30b2fd Michael Hanselmann
def GetClient():
620 af30b2fd Michael Hanselmann
  # TODO: Cache object?
621 b33e986b Iustin Pop
  try:
622 b33e986b Iustin Pop
    client = luxi.Client()
623 b33e986b Iustin Pop
  except luxi.NoMasterError:
624 b33e986b Iustin Pop
    master, myself = ssconf.GetMasterAndMyself()
625 b33e986b Iustin Pop
    if master != myself:
626 b33e986b Iustin Pop
      raise errors.OpPrereqError("This is not the master node, please connect"
627 b33e986b Iustin Pop
                                 " to node '%s' and rerun the command" %
628 b33e986b Iustin Pop
                                 master)
629 b33e986b Iustin Pop
    else:
630 b33e986b Iustin Pop
      raise
631 b33e986b Iustin Pop
  return client
632 af30b2fd Michael Hanselmann
633 af30b2fd Michael Hanselmann
634 73702ee7 Iustin Pop
def FormatError(err):
635 73702ee7 Iustin Pop
  """Return a formatted error message for a given error.
636 73702ee7 Iustin Pop

637 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
638 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
639 73702ee7 Iustin Pop
  second, a string describing the error message (not
640 73702ee7 Iustin Pop
  newline-terminated).
641 73702ee7 Iustin Pop

642 73702ee7 Iustin Pop
  """
643 73702ee7 Iustin Pop
  retcode = 1
644 73702ee7 Iustin Pop
  obuf = StringIO()
645 e2e521d0 Iustin Pop
  msg = str(err)
646 73702ee7 Iustin Pop
  if isinstance(err, errors.ConfigurationError):
647 e2e521d0 Iustin Pop
    txt = "Corrupt configuration file: %s" % msg
648 46fbdd04 Iustin Pop
    logging.error(txt)
649 e2e521d0 Iustin Pop
    obuf.write(txt + "\n")
650 73702ee7 Iustin Pop
    obuf.write("Aborting.")
651 73702ee7 Iustin Pop
    retcode = 2
652 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksAbort):
653 73702ee7 Iustin Pop
    obuf.write("Failure: hooks execution failed:\n")
654 73702ee7 Iustin Pop
    for node, script, out in err.args[0]:
655 73702ee7 Iustin Pop
      if out:
656 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s, output: %s\n" %
657 73702ee7 Iustin Pop
                   (node, script, out))
658 73702ee7 Iustin Pop
      else:
659 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s (no output)\n" %
660 73702ee7 Iustin Pop
                   (node, script))
661 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksFailure):
662 e2e521d0 Iustin Pop
    obuf.write("Failure: hooks general failure: %s" % msg)
663 73702ee7 Iustin Pop
  elif isinstance(err, errors.ResolverError):
664 73702ee7 Iustin Pop
    this_host = utils.HostInfo.SysName()
665 73702ee7 Iustin Pop
    if err.args[0] == this_host:
666 73702ee7 Iustin Pop
      msg = "Failure: can't resolve my own hostname ('%s')"
667 73702ee7 Iustin Pop
    else:
668 73702ee7 Iustin Pop
      msg = "Failure: can't resolve hostname '%s'"
669 73702ee7 Iustin Pop
    obuf.write(msg % err.args[0])
670 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpPrereqError):
671 73702ee7 Iustin Pop
    obuf.write("Failure: prerequisites not met for this"
672 e2e521d0 Iustin Pop
               " operation:\n%s" % msg)
673 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpExecError):
674 e2e521d0 Iustin Pop
    obuf.write("Failure: command execution error:\n%s" % msg)
675 73702ee7 Iustin Pop
  elif isinstance(err, errors.TagError):
676 e2e521d0 Iustin Pop
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
677 686d7433 Iustin Pop
  elif isinstance(err, errors.JobQueueDrainError):
678 686d7433 Iustin Pop
    obuf.write("Failure: the job queue is marked for drain and doesn't"
679 686d7433 Iustin Pop
               " accept new requests\n")
680 f87b405e Michael Hanselmann
  elif isinstance(err, errors.JobQueueFull):
681 f87b405e Michael Hanselmann
    obuf.write("Failure: the job queue is full and doesn't accept new"
682 f87b405e Michael Hanselmann
               " job submissions until old jobs are archived\n")
683 a5728081 Guido Trotter
  elif isinstance(err, errors.TypeEnforcementError):
684 a5728081 Guido Trotter
    obuf.write("Parameter Error: %s" % msg)
685 c1ce76bb Iustin Pop
  elif isinstance(err, errors.ParameterError):
686 c1ce76bb Iustin Pop
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
687 73702ee7 Iustin Pop
  elif isinstance(err, errors.GenericError):
688 e2e521d0 Iustin Pop
    obuf.write("Unhandled Ganeti error: %s" % msg)
689 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.NoMasterError):
690 03a8dbdc Iustin Pop
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
691 082c5adb Michael Hanselmann
               " and listening for connections?")
692 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.TimeoutError):
693 03a8dbdc Iustin Pop
    obuf.write("Timeout while talking to the master daemon. Error:\n"
694 03a8dbdc Iustin Pop
               "%s" % msg)
695 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.ProtocolError):
696 03a8dbdc Iustin Pop
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
697 03a8dbdc Iustin Pop
               "%s" % msg)
698 e9d741b6 Iustin Pop
  elif isinstance(err, JobSubmittedException):
699 e9d741b6 Iustin Pop
    obuf.write("JobID: %s\n" % err.args[0])
700 e9d741b6 Iustin Pop
    retcode = 0
701 73702ee7 Iustin Pop
  else:
702 e2e521d0 Iustin Pop
    obuf.write("Unhandled exception: %s" % msg)
703 73702ee7 Iustin Pop
  return retcode, obuf.getvalue().rstrip('\n')
704 73702ee7 Iustin Pop
705 73702ee7 Iustin Pop
706 de47cf8f Guido Trotter
def GenericMain(commands, override=None, aliases=None):
707 a8083063 Iustin Pop
  """Generic main function for all the gnt-* commands.
708 a8083063 Iustin Pop

709 334d1483 Iustin Pop
  Arguments:
710 334d1483 Iustin Pop
    - commands: a dictionary with a special structure, see the design doc
711 334d1483 Iustin Pop
                for command line handling.
712 334d1483 Iustin Pop
    - override: if not None, we expect a dictionary with keys that will
713 334d1483 Iustin Pop
                override command line options; this can be used to pass
714 334d1483 Iustin Pop
                options from the scripts to generic functions
715 de47cf8f Guido Trotter
    - aliases: dictionary with command aliases {'alias': 'target, ...}
716 a8083063 Iustin Pop

717 a8083063 Iustin Pop
  """
718 a8083063 Iustin Pop
  # save the program name and the entire command line for later logging
719 a8083063 Iustin Pop
  if sys.argv:
720 a8083063 Iustin Pop
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
721 a8083063 Iustin Pop
    if len(sys.argv) >= 2:
722 a8083063 Iustin Pop
      binary += " " + sys.argv[1]
723 a8083063 Iustin Pop
      old_cmdline = " ".join(sys.argv[2:])
724 a8083063 Iustin Pop
    else:
725 a8083063 Iustin Pop
      old_cmdline = ""
726 a8083063 Iustin Pop
  else:
727 a8083063 Iustin Pop
    binary = "<unknown program>"
728 a8083063 Iustin Pop
    old_cmdline = ""
729 a8083063 Iustin Pop
730 de47cf8f Guido Trotter
  if aliases is None:
731 de47cf8f Guido Trotter
    aliases = {}
732 de47cf8f Guido Trotter
733 de47cf8f Guido Trotter
  func, options, args = _ParseArgs(sys.argv, commands, aliases)
734 a8083063 Iustin Pop
  if func is None: # parse error
735 a8083063 Iustin Pop
    return 1
736 a8083063 Iustin Pop
737 334d1483 Iustin Pop
  if override is not None:
738 334d1483 Iustin Pop
    for key, val in override.iteritems():
739 334d1483 Iustin Pop
      setattr(options, key, val)
740 334d1483 Iustin Pop
741 82d9caef Iustin Pop
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
742 82d9caef Iustin Pop
                     stderr_logging=True, program=binary)
743 a8083063 Iustin Pop
744 a8083063 Iustin Pop
  if old_cmdline:
745 46fbdd04 Iustin Pop
    logging.info("run with arguments '%s'", old_cmdline)
746 a8083063 Iustin Pop
  else:
747 46fbdd04 Iustin Pop
    logging.info("run with no arguments")
748 a8083063 Iustin Pop
749 a8083063 Iustin Pop
  try:
750 a4af651e Iustin Pop
    result = func(options, args)
751 d8353c3a Iustin Pop
  except (errors.GenericError, luxi.ProtocolError,
752 d8353c3a Iustin Pop
          JobSubmittedException), err:
753 a4af651e Iustin Pop
    result, err_msg = FormatError(err)
754 5bbd3f7f Michael Hanselmann
    logging.exception("Error during command processing")
755 46fbdd04 Iustin Pop
    ToStderr(err_msg)
756 a8083063 Iustin Pop
757 a8083063 Iustin Pop
  return result
758 137161c9 Michael Hanselmann
759 137161c9 Michael Hanselmann
760 16be8703 Iustin Pop
def GenerateTable(headers, fields, separator, data,
761 9fbfbb7b Iustin Pop
                  numfields=None, unitfields=None,
762 9fbfbb7b Iustin Pop
                  units=None):
763 137161c9 Michael Hanselmann
  """Prints a table with headers and different fields.
764 137161c9 Michael Hanselmann

765 9fbfbb7b Iustin Pop
  @type headers: dict
766 9fbfbb7b Iustin Pop
  @param headers: dictionary mapping field names to headers for
767 9fbfbb7b Iustin Pop
      the table
768 9fbfbb7b Iustin Pop
  @type fields: list
769 9fbfbb7b Iustin Pop
  @param fields: the field names corresponding to each row in
770 9fbfbb7b Iustin Pop
      the data field
771 9fbfbb7b Iustin Pop
  @param separator: the separator to be used; if this is None,
772 9fbfbb7b Iustin Pop
      the default 'smart' algorithm is used which computes optimal
773 9fbfbb7b Iustin Pop
      field width, otherwise just the separator is used between
774 9fbfbb7b Iustin Pop
      each field
775 9fbfbb7b Iustin Pop
  @type data: list
776 9fbfbb7b Iustin Pop
  @param data: a list of lists, each sublist being one row to be output
777 9fbfbb7b Iustin Pop
  @type numfields: list
778 9fbfbb7b Iustin Pop
  @param numfields: a list with the fields that hold numeric
779 9fbfbb7b Iustin Pop
      values and thus should be right-aligned
780 9fbfbb7b Iustin Pop
  @type unitfields: list
781 9fbfbb7b Iustin Pop
  @param unitfields: a list with the fields that hold numeric
782 9fbfbb7b Iustin Pop
      values that should be formatted with the units field
783 9fbfbb7b Iustin Pop
  @type units: string or None
784 9fbfbb7b Iustin Pop
  @param units: the units we should use for formatting, or None for
785 9fbfbb7b Iustin Pop
      automatic choice (human-readable for non-separator usage, otherwise
786 9fbfbb7b Iustin Pop
      megabytes); this is a one-letter string
787 137161c9 Michael Hanselmann

788 137161c9 Michael Hanselmann
  """
789 9fbfbb7b Iustin Pop
  if units is None:
790 9fbfbb7b Iustin Pop
    if separator:
791 9fbfbb7b Iustin Pop
      units = "m"
792 9fbfbb7b Iustin Pop
    else:
793 9fbfbb7b Iustin Pop
      units = "h"
794 9fbfbb7b Iustin Pop
795 137161c9 Michael Hanselmann
  if numfields is None:
796 137161c9 Michael Hanselmann
    numfields = []
797 137161c9 Michael Hanselmann
  if unitfields is None:
798 137161c9 Michael Hanselmann
    unitfields = []
799 137161c9 Michael Hanselmann
800 00430f8e Iustin Pop
  numfields = utils.FieldSet(*numfields)
801 00430f8e Iustin Pop
  unitfields = utils.FieldSet(*unitfields)
802 00430f8e Iustin Pop
803 137161c9 Michael Hanselmann
  format_fields = []
804 137161c9 Michael Hanselmann
  for field in fields:
805 01ca31ae Iustin Pop
    if headers and field not in headers:
806 ea5a5b74 Guido Trotter
      # TODO: handle better unknown fields (either revert to old
807 71c1af58 Iustin Pop
      # style of raising exception, or deal more intelligently with
808 71c1af58 Iustin Pop
      # variable fields)
809 71c1af58 Iustin Pop
      headers[field] = field
810 137161c9 Michael Hanselmann
    if separator is not None:
811 137161c9 Michael Hanselmann
      format_fields.append("%s")
812 00430f8e Iustin Pop
    elif numfields.Matches(field):
813 137161c9 Michael Hanselmann
      format_fields.append("%*s")
814 137161c9 Michael Hanselmann
    else:
815 137161c9 Michael Hanselmann
      format_fields.append("%-*s")
816 137161c9 Michael Hanselmann
817 137161c9 Michael Hanselmann
  if separator is None:
818 137161c9 Michael Hanselmann
    mlens = [0 for name in fields]
819 137161c9 Michael Hanselmann
    format = ' '.join(format_fields)
820 137161c9 Michael Hanselmann
  else:
821 137161c9 Michael Hanselmann
    format = separator.replace("%", "%%").join(format_fields)
822 137161c9 Michael Hanselmann
823 137161c9 Michael Hanselmann
  for row in data:
824 dcbd6288 Guido Trotter
    if row is None:
825 dcbd6288 Guido Trotter
      continue
826 137161c9 Michael Hanselmann
    for idx, val in enumerate(row):
827 00430f8e Iustin Pop
      if unitfields.Matches(fields[idx]):
828 137161c9 Michael Hanselmann
        try:
829 137161c9 Michael Hanselmann
          val = int(val)
830 137161c9 Michael Hanselmann
        except ValueError:
831 137161c9 Michael Hanselmann
          pass
832 137161c9 Michael Hanselmann
        else:
833 9fbfbb7b Iustin Pop
          val = row[idx] = utils.FormatUnit(val, units)
834 01ca31ae Iustin Pop
      val = row[idx] = str(val)
835 137161c9 Michael Hanselmann
      if separator is None:
836 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(val))
837 137161c9 Michael Hanselmann
838 16be8703 Iustin Pop
  result = []
839 137161c9 Michael Hanselmann
  if headers:
840 137161c9 Michael Hanselmann
    args = []
841 137161c9 Michael Hanselmann
    for idx, name in enumerate(fields):
842 137161c9 Michael Hanselmann
      hdr = headers[name]
843 137161c9 Michael Hanselmann
      if separator is None:
844 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(hdr))
845 137161c9 Michael Hanselmann
        args.append(mlens[idx])
846 137161c9 Michael Hanselmann
      args.append(hdr)
847 16be8703 Iustin Pop
    result.append(format % tuple(args))
848 137161c9 Michael Hanselmann
849 137161c9 Michael Hanselmann
  for line in data:
850 137161c9 Michael Hanselmann
    args = []
851 dcbd6288 Guido Trotter
    if line is None:
852 dcbd6288 Guido Trotter
      line = ['-' for _ in fields]
853 137161c9 Michael Hanselmann
    for idx in xrange(len(fields)):
854 137161c9 Michael Hanselmann
      if separator is None:
855 137161c9 Michael Hanselmann
        args.append(mlens[idx])
856 137161c9 Michael Hanselmann
      args.append(line[idx])
857 16be8703 Iustin Pop
    result.append(format % tuple(args))
858 16be8703 Iustin Pop
859 16be8703 Iustin Pop
  return result
860 3386e7a9 Iustin Pop
861 3386e7a9 Iustin Pop
862 3386e7a9 Iustin Pop
def FormatTimestamp(ts):
863 3386e7a9 Iustin Pop
  """Formats a given timestamp.
864 3386e7a9 Iustin Pop

865 3386e7a9 Iustin Pop
  @type ts: timestamp
866 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
867 3386e7a9 Iustin Pop

868 3386e7a9 Iustin Pop
  @rtype: string
869 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
870 3386e7a9 Iustin Pop

871 3386e7a9 Iustin Pop
  """
872 e0ec0ff6 Iustin Pop
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
873 e0ec0ff6 Iustin Pop
    return '?'
874 3386e7a9 Iustin Pop
  sec, usec = ts
875 3386e7a9 Iustin Pop
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
876 2241e2b9 Iustin Pop
877 2241e2b9 Iustin Pop
878 2241e2b9 Iustin Pop
def ParseTimespec(value):
879 2241e2b9 Iustin Pop
  """Parse a time specification.
880 2241e2b9 Iustin Pop

881 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
882 2241e2b9 Iustin Pop

883 2241e2b9 Iustin Pop
    - s: seconds
884 2241e2b9 Iustin Pop
    - m: minutes
885 2241e2b9 Iustin Pop
    - h: hours
886 2241e2b9 Iustin Pop
    - d: day
887 2241e2b9 Iustin Pop
    - w: weeks
888 2241e2b9 Iustin Pop

889 2241e2b9 Iustin Pop
  Without any suffix, the value will be taken to be in seconds.
890 2241e2b9 Iustin Pop

891 2241e2b9 Iustin Pop
  """
892 2241e2b9 Iustin Pop
  value = str(value)
893 2241e2b9 Iustin Pop
  if not value:
894 2241e2b9 Iustin Pop
    raise errors.OpPrereqError("Empty time specification passed")
895 2241e2b9 Iustin Pop
  suffix_map = {
896 2241e2b9 Iustin Pop
    's': 1,
897 2241e2b9 Iustin Pop
    'm': 60,
898 2241e2b9 Iustin Pop
    'h': 3600,
899 2241e2b9 Iustin Pop
    'd': 86400,
900 2241e2b9 Iustin Pop
    'w': 604800,
901 2241e2b9 Iustin Pop
    }
902 2241e2b9 Iustin Pop
  if value[-1] not in suffix_map:
903 2241e2b9 Iustin Pop
    try:
904 2241e2b9 Iustin Pop
      value = int(value)
905 2241e2b9 Iustin Pop
    except ValueError:
906 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
907 2241e2b9 Iustin Pop
  else:
908 2241e2b9 Iustin Pop
    multiplier = suffix_map[value[-1]]
909 2241e2b9 Iustin Pop
    value = value[:-1]
910 2241e2b9 Iustin Pop
    if not value: # no data left after stripping the suffix
911 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification (only"
912 2241e2b9 Iustin Pop
                                 " suffix passed)")
913 2241e2b9 Iustin Pop
    try:
914 2241e2b9 Iustin Pop
      value = int(value) * multiplier
915 2241e2b9 Iustin Pop
    except ValueError:
916 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
917 2241e2b9 Iustin Pop
  return value
918 46fbdd04 Iustin Pop
919 46fbdd04 Iustin Pop
920 4040a784 Iustin Pop
def GetOnlineNodes(nodes, cl=None, nowarn=False):
921 4040a784 Iustin Pop
  """Returns the names of online nodes.
922 4040a784 Iustin Pop

923 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
924 4040a784 Iustin Pop
  the online nodes.
925 4040a784 Iustin Pop

926 4040a784 Iustin Pop
  @param nodes: if not empty, use only this subset of nodes (minus the
927 4040a784 Iustin Pop
      offline ones)
928 4040a784 Iustin Pop
  @param cl: if not None, luxi client to use
929 4040a784 Iustin Pop
  @type nowarn: boolean
930 4040a784 Iustin Pop
  @param nowarn: by default, this function will output a note with the
931 4040a784 Iustin Pop
      offline nodes that are skipped; if this parameter is True the
932 4040a784 Iustin Pop
      note is not displayed
933 4040a784 Iustin Pop

934 4040a784 Iustin Pop
  """
935 4040a784 Iustin Pop
  if cl is None:
936 4040a784 Iustin Pop
    cl = GetClient()
937 4040a784 Iustin Pop
938 2e7b8369 Iustin Pop
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
939 2e7b8369 Iustin Pop
                         use_locking=False)
940 4040a784 Iustin Pop
  offline = [row[0] for row in result if row[1]]
941 4040a784 Iustin Pop
  if offline and not nowarn:
942 4040a784 Iustin Pop
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
943 4040a784 Iustin Pop
  return [row[0] for row in result if not row[1]]
944 4040a784 Iustin Pop
945 4040a784 Iustin Pop
946 46fbdd04 Iustin Pop
def _ToStream(stream, txt, *args):
947 46fbdd04 Iustin Pop
  """Write a message to a stream, bypassing the logging system
948 46fbdd04 Iustin Pop

949 46fbdd04 Iustin Pop
  @type stream: file object
950 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
951 46fbdd04 Iustin Pop
  @type txt: str
952 46fbdd04 Iustin Pop
  @param txt: the message
953 46fbdd04 Iustin Pop

954 46fbdd04 Iustin Pop
  """
955 46fbdd04 Iustin Pop
  if args:
956 46fbdd04 Iustin Pop
    args = tuple(args)
957 46fbdd04 Iustin Pop
    stream.write(txt % args)
958 46fbdd04 Iustin Pop
  else:
959 46fbdd04 Iustin Pop
    stream.write(txt)
960 46fbdd04 Iustin Pop
  stream.write('\n')
961 46fbdd04 Iustin Pop
  stream.flush()
962 46fbdd04 Iustin Pop
963 46fbdd04 Iustin Pop
964 46fbdd04 Iustin Pop
def ToStdout(txt, *args):
965 46fbdd04 Iustin Pop
  """Write a message to stdout only, bypassing the logging system
966 46fbdd04 Iustin Pop

967 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
968 46fbdd04 Iustin Pop

969 46fbdd04 Iustin Pop
  @type txt: str
970 46fbdd04 Iustin Pop
  @param txt: the message
971 46fbdd04 Iustin Pop

972 46fbdd04 Iustin Pop
  """
973 46fbdd04 Iustin Pop
  _ToStream(sys.stdout, txt, *args)
974 46fbdd04 Iustin Pop
975 46fbdd04 Iustin Pop
976 46fbdd04 Iustin Pop
def ToStderr(txt, *args):
977 46fbdd04 Iustin Pop
  """Write a message to stderr only, bypassing the logging system
978 46fbdd04 Iustin Pop

979 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
980 46fbdd04 Iustin Pop

981 46fbdd04 Iustin Pop
  @type txt: str
982 46fbdd04 Iustin Pop
  @param txt: the message
983 46fbdd04 Iustin Pop

984 46fbdd04 Iustin Pop
  """
985 46fbdd04 Iustin Pop
  _ToStream(sys.stderr, txt, *args)
986 479636a3 Iustin Pop
987 479636a3 Iustin Pop
988 479636a3 Iustin Pop
class JobExecutor(object):
989 479636a3 Iustin Pop
  """Class which manages the submission and execution of multiple jobs.
990 479636a3 Iustin Pop

991 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
992 479636a3 Iustin Pop
  GetResults() calls.
993 479636a3 Iustin Pop

994 479636a3 Iustin Pop
  """
995 479636a3 Iustin Pop
  def __init__(self, cl=None, verbose=True):
996 479636a3 Iustin Pop
    self.queue = []
997 479636a3 Iustin Pop
    if cl is None:
998 479636a3 Iustin Pop
      cl = GetClient()
999 479636a3 Iustin Pop
    self.cl = cl
1000 479636a3 Iustin Pop
    self.verbose = verbose
1001 f2921752 Iustin Pop
    self.jobs = []
1002 479636a3 Iustin Pop
1003 479636a3 Iustin Pop
  def QueueJob(self, name, *ops):
1004 f2921752 Iustin Pop
    """Record a job for later submit.
1005 479636a3 Iustin Pop

1006 479636a3 Iustin Pop
    @type name: string
1007 479636a3 Iustin Pop
    @param name: a description of the job, will be used in WaitJobSet
1008 479636a3 Iustin Pop
    """
1009 f2921752 Iustin Pop
    self.queue.append((name, ops))
1010 f2921752 Iustin Pop
1011 f2921752 Iustin Pop
  def SubmitPending(self):
1012 f2921752 Iustin Pop
    """Submit all pending jobs.
1013 f2921752 Iustin Pop

1014 f2921752 Iustin Pop
    """
1015 f2921752 Iustin Pop
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1016 f2921752 Iustin Pop
    for ((status, data), (name, _)) in zip(results, self.queue):
1017 f2921752 Iustin Pop
      self.jobs.append((status, data, name))
1018 479636a3 Iustin Pop
1019 479636a3 Iustin Pop
  def GetResults(self):
1020 479636a3 Iustin Pop
    """Wait for and return the results of all jobs.
1021 479636a3 Iustin Pop

1022 479636a3 Iustin Pop
    @rtype: list
1023 479636a3 Iustin Pop
    @return: list of tuples (success, job results), in the same order
1024 479636a3 Iustin Pop
        as the submitted jobs; if a job has failed, instead of the result
1025 479636a3 Iustin Pop
        there will be the error message
1026 479636a3 Iustin Pop

1027 479636a3 Iustin Pop
    """
1028 f2921752 Iustin Pop
    if not self.jobs:
1029 f2921752 Iustin Pop
      self.SubmitPending()
1030 479636a3 Iustin Pop
    results = []
1031 479636a3 Iustin Pop
    if self.verbose:
1032 f2921752 Iustin Pop
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1033 f2921752 Iustin Pop
      if ok_jobs:
1034 f2921752 Iustin Pop
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1035 f2921752 Iustin Pop
    for submit_status, jid, name in self.jobs:
1036 f2921752 Iustin Pop
      if not submit_status:
1037 f2921752 Iustin Pop
        ToStderr("Failed to submit job for %s: %s", name, jid)
1038 f2921752 Iustin Pop
        results.append((False, jid))
1039 f2921752 Iustin Pop
        continue
1040 479636a3 Iustin Pop
      if self.verbose:
1041 479636a3 Iustin Pop
        ToStdout("Waiting for job %s for %s...", jid, name)
1042 479636a3 Iustin Pop
      try:
1043 479636a3 Iustin Pop
        job_result = PollJob(jid, cl=self.cl)
1044 479636a3 Iustin Pop
        success = True
1045 479636a3 Iustin Pop
      except (errors.GenericError, luxi.ProtocolError), err:
1046 479636a3 Iustin Pop
        _, job_result = FormatError(err)
1047 479636a3 Iustin Pop
        success = False
1048 479636a3 Iustin Pop
        # the error message will always be shown, verbose or not
1049 479636a3 Iustin Pop
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1050 479636a3 Iustin Pop
1051 479636a3 Iustin Pop
      results.append((success, job_result))
1052 479636a3 Iustin Pop
    return results
1053 479636a3 Iustin Pop
1054 479636a3 Iustin Pop
  def WaitOrShow(self, wait):
1055 479636a3 Iustin Pop
    """Wait for job results or only print the job IDs.
1056 479636a3 Iustin Pop

1057 479636a3 Iustin Pop
    @type wait: boolean
1058 479636a3 Iustin Pop
    @param wait: whether to wait or not
1059 479636a3 Iustin Pop

1060 479636a3 Iustin Pop
    """
1061 479636a3 Iustin Pop
    if wait:
1062 479636a3 Iustin Pop
      return self.GetResults()
1063 479636a3 Iustin Pop
    else:
1064 f2921752 Iustin Pop
      if not self.jobs:
1065 f2921752 Iustin Pop
        self.SubmitPending()
1066 f2921752 Iustin Pop
      for status, result, name in self.jobs:
1067 f2921752 Iustin Pop
        if status:
1068 f2921752 Iustin Pop
          ToStdout("%s: %s", result, name)
1069 f2921752 Iustin Pop
        else:
1070 f2921752 Iustin Pop
          ToStderr("Failure for %s: %s", name, result)