Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 63d44c55

History | View | Annotate | Download (35.7 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 c38c44ad Michael Hanselmann
from optparse import (OptionParser, TitledHelpFormatter,
42 38206f3c Iustin Pop
                      Option, OptionValueError)
43 a8083063 Iustin Pop
44 03298ebe Michael Hanselmann
45 ceab32dd Iustin Pop
__all__ = ["DEBUG_OPT", "NOHDR_OPT", "SEP_OPT", "GenericMain",
46 af30b2fd Michael Hanselmann
           "SubmitOpCode", "GetClient",
47 552c8dff Michael Hanselmann
           "cli_option",
48 a8469393 Iustin Pop
           "GenerateTable", "AskUser",
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 863d7f46 Michael Hanselmann
           "ArgJobId", "ArgSuggest", "ArgUnknown", "ArgFile", "ArgCommand",
56 83ec7961 Michael Hanselmann
           "ArgInstance", "ArgNode", "ArgChoice", "ArgHost",
57 4a265c08 Michael Hanselmann
           "ARGS_NONE", "ARGS_ONE_INSTANCE", "ARGS_ONE_NODE",
58 4a265c08 Michael Hanselmann
           "ARGS_MANY_INSTANCES", "ARGS_MANY_NODES",
59 63d44c55 Michael Hanselmann
           "OPT_COMPL_ONE_NODE", "OPT_COMPL_ONE_INSTANCE",
60 63d44c55 Michael Hanselmann
           "OPT_COMPL_MANY_NODES",
61 63d44c55 Michael Hanselmann
           "OPT_COMPL_ONE_OS", "OPT_COMPL_ONE_IALLOCATOR",
62 846baef9 Iustin Pop
           ]
63 846baef9 Iustin Pop
64 8b46606c Guido Trotter
NO_PREFIX = "no_"
65 8b46606c Guido Trotter
UN_PREFIX = "-"
66 846baef9 Iustin Pop
67 03298ebe Michael Hanselmann
68 863d7f46 Michael Hanselmann
class _Argument:
69 dff85078 Michael Hanselmann
  def __init__(self, min=0, max=None):
70 863d7f46 Michael Hanselmann
    self.min = min
71 863d7f46 Michael Hanselmann
    self.max = max
72 863d7f46 Michael Hanselmann
73 863d7f46 Michael Hanselmann
  def __repr__(self):
74 863d7f46 Michael Hanselmann
    return ("<%s min=%s max=%s>" %
75 863d7f46 Michael Hanselmann
            (self.__class__.__name__, self.min, self.max))
76 863d7f46 Michael Hanselmann
77 863d7f46 Michael Hanselmann
78 863d7f46 Michael Hanselmann
class ArgSuggest(_Argument):
79 863d7f46 Michael Hanselmann
  """Suggesting argument.
80 863d7f46 Michael Hanselmann

81 863d7f46 Michael Hanselmann
  Value can be any of the ones passed to the constructor.
82 863d7f46 Michael Hanselmann

83 863d7f46 Michael Hanselmann
  """
84 863d7f46 Michael Hanselmann
  def __init__(self, min=0, max=None, choices=None):
85 863d7f46 Michael Hanselmann
    _Argument.__init__(self, min=min, max=max)
86 863d7f46 Michael Hanselmann
    self.choices = choices
87 863d7f46 Michael Hanselmann
88 863d7f46 Michael Hanselmann
  def __repr__(self):
89 863d7f46 Michael Hanselmann
    return ("<%s min=%s max=%s choices=%r>" %
90 863d7f46 Michael Hanselmann
            (self.__class__.__name__, self.min, self.max, self.choices))
91 863d7f46 Michael Hanselmann
92 863d7f46 Michael Hanselmann
93 863d7f46 Michael Hanselmann
class ArgChoice(ArgSuggest):
94 863d7f46 Michael Hanselmann
  """Choice argument.
95 863d7f46 Michael Hanselmann

96 863d7f46 Michael Hanselmann
  Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
97 863d7f46 Michael Hanselmann
  but value must be one of the choices.
98 863d7f46 Michael Hanselmann

99 863d7f46 Michael Hanselmann
  """
100 863d7f46 Michael Hanselmann
101 863d7f46 Michael Hanselmann
102 863d7f46 Michael Hanselmann
class ArgUnknown(_Argument):
103 863d7f46 Michael Hanselmann
  """Unknown argument to program (e.g. determined at runtime).
104 863d7f46 Michael Hanselmann

105 863d7f46 Michael Hanselmann
  """
106 863d7f46 Michael Hanselmann
107 863d7f46 Michael Hanselmann
108 863d7f46 Michael Hanselmann
class ArgInstance(_Argument):
109 863d7f46 Michael Hanselmann
  """Instances argument.
110 863d7f46 Michael Hanselmann

111 863d7f46 Michael Hanselmann
  """
112 863d7f46 Michael Hanselmann
113 863d7f46 Michael Hanselmann
114 863d7f46 Michael Hanselmann
class ArgNode(_Argument):
115 863d7f46 Michael Hanselmann
  """Node argument.
116 863d7f46 Michael Hanselmann

117 863d7f46 Michael Hanselmann
  """
118 863d7f46 Michael Hanselmann
119 863d7f46 Michael Hanselmann
class ArgJobId(_Argument):
120 863d7f46 Michael Hanselmann
  """Job ID argument.
121 863d7f46 Michael Hanselmann

122 863d7f46 Michael Hanselmann
  """
123 863d7f46 Michael Hanselmann
124 863d7f46 Michael Hanselmann
125 863d7f46 Michael Hanselmann
class ArgFile(_Argument):
126 863d7f46 Michael Hanselmann
  """File path argument.
127 863d7f46 Michael Hanselmann

128 863d7f46 Michael Hanselmann
  """
129 863d7f46 Michael Hanselmann
130 863d7f46 Michael Hanselmann
131 863d7f46 Michael Hanselmann
class ArgCommand(_Argument):
132 863d7f46 Michael Hanselmann
  """Command argument.
133 863d7f46 Michael Hanselmann

134 863d7f46 Michael Hanselmann
  """
135 863d7f46 Michael Hanselmann
136 863d7f46 Michael Hanselmann
137 83ec7961 Michael Hanselmann
class ArgHost(_Argument):
138 83ec7961 Michael Hanselmann
  """Host argument.
139 83ec7961 Michael Hanselmann

140 83ec7961 Michael Hanselmann
  """
141 83ec7961 Michael Hanselmann
142 83ec7961 Michael Hanselmann
143 4a265c08 Michael Hanselmann
ARGS_NONE = []
144 4a265c08 Michael Hanselmann
ARGS_MANY_INSTANCES = [ArgInstance()]
145 4a265c08 Michael Hanselmann
ARGS_MANY_NODES = [ArgNode()]
146 4a265c08 Michael Hanselmann
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
147 4a265c08 Michael Hanselmann
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
148 4a265c08 Michael Hanselmann
149 4a265c08 Michael Hanselmann
150 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
151 846baef9 Iustin Pop
  """Extract the tag type object.
152 846baef9 Iustin Pop

153 846baef9 Iustin Pop
  Note that this function will modify its args parameter.
154 846baef9 Iustin Pop

155 846baef9 Iustin Pop
  """
156 846baef9 Iustin Pop
  if not hasattr(opts, "tag_type"):
157 846baef9 Iustin Pop
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
158 846baef9 Iustin Pop
  kind = opts.tag_type
159 846baef9 Iustin Pop
  if kind == constants.TAG_CLUSTER:
160 846baef9 Iustin Pop
    retval = kind, kind
161 846baef9 Iustin Pop
  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
162 846baef9 Iustin Pop
    if not args:
163 0c434948 Iustin Pop
      raise errors.OpPrereqError("no arguments passed to the command")
164 846baef9 Iustin Pop
    name = args.pop(0)
165 846baef9 Iustin Pop
    retval = kind, name
166 846baef9 Iustin Pop
  else:
167 846baef9 Iustin Pop
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
168 846baef9 Iustin Pop
  return retval
169 846baef9 Iustin Pop
170 846baef9 Iustin Pop
171 810c50b7 Iustin Pop
def _ExtendTags(opts, args):
172 810c50b7 Iustin Pop
  """Extend the args if a source file has been given.
173 810c50b7 Iustin Pop

174 810c50b7 Iustin Pop
  This function will extend the tags with the contents of the file
175 810c50b7 Iustin Pop
  passed in the 'tags_source' attribute of the opts parameter. A file
176 810c50b7 Iustin Pop
  named '-' will be replaced by stdin.
177 810c50b7 Iustin Pop

178 810c50b7 Iustin Pop
  """
179 810c50b7 Iustin Pop
  fname = opts.tags_source
180 810c50b7 Iustin Pop
  if fname is None:
181 810c50b7 Iustin Pop
    return
182 810c50b7 Iustin Pop
  if fname == "-":
183 810c50b7 Iustin Pop
    new_fh = sys.stdin
184 810c50b7 Iustin Pop
  else:
185 810c50b7 Iustin Pop
    new_fh = open(fname, "r")
186 810c50b7 Iustin Pop
  new_data = []
187 810c50b7 Iustin Pop
  try:
188 810c50b7 Iustin Pop
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
189 810c50b7 Iustin Pop
    # because of python bug 1633941
190 810c50b7 Iustin Pop
    while True:
191 810c50b7 Iustin Pop
      line = new_fh.readline()
192 810c50b7 Iustin Pop
      if not line:
193 810c50b7 Iustin Pop
        break
194 810c50b7 Iustin Pop
      new_data.append(line.strip())
195 810c50b7 Iustin Pop
  finally:
196 810c50b7 Iustin Pop
    new_fh.close()
197 810c50b7 Iustin Pop
  args.extend(new_data)
198 810c50b7 Iustin Pop
199 810c50b7 Iustin Pop
200 846baef9 Iustin Pop
def ListTags(opts, args):
201 846baef9 Iustin Pop
  """List the tags on a given object.
202 846baef9 Iustin Pop

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

208 846baef9 Iustin Pop
  """
209 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
210 846baef9 Iustin Pop
  op = opcodes.OpGetTags(kind=kind, name=name)
211 846baef9 Iustin Pop
  result = SubmitOpCode(op)
212 846baef9 Iustin Pop
  result = list(result)
213 846baef9 Iustin Pop
  result.sort()
214 846baef9 Iustin Pop
  for tag in result:
215 03298ebe Michael Hanselmann
    ToStdout(tag)
216 846baef9 Iustin Pop
217 846baef9 Iustin Pop
218 846baef9 Iustin Pop
def AddTags(opts, args):
219 846baef9 Iustin Pop
  """Add tags on a given object.
220 846baef9 Iustin Pop

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

226 846baef9 Iustin Pop
  """
227 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
228 810c50b7 Iustin Pop
  _ExtendTags(opts, args)
229 846baef9 Iustin Pop
  if not args:
230 846baef9 Iustin Pop
    raise errors.OpPrereqError("No tags to be added")
231 846baef9 Iustin Pop
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
232 846baef9 Iustin Pop
  SubmitOpCode(op)
233 846baef9 Iustin Pop
234 846baef9 Iustin Pop
235 846baef9 Iustin Pop
def RemoveTags(opts, args):
236 846baef9 Iustin Pop
  """Remove tags from a given object.
237 846baef9 Iustin Pop

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

243 846baef9 Iustin Pop
  """
244 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
245 810c50b7 Iustin Pop
  _ExtendTags(opts, args)
246 846baef9 Iustin Pop
  if not args:
247 846baef9 Iustin Pop
    raise errors.OpPrereqError("No tags to be removed")
248 846baef9 Iustin Pop
  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
249 846baef9 Iustin Pop
  SubmitOpCode(op)
250 846baef9 Iustin Pop
251 a8083063 Iustin Pop
252 a8083063 Iustin Pop
def check_unit(option, opt, value):
253 65fe4693 Iustin Pop
  """OptParsers custom converter for units.
254 65fe4693 Iustin Pop

255 65fe4693 Iustin Pop
  """
256 a8083063 Iustin Pop
  try:
257 a8083063 Iustin Pop
    return utils.ParseUnit(value)
258 a8083063 Iustin Pop
  except errors.UnitParseError, err:
259 3ecf6786 Iustin Pop
    raise OptionValueError("option %s: %s" % (opt, err))
260 a8083063 Iustin Pop
261 a8083063 Iustin Pop
262 a8469393 Iustin Pop
def _SplitKeyVal(opt, data):
263 a8469393 Iustin Pop
  """Convert a KeyVal string into a dict.
264 a8469393 Iustin Pop

265 a8469393 Iustin Pop
  This function will convert a key=val[,...] string into a dict. Empty
266 a8469393 Iustin Pop
  values will be converted specially: keys which have the prefix 'no_'
267 a8469393 Iustin Pop
  will have the value=False and the prefix stripped, the others will
268 a8469393 Iustin Pop
  have value=True.
269 a8469393 Iustin Pop

270 a8469393 Iustin Pop
  @type opt: string
271 a8469393 Iustin Pop
  @param opt: a string holding the option name for which we process the
272 a8469393 Iustin Pop
      data, used in building error messages
273 a8469393 Iustin Pop
  @type data: string
274 a8469393 Iustin Pop
  @param data: a string of the format key=val,key=val,...
275 a8469393 Iustin Pop
  @rtype: dict
276 a8469393 Iustin Pop
  @return: {key=val, key=val}
277 a8469393 Iustin Pop
  @raises errors.ParameterError: if there are duplicate keys
278 a8469393 Iustin Pop

279 a8469393 Iustin Pop
  """
280 a8469393 Iustin Pop
  kv_dict = {}
281 4f31882e Guido Trotter
  if data:
282 4f31882e Guido Trotter
    for elem in data.split(","):
283 4f31882e Guido Trotter
      if "=" in elem:
284 4f31882e Guido Trotter
        key, val = elem.split("=", 1)
285 a8469393 Iustin Pop
      else:
286 4f31882e Guido Trotter
        if elem.startswith(NO_PREFIX):
287 4f31882e Guido Trotter
          key, val = elem[len(NO_PREFIX):], False
288 4f31882e Guido Trotter
        elif elem.startswith(UN_PREFIX):
289 4f31882e Guido Trotter
          key, val = elem[len(UN_PREFIX):], None
290 4f31882e Guido Trotter
        else:
291 4f31882e Guido Trotter
          key, val = elem, True
292 4f31882e Guido Trotter
      if key in kv_dict:
293 4f31882e Guido Trotter
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
294 4f31882e Guido Trotter
                                    (key, opt))
