Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 48166551

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 73b90123 Michael Hanselmann
151 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
152 846baef9 Iustin Pop
  """Extract the tag type object.
153 846baef9 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

504 a8005e17 Michael Hanselmann
  Algorithm:
505 a8005e17 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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