Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ e948770c

History | View | Annotate | Download (35.8 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 2d3ed64b Michael Hanselmann
           "OPT_COMPL_INST_ADD_NODES",
63 846baef9 Iustin Pop
           ]
64 846baef9 Iustin Pop
65 8b46606c Guido Trotter
NO_PREFIX = "no_"
66 8b46606c Guido Trotter
UN_PREFIX = "-"
67 846baef9 Iustin Pop
68 03298ebe Michael Hanselmann
69 863d7f46 Michael Hanselmann
class _Argument:
70 dff85078 Michael Hanselmann
  def __init__(self, min=0, max=None):
71 863d7f46 Michael Hanselmann
    self.min = min
72 863d7f46 Michael Hanselmann
    self.max = max
73 863d7f46 Michael Hanselmann
74 863d7f46 Michael Hanselmann
  def __repr__(self):
75 863d7f46 Michael Hanselmann
    return ("<%s min=%s max=%s>" %
76 863d7f46 Michael Hanselmann
            (self.__class__.__name__, self.min, self.max))
77 863d7f46 Michael Hanselmann
78 863d7f46 Michael Hanselmann
79 863d7f46 Michael Hanselmann
class ArgSuggest(_Argument):
80 863d7f46 Michael Hanselmann
  """Suggesting argument.
81 863d7f46 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

507 a8005e17 Michael Hanselmann
  Algorithm:
508 a8005e17 Michael Hanselmann

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

511 a8005e17 Michael Hanselmann
    1. For each argument in definition
512 a8005e17 Michael Hanselmann

513 a8005e17 Michael Hanselmann
      1. Keep running count of minimum number of values (min_count)
514 a8005e17 Michael Hanselmann
      1. Keep running count of maximum number of values (max_count)
515 a8005e17 Michael Hanselmann
      1. If it has an unlimited number of values
516 a8005e17 Michael Hanselmann

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

519 a8005e17 Michael Hanselmann
    1. If last argument has limited number of values
520 a8005e17 Michael Hanselmann

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

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

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

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

598 c41eea6e Iustin Pop
  @param text: the question to ask
599 a8083063 Iustin Pop

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

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

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

655 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
656 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
657 e9d741b6 Iustin Pop

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1043 3386e7a9 Iustin Pop
  @type ts: timestamp
1044 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1045 3386e7a9 Iustin Pop

1046 3386e7a9 Iustin Pop
  @rtype: string
1047 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
1048 3386e7a9 Iustin Pop

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

1059 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
1060 2241e2b9 Iustin Pop

1061 2241e2b9 Iustin Pop
    - s: seconds
1062 2241e2b9 Iustin Pop
    - m: minutes
1063 2241e2b9 Iustin Pop
    - h: hours
1064 2241e2b9 Iustin Pop
    - d: day
1065 2241e2b9 Iustin Pop
    - w: weeks
1066 2241e2b9 Iustin Pop

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

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

1101 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
1102 4040a784 Iustin Pop
  the online nodes.
1103 4040a784 Iustin Pop

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

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

1127 46fbdd04 Iustin Pop
  @type stream: file object
1128 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
1129 46fbdd04 Iustin Pop
  @type txt: str
1130 46fbdd04 Iustin Pop
  @param txt: the message
1131 46fbdd04 Iustin Pop

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

1145 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1146 46fbdd04 Iustin Pop

1147 46fbdd04 Iustin Pop
  @type txt: str
1148 46fbdd04 Iustin Pop
  @param txt: the message
1149 46fbdd04 Iustin Pop

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

1157 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1158 46fbdd04 Iustin Pop

1159 46fbdd04 Iustin Pop
  @type txt: str
1160 46fbdd04 Iustin Pop
  @param txt: the message
1161 46fbdd04 Iustin Pop

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

1169 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
1170 479636a3 Iustin Pop
  GetResults() calls.
1171 479636a3 Iustin Pop

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

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

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

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

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

1235 479636a3 Iustin Pop
    @type wait: boolean
1236 479636a3 Iustin Pop
    @param wait: whether to wait or not
1237 479636a3 Iustin Pop

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