295 4f31882e Guido Trotter
      kv_dict[key] = val
296 a8469393 Iustin Pop
  return kv_dict
297 a8469393 Iustin Pop
298 a8469393 Iustin Pop
299 a8469393 Iustin Pop
def check_ident_key_val(option, opt, value):
300 552c8dff Michael Hanselmann
  """Custom parser for ident:key=val,key=val options.
301 552c8dff Michael Hanselmann

302 552c8dff Michael Hanselmann
  This will store the parsed values as a tuple (ident, {key: val}). As such,
303 552c8dff Michael Hanselmann
  multiple uses of this option via action=append is possible.
304 a8469393 Iustin Pop

305 a8469393 Iustin Pop
  """
306 a8469393 Iustin Pop
  if ":" not in value:
307 8b46606c Guido Trotter
    ident, rest = value, ''
308 a8469393 Iustin Pop
  else:
309 a8469393 Iustin Pop
    ident, rest = value.split(":", 1)
310 8b46606c Guido Trotter
311 8b46606c Guido Trotter
  if ident.startswith(NO_PREFIX):
312 8b46606c Guido Trotter
    if rest:
313 8b46606c Guido Trotter
      msg = "Cannot pass options when removing parameter groups: %s" % value
314 8b46606c Guido Trotter
      raise errors.ParameterError(msg)
315 8b46606c Guido Trotter
    retval = (ident[len(NO_PREFIX):], False)
316 8b46606c Guido Trotter
  elif ident.startswith(UN_PREFIX):
317 8b46606c Guido Trotter
    if rest:
318 8b46606c Guido Trotter
      msg = "Cannot pass options when removing parameter groups: %s" % value
319 8b46606c Guido Trotter
      raise errors.ParameterError(msg)
320 8b46606c Guido Trotter
    retval = (ident[len(UN_PREFIX):], None)
321 8b46606c Guido Trotter
  else:
322 a8469393 Iustin Pop
    kv_dict = _SplitKeyVal(opt, rest)
323 a8469393 Iustin Pop
    retval = (ident, kv_dict)
324 a8469393 Iustin Pop
  return retval
325 a8469393 Iustin Pop
326 a8469393 Iustin Pop
327 a8469393 Iustin Pop
def check_key_val(option, opt, value):
328 552c8dff Michael Hanselmann
  """Custom parser class for key=val,key=val options.
329 552c8dff Michael Hanselmann

330 552c8dff Michael Hanselmann
  This will store the parsed values as a dict {key: val}.
331 a8469393 Iustin Pop

332 a8469393 Iustin Pop
  """
333 a8469393 Iustin Pop
  return _SplitKeyVal(opt, value)
334 a8469393 Iustin Pop
335 a8469393 Iustin Pop
336 63d44c55 Michael Hanselmann
# completion_suggestion is normally a list. Using numeric values not evaluating
337 63d44c55 Michael Hanselmann
# to False for dynamic completion.
338 63d44c55 Michael Hanselmann
(OPT_COMPL_MANY_NODES,
339 63d44c55 Michael Hanselmann
 OPT_COMPL_ONE_NODE,
340 63d44c55 Michael Hanselmann
 OPT_COMPL_ONE_INSTANCE,
341 63d44c55 Michael Hanselmann
 OPT_COMPL_ONE_OS,
342 63d44c55 Michael Hanselmann
 OPT_COMPL_ONE_IALLOCATOR) = range(100, 105)
343 63d44c55 Michael Hanselmann
344 63d44c55 Michael Hanselmann
OPT_COMPL_ALL = frozenset([
345 63d44c55 Michael Hanselmann
  OPT_COMPL_MANY_NODES,
346 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_NODE,
347 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_INSTANCE,
348 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_OS,
349 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_IALLOCATOR,
350 63d44c55 Michael Hanselmann
  ])
