Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 8381fa2d

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

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

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

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

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

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

283 a8469393 Iustin Pop
  """
284 a8469393 Iustin Pop
  if ":" not in value:
285 8b46606c Guido Trotter
    ident, rest = value, ''
286 a8469393 Iustin Pop
  else:
287 a8469393 Iustin Pop
    ident, rest = value.split(":", 1)
288 8b46606c Guido Trotter
289 8b46606c Guido Trotter
  if ident.startswith(NO_PREFIX):
290 8b46606c Guido Trotter
    if rest:
291 8b46606c Guido Trotter
      msg = "Cannot pass options when removing parameter groups: %s" % value
292 8b46606c Guido Trotter
      raise errors.ParameterError(msg)
293 8b46606c Guido Trotter
    retval = (ident[len(NO_PREFIX):], False)
294 8b46606c Guido Trotter
  elif ident.startswith(UN_PREFIX):
295 8b46606c Guido Trotter
    if rest:
296 8b46606c Guido Trotter
      msg = "Cannot pass options when removing parameter groups: %s" % value
297 8b46606c Guido Trotter
      raise errors.ParameterError(msg)
298 8b46606c Guido Trotter
    retval = (ident[len(UN_PREFIX):], None)
299 8b46606c Guido Trotter
  else:
300 a8469393 Iustin Pop
    kv_dict = _SplitKeyVal(opt, rest)
301 a8469393 Iustin Pop
    retval = (ident, kv_dict)
302 a8469393 Iustin Pop
  return retval
303 a8469393 Iustin Pop
304 a8469393 Iustin Pop
305 a8469393 Iustin Pop
class IdentKeyValOption(Option):
306 a8469393 Iustin Pop
  """Custom option class for ident:key=val,key=val options.
307 a8469393 Iustin Pop

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

311 a8469393 Iustin Pop
  """
312 a8469393 Iustin Pop
  TYPES = Option.TYPES + ("identkeyval",)
313 a8469393 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
314 a8469393 Iustin Pop
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
315 a8469393 Iustin Pop
316 a8469393 Iustin Pop
317 a8469393 Iustin Pop
def check_key_val(option, opt, value):
318 a8469393 Iustin Pop
  """Custom parser for the KeyVal option type.
319 a8469393 Iustin Pop

320 a8469393 Iustin Pop
  """
321 a8469393 Iustin Pop
  return _SplitKeyVal(opt, value)
322 a8469393 Iustin Pop
323 a8469393 Iustin Pop
324 a8469393 Iustin Pop
class KeyValOption(Option):
325 a8469393 Iustin Pop
  """Custom option class for key=val,key=val options.
326 a8469393 Iustin Pop

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

329 a8469393 Iustin Pop
  """
330 a8469393 Iustin Pop
  TYPES = Option.TYPES + ("keyval",)
331 a8469393 Iustin Pop
  TYPE_CHECKER = copy.copy(Option.TYPE_CHECKER)
332 a8469393 Iustin Pop
  TYPE_CHECKER["keyval"] = check_key_val
333 a8469393 Iustin Pop
334 a8469393 Iustin Pop
335 a8083063 Iustin Pop
# optparse.py sets make_option, so we do it for our own option class, too
336 a8083063 Iustin Pop
cli_option = CliOption
337 a8469393 Iustin Pop
ikv_option = IdentKeyValOption
338 a8469393 Iustin Pop
keyval_option = KeyValOption
339 a8083063 Iustin Pop
340 a8083063 Iustin Pop
341 de47cf8f Guido Trotter
def _ParseArgs(argv, commands, aliases):
342 c41eea6e Iustin Pop
  """Parser for the command line arguments.
343 a8083063 Iustin Pop

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

347 c41eea6e Iustin Pop
  @param argv: the command line
348 c41eea6e Iustin Pop
  @param commands: dictionary with special contents, see the design
349 c41eea6e Iustin Pop
      doc for cmdline handling
350 c41eea6e Iustin Pop
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
351 098c0958 Michael Hanselmann

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

