Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 08896026

History | View | Annotate | Download (32 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 d48031a1 Iustin Pop
           "GetOnlineNodes", "JobExecutor", "SYNC_OPT", "CONFIRM_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 d48031a1 Iustin Pop
CONFIRM_OPT = make_option("--yes", dest="confirm", action="store_true",
186 d48031a1 Iustin Pop
                          default=False, help="Do not require confirmation")
187 d48031a1 Iustin Pop
188 810c50b7 Iustin Pop
TAG_SRC_OPT = make_option("--from", dest="tags_source",
189 810c50b7 Iustin Pop
                          default=None, help="File with tag names")
190 810c50b7 Iustin Pop
191 94428652 Iustin Pop
SUBMIT_OPT = make_option("--submit", dest="submit_only",
192 94428652 Iustin Pop
                         default=False, action="store_true",
193 94428652 Iustin Pop
                         help="Submit the job and return the job ID, but"
194 94428652 Iustin Pop
                         " don't wait for the job to finish")
195 94428652 Iustin Pop
196 ec79568d Iustin Pop
SYNC_OPT = make_option("--sync", dest="do_locking",
197 ec79568d Iustin Pop
                       default=False, action="store_true",
198 ec79568d Iustin Pop
                       help="Grab locks while doing the queries"
199 ec79568d Iustin Pop
                       " in order to ensure more consistent results")
200 ec79568d Iustin Pop
201 64c65a2a Iustin Pop
_DRY_RUN_OPT = make_option("--dry-run", default=False,
202 64c65a2a Iustin Pop
                          action="store_true",
203 64c65a2a Iustin Pop
                          help="Do not execute the operation, just run the"
204 64c65a2a Iustin Pop
                          " check steps and verify it it could be executed")
205 64c65a2a Iustin Pop
206 60d49723 Michael Hanselmann
207 a8083063 Iustin Pop
def ARGS_FIXED(val):
208 a8083063 Iustin Pop
  """Macro-like function denoting a fixed number of arguments"""
209 a8083063 Iustin Pop
  return -val
210 a8083063 Iustin Pop
211 a8083063 Iustin Pop
212 a8083063 Iustin Pop
def ARGS_ATLEAST(val):
213 a8083063 Iustin Pop
  """Macro-like function denoting a minimum number of arguments"""
214 a8083063 Iustin Pop
  return val
215 a8083063 Iustin Pop
216 a8083063 Iustin Pop
217 a8083063 Iustin Pop
ARGS_NONE = None
218 a8083063 Iustin Pop
ARGS_ONE = ARGS_FIXED(1)
219 a8083063 Iustin Pop
ARGS_ANY = ARGS_ATLEAST(0)
220 a8083063 Iustin Pop
221 a8083063 Iustin Pop
222 a8083063 Iustin Pop
def check_unit(option, opt, value):
223 65fe4693 Iustin Pop
  """OptParsers custom converter for units.
224 65fe4693 Iustin Pop

225 65fe4693 Iustin Pop
  """
226 a8083063 Iustin Pop
  try:
227 a8083063 Iustin Pop
    return utils.ParseUnit(value)
228 a8083063 Iustin Pop
  except errors.UnitParseError, err:
229 3ecf6786 Iustin Pop
    raise OptionValueError("option %s: %s" % (opt, err))
230 a8083063 Iustin Pop
231 a8083063 Iustin Pop
232 a8083063 Iustin Pop
class CliOption(Option):
233 65fe4693 Iustin Pop
  """Custom option class for optparse.
234 65fe4693 Iustin Pop

235 65fe4693 Iustin Pop
  """
236 a8083063 Iustin Pop
  TYPES = Option.TYPES + ("unit",)
237 a8083063 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
238 a8083063 Iustin Pop
  TYPE_CHECKER["unit"] = check_unit
239 a8083063 Iustin Pop
240 a8083063 Iustin Pop
241 a8469393 Iustin Pop
def _SplitKeyVal(opt, data):
242 a8469393 Iustin Pop
  """Convert a KeyVal string into a dict.
243 a8469393 Iustin Pop

244 a8469393 Iustin Pop
  This function will convert a key=val[,...] string into a dict. Empty
245 a8469393 Iustin Pop
  values will be converted specially: keys which have the prefix 'no_'
246 a8469393 Iustin Pop
  will have the value=False and the prefix stripped, the others will
247 a8469393 Iustin Pop
  have value=True.
248 a8469393 Iustin Pop

249 a8469393 Iustin Pop
  @type opt: string
250 a8469393 Iustin Pop
  @param opt: a string holding the option name for which we process the
251 a8469393 Iustin Pop
      data, used in building error messages
252 a8469393 Iustin Pop
  @type data: string
253 a8469393 Iustin Pop
  @param data: a string of the format key=val,key=val,...
254 a8469393 Iustin Pop
  @rtype: dict
255 a8469393 Iustin Pop
  @return: {key=val, key=val}
256 a8469393 Iustin Pop
  @raises errors.ParameterError: if there are duplicate keys
257 a8469393 Iustin Pop

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

282 a8469393 Iustin Pop
  """
283 a8469393 Iustin Pop
  if ":" not in value:
284 a8469393 Iustin Pop
    retval =  (value, {})
285 a8469393 Iustin Pop
  else:
286 a8469393 Iustin Pop
    ident, rest = value.split(":", 1)
287 a8469393 Iustin Pop
    kv_dict = _SplitKeyVal(opt, rest)
288 a8469393 Iustin Pop
    retval = (ident, kv_dict)
289 a8469393 Iustin Pop
  return retval
290 a8469393 Iustin Pop
291 a8469393 Iustin Pop
292 a8469393 Iustin Pop
class IdentKeyValOption(Option):
293 a8469393 Iustin Pop
  """Custom option class for ident:key=val,key=val options.
294 a8469393 Iustin Pop

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

298 a8469393 Iustin Pop
  """
299 a8469393 Iustin Pop
  TYPES = Option.TYPES + ("identkeyval",)
300 a8469393 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
301 a8469393 Iustin Pop
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
302 a8469393 Iustin Pop
303 a8469393 Iustin Pop
304 a8469393 Iustin Pop
def check_key_val(option, opt, value):
305 a8469393 Iustin Pop
  """Custom parser for the KeyVal option type.
306 a8469393 Iustin Pop

307 a8469393 Iustin Pop
  """
308 a8469393 Iustin Pop
  return _SplitKeyVal(opt, value)
309 a8469393 Iustin Pop
310 a8469393 Iustin Pop
311 a8469393 Iustin Pop
class KeyValOption(Option):
312 a8469393 Iustin Pop
  """Custom option class for key=val,key=val options.
313 a8469393 Iustin Pop

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

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

331 c41eea6e Iustin Pop
  This function parses the arguements and returns the function which
332 c41eea6e Iustin Pop
  must be executed together with its (modified) arguments.
333 a8083063 Iustin Pop

334 c41eea6e Iustin Pop
  @param argv: the command line
335 c41eea6e Iustin Pop
  @param commands: dictionary with special contents, see the design
336 c41eea6e Iustin Pop
      doc for cmdline handling
337 c41eea6e Iustin Pop
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
338 098c0958 Michael Hanselmann

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

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

433 c41eea6e Iustin Pop
  @param text: the question to ask
434 a8083063 Iustin Pop

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

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

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

490 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
491 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
492 e9d741b6 Iustin Pop

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

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

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

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

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

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

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

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

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

613 64c65a2a Iustin Pop
  It will also add the dry-run parameter from the options passed, if true.
614 64c65a2a Iustin Pop

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

643 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
644 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
645 73702ee7 Iustin Pop
  second, a string describing the error message (not
646 73702ee7 Iustin Pop
  newline-terminated).
647 73702ee7 Iustin Pop

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

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

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

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

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

871 3386e7a9 Iustin Pop
  @type ts: timestamp
872 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
873 3386e7a9 Iustin Pop

874 3386e7a9 Iustin Pop
  @rtype: string
875 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
876 3386e7a9 Iustin Pop

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

887 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
888 2241e2b9 Iustin Pop

889 2241e2b9 Iustin Pop
    - s: seconds
890 2241e2b9 Iustin Pop
    - m: minutes
891 2241e2b9 Iustin Pop
    - h: hours
892 2241e2b9 Iustin Pop
    - d: day
893 2241e2b9 Iustin Pop
    - w: weeks
894 2241e2b9 Iustin Pop

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

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

929 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
930 4040a784 Iustin Pop
  the online nodes.
931 4040a784 Iustin Pop

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

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

955 46fbdd04 Iustin Pop
  @type stream: file object
956 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
957 46fbdd04 Iustin Pop
  @type txt: str
958 46fbdd04 Iustin Pop
  @param txt: the message
959 46fbdd04 Iustin Pop

960 46fbdd04 Iustin Pop
  """
961 46fbdd04 Iustin Pop
  if args:
962 46fbdd04 Iustin Pop
    args = tuple(args)
963 46fbdd04 Iustin Pop
    stream.write(txt % args)
964 46fbdd04 Iustin Pop
  else:
965 46fbdd04 Iustin Pop
    stream.write(txt)
966 46fbdd04 Iustin Pop
  stream.write('\n')
967 46fbdd04 Iustin Pop
  stream.flush()
968 46fbdd04 Iustin Pop
969 46fbdd04 Iustin Pop
970 46fbdd04 Iustin Pop
def ToStdout(txt, *args):
971 46fbdd04 Iustin Pop
  """Write a message to stdout only, bypassing the logging system
972 46fbdd04 Iustin Pop

973 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
974 46fbdd04 Iustin Pop

975 46fbdd04 Iustin Pop
  @type txt: str
976 46fbdd04 Iustin Pop
  @param txt: the message
977 46fbdd04 Iustin Pop

978 46fbdd04 Iustin Pop
  """
979 46fbdd04 Iustin Pop
  _ToStream(sys.stdout, txt, *args)
980 46fbdd04 Iustin Pop
981 46fbdd04 Iustin Pop
982 46fbdd04 Iustin Pop
def ToStderr(txt, *args):
983 46fbdd04 Iustin Pop
  """Write a message to stderr only, bypassing the logging system
984 46fbdd04 Iustin Pop

985 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
986 46fbdd04 Iustin Pop

987 46fbdd04 Iustin Pop
  @type txt: str
988 46fbdd04 Iustin Pop
  @param txt: the message
989 46fbdd04 Iustin Pop

990 46fbdd04 Iustin Pop
  """
991 46fbdd04 Iustin Pop
  _ToStream(sys.stderr, txt, *args)
992 479636a3 Iustin Pop
993 479636a3 Iustin Pop
994 479636a3 Iustin Pop
class JobExecutor(object):
995 479636a3 Iustin Pop
  """Class which manages the submission and execution of multiple jobs.
996 479636a3 Iustin Pop

997 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
998 479636a3 Iustin Pop
  GetResults() calls.
999 479636a3 Iustin Pop

1000 479636a3 Iustin Pop
  """
1001 479636a3 Iustin Pop
  def __init__(self, cl=None, verbose=True):
1002 479636a3 Iustin Pop
    self.queue = []
1003 479636a3 Iustin Pop
    if cl is None:
1004 479636a3 Iustin Pop
      cl = GetClient()
1005 479636a3 Iustin Pop
    self.cl = cl
1006 479636a3 Iustin Pop
    self.verbose = verbose
1007 23b4b983 Iustin Pop
    self.jobs = []
1008 479636a3 Iustin Pop
1009 479636a3 Iustin Pop
  def QueueJob(self, name, *ops):
1010 23b4b983 Iustin Pop
    """Record a job for later submit.
1011 479636a3 Iustin Pop

1012 479636a3 Iustin Pop
    @type name: string
1013 479636a3 Iustin Pop
    @param name: a description of the job, will be used in WaitJobSet
1014 479636a3 Iustin Pop
    """
1015 23b4b983 Iustin Pop
    self.queue.append((name, ops))
1016 23b4b983 Iustin Pop
1017 23b4b983 Iustin Pop
1018 23b4b983 Iustin Pop
  def SubmitPending(self):
1019 23b4b983 Iustin Pop
    """Submit all pending jobs.
1020 23b4b983 Iustin Pop

1021 23b4b983 Iustin Pop
    """
1022 23b4b983 Iustin Pop
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1023 23b4b983 Iustin Pop
    for ((status, data), (name, _)) in zip(results, self.queue):
1024 23b4b983 Iustin Pop
      self.jobs.append((status, data, name))
1025 479636a3 Iustin Pop
1026 479636a3 Iustin Pop
  def GetResults(self):
1027 479636a3 Iustin Pop
    """Wait for and return the results of all jobs.
1028 479636a3 Iustin Pop

1029 479636a3 Iustin Pop
    @rtype: list
1030 479636a3 Iustin Pop
    @return: list of tuples (success, job results), in the same order
1031 479636a3 Iustin Pop
        as the submitted jobs; if a job has failed, instead of the result
1032 479636a3 Iustin Pop
        there will be the error message
1033 479636a3 Iustin Pop

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

1064 479636a3 Iustin Pop
    @type wait: boolean
1065 479636a3 Iustin Pop
    @param wait: whether to wait or not
1066 479636a3 Iustin Pop

1067 479636a3 Iustin Pop
    """
1068 479636a3 Iustin Pop
    if wait:
1069 479636a3 Iustin Pop
      return self.GetResults()
1070 479636a3 Iustin Pop
    else:
1071 23b4b983 Iustin Pop
      if not self.jobs:
1072 23b4b983 Iustin Pop
        self.SubmitPending()
1073 23b4b983 Iustin Pop
      for status, result, name in self.jobs:
1074 23b4b983 Iustin Pop
        if status:
1075 23b4b983 Iustin Pop
          ToStdout("%s: %s", result, name)
1076 23b4b983 Iustin Pop
        else:
1077 23b4b983 Iustin Pop
          ToStderr("Failure for %s: %s", name, result)