351 63d44c55 Michael Hanselmann
352 63d44c55 Michael Hanselmann
353 552c8dff Michael Hanselmann
class CliOption(Option):
354 552c8dff Michael Hanselmann
  """Custom option class for optparse.
355 a8469393 Iustin Pop

356 a8469393 Iustin Pop
  """
357 863d7f46 Michael Hanselmann
  ATTRS = Option.ATTRS + [
358 863d7f46 Michael Hanselmann
    "completion_suggest",
359 863d7f46 Michael Hanselmann
    ]
360 552c8dff Michael Hanselmann
  TYPES = Option.TYPES + (
361 552c8dff Michael Hanselmann
    "identkeyval",
362 552c8dff Michael Hanselmann
    "keyval",
363 552c8dff Michael Hanselmann
    "unit",
364 552c8dff Michael Hanselmann
    )
365 552c8dff Michael Hanselmann
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
366 552c8dff Michael Hanselmann
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
367 a8469393 Iustin Pop
  TYPE_CHECKER["keyval"] = check_key_val
368 552c8dff Michael Hanselmann
  TYPE_CHECKER["unit"] = check_unit
369 a8469393 Iustin Pop
370 a8469393 Iustin Pop
371 a8083063 Iustin Pop
# optparse.py sets make_option, so we do it for our own option class, too
372 a8083063 Iustin Pop
cli_option = CliOption
373 a8083063 Iustin Pop
374 a8083063 Iustin Pop
375 c38c44ad Michael Hanselmann
DEBUG_OPT = cli_option("-d", "--debug", default=False,
376 c38c44ad Michael Hanselmann
                       action="store_true",
377 c38c44ad Michael Hanselmann
                       help="Turn debugging on")
378 c38c44ad Michael Hanselmann
379 c38c44ad Michael Hanselmann
NOHDR_OPT = cli_option("--no-headers", default=False,
380 c38c44ad Michael Hanselmann
                       action="store_true", dest="no_headers",
381 c38c44ad Michael Hanselmann
                       help="Don't display column headers")
382 c38c44ad Michael Hanselmann
383 c38c44ad Michael Hanselmann
SEP_OPT = cli_option("--separator", default=None,
384 c38c44ad Michael Hanselmann
                     action="store", dest="separator",
385 c38c44ad Michael Hanselmann
                     help=("Separator between output fields"
386 c38c44ad Michael Hanselmann
                           " (defaults to one space)"))
387 c38c44ad Michael Hanselmann
388 c38c44ad Michael Hanselmann
USEUNITS_OPT = cli_option("--units", default=None,
389 c38c44ad Michael Hanselmann
                          dest="units", choices=('h', 'm', 'g', 't'),
390 c38c44ad Michael Hanselmann
                          help="Specify units for output (one of hmgt)")
391 c38c44ad Michael Hanselmann
392 c38c44ad Michael Hanselmann
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
393 c38c44ad Michael Hanselmann
                        type="string", metavar="FIELDS",
394 c38c44ad Michael Hanselmann
                        help="Comma separated list of output fields")
395 c38c44ad Michael Hanselmann
396 c38c44ad Michael Hanselmann
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
397 c38c44ad Michael Hanselmann
                       default=False, help="Force the operation")
398 c38c44ad Michael Hanselmann
399 c38c44ad Michael Hanselmann
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
400 c38c44ad Michael Hanselmann
                         default=False, help="Do not require confirmation")
401 c38c44ad Michael Hanselmann
402 c38c44ad Michael Hanselmann
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
403 c38c44ad Michael Hanselmann
                         default=None, help="File with tag names")
404 c38c44ad Michael Hanselmann
405 c38c44ad Michael Hanselmann
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
406 c38c44ad Michael Hanselmann
                        default=False, action="store_true",
407 c38c44ad Michael Hanselmann
                        help=("Submit the job and return the job ID, but"
408 c38c44ad Michael Hanselmann
                              " don't wait for the job to finish"))
409 c38c44ad Michael Hanselmann
410 c38c44ad Michael Hanselmann
SYNC_OPT = cli_option("--sync", dest="do_locking",
411 c38c44ad Michael Hanselmann
                      default=False, action="store_true",
412 c38c44ad Michael Hanselmann
                      help=("Grab locks while doing the queries"
413 c38c44ad Michael Hanselmann
                            " in order to ensure more consistent results"))
414 c38c44ad Michael Hanselmann
415 c38c44ad Michael Hanselmann
_DRY_RUN_OPT = cli_option("--dry-run", default=False,
416 c38c44ad Michael Hanselmann
                          action="store_true",
417 c38c44ad Michael Hanselmann
                          help=("Do not execute the operation, just run the"
418 c38c44ad Michael Hanselmann
                                " check steps and verify it it could be"
419 c38c44ad Michael Hanselmann
                                " executed"))
420 c38c44ad Michael Hanselmann
421 c38c44ad Michael Hanselmann
422 de47cf8f Guido Trotter
def _ParseArgs(argv, commands, aliases):
423 c41eea6e Iustin Pop
  """Parser for the command line arguments.
424 a8083063 Iustin Pop

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

428 c41eea6e Iustin Pop
  @param argv: the command line
429 c41eea6e Iustin Pop
  @param commands: dictionary with special contents, see the design
430 c41eea6e Iustin Pop
      doc for cmdline handling
431 c41eea6e Iustin Pop
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
432 098c0958 Michael Hanselmann

433 a8083063 Iustin Pop
  """
434 a8083063 Iustin Pop
  if len(argv) == 0:
435 a8083063 Iustin Pop
    binary = "<command>"
436 a8083063 Iustin Pop
  else:
437 a8083063 Iustin Pop
    binary = argv[0].split("/")[-1]
438 a8083063 Iustin Pop
439 a8083063 Iustin Pop
  if len(argv) > 1 and argv[1] == "--version":
440 03298ebe Michael Hanselmann
    ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
441 a8083063 Iustin Pop
    # Quit right away. That way we don't have to care about this special
442 a8083063 Iustin Pop
    # argument. optparse.py does it the same.
443 a8083063 Iustin Pop
    sys.exit(0)
444 a8083063 Iustin Pop
445 de47cf8f Guido Trotter
  if len(argv) < 2 or not (argv[1] in commands or
446 70a35b6f Guido Trotter
                           argv[1] in aliases):
447 a8083063 Iustin Pop
    # let's do a nice thing
448 a8083063 Iustin Pop
    sortedcmds = commands.keys()
449 a8083063 Iustin Pop
    sortedcmds.sort()
450 03298ebe Michael Hanselmann
451 03298ebe Michael Hanselmann
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
452 03298ebe Michael Hanselmann
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
453 03298ebe Michael Hanselmann
    ToStdout("")
454 03298ebe Michael Hanselmann
455 a8083063 Iustin Pop
    # compute the max line length for cmd + usage
456 4e713df6 Iustin Pop
    mlen = max([len(" %s" % cmd) for cmd in commands])
457 a8083063 Iustin Pop
    mlen = min(60, mlen) # should not get here...
458 03298ebe Michael Hanselmann
459 a8083063 Iustin Pop
    # and format a nice command list
460 03298ebe Michael Hanselmann
    ToStdout("Commands:")
461 a8083063 Iustin Pop
    for cmd in sortedcmds:
462 4e713df6 Iustin Pop
      cmdstr = " %s" % (cmd,)
463 9a033156 Iustin Pop
      help_text = commands[cmd][4]
464 03298ebe Michael Hanselmann
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
465 03298ebe Michael Hanselmann
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
466 a8083063 Iustin Pop
      for line in help_lines:
467 03298ebe Michael Hanselmann
        ToStdout("%-*s   %s", mlen, "", line)
468 03298ebe Michael Hanselmann
469 03298ebe Michael Hanselmann
    ToStdout("")
470 03298ebe Michael Hanselmann
471 a8083063 Iustin Pop
    return None, None, None
472 de47cf8f Guido Trotter
473 de47cf8f Guido Trotter
  # get command, unalias it, and look it up in commands
474 a8083063 Iustin Pop
  cmd = argv.pop(1)
475 de47cf8f Guido Trotter
  if cmd in aliases:
476 de47cf8f Guido Trotter
    if cmd in commands:
477 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
478 de47cf8f Guido Trotter
                                   " command" % cmd)
479 de47cf8f Guido Trotter
480 de47cf8f Guido Trotter
    if aliases[cmd] not in commands:
481 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
482 de47cf8f Guido Trotter
                                   " command '%s'" % (cmd, aliases[cmd]))
483 de47cf8f Guido Trotter
484 de47cf8f Guido Trotter
    cmd = aliases[cmd]
485 de47cf8f Guido Trotter
486 a8005e17 Michael Hanselmann
  func, args_def, parser_opts, usage, description = commands[cmd]
487 64c65a2a Iustin Pop
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT],
488 a8083063 Iustin Pop
                        description=description,
489 a8083063 Iustin Pop
                        formatter=TitledHelpFormatter(),
490 a8083063 Iustin Pop
                        usage="%%prog %s %s" % (cmd, usage))
491 a8083063 Iustin Pop
  parser.disable_interspersed_args()