426 60d49723 Michael Hanselmann
  """
427 60d49723 Michael Hanselmann
  if value and ':' in value:
428 60d49723 Michael Hanselmann
    return value.split(':', 1)
429 60d49723 Michael Hanselmann
  else:
430 60d49723 Michael Hanselmann
    return (value, None)
431 60d49723 Michael Hanselmann
432 60d49723 Michael Hanselmann
433 4331f6cd Michael Hanselmann
def UsesRPC(fn):
434 4331f6cd Michael Hanselmann
  def wrapper(*args, **kwargs):
435 4331f6cd Michael Hanselmann
    rpc.Init()
436 4331f6cd Michael Hanselmann
    try:
437 4331f6cd Michael Hanselmann
      return fn(*args, **kwargs)
438 4331f6cd Michael Hanselmann
    finally:
439 4331f6cd Michael Hanselmann
      rpc.Shutdown()
440 4331f6cd Michael Hanselmann
  return wrapper
441 4331f6cd Michael Hanselmann
442 4331f6cd Michael Hanselmann
443 47988778 Iustin Pop
def AskUser(text, choices=None):
444 47988778 Iustin Pop
  """Ask the user a question.
445 a8083063 Iustin Pop

446 c41eea6e Iustin Pop
  @param text: the question to ask
447 a8083063 Iustin Pop

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

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

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

503 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
504 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
505 e9d741b6 Iustin Pop

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

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

514 0a1e74d9 Iustin Pop
  @type ops: list
515 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
516 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
517 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
518 0a1e74d9 Iustin Pop
             if None, a new client will be created
519 a8083063 Iustin Pop

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

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

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

603 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
604 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
605 0a1e74d9 Iustin Pop
  interaction functions.
606 0a1e74d9 Iustin Pop

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

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

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

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

656 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
657 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
658 73702ee7 Iustin Pop
  second, a string describing the error message (not
659 73702ee7 Iustin Pop
  newline-terminated).
660 73702ee7 Iustin Pop

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

728 334d1483 Iustin Pop
  Arguments:
729 334d1483 Iustin Pop
    - commands: a dictionary with a special structure, see the design doc
730 334d1483 Iustin Pop
                for command line handling.
731 334d1483 Iustin Pop
    - override: if not None, we expect a dictionary with keys that will
732 334d1483 Iustin Pop
                override command line options; this can be used to pass
733 334d1483 Iustin Pop
                options from the scripts to generic functions
