Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ f2921752

History | View | Annotate | Download (31.5 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 ceab32dd Iustin Pop
__all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain",
45 af30b2fd Michael Hanselmann
           "SubmitOpCode", "GetClient",
46 a8469393 Iustin Pop
           "cli_option", "ikv_option", "keyval_option",
47 a8469393 Iustin Pop
           "GenerateTable", "AskUser",
48 a8083063 Iustin Pop
           "ARGS_NONE", "ARGS_FIXED", "ARGS_ATLEAST", "ARGS_ANY", "ARGS_ONE",
49 94428652 Iustin Pop
           "USEUNITS_OPT", "FIELDS_OPT", "FORCE_OPT", "SUBMIT_OPT",
50 810c50b7 Iustin Pop
           "ListTags", "AddTags", "RemoveTags", "TAG_SRC_OPT",
51 94428652 Iustin Pop
           "FormatError", "SplitNodeOption", "SubmitOrSend",
52 07cd723a Iustin Pop
           "JobSubmittedException", "FormatTimestamp", "ParseTimespec",
53 a5728081 Guido Trotter
           "ToStderr", "ToStdout", "UsesRPC",
54 ec79568d Iustin Pop
           "GetOnlineNodes", "JobExecutor", "SYNC_OPT",
55 846baef9 Iustin Pop
           ]
56 846baef9 Iustin Pop
57 846baef9 Iustin Pop
58 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
59 846baef9 Iustin Pop
  """Extract the tag type object.
60 846baef9 Iustin Pop

61 846baef9 Iustin Pop
  Note that this function will modify its args parameter.
62 846baef9 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

425 c41eea6e Iustin Pop
  @param text: the question to ask
426 a8083063 Iustin Pop

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

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

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

482 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
483 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
484 e9d741b6 Iustin Pop

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

487 e9d741b6 Iustin Pop
  """
488 e9d741b6 Iustin Pop
489 e9d741b6 Iustin Pop
490 0a1e74d9 Iustin Pop
def SendJob(ops, cl=None):
491 0a1e74d9 Iustin Pop
  """Function to submit an opcode without waiting for the results.
492 a8083063 Iustin Pop

493 0a1e74d9 Iustin Pop
  @type ops: list
494 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
495 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
496 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
497 0a1e74d9 Iustin Pop
             if None, a new client will be created
498 a8083063 Iustin Pop

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

511 0a1e74d9 Iustin Pop
  @type job_id: job identified
512 0a1e74d9 Iustin Pop
  @param job_id: the job to poll for results
513 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
514 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
515 0a1e74d9 Iustin Pop
             if None, a new client will be created
516 0a1e74d9 Iustin Pop

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

582 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
583 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
584 0a1e74d9 Iustin Pop
  interaction functions.
585 0a1e74d9 Iustin Pop

586 0a1e74d9 Iustin Pop
  """
587 0a1e74d9 Iustin Pop
  if cl is None:
588 0a1e74d9 Iustin Pop
    cl = GetClient()
589 0a1e74d9 Iustin Pop
590 0a1e74d9 Iustin Pop
  job_id = SendJob([op], cl)
591 0a1e74d9 Iustin Pop
592 53c04d04 Iustin Pop
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
593 53c04d04 Iustin Pop
594 53c04d04 Iustin Pop
  return op_results[0]
595 0a1e74d9 Iustin Pop
596 0a1e74d9 Iustin Pop
597 94428652 Iustin Pop
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
598 94428652 Iustin Pop
  """Wrapper around SubmitOpCode or SendJob.
599 94428652 Iustin Pop

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

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

631 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
632 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
633 73702ee7 Iustin Pop
  second, a string describing the error message (not
634 73702ee7 Iustin Pop
  newline-terminated).
635 73702ee7 Iustin Pop

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

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

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

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

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

861 3386e7a9 Iustin Pop
  @type ts: timestamp
862 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
863 3386e7a9 Iustin Pop

864 3386e7a9 Iustin Pop
  @rtype: string
865 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
866 3386e7a9 Iustin Pop

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

877 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
878 2241e2b9 Iustin Pop

879 2241e2b9 Iustin Pop
    - s: seconds
880 2241e2b9 Iustin Pop
    - m: minutes
881 2241e2b9 Iustin Pop
    - h: hours
882 2241e2b9 Iustin Pop
    - d: day
883 2241e2b9 Iustin Pop
    - w: weeks
884 2241e2b9 Iustin Pop

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

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

919 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
920 4040a784 Iustin Pop
  the online nodes.
921 4040a784 Iustin Pop

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

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

945 46fbdd04 Iustin Pop
  @type stream: file object
946 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
947 46fbdd04 Iustin Pop
  @type txt: str
948 46fbdd04 Iustin Pop
  @param txt: the message
949 46fbdd04 Iustin Pop

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

963 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
964 46fbdd04 Iustin Pop

965 46fbdd04 Iustin Pop
  @type txt: str
966 46fbdd04 Iustin Pop
  @param txt: the message
967 46fbdd04 Iustin Pop

968 46fbdd04 Iustin Pop
  """
969 46fbdd04 Iustin Pop
  _ToStream(sys.stdout, txt, *args)
970 46fbdd04 Iustin Pop
971 46fbdd04 Iustin Pop
972 46fbdd04 Iustin Pop
def ToStderr(txt, *args):
973 46fbdd04 Iustin Pop
  """Write a message to stderr only, bypassing the logging system
974 46fbdd04 Iustin Pop

975 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
976 46fbdd04 Iustin Pop

977 46fbdd04 Iustin Pop
  @type txt: str
978 46fbdd04 Iustin Pop
  @param txt: the message
979 46fbdd04 Iustin Pop

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

987 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
988 479636a3 Iustin Pop
  GetResults() calls.
989 479636a3 Iustin Pop

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

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

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

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

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

1054 479636a3 Iustin Pop
    @type wait: boolean
1055 479636a3 Iustin Pop
    @param wait: whether to wait or not
1056 479636a3 Iustin Pop

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