492 a8083063 Iustin Pop
  options, args = parser.parse_args()
493 a8005e17 Michael Hanselmann
494 a8005e17 Michael Hanselmann
  if not _CheckArguments(cmd, args_def, args):
495 a8083063 Iustin Pop
    return None, None, None
496 a8083063 Iustin Pop
497 a8083063 Iustin Pop
  return func, options, args
498 a8083063 Iustin Pop
499 a8083063 Iustin Pop
500 a8005e17 Michael Hanselmann
def _CheckArguments(cmd, args_def, args):
501 a8005e17 Michael Hanselmann
  """Verifies the arguments using the argument definition.
502 a8005e17 Michael Hanselmann

503 a8005e17 Michael Hanselmann
  Algorithm:
504 a8005e17 Michael Hanselmann

505 a8005e17 Michael Hanselmann
    1. Abort with error if values specified by user but none expected.
506 a8005e17 Michael Hanselmann

507 a8005e17 Michael Hanselmann
    1. For each argument in definition
508 a8005e17 Michael Hanselmann

509 a8005e17 Michael Hanselmann
      1. Keep running count of minimum number of values (min_count)
510 a8005e17 Michael Hanselmann
      1. Keep running count of maximum number of values (max_count)
511 a8005e17 Michael Hanselmann
      1. If it has an unlimited number of values
512 a8005e17 Michael Hanselmann

513 a8005e17 Michael Hanselmann
        1. Abort with error if it's not the last argument in the definition
514 a8005e17 Michael Hanselmann

515 a8005e17 Michael Hanselmann
    1. If last argument has limited number of values
516 a8005e17 Michael Hanselmann

517 a8005e17 Michael Hanselmann
      1. Abort with error if number of values doesn't match or is too large
518 a8005e17 Michael Hanselmann

519 a8005e17 Michael Hanselmann
    1. Abort with error if user didn't pass enough values (min_count)
520 a8005e17 Michael Hanselmann

521 a8005e17 Michael Hanselmann
  """
522 a8005e17 Michael Hanselmann
  if args and not args_def:
523 a8005e17 Michael Hanselmann
    ToStderr("Error: Command %s expects no arguments", cmd)
524 a8005e17 Michael Hanselmann
    return False
525 a8005e17 Michael Hanselmann
526 a8005e17 Michael Hanselmann
  min_count = None
527 a8005e17 Michael Hanselmann
  max_count = None
528 a8005e17 Michael Hanselmann
  check_max = None
529 a8005e17 Michael Hanselmann
530 a8005e17 Michael Hanselmann
  last_idx = len(args_def) - 1
531 a8005e17 Michael Hanselmann
532 a8005e17 Michael Hanselmann
  for idx, arg in enumerate(args_def):
533 a8005e17 Michael Hanselmann
    if min_count is None:
534 a8005e17 Michael Hanselmann
      min_count = arg.min
535 a8005e17 Michael Hanselmann
    elif arg.min is not None:
536 a8005e17 Michael Hanselmann
      min_count += arg.min
537 a8005e17 Michael Hanselmann
538 a8005e17 Michael Hanselmann
    if max_count is None:
539 a8005e17 Michael Hanselmann
      max_count = arg.max
540 a8005e17 Michael Hanselmann
    elif arg.max is not None:
541 a8005e17 Michael Hanselmann
      max_count += arg.max
542 a8005e17 Michael Hanselmann
543 a8005e17 Michael Hanselmann
    if idx == last_idx:
544 a8005e17 Michael Hanselmann
      check_max = (arg.max is not None)
545 a8005e17 Michael Hanselmann
546 a8005e17 Michael Hanselmann
    elif arg.max is None:
547 a8005e17 Michael Hanselmann
      raise errors.ProgrammerError("Only the last argument can have max=None")
548 a8005e17 Michael Hanselmann
549 a8005e17 Michael Hanselmann
  if check_max:
550 a8005e17 Michael Hanselmann
    # Command with exact number of arguments
551 a8005e17 Michael Hanselmann
    if (min_count is not None and max_count is not None and
552 a8005e17 Michael Hanselmann
        min_count == max_count and len(args) != min_count):
553 a8005e17 Michael Hanselmann
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
554 a8005e17 Michael Hanselmann
      return False
555 a8005e17 Michael Hanselmann
556 a8005e17 Michael Hanselmann
    # Command with limited number of arguments
557 a8005e17 Michael Hanselmann
    if max_count is not None and len(args) > max_count:
558 a8005e17 Michael Hanselmann
      ToStderr("Error: Command %s expects only %d argument(s)",
559 a8005e17 Michael Hanselmann
               cmd, max_count)
560 a8005e17 Michael Hanselmann
      return False
561 a8005e17 Michael Hanselmann
562 a8005e17 Michael Hanselmann
  # Command with some required arguments
563 a8005e17 Michael Hanselmann
  if min_count is not None and len(args) < min_count:
564 a8005e17 Michael Hanselmann
    ToStderr("Error: Command %s expects at least %d argument(s)",
565 a8005e17 Michael Hanselmann
             cmd, min_count)
566 a8005e17 Michael Hanselmann
    return False
567 a8005e17 Michael Hanselmann
568 a8005e17 Michael Hanselmann
  return True
569 a8005e17 Michael Hanselmann
570 a8005e17 Michael Hanselmann
571 60d49723 Michael Hanselmann
def SplitNodeOption(value):
572 60d49723 Michael Hanselmann
  """Splits the value of a --node option.
573 60d49723 Michael Hanselmann

574 60d49723 Michael Hanselmann
  """
575 60d49723 Michael Hanselmann
  if value and ':' in value:
576 60d49723 Michael Hanselmann
    return value.split(':', 1)
577 60d49723 Michael Hanselmann
  else:
578 60d49723 Michael Hanselmann
    return (value, None)
579 60d49723 Michael Hanselmann
580 60d49723 Michael Hanselmann
581 4331f6cd Michael Hanselmann
def UsesRPC(fn):
582 4331f6cd Michael Hanselmann
  def wrapper(*args, **kwargs):
583 4331f6cd Michael Hanselmann
    rpc.Init()
584 4331f6cd Michael Hanselmann
    try:
585 4331f6cd Michael Hanselmann
      return fn(*args, **kwargs)
586 4331f6cd Michael Hanselmann
    finally:
587 4331f6cd Michael Hanselmann
      rpc.Shutdown()
588 4331f6cd Michael Hanselmann
  return wrapper
589 4331f6cd Michael Hanselmann
590 4331f6cd Michael Hanselmann
591 47988778 Iustin Pop
def AskUser(text, choices=None):
592 47988778 Iustin Pop
  """Ask the user a question.
593 a8083063 Iustin Pop

594 c41eea6e Iustin Pop
  @param text: the question to ask
595 a8083063 Iustin Pop

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

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

605 a8083063 Iustin Pop
  """
606 47988778 Iustin Pop
  if choices is None:
607 47988778 Iustin Pop
    choices = [('y', True, 'Perform the operation'),
608 47988778 Iustin Pop
               ('n', False, 'Do not perform the operation')]
609 47988778 Iustin Pop
  if not choices or not isinstance(choices, list):
610 5bbd3f7f Michael Hanselmann
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
611 47988778 Iustin Pop
  for entry in choices:
612 47988778 Iustin Pop
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
613 5bbd3f7f Michael Hanselmann
      raise errors.ProgrammerError("Invalid choices element to AskUser")
614 47988778 Iustin Pop
615 47988778 Iustin Pop
  answer = choices[-1][1]
616 47988778 Iustin Pop
  new_text = []
617 47988778 Iustin Pop
  for line in text.splitlines():
618 47988778 Iustin Pop
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
619 47988778 Iustin Pop
  text = "\n".join(new_text)
620 a8083063 Iustin Pop
  try:
621 3023170f Iustin Pop
    f = file("/dev/tty", "a+")
622 a8083063 Iustin Pop
  except IOError:
623 47988778 Iustin Pop
    return answer
624 a8083063 Iustin Pop
  try:
625 47988778 Iustin Pop
    chars = [entry[0] for entry in choices]
626 47988778 Iustin Pop
    chars[-1] = "[%s]" % chars[-1]
627 47988778 Iustin Pop
    chars.append('?')
628 47988778 Iustin Pop
    maps = dict([(entry[0], entry[1]) for entry in choices])
629 47988778 Iustin Pop
    while True:
630 47988778 Iustin Pop
      f.write(text)
631 47988778 Iustin Pop
      f.write('\n')
632 47988778 Iustin Pop
      f.write("/".join(chars))
633 47988778 Iustin Pop
      f.write(": ")
634 47988778 Iustin Pop
      line = f.readline(2).strip().lower()
635 47988778 Iustin Pop
      if line in maps:
636 47988778 Iustin Pop
        answer = maps[line]
637 47988778 Iustin Pop
        break
638 47988778 Iustin Pop
      elif line == '?':
639 47988778 Iustin Pop
        for entry in choices:
640 47988778 Iustin Pop
          f.write(" %s - %s\n" % (entry[0], entry[2]))
641 47988778 Iustin Pop
        f.write("\n")
642 47988778 Iustin Pop
        continue
643 a8083063 Iustin Pop
  finally:
