Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 4040a784

History | View | Annotate | Download (29.2 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 a604f165 Iustin Pop
           "ValidateBeParams",
54 46fbdd04 Iustin Pop
           "ToStderr", "ToStdout",
55 4331f6cd Michael Hanselmann
           "UsesRPC",
56 4040a784 Iustin Pop
           "GetOnlineNodes",
57 846baef9 Iustin Pop
           ]
58 846baef9 Iustin Pop
59 846baef9 Iustin Pop
60 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
61 846baef9 Iustin Pop
  """Extract the tag type object.
62 846baef9 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

320 c41eea6e Iustin Pop
  This function parses the arguements and returns the function which
321 c41eea6e Iustin Pop
  must be executed together with its (modified) arguments.
322 a8083063 Iustin Pop

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

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

402 60d49723 Michael Hanselmann
  """
403 60d49723 Michael Hanselmann
  if value and ':' in value:
404 60d49723 Michael Hanselmann
    return value.split(':', 1)
405 60d49723 Michael Hanselmann
  else:
406 60d49723 Michael Hanselmann
    return (value, None)
407 60d49723 Michael Hanselmann
408 60d49723 Michael Hanselmann
409 a604f165 Iustin Pop
def ValidateBeParams(bep):
410 a604f165 Iustin Pop
  """Parse and check the given beparams.
411 a604f165 Iustin Pop

412 a604f165 Iustin Pop
  The function will update in-place the given dictionary.
413 a604f165 Iustin Pop

414 a604f165 Iustin Pop
  @type bep: dict
415 a604f165 Iustin Pop
  @param bep: input beparams
416 a604f165 Iustin Pop
  @raise errors.ParameterError: if the input values are not OK
417 a604f165 Iustin Pop
  @raise errors.UnitParseError: if the input values are not OK
418 a604f165 Iustin Pop

419 a604f165 Iustin Pop
  """
420 a604f165 Iustin Pop
  if constants.BE_MEMORY in bep:
421 a604f165 Iustin Pop
    bep[constants.BE_MEMORY] = utils.ParseUnit(bep[constants.BE_MEMORY])
422 a604f165 Iustin Pop
423 a604f165 Iustin Pop
  if constants.BE_VCPUS in bep:
424 a604f165 Iustin Pop
    try:
425 a604f165 Iustin Pop
      bep[constants.BE_VCPUS] = int(bep[constants.BE_VCPUS])
426 a604f165 Iustin Pop
    except ValueError:
427 a604f165 Iustin Pop
      raise errors.ParameterError("Invalid number of VCPUs")
428 a604f165 Iustin Pop
429 a604f165 Iustin Pop
430 4331f6cd Michael Hanselmann
def UsesRPC(fn):
431 4331f6cd Michael Hanselmann
  def wrapper(*args, **kwargs):
432 4331f6cd Michael Hanselmann
    rpc.Init()
433 4331f6cd Michael Hanselmann
    try:
434 4331f6cd Michael Hanselmann
      return fn(*args, **kwargs)
435 4331f6cd Michael Hanselmann
    finally:
436 4331f6cd Michael Hanselmann
      rpc.Shutdown()
437 4331f6cd Michael Hanselmann
  return wrapper
438 4331f6cd Michael Hanselmann
439 4331f6cd Michael Hanselmann
440 47988778 Iustin Pop
def AskUser(text, choices=None):
441 47988778 Iustin Pop
  """Ask the user a question.
442 a8083063 Iustin Pop

443 c41eea6e Iustin Pop
  @param text: the question to ask
444 a8083063 Iustin Pop

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

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

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

500 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
501 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
502 e9d741b6 Iustin Pop

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

505 e9d741b6 Iustin Pop
  """
506 e9d741b6 Iustin Pop
507 e9d741b6 Iustin Pop
508 0a1e74d9 Iustin Pop
def SendJob(ops, cl=None):
509 0a1e74d9 Iustin Pop
  """Function to submit an opcode without waiting for the results.
510 a8083063 Iustin Pop

511 0a1e74d9 Iustin Pop
  @type ops: list
512 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
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 a8083063 Iustin Pop

517 a8083063 Iustin Pop
  """
518 e2212007 Iustin Pop
  if cl is None:
519 b33e986b Iustin Pop
    cl = GetClient()
520 685ee993 Iustin Pop
521 0a1e74d9 Iustin Pop
  job_id = cl.SubmitJob(ops)
522 0a1e74d9 Iustin Pop
523 0a1e74d9 Iustin Pop
  return job_id
524 0a1e74d9 Iustin Pop
525 0a1e74d9 Iustin Pop
526 281606c1 Michael Hanselmann
def PollJob(job_id, cl=None, feedback_fn=None):
527 0a1e74d9 Iustin Pop
  """Function to poll for the result of a job.
528 0a1e74d9 Iustin Pop

529 0a1e74d9 Iustin Pop
  @type job_id: job identified
530 0a1e74d9 Iustin Pop
  @param job_id: the job to poll for results
531 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
532 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
533 0a1e74d9 Iustin Pop
             if None, a new client will be created
534 0a1e74d9 Iustin Pop

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

599 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
600 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
601 0a1e74d9 Iustin Pop
  interaction functions.
602 0a1e74d9 Iustin Pop

603 0a1e74d9 Iustin Pop
  """
604 0a1e74d9 Iustin Pop
  if cl is None:
605 0a1e74d9 Iustin Pop
    cl = GetClient()
606 0a1e74d9 Iustin Pop
607 0a1e74d9 Iustin Pop
  job_id = SendJob([op], cl)
608 0a1e74d9 Iustin Pop
609 53c04d04 Iustin Pop
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
610 53c04d04 Iustin Pop
611 53c04d04 Iustin Pop
  return op_results[0]
612 0a1e74d9 Iustin Pop
613 0a1e74d9 Iustin Pop
614 94428652 Iustin Pop
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
615 94428652 Iustin Pop
  """Wrapper around SubmitOpCode or SendJob.
616 94428652 Iustin Pop

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

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

648 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
649 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
650 73702ee7 Iustin Pop
  second, a string describing the error message (not
651 73702ee7 Iustin Pop
  newline-terminated).
652 73702ee7 Iustin Pop

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

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

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

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

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

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

873 3386e7a9 Iustin Pop
  @rtype: string
874 3386e7a9 Iustin Pop
  @returns: a string with the formatted timestamp
875 3386e7a9 Iustin Pop

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

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

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

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

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

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

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

939 4040a784 Iustin Pop
  """
940 4040a784 Iustin Pop
  if cl is None:
941 4040a784 Iustin Pop
    cl = GetClient()
942 4040a784 Iustin Pop
943 4040a784 Iustin Pop
  op = opcodes.OpQueryNodes(output_fields=["name", "offline"],
944 4040a784 Iustin Pop
                            names=nodes)
945 4040a784 Iustin Pop
  result = SubmitOpCode(op, cl=cl)
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)