734 de47cf8f Guido Trotter
    - aliases: dictionary with command aliases {'alias': 'target, ...}
735 a8083063 Iustin Pop

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

790 9fbfbb7b Iustin Pop
  @type headers: dict
791 9fbfbb7b Iustin Pop
  @param headers: dictionary mapping field names to headers for
792 9fbfbb7b Iustin Pop
      the table
793 9fbfbb7b Iustin Pop
  @type fields: list
794 9fbfbb7b Iustin Pop
  @param fields: the field names corresponding to each row in
795 9fbfbb7b Iustin Pop
      the data field
796 9fbfbb7b Iustin Pop
  @param separator: the separator to be used; if this is None,
797 9fbfbb7b Iustin Pop
      the default 'smart' algorithm is used which computes optimal
798 9fbfbb7b Iustin Pop
      field width, otherwise just the separator is used between
799 9fbfbb7b Iustin Pop
      each field
800 9fbfbb7b Iustin Pop
  @type data: list
801 9fbfbb7b Iustin Pop
  @param data: a list of lists, each sublist being one row to be output
802 9fbfbb7b Iustin Pop
  @type numfields: list
803 9fbfbb7b Iustin Pop
  @param numfields: a list with the fields that hold numeric
804 9fbfbb7b Iustin Pop
      values and thus should be right-aligned
805 9fbfbb7b Iustin Pop
  @type unitfields: list
806 9fbfbb7b Iustin Pop
  @param unitfields: a list with the fields that hold numeric
807 9fbfbb7b Iustin Pop
      values that should be formatted with the units field
808 9fbfbb7b Iustin Pop
  @type units: string or None
809 9fbfbb7b Iustin Pop
  @param units: the units we should use for formatting, or None for
810 9fbfbb7b Iustin Pop
      automatic choice (human-readable for non-separator usage, otherwise
811 9fbfbb7b Iustin Pop
      megabytes); this is a one-letter string
812 137161c9 Michael Hanselmann

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

890 3386e7a9 Iustin Pop
  @type ts: timestamp
891 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
892 3386e7a9 Iustin Pop

893 3386e7a9 Iustin Pop
  @rtype: string
894 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
895 3386e7a9 Iustin Pop

896 3386e7a9 Iustin Pop
  """
897 e0ec0ff6 Iustin Pop
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
898 e0ec0ff6 Iustin Pop
    return '?'
899 3386e7a9 Iustin Pop
  sec, usec = ts
900 3386e7a9 Iustin Pop
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
901 2241e2b9 Iustin Pop
902 2241e2b9 Iustin Pop
903 2241e2b9 Iustin Pop
def ParseTimespec(value):
904 2241e2b9 Iustin Pop
  """Parse a time specification.
905 2241e2b9 Iustin Pop

906 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
907 2241e2b9 Iustin Pop

908 2241e2b9 Iustin Pop
    - s: seconds
909 2241e2b9 Iustin Pop
    - m: minutes
910 2241e2b9 Iustin Pop
    - h: hours
911 2241e2b9 Iustin Pop
    - d: day
912 2241e2b9 Iustin Pop
    - w: weeks
913 2241e2b9 Iustin Pop

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

916 2241e2b9 Iustin Pop
  """
917 2241e2b9 Iustin Pop
  value = str(value)
918 2241e2b9 Iustin Pop
  if not value:
919 2241e2b9 Iustin Pop
    raise errors.OpPrereqError("Empty time specification passed")
920 2241e2b9 Iustin Pop
  suffix_map = {
921 2241e2b9 Iustin Pop
    's': 1,
922 2241e2b9 Iustin Pop
    'm': 60,
923 2241e2b9 Iustin Pop
    'h': 3600,
924 2241e2b9 Iustin Pop
    'd': 86400,
925 2241e2b9 Iustin Pop
    'w': 604800,
926 2241e2b9 Iustin Pop
    }
927 2241e2b9 Iustin Pop
  if value[-1] not in suffix_map:
928 2241e2b9 Iustin Pop
    try:
929 2241e2b9 Iustin Pop
      value = int(value)
930 2241e2b9 Iustin Pop
    except ValueError:
931 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
932 2241e2b9 Iustin Pop
  else:
933 2241e2b9 Iustin Pop
    multiplier = suffix_map[value[-1]]
934 2241e2b9 Iustin Pop
    value = value[:-1]
935 2241e2b9 Iustin Pop
    if not value: # no data left after stripping the suffix
936 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification (only"
937 2241e2b9 Iustin Pop
                                 " suffix passed)")
938 2241e2b9 Iustin Pop
    try:
939 2241e2b9 Iustin Pop
      value = int(value) * multiplier
940 2241e2b9 Iustin Pop
    except ValueError:
941 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
942 2241e2b9 Iustin Pop
  return value
943 46fbdd04 Iustin Pop
944 46fbdd04 Iustin Pop
945 4040a784 Iustin Pop
def GetOnlineNodes(nodes, cl=None, nowarn=False):
946 4040a784 Iustin Pop
  """Returns the names of online nodes.
947 4040a784 Iustin Pop

948 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
949 4040a784 Iustin Pop
  the online nodes.
950 4040a784 Iustin Pop

951 4040a784 Iustin Pop
  @param nodes: if not empty, use only this subset of nodes (minus the
952 4040a784 Iustin Pop
      offline ones)
953 4040a784 Iustin Pop
  @param cl: if not None, luxi client to use
954 4040a784 Iustin Pop
  @type nowarn: boolean
955 4040a784 Iustin Pop
  @param nowarn: by default, this function will output a note with the
956 4040a784 Iustin Pop
      offline nodes that are skipped; if this parameter is True the
957 4040a784 Iustin Pop
      note is not displayed
958 4040a784 Iustin Pop

959 4040a784 Iustin Pop
  """
960 4040a784 Iustin Pop
  if cl is None:
961 4040a784 Iustin Pop
    cl = GetClient()
962 4040a784 Iustin Pop
963 2e7b8369 Iustin Pop
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
964 2e7b8369 Iustin Pop
                         use_locking=False)
965 4040a784 Iustin Pop
  offline = [row[0] for row in result if row[1]]
966 4040a784 Iustin Pop
  if offline and not nowarn:
967 4040a784 Iustin Pop
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
968 4040a784 Iustin Pop
  return [row[0] for row in result if not row[1]]
969 4040a784 Iustin Pop
970 4040a784 Iustin Pop
971 46fbdd04 Iustin Pop
def _ToStream(stream, txt, *args):
972 46fbdd04 Iustin Pop
  """Write a message to a stream, bypassing the logging system
973 46fbdd04 Iustin Pop

974 46fbdd04 Iustin Pop
  @type stream: file object
975 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
976 46fbdd04 Iustin Pop
  @type txt: str
977 46fbdd04 Iustin Pop
  @param txt: the message
978 46fbdd04 Iustin Pop

979 46fbdd04 Iustin Pop
  """
980 46fbdd04 Iustin Pop
  if args:
981 46fbdd04 Iustin Pop
    args = tuple(args)
982 46fbdd04 Iustin Pop
    stream.write(txt % args)
983 46fbdd04 Iustin Pop
  else:
984 46fbdd04 Iustin Pop
    stream.write(txt)
985 46fbdd04 Iustin Pop
  stream.write('\n')
986 46fbdd04 Iustin Pop
  stream.flush()
987 46fbdd04 Iustin Pop
988 46fbdd04 Iustin Pop
989 46fbdd04 Iustin Pop
def ToStdout(txt, *args):
990 46fbdd04 Iustin Pop
  """Write a message to stdout only, bypassing the logging system
991 46fbdd04 Iustin Pop

992 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
993 46fbdd04 Iustin Pop

994 46fbdd04 Iustin Pop
  @type txt: str
995 46fbdd04 Iustin Pop
  @param txt: the message
996 46fbdd04 Iustin Pop

997 46fbdd04 Iustin Pop
  """
998 46fbdd04 Iustin Pop
  _ToStream(sys.stdout, txt, *args)
999 46fbdd04 Iustin Pop
1000 46fbdd04 Iustin Pop
1001 46fbdd04 Iustin Pop
def ToStderr(txt, *args):
1002 46fbdd04 Iustin Pop
  """Write a message to stderr only, bypassing the logging system
1003 46fbdd04 Iustin Pop

1004 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1005 46fbdd04 Iustin Pop

1006 46fbdd04 Iustin Pop
  @type txt: str
1007 46fbdd04 Iustin Pop
  @param txt: the message
1008 46fbdd04 Iustin Pop

1009 46fbdd04 Iustin Pop
  """
1010 46fbdd04 Iustin Pop
  _ToStream(sys.stderr, txt, *args)
1011 479636a3 Iustin Pop
1012 479636a3 Iustin Pop
1013 479636a3 Iustin Pop
class JobExecutor(object):
1014 479636a3 Iustin Pop
  """Class which manages the submission and execution of multiple jobs.
1015 479636a3 Iustin Pop

1016 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
1017 479636a3 Iustin Pop
  GetResults() calls.
1018 479636a3 Iustin Pop

1019 479636a3 Iustin Pop
  """
1020 479636a3 Iustin Pop
  def __init__(self, cl=None, verbose=True):
1021 479636a3 Iustin Pop
    self.queue = []
1022 479636a3 Iustin Pop
    if cl is None:
1023 479636a3 Iustin Pop
      cl = GetClient()
1024 479636a3 Iustin Pop
    self.cl = cl
1025 479636a3 Iustin Pop
    self.verbose = verbose
1026 23b4b983 Iustin Pop
    self.jobs = []
1027 479636a3 Iustin Pop
1028 479636a3 Iustin Pop
  def QueueJob(self, name, *ops):
1029 23b4b983 Iustin Pop
    """Record a job for later submit.
1030 479636a3 Iustin Pop

1031 479636a3 Iustin Pop
    @type name: string
1032 479636a3 Iustin Pop
    @param name: a description of the job, will be used in WaitJobSet
1033 479636a3 Iustin Pop
    """
1034 23b4b983 Iustin Pop
    self.queue.append((name, ops))
1035 23b4b983 Iustin Pop
1036 23b4b983 Iustin Pop
  def SubmitPending(self):
1037 23b4b983 Iustin Pop
    """Submit all pending jobs.
1038 23b4b983 Iustin Pop

1039 23b4b983 Iustin Pop
    """
1040 23b4b983 Iustin Pop
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1041 23b4b983 Iustin Pop
    for ((status, data), (name, _)) in zip(results, self.queue):
1042 23b4b983 Iustin Pop
      self.jobs.append((status, data, name))
1043 479636a3 Iustin Pop
1044 479636a3 Iustin Pop
  def GetResults(self):
1045 479636a3 Iustin Pop
    """Wait for and return the results of all jobs.
1046 479636a3 Iustin Pop

1047 479636a3 Iustin Pop
    @rtype: list
1048 479636a3 Iustin Pop
    @return: list of tuples (success, job results), in the same order
1049 479636a3 Iustin Pop
        as the submitted jobs; if a job has failed, instead of the result
1050 479636a3 Iustin Pop
        there will be the error message
1051 479636a3 Iustin Pop

1052 479636a3 Iustin Pop
    """
1053 23b4b983 Iustin Pop
    if not self.jobs:
1054 23b4b983 Iustin Pop
      self.SubmitPending()
1055 479636a3 Iustin Pop
    results = []
1056 479636a3 Iustin Pop
    if self.verbose:
1057 23b4b983 Iustin Pop
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1058 23b4b983 Iustin Pop
      if ok_jobs:
1059 23b4b983 Iustin Pop
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1060 23b4b983 Iustin Pop
    for submit_status, jid, name in self.jobs:
1061 23b4b983 Iustin Pop
      if not submit_status:
1062 23b4b983 Iustin Pop
        ToStderr("Failed to submit job for %s: %s", name, jid)
1063 23b4b983 Iustin Pop
        results.append((False, jid))
1064 23b4b983 Iustin Pop
        continue
1065 479636a3 Iustin Pop
      if self.verbose:
1066 479636a3 Iustin Pop
        ToStdout("Waiting for job %s for %s...", jid, name)
1067 479636a3 Iustin Pop
      try:
1068 479636a3 Iustin Pop
        job_result = PollJob(jid, cl=self.cl)
1069 479636a3 Iustin Pop
        success = True
1070 479636a3 Iustin Pop
      except (errors.GenericError, luxi.ProtocolError), err:
1071 479636a3 Iustin Pop
        _, job_result = FormatError(err)
1072 479636a3 Iustin Pop
        success = False
1073 479636a3 Iustin Pop
        # the error message will always be shown, verbose or not
1074 479636a3 Iustin Pop
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1075 479636a3 Iustin Pop
1076 479636a3 Iustin Pop
      results.append((success, job_result))
1077 479636a3 Iustin Pop
    return results
1078 479636a3 Iustin Pop
1079 479636a3 Iustin Pop
  def WaitOrShow(self, wait):
1080 479636a3 Iustin Pop
    """Wait for job results or only print the job IDs.
1081 479636a3 Iustin Pop

1082 479636a3 Iustin Pop
    @type wait: boolean
1083 479636a3 Iustin Pop
    @param wait: whether to wait or not
1084 479636a3 Iustin Pop

1085 479636a3 Iustin Pop
    """
1086 479636a3 Iustin Pop
    if wait:
1087 479636a3 Iustin Pop
      return self.GetResults()
1088 479636a3 Iustin Pop
    else:
1089 23b4b983 Iustin Pop
      if not self.jobs:
1090 23b4b983 Iustin Pop
        self.SubmitPending()
1091 23b4b983 Iustin Pop
      for status, result, name in self.jobs:
1092 23b4b983 Iustin Pop
        if status:
1093 23b4b983 Iustin Pop
          ToStdout("%s: %s", result, name)
1094 23b4b983 Iustin Pop
        else:
1095 23b4b983 Iustin Pop
          ToStderr("Failure for %s: %s", name, result)