644 a8083063 Iustin Pop
    f.close()
645 a8083063 Iustin Pop
  return answer
646 a8083063 Iustin Pop
647 a8083063 Iustin Pop
648 e9d741b6 Iustin Pop
class JobSubmittedException(Exception):
649 e9d741b6 Iustin Pop
  """Job was submitted, client should exit.
650 e9d741b6 Iustin Pop

651 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
652 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
653 e9d741b6 Iustin Pop

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

656 e9d741b6 Iustin Pop
  """
657 e9d741b6 Iustin Pop
658 e9d741b6 Iustin Pop
659 0a1e74d9 Iustin Pop
def SendJob(ops, cl=None):
660 0a1e74d9 Iustin Pop
  """Function to submit an opcode without waiting for the results.
661 a8083063 Iustin Pop

662 0a1e74d9 Iustin Pop
  @type ops: list
663 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
664 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
665 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
666 0a1e74d9 Iustin Pop
             if None, a new client will be created
667 a8083063 Iustin Pop

668 a8083063 Iustin Pop
  """
669 e2212007 Iustin Pop
  if cl is None:
670 b33e986b Iustin Pop
    cl = GetClient()
671 685ee993 Iustin Pop
672 0a1e74d9 Iustin Pop
  job_id = cl.SubmitJob(ops)
673 0a1e74d9 Iustin Pop
674 0a1e74d9 Iustin Pop
  return job_id
675 0a1e74d9 Iustin Pop
676 0a1e74d9 Iustin Pop
677 281606c1 Michael Hanselmann
def PollJob(job_id, cl=None, feedback_fn=None):
678 0a1e74d9 Iustin Pop
  """Function to poll for the result of a job.
679 0a1e74d9 Iustin Pop

680 0a1e74d9 Iustin Pop
  @type job_id: job identified
681 0a1e74d9 Iustin Pop
  @param job_id: the job to poll for results
682 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
683 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
684 0a1e74d9 Iustin Pop
             if None, a new client will be created
685 0a1e74d9 Iustin Pop

686 0a1e74d9 Iustin Pop
  """
687 0a1e74d9 Iustin Pop
  if cl is None:
688 0a1e74d9 Iustin Pop
    cl = GetClient()
689 685ee993 Iustin Pop
690 6c5a7090 Michael Hanselmann
  prev_job_info = None
691 6c5a7090 Michael Hanselmann
  prev_logmsg_serial = None
692 6c5a7090 Michael Hanselmann
693 685ee993 Iustin Pop
  while True:
694 6c5a7090 Michael Hanselmann
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
695 6c5a7090 Michael Hanselmann
                                 prev_logmsg_serial)
696 6c5a7090 Michael Hanselmann
    if not result:
697 685ee993 Iustin Pop
      # job not found, go away!
698 0bbe448c Michael Hanselmann
      raise errors.JobLost("Job with id %s lost" % job_id)
699 685ee993 Iustin Pop
700 6c5a7090 Michael Hanselmann
    # Split result, a tuple of (field values, log entries)
701 6c5a7090 Michael Hanselmann
    (job_info, log_entries) = result
702 6c5a7090 Michael Hanselmann
    (status, ) = job_info
703 6c5a7090 Michael Hanselmann
704 6c5a7090 Michael Hanselmann
    if log_entries:
705 6c5a7090 Michael Hanselmann
      for log_entry in log_entries:
706 6c5a7090 Michael Hanselmann
        (serial, timestamp, _, message) = log_entry
707 6c5a7090 Michael Hanselmann
        if callable(feedback_fn):
708 6c5a7090 Michael Hanselmann
          feedback_fn(log_entry[1:])
709 6c5a7090 Michael Hanselmann
        else:
710 26f15862 Iustin Pop
          encoded = utils.SafeEncode(message)
711 03298ebe Michael Hanselmann
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
712 6c5a7090 Michael Hanselmann
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
713 6c5a7090 Michael Hanselmann
714 0bbe448c Michael Hanselmann
    # TODO: Handle canceled and archived jobs
715 fbf0262f Michael Hanselmann
    elif status in (constants.JOB_STATUS_SUCCESS,
716 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_ERROR,
717 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_CANCELING,
718 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_CANCELED):
719 685ee993 Iustin Pop
      break
720 6c5a7090 Michael Hanselmann
721 6c5a7090 Michael Hanselmann
    prev_job_info = job_info
722 685ee993 Iustin Pop
723 0e050889 Iustin Pop
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
724 0bbe448c Michael Hanselmann
  if not jobs:
725 0bbe448c Michael Hanselmann
    raise errors.JobLost("Job with id %s lost" % job_id)
726 685ee993 Iustin Pop
727 0e050889 Iustin Pop
  status, opstatus, result = jobs[0]
728 0bbe448c Michael Hanselmann
  if status == constants.JOB_STATUS_SUCCESS:
729 53c04d04 Iustin Pop
    return result
730 fbf0262f Michael Hanselmann
  elif status in (constants.JOB_STATUS_CANCELING,
731 fbf0262f Michael Hanselmann
                  constants.JOB_STATUS_CANCELED):
732 fbf0262f Michael Hanselmann
    raise errors.OpExecError("Job was canceled")
733 0bbe448c Michael Hanselmann
  else:
734 0e050889 Iustin Pop
    has_ok = False
735 0e050889 Iustin Pop
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
736 0e050889 Iustin Pop
      if status == constants.OP_STATUS_SUCCESS:
737 0e050889 Iustin Pop
        has_ok = True
738 0e050889 Iustin Pop
      elif status == constants.OP_STATUS_ERROR:
739 bcb66fca Iustin Pop
        errors.MaybeRaise(msg)
740 0e050889 Iustin Pop
        if has_ok:
741 0e050889 Iustin Pop
          raise errors.OpExecError("partial failure (opcode %d): %s" %
742 0e050889 Iustin Pop
                                   (idx, msg))
743 0e050889 Iustin Pop
        else:
744 0e050889 Iustin Pop
          raise errors.OpExecError(str(msg))
745 0e050889 Iustin Pop
    # default failure mode
746 0bbe448c Michael Hanselmann
    raise errors.OpExecError(result)
747 ceab32dd Iustin Pop
748 ceab32dd Iustin Pop
749 0a1e74d9 Iustin Pop
def SubmitOpCode(op, cl=None, feedback_fn=None):
750 0a1e74d9 Iustin Pop
  """Legacy function to submit an opcode.
751 0a1e74d9 Iustin Pop

752 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
753 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
754 0a1e74d9 Iustin Pop
  interaction functions.
755 0a1e74d9 Iustin Pop

756 0a1e74d9 Iustin Pop
  """
757 0a1e74d9 Iustin Pop
  if cl is None:
758 0a1e74d9 Iustin Pop
    cl = GetClient()
759 0a1e74d9 Iustin Pop
760 0a1e74d9 Iustin Pop
  job_id = SendJob([op], cl)
761 0a1e74d9 Iustin Pop
762 53c04d04 Iustin Pop
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
763 53c04d04 Iustin Pop
764 53c04d04 Iustin Pop
  return op_results[0]
765 0a1e74d9 Iustin Pop
766 0a1e74d9 Iustin Pop
767 94428652 Iustin Pop
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
768 94428652 Iustin Pop
  """Wrapper around SubmitOpCode or SendJob.
769 94428652 Iustin Pop

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

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

777 94428652 Iustin Pop
  """
778 64c65a2a Iustin Pop
  if opts and opts.dry_run:
779 64c65a2a Iustin Pop
    op.dry_run = opts.dry_run
780 94428652 Iustin Pop
  if opts and opts.submit_only:
781 e9d741b6 Iustin Pop
    job_id = SendJob([op], cl=cl)
782 e9d741b6 Iustin Pop
    raise JobSubmittedException(job_id)
783 94428652 Iustin Pop
  else:
784 94428652 Iustin Pop
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
785 94428652 Iustin Pop
786 94428652 Iustin Pop
787 af30b2fd Michael Hanselmann
def GetClient():
788 af30b2fd Michael Hanselmann
  # TODO: Cache object?
789 b33e986b Iustin Pop
  try:
790 b33e986b Iustin Pop
    client = luxi.Client()
791 b33e986b Iustin Pop
  except luxi.NoMasterError:
792 b33e986b Iustin Pop
    master, myself = ssconf.GetMasterAndMyself()
793 b33e986b Iustin Pop
    if master != myself:
794 b33e986b Iustin Pop
      raise errors.OpPrereqError("This is not the master node, please connect"
795 b33e986b Iustin Pop
                                 " to node '%s' and rerun the command" %
796 b33e986b Iustin Pop
                                 master)
797 b33e986b Iustin Pop
    else:
798 b33e986b Iustin Pop
      raise
799 b33e986b Iustin Pop
  return client
800 af30b2fd Michael Hanselmann
801 af30b2fd Michael Hanselmann
802 73702ee7 Iustin Pop
def FormatError(err):
803 73702ee7 Iustin Pop
  """Return a formatted error message for a given error.
804 73702ee7 Iustin Pop

805 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
806 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
807 73702ee7 Iustin Pop
  second, a string describing the error message (not
808 73702ee7 Iustin Pop
  newline-terminated).
809 73702ee7 Iustin Pop

810 73702ee7 Iustin Pop
  """
811 73702ee7 Iustin Pop
  retcode = 1
812 73702ee7 Iustin Pop
  obuf = StringIO()
813 e2e521d0 Iustin Pop
  msg = str(err)
814 73702ee7 Iustin Pop
  if isinstance(err, errors.ConfigurationError):
815 e2e521d0 Iustin Pop
    txt = "Corrupt configuration file: %s" % msg
816 46fbdd04 Iustin Pop
    logging.error(txt)
817 e2e521d0 Iustin Pop
    obuf.write(txt + "\n")
818 73702ee7 Iustin Pop
    obuf.write("Aborting.")
819 73702ee7 Iustin Pop
    retcode = 2
820 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksAbort):
821 73702ee7 Iustin Pop
    obuf.write("Failure: hooks execution failed:\n")
822 73702ee7 Iustin Pop
    for node, script, out in err.args[0]:
823 73702ee7 Iustin Pop
      if out:
824 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s, output: %s\n" %
825 73702ee7 Iustin Pop
                   (node, script, out))
826 73702ee7 Iustin Pop
      else:
827 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s (no output)\n" %
828 73702ee7 Iustin Pop
                   (node, script))
829 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksFailure):
830 e2e521d0 Iustin Pop
    obuf.write("Failure: hooks general failure: %s" % msg)
831 73702ee7 Iustin Pop
  elif isinstance(err, errors.ResolverError):
832 73702ee7 Iustin Pop
    this_host = utils.HostInfo.SysName()
833 73702ee7 Iustin Pop
    if err.args[0] == this_host:
834 73702ee7 Iustin Pop
      msg = "Failure: can't resolve my own hostname ('%s')"
835 73702ee7 Iustin Pop
    else:
836 73702ee7 Iustin Pop
      msg = "Failure: can't resolve hostname '%s'"
837 73702ee7 Iustin Pop
    obuf.write(msg % err.args[0])
838 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpPrereqError):
839 73702ee7 Iustin Pop
    obuf.write("Failure: prerequisites not met for this"
840 e2e521d0 Iustin Pop
               " operation:\n%s" % msg)
841 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpExecError):
842 e2e521d0 Iustin Pop
    obuf.write("Failure: command execution error:\n%s" % msg)
843 73702ee7 Iustin Pop
  elif isinstance(err, errors.TagError):
844 e2e521d0 Iustin Pop
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
845 686d7433 Iustin Pop
  elif isinstance(err, errors.JobQueueDrainError):
846 686d7433 Iustin Pop
    obuf.write("Failure: the job queue is marked for drain and doesn't"
847 686d7433 Iustin Pop
               " accept new requests\n")
848 f87b405e Michael Hanselmann
  elif isinstance(err, errors.JobQueueFull):
849 f87b405e Michael Hanselmann
    obuf.write("Failure: the job queue is full and doesn't accept new"
850 f87b405e Michael Hanselmann
               " job submissions until old jobs are archived\n")
851 a5728081 Guido Trotter
  elif isinstance(err, errors.TypeEnforcementError):
852 a5728081 Guido Trotter
    obuf.write("Parameter Error: %s" % msg)
853 c1ce76bb Iustin Pop
  elif isinstance(err, errors.ParameterError):
854 c1ce76bb Iustin Pop
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
855 73702ee7 Iustin Pop
  elif isinstance(err, errors.GenericError):
856 e2e521d0 Iustin Pop
    obuf.write("Unhandled Ganeti error: %s" % msg)
857 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.NoMasterError):
858 03a8dbdc Iustin Pop
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
859 082c5adb Michael Hanselmann
               " and listening for connections?")
860 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.TimeoutError):
861 03a8dbdc Iustin Pop
    obuf.write("Timeout while talking to the master daemon. Error:\n"
862 03a8dbdc Iustin Pop
               "%s" % msg)
863 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.ProtocolError):
864 03a8dbdc Iustin Pop
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
865 03a8dbdc Iustin Pop
               "%s" % msg)
866 e9d741b6 Iustin Pop
  elif isinstance(err, JobSubmittedException):
867 e9d741b6 Iustin Pop
    obuf.write("JobID: %s\n" % err.args[0])
868 e9d741b6 Iustin Pop
    retcode = 0
869 73702ee7 Iustin Pop
  else:
870 e2e521d0 Iustin Pop
    obuf.write("Unhandled exception: %s" % msg)
871 73702ee7 Iustin Pop
  return retcode, obuf.getvalue().rstrip('\n')
872 73702ee7 Iustin Pop
873 73702ee7 Iustin Pop
874 de47cf8f Guido Trotter
def GenericMain(commands, override=None, aliases=None):
875 a8083063 Iustin Pop
  """Generic main function for all the gnt-* commands.
876 a8083063 Iustin Pop

877 334d1483 Iustin Pop
  Arguments:
878 334d1483 Iustin Pop
    - commands: a dictionary with a special structure, see the design doc
879 334d1483 Iustin Pop
                for command line handling.
880 334d1483 Iustin Pop
    - override: if not None, we expect a dictionary with keys that will
881 334d1483 Iustin Pop
                override command line options; this can be used to pass
882 334d1483 Iustin Pop
                options from the scripts to generic functions
883 de47cf8f Guido Trotter
    - aliases: dictionary with command aliases {'alias': 'target, ...}
884 a8083063 Iustin Pop

885 a8083063 Iustin Pop
  """
886 a8083063 Iustin Pop
  # save the program name and the entire command line for later logging
887 a8083063 Iustin Pop
  if sys.argv:
888 a8083063 Iustin Pop
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
889 a8083063 Iustin Pop
    if len(sys.argv) >= 2:
890 a8083063 Iustin Pop
      binary += " " + sys.argv[1]
891 a8083063 Iustin Pop
      old_cmdline = " ".join(sys.argv[2:])
892 a8083063 Iustin Pop
    else:
893 a8083063 Iustin Pop
      old_cmdline = ""
894 a8083063 Iustin Pop
  else:
895 a8083063 Iustin Pop
    binary = "<unknown program>"
896 a8083063 Iustin Pop
    old_cmdline = ""
897 a8083063 Iustin Pop
898 de47cf8f Guido Trotter
  if aliases is None:
899 de47cf8f Guido Trotter
    aliases = {}
900 de47cf8f Guido Trotter
901 3126878d Guido Trotter
  try:
902 3126878d Guido Trotter
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
903 3126878d Guido Trotter
  except errors.ParameterError, err:
904 3126878d Guido Trotter
    result, err_msg = FormatError(err)
905 3126878d Guido Trotter
    ToStderr(err_msg)
906 3126878d Guido Trotter
    return 1
907 3126878d Guido Trotter
908 a8083063 Iustin Pop
  if func is None: # parse error
909 a8083063 Iustin Pop
    return 1
910 a8083063 Iustin Pop
911 334d1483 Iustin Pop
  if override is not None:
912 334d1483 Iustin Pop
    for key, val in override.iteritems():
913 334d1483 Iustin Pop
      setattr(options, key, val)
914 334d1483 Iustin Pop
915 82d9caef Iustin Pop
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
916 82d9caef Iustin Pop
                     stderr_logging=True, program=binary)
917 a8083063 Iustin Pop
918 a8083063 Iustin Pop
  if old_cmdline:
919 46fbdd04 Iustin Pop
    logging.info("run with arguments '%s'", old_cmdline)
920 a8083063 Iustin Pop
  else:
921 46fbdd04 Iustin Pop
    logging.info("run with no arguments")
922 a8083063 Iustin Pop
923 a8083063 Iustin Pop
  try:
924 a4af651e Iustin Pop
    result = func(options, args)
925 d8353c3a Iustin Pop
  except (errors.GenericError, luxi.ProtocolError,
926 d8353c3a Iustin Pop
          JobSubmittedException), err:
927 a4af651e Iustin Pop
    result, err_msg = FormatError(err)
928 5bbd3f7f Michael Hanselmann
    logging.exception("Error during command processing")
929 46fbdd04 Iustin Pop
    ToStderr(err_msg)
930 a8083063 Iustin Pop
931 a8083063 Iustin Pop
  return result
932 137161c9 Michael Hanselmann
933 137161c9 Michael Hanselmann
934 16be8703 Iustin Pop
def GenerateTable(headers, fields, separator, data,
935 9fbfbb7b Iustin Pop
                  numfields=None, unitfields=None,
936 9fbfbb7b Iustin Pop
                  units=None):
937 137161c9 Michael Hanselmann
  """Prints a table with headers and different fields.
938 137161c9 Michael Hanselmann

939 9fbfbb7b Iustin Pop
  @type headers: dict
940 9fbfbb7b Iustin Pop
  @param headers: dictionary mapping field names to headers for
941 9fbfbb7b Iustin Pop
      the table
942 9fbfbb7b Iustin Pop
  @type fields: list
943 9fbfbb7b Iustin Pop
  @param fields: the field names corresponding to each row in
944 9fbfbb7b Iustin Pop
      the data field
945 9fbfbb7b Iustin Pop
  @param separator: the separator to be used; if this is None,
946 9fbfbb7b Iustin Pop
      the default 'smart' algorithm is used which computes optimal
947 9fbfbb7b Iustin Pop
      field width, otherwise just the separator is used between
948 9fbfbb7b Iustin Pop
      each field
949 9fbfbb7b Iustin Pop
  @type data: list
950 9fbfbb7b Iustin Pop
  @param data: a list of lists, each sublist being one row to be output
951 9fbfbb7b Iustin Pop
  @type numfields: list
952 9fbfbb7b Iustin Pop
  @param numfields: a list with the fields that hold numeric
953 9fbfbb7b Iustin Pop
      values and thus should be right-aligned
954 9fbfbb7b Iustin Pop
  @type unitfields: list
955 9fbfbb7b Iustin Pop
  @param unitfields: a list with the fields that hold numeric
956 9fbfbb7b Iustin Pop
      values that should be formatted with the units field
957 9fbfbb7b Iustin Pop
  @type units: string or None
958 9fbfbb7b Iustin Pop
  @param units: the units we should use for formatting, or None for
959 9fbfbb7b Iustin Pop
      automatic choice (human-readable for non-separator usage, otherwise
960 9fbfbb7b Iustin Pop
      megabytes); this is a one-letter string
961 137161c9 Michael Hanselmann

962 137161c9 Michael Hanselmann
  """
963 9fbfbb7b Iustin Pop
  if units is None:
964 9fbfbb7b Iustin Pop
    if separator:
965 9fbfbb7b Iustin Pop
      units = "m"
966 9fbfbb7b Iustin Pop
    else:
967 9fbfbb7b Iustin Pop
      units = "h"
968 9fbfbb7b Iustin Pop
969 137161c9 Michael Hanselmann
  if numfields is None:
970 137161c9 Michael Hanselmann
    numfields = []
971 137161c9 Michael Hanselmann
  if unitfields is None:
972 137161c9 Michael Hanselmann
    unitfields = []
973 137161c9 Michael Hanselmann
974 00430f8e Iustin Pop
  numfields = utils.FieldSet(*numfields)
975 00430f8e Iustin Pop
  unitfields = utils.FieldSet(*unitfields)
976 00430f8e Iustin Pop
977 137161c9 Michael Hanselmann
  format_fields = []
978 137161c9 Michael Hanselmann
  for field in fields:
979 01ca31ae Iustin Pop
    if headers and field not in headers:
980 ea5a5b74 Guido Trotter
      # TODO: handle better unknown fields (either revert to old
981 71c1af58 Iustin Pop
      # style of raising exception, or deal more intelligently with
982 71c1af58 Iustin Pop
      # variable fields)
983 71c1af58 Iustin Pop
      headers[field] = field
984 137161c9 Michael Hanselmann
    if separator is not None:
985 137161c9 Michael Hanselmann
      format_fields.append("%s")
986 00430f8e Iustin Pop
    elif numfields.Matches(field):
987 137161c9 Michael Hanselmann
      format_fields.append("%*s")
988 137161c9 Michael Hanselmann
    else:
989 137161c9 Michael Hanselmann
      format_fields.append("%-*s")
990 137161c9 Michael Hanselmann
991 137161c9 Michael Hanselmann
  if separator is None:
992 137161c9 Michael Hanselmann
    mlens = [0 for name in fields]
993 137161c9 Michael Hanselmann
    format = ' '.join(format_fields)
994 137161c9 Michael Hanselmann
  else:
995 137161c9 Michael Hanselmann
    format = separator.replace("%", "%%").join(format_fields)
996 137161c9 Michael Hanselmann
997 137161c9 Michael Hanselmann
  for row in data:
998 dcbd6288 Guido Trotter
    if row is None:
999 dcbd6288 Guido Trotter
      continue
1000 137161c9 Michael Hanselmann
    for idx, val in enumerate(row):
1001 00430f8e Iustin Pop
      if unitfields.Matches(fields[idx]):
1002 137161c9 Michael Hanselmann
        try:
1003 137161c9 Michael Hanselmann
          val = int(val)
1004 137161c9 Michael Hanselmann
        except ValueError:
1005 137161c9 Michael Hanselmann
          pass
1006 137161c9 Michael Hanselmann
        else:
1007 9fbfbb7b Iustin Pop
          val = row[idx] = utils.FormatUnit(val, units)
1008 01ca31ae Iustin Pop
      val = row[idx] = str(val)
1009 137161c9 Michael Hanselmann
      if separator is None:
1010 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(val))
1011 137161c9 Michael Hanselmann
1012 16be8703 Iustin Pop
  result = []
1013 137161c9 Michael Hanselmann
  if headers:
1014 137161c9 Michael Hanselmann
    args = []
1015 137161c9 Michael Hanselmann
    for idx, name in enumerate(fields):
1016 137161c9 Michael Hanselmann
      hdr = headers[name]
1017 137161c9 Michael Hanselmann
      if separator is None:
1018 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(hdr))
1019 137161c9 Michael Hanselmann
        args.append(mlens[idx])
1020 137161c9 Michael Hanselmann
      args.append(hdr)
1021 16be8703 Iustin Pop
    result.append(format % tuple(args))
1022 137161c9 Michael Hanselmann
1023 137161c9 Michael Hanselmann
  for line in data:
1024 137161c9 Michael Hanselmann
    args = []
1025 dcbd6288 Guido Trotter
    if line is None:
1026 dcbd6288 Guido Trotter
      line = ['-' for _ in fields]
1027 137161c9 Michael Hanselmann
    for idx in xrange(len(fields)):
1028 137161c9 Michael Hanselmann
      if separator is None:
1029 137161c9 Michael Hanselmann
        args.append(mlens[idx])
1030 137161c9 Michael Hanselmann
      args.append(line[idx])
1031 16be8703 Iustin Pop
    result.append(format % tuple(args))
1032 16be8703 Iustin Pop
1033 16be8703 Iustin Pop
  return result
1034 3386e7a9 Iustin Pop
1035 3386e7a9 Iustin Pop
1036 3386e7a9 Iustin Pop
def FormatTimestamp(ts):
1037 3386e7a9 Iustin Pop
  """Formats a given timestamp.
1038 3386e7a9 Iustin Pop

1039 3386e7a9 Iustin Pop
  @type ts: timestamp
1040 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1041 3386e7a9 Iustin Pop

1042 3386e7a9 Iustin Pop
  @rtype: string
1043 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
1044 3386e7a9 Iustin Pop

1045 3386e7a9 Iustin Pop
  """
1046 e0ec0ff6 Iustin Pop
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1047 e0ec0ff6 Iustin Pop
    return '?'
1048 3386e7a9 Iustin Pop
  sec, usec = ts
1049 3386e7a9 Iustin Pop
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1050 2241e2b9 Iustin Pop
1051 2241e2b9 Iustin Pop
1052 2241e2b9 Iustin Pop
def ParseTimespec(value):
1053 2241e2b9 Iustin Pop
  """Parse a time specification.
1054 2241e2b9 Iustin Pop

1055 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
1056 2241e2b9 Iustin Pop

1057 2241e2b9 Iustin Pop
    - s: seconds
1058 2241e2b9 Iustin Pop
    - m: minutes
1059 2241e2b9 Iustin Pop
    - h: hours
1060 2241e2b9 Iustin Pop
    - d: day
1061 2241e2b9 Iustin Pop
    - w: weeks
1062 2241e2b9 Iustin Pop

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

1065 2241e2b9 Iustin Pop
  """
1066 2241e2b9 Iustin Pop
  value = str(value)
1067 2241e2b9 Iustin Pop
  if not value:
1068 2241e2b9 Iustin Pop
    raise errors.OpPrereqError("Empty time specification passed")
1069 2241e2b9 Iustin Pop
  suffix_map = {
1070 2241e2b9 Iustin Pop
    's': 1,
1071 2241e2b9 Iustin Pop
    'm': 60,
1072 2241e2b9 Iustin Pop
    'h': 3600,
1073 2241e2b9 Iustin Pop
    'd': 86400,
1074 2241e2b9 Iustin Pop
    'w': 604800,
1075 2241e2b9 Iustin Pop
    }
1076 2241e2b9 Iustin Pop
  if value[-1] not in suffix_map:
1077 2241e2b9 Iustin Pop
    try:
1078 2241e2b9 Iustin Pop
      value = int(value)
1079 2241e2b9 Iustin Pop
    except ValueError:
1080 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1081 2241e2b9 Iustin Pop
  else:
1082 2241e2b9 Iustin Pop
    multiplier = suffix_map[value[-1]]
1083 2241e2b9 Iustin Pop
    value = value[:-1]
1084 2241e2b9 Iustin Pop
    if not value: # no data left after stripping the suffix
1085 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification (only"
1086 2241e2b9 Iustin Pop
                                 " suffix passed)")
1087 2241e2b9 Iustin Pop
    try:
1088 2241e2b9 Iustin Pop
      value = int(value) * multiplier
1089 2241e2b9 Iustin Pop
    except ValueError:
1090 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1091 2241e2b9 Iustin Pop
  return value
1092 46fbdd04 Iustin Pop
1093 46fbdd04 Iustin Pop
1094 4040a784 Iustin Pop
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1095 4040a784 Iustin Pop
  """Returns the names of online nodes.
1096 4040a784 Iustin Pop

1097 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
1098 4040a784 Iustin Pop
  the online nodes.
1099 4040a784 Iustin Pop

1100 4040a784 Iustin Pop
  @param nodes: if not empty, use only this subset of nodes (minus the
1101 4040a784 Iustin Pop
      offline ones)
1102 4040a784 Iustin Pop
  @param cl: if not None, luxi client to use
1103 4040a784 Iustin Pop
  @type nowarn: boolean
1104 4040a784 Iustin Pop
  @param nowarn: by default, this function will output a note with the
1105 4040a784 Iustin Pop
      offline nodes that are skipped; if this parameter is True the
1106 4040a784 Iustin Pop
      note is not displayed
1107 4040a784 Iustin Pop

1108 4040a784 Iustin Pop
  """
1109 4040a784 Iustin Pop
  if cl is None:
1110 4040a784 Iustin Pop
    cl = GetClient()
1111 4040a784 Iustin Pop
1112 2e7b8369 Iustin Pop
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1113 2e7b8369 Iustin Pop
                         use_locking=False)
1114 4040a784 Iustin Pop
  offline = [row[0] for row in result if row[1]]
1115 4040a784 Iustin Pop
  if offline and not nowarn:
1116 4040a784 Iustin Pop
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1117 4040a784 Iustin Pop
  return [row[0] for row in result if not row[1]]
1118 4040a784 Iustin Pop
1119 4040a784 Iustin Pop
1120 46fbdd04 Iustin Pop
def _ToStream(stream, txt, *args):
1121 46fbdd04 Iustin Pop
  """Write a message to a stream, bypassing the logging system
1122 46fbdd04 Iustin Pop

1123 46fbdd04 Iustin Pop
  @type stream: file object
1124 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
1125 46fbdd04 Iustin Pop
  @type txt: str
1126 46fbdd04 Iustin Pop
  @param txt: the message
1127 46fbdd04 Iustin Pop

1128 46fbdd04 Iustin Pop
  """
1129 46fbdd04 Iustin Pop
  if args:
1130 46fbdd04 Iustin Pop
    args = tuple(args)
1131 46fbdd04 Iustin Pop
    stream.write(txt % args)
1132 46fbdd04 Iustin Pop
  else:
1133 46fbdd04 Iustin Pop
    stream.write(txt)
1134 46fbdd04 Iustin Pop
  stream.write('\n')
1135 46fbdd04 Iustin Pop
  stream.flush()
1136 46fbdd04 Iustin Pop
1137 46fbdd04 Iustin Pop
1138 46fbdd04 Iustin Pop
def ToStdout(txt, *args):
1139 46fbdd04 Iustin Pop
  """Write a message to stdout only, bypassing the logging system
1140 46fbdd04 Iustin Pop

1141 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1142 46fbdd04 Iustin Pop

1143 46fbdd04 Iustin Pop
  @type txt: str
1144 46fbdd04 Iustin Pop
  @param txt: the message
1145 46fbdd04 Iustin Pop

1146 46fbdd04 Iustin Pop
  """
1147 46fbdd04 Iustin Pop
  _ToStream(sys.stdout, txt, *args)
1148 46fbdd04 Iustin Pop
1149 46fbdd04 Iustin Pop
1150 46fbdd04 Iustin Pop
def ToStderr(txt, *args):
1151 46fbdd04 Iustin Pop
  """Write a message to stderr only, bypassing the logging system
1152 46fbdd04 Iustin Pop

1153 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1154 46fbdd04 Iustin Pop

1155 46fbdd04 Iustin Pop
  @type txt: str
1156 46fbdd04 Iustin Pop
  @param txt: the message
1157 46fbdd04 Iustin Pop

1158 46fbdd04 Iustin Pop
  """
1159 46fbdd04 Iustin Pop
  _ToStream(sys.stderr, txt, *args)
1160 479636a3 Iustin Pop
1161 479636a3 Iustin Pop
1162 479636a3 Iustin Pop
class JobExecutor(object):
1163 479636a3 Iustin Pop
  """Class which manages the submission and execution of multiple jobs.
1164 479636a3 Iustin Pop

1165 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
1166 479636a3 Iustin Pop
  GetResults() calls.
1167 479636a3 Iustin Pop

1168 479636a3 Iustin Pop
  """
1169 479636a3 Iustin Pop
  def __init__(self, cl=None, verbose=True):
1170 479636a3 Iustin Pop
    self.queue = []
1171 479636a3 Iustin Pop
    if cl is None:
1172 479636a3 Iustin Pop
      cl = GetClient()
1173 479636a3 Iustin Pop
    self.cl = cl
1174 479636a3 Iustin Pop
    self.verbose = verbose
1175 23b4b983 Iustin Pop
    self.jobs = []
1176 479636a3 Iustin Pop
1177 479636a3 Iustin Pop
  def QueueJob(self, name, *ops):
1178 23b4b983 Iustin Pop
    """Record a job for later submit.
1179 479636a3 Iustin Pop

1180 479636a3 Iustin Pop
    @type name: string
1181 479636a3 Iustin Pop
    @param name: a description of the job, will be used in WaitJobSet
1182 479636a3 Iustin Pop
    """
1183 23b4b983 Iustin Pop
    self.queue.append((name, ops))
1184 23b4b983 Iustin Pop
1185 23b4b983 Iustin Pop
  def SubmitPending(self):
1186 23b4b983 Iustin Pop
    """Submit all pending jobs.
1187 23b4b983 Iustin Pop

1188 23b4b983 Iustin Pop
    """
1189 23b4b983 Iustin Pop
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1190 23b4b983 Iustin Pop
    for ((status, data), (name, _)) in zip(results, self.queue):
1191 23b4b983 Iustin Pop
      self.jobs.append((status, data, name))
1192 479636a3 Iustin Pop
1193 479636a3 Iustin Pop
  def GetResults(self):
1194 479636a3 Iustin Pop
    """Wait for and return the results of all jobs.
1195 479636a3 Iustin Pop

1196 479636a3 Iustin Pop
    @rtype: list
1197 479636a3 Iustin Pop
    @return: list of tuples (success, job results), in the same order
1198 479636a3 Iustin Pop
        as the submitted jobs; if a job has failed, instead of the result
1199 479636a3 Iustin Pop
        there will be the error message
1200 479636a3 Iustin Pop

1201 479636a3 Iustin Pop
    """
1202 23b4b983 Iustin Pop
    if not self.jobs:
1203 23b4b983 Iustin Pop
      self.SubmitPending()
1204 479636a3 Iustin Pop
    results = []
1205 479636a3 Iustin Pop
    if self.verbose:
1206 23b4b983 Iustin Pop
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1207 23b4b983 Iustin Pop
      if ok_jobs:
1208 23b4b983 Iustin Pop
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1209 23b4b983 Iustin Pop
    for submit_status, jid, name in self.jobs:
1210 23b4b983 Iustin Pop
      if not submit_status:
1211 23b4b983 Iustin Pop
        ToStderr("Failed to submit job for %s: %s", name, jid)
1212 23b4b983 Iustin Pop
        results.append((False, jid))
1213 23b4b983 Iustin Pop
        continue
1214 479636a3 Iustin Pop
      if self.verbose:
1215 479636a3 Iustin Pop
        ToStdout("Waiting for job %s for %s...", jid, name)
1216 479636a3 Iustin Pop
      try:
1217 479636a3 Iustin Pop
        job_result = PollJob(jid, cl=self.cl)
1218 479636a3 Iustin Pop
        success = True
1219 479636a3 Iustin Pop
      except (errors.GenericError, luxi.ProtocolError), err:
1220 479636a3 Iustin Pop
        _, job_result = FormatError(err)
1221 479636a3 Iustin Pop
        success = False
1222 479636a3 Iustin Pop
        # the error message will always be shown, verbose or not
1223 479636a3 Iustin Pop
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1224 479636a3 Iustin Pop
1225 479636a3 Iustin Pop
      results.append((success, job_result))
1226 479636a3 Iustin Pop
    return results
1227 479636a3 Iustin Pop
1228 479636a3 Iustin Pop
  def WaitOrShow(self, wait):
1229 479636a3 Iustin Pop
    """Wait for job results or only print the job IDs.
1230 479636a3 Iustin Pop

1231 479636a3 Iustin Pop
    @type wait: boolean
1232 479636a3 Iustin Pop
    @param wait: whether to wait or not
1233 479636a3 Iustin Pop

1234 479636a3 Iustin Pop
    """
1235 479636a3 Iustin Pop
    if wait:
1236 479636a3 Iustin Pop
      return self.GetResults()
1237 479636a3 Iustin Pop
    else:
1238 23b4b983 Iustin Pop
      if not self.jobs:
1239 23b4b983 Iustin Pop
        self.SubmitPending()
1240 23b4b983 Iustin Pop
      for status, result, name in self.jobs:
1241 23b4b983 Iustin Pop
        if status:
1242 23b4b983 Iustin Pop
          ToStdout("%s: %s", result, name)
1243 23b4b983 Iustin Pop
        else:
1244 23b4b983 Iustin Pop
          ToStderr("Failure for %s: %s", name, result)