Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ e3876ccb

History | View | Annotate | Download (39.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 4abc4f1e Iustin Pop
__all__ = [
46 4abc4f1e Iustin Pop
  # Command line options
47 087ed2ed Iustin Pop
  "BACKEND_OPT",
48 4abc4f1e Iustin Pop
  "CONFIRM_OPT",
49 4abc4f1e Iustin Pop
  "DEBUG_OPT",
50 a0c9776a Iustin Pop
  "DEBUG_SIMERR_OPT",
51 4f365444 Iustin Pop
  "DISK_TEMPLATE_OPT",
52 e3876ccb Iustin Pop
  "DISK_OPT",
53 4abc4f1e Iustin Pop
  "FIELDS_OPT",
54 4a25828c Iustin Pop
  "FILESTORE_DIR_OPT",
55 0f87c43e Iustin Pop
  "FILESTORE_DRIVER_OPT",
56 073271f6 Iustin Pop
  "HVLIST_OPT",
57 48f212d7 Iustin Pop
  "HVOPTS_OPT",
58 236fd9c4 Iustin Pop
  "HYPERVISOR_OPT",
59 4eb62659 Iustin Pop
  "IALLOCATOR_OPT",
60 4abc4f1e Iustin Pop
  "FORCE_OPT",
61 7d3a9fab Iustin Pop
  "NET_OPT",
62 4abc4f1e Iustin Pop
  "NOHDR_OPT",
63 91e0748c Iustin Pop
  "NOIPCHECK_OPT",
64 26023ecd Iustin Pop
  "NONICS_OPT",
65 3f75b4f3 Iustin Pop
  "NWSYNC_OPT",
66 d3ed23ff Iustin Pop
  "OS_OPT",
67 4abc4f1e Iustin Pop
  "SEP_OPT",
68 4abc4f1e Iustin Pop
  "SUBMIT_OPT",
69 4abc4f1e Iustin Pop
  "SYNC_OPT",
70 4abc4f1e Iustin Pop
  "TAG_SRC_OPT",
71 4abc4f1e Iustin Pop
  "USEUNITS_OPT",
72 9cdb9578 Iustin Pop
  "VERBOSE_OPT",
73 4abc4f1e Iustin Pop
  # Generic functions for CLI programs
74 4abc4f1e Iustin Pop
  "GenericMain",
75 4abc4f1e Iustin Pop
  "GetClient",
76 4abc4f1e Iustin Pop
  "GetOnlineNodes",
77 4abc4f1e Iustin Pop
  "JobExecutor",
78 4abc4f1e Iustin Pop
  "JobSubmittedException",
79 4abc4f1e Iustin Pop
  "ParseTimespec",
80 4abc4f1e Iustin Pop
  "SubmitOpCode",
81 4abc4f1e Iustin Pop
  "SubmitOrSend",
82 4abc4f1e Iustin Pop
  "UsesRPC",
83 4abc4f1e Iustin Pop
  # Formatting functions
84 4abc4f1e Iustin Pop
  "ToStderr", "ToStdout",
85 4abc4f1e Iustin Pop
  "FormatError",
86 4abc4f1e Iustin Pop
  "GenerateTable",
87 4abc4f1e Iustin Pop
  "AskUser",
88 4abc4f1e Iustin Pop
  "FormatTimestamp",
89 4abc4f1e Iustin Pop
  # Tags functions
90 4abc4f1e Iustin Pop
  "ListTags",
91 4abc4f1e Iustin Pop
  "AddTags",
92 4abc4f1e Iustin Pop
  "RemoveTags",
93 4abc4f1e Iustin Pop
  # command line options support infrastructure
94 4abc4f1e Iustin Pop
  "ARGS_MANY_INSTANCES",
95 4abc4f1e Iustin Pop
  "ARGS_MANY_NODES",
96 4abc4f1e Iustin Pop
  "ARGS_NONE",
97 4abc4f1e Iustin Pop
  "ARGS_ONE_INSTANCE",
98 4abc4f1e Iustin Pop
  "ARGS_ONE_NODE",
99 4abc4f1e Iustin Pop
  "ArgChoice",
100 4abc4f1e Iustin Pop
  "ArgCommand",
101 4abc4f1e Iustin Pop
  "ArgFile",
102 4abc4f1e Iustin Pop
  "ArgHost",
103 4abc4f1e Iustin Pop
  "ArgInstance",
104 4abc4f1e Iustin Pop
  "ArgJobId",
105 4abc4f1e Iustin Pop
  "ArgNode",
106 4abc4f1e Iustin Pop
  "ArgSuggest",
107 4abc4f1e Iustin Pop
  "ArgUnknown",
108 4abc4f1e Iustin Pop
  "OPT_COMPL_INST_ADD_NODES",
109 4abc4f1e Iustin Pop
  "OPT_COMPL_MANY_NODES",
110 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_IALLOCATOR",
111 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_INSTANCE",
112 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_NODE",
113 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_OS",
114 4abc4f1e Iustin Pop
  "cli_option",
115 4abc4f1e Iustin Pop
  "SplitNodeOption",
116 4abc4f1e Iustin Pop
  ]
117 846baef9 Iustin Pop
118 8b46606c Guido Trotter
NO_PREFIX = "no_"
119 8b46606c Guido Trotter
UN_PREFIX = "-"
120 846baef9 Iustin Pop
121 03298ebe Michael Hanselmann
122 863d7f46 Michael Hanselmann
class _Argument:
123 dff85078 Michael Hanselmann
  def __init__(self, min=0, max=None):
124 863d7f46 Michael Hanselmann
    self.min = min
125 863d7f46 Michael Hanselmann
    self.max = max
126 863d7f46 Michael Hanselmann
127 863d7f46 Michael Hanselmann
  def __repr__(self):
128 863d7f46 Michael Hanselmann
    return ("<%s min=%s max=%s>" %
129 863d7f46 Michael Hanselmann
            (self.__class__.__name__, self.min, self.max))
130 863d7f46 Michael Hanselmann
131 863d7f46 Michael Hanselmann
132 863d7f46 Michael Hanselmann
class ArgSuggest(_Argument):
133 863d7f46 Michael Hanselmann
  """Suggesting argument.
134 863d7f46 Michael Hanselmann

135 863d7f46 Michael Hanselmann
  Value can be any of the ones passed to the constructor.
136 863d7f46 Michael Hanselmann

137 863d7f46 Michael Hanselmann
  """
138 863d7f46 Michael Hanselmann
  def __init__(self, min=0, max=None, choices=None):
139 863d7f46 Michael Hanselmann
    _Argument.__init__(self, min=min, max=max)
140 863d7f46 Michael Hanselmann
    self.choices = choices
141 863d7f46 Michael Hanselmann
142 863d7f46 Michael Hanselmann
  def __repr__(self):
143 863d7f46 Michael Hanselmann
    return ("<%s min=%s max=%s choices=%r>" %
144 863d7f46 Michael Hanselmann
            (self.__class__.__name__, self.min, self.max, self.choices))
145 863d7f46 Michael Hanselmann
146 863d7f46 Michael Hanselmann
147 863d7f46 Michael Hanselmann
class ArgChoice(ArgSuggest):
148 863d7f46 Michael Hanselmann
  """Choice argument.
149 863d7f46 Michael Hanselmann

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

153 863d7f46 Michael Hanselmann
  """
154 863d7f46 Michael Hanselmann
155 863d7f46 Michael Hanselmann
156 863d7f46 Michael Hanselmann
class ArgUnknown(_Argument):
157 863d7f46 Michael Hanselmann
  """Unknown argument to program (e.g. determined at runtime).
158 863d7f46 Michael Hanselmann

159 863d7f46 Michael Hanselmann
  """
160 863d7f46 Michael Hanselmann
161 863d7f46 Michael Hanselmann
162 863d7f46 Michael Hanselmann
class ArgInstance(_Argument):
163 863d7f46 Michael Hanselmann
  """Instances argument.
164 863d7f46 Michael Hanselmann

165 863d7f46 Michael Hanselmann
  """
166 863d7f46 Michael Hanselmann
167 863d7f46 Michael Hanselmann
168 863d7f46 Michael Hanselmann
class ArgNode(_Argument):
169 863d7f46 Michael Hanselmann
  """Node argument.
170 863d7f46 Michael Hanselmann

171 863d7f46 Michael Hanselmann
  """
172 863d7f46 Michael Hanselmann
173 863d7f46 Michael Hanselmann
class ArgJobId(_Argument):
174 863d7f46 Michael Hanselmann
  """Job ID argument.
175 863d7f46 Michael Hanselmann

176 863d7f46 Michael Hanselmann
  """
177 863d7f46 Michael Hanselmann
178 863d7f46 Michael Hanselmann
179 863d7f46 Michael Hanselmann
class ArgFile(_Argument):
180 863d7f46 Michael Hanselmann
  """File path argument.
181 863d7f46 Michael Hanselmann

182 863d7f46 Michael Hanselmann
  """
183 863d7f46 Michael Hanselmann
184 863d7f46 Michael Hanselmann
185 863d7f46 Michael Hanselmann
class ArgCommand(_Argument):
186 863d7f46 Michael Hanselmann
  """Command argument.
187 863d7f46 Michael Hanselmann

188 863d7f46 Michael Hanselmann
  """
189 863d7f46 Michael Hanselmann
190 863d7f46 Michael Hanselmann
191 83ec7961 Michael Hanselmann
class ArgHost(_Argument):
192 83ec7961 Michael Hanselmann
  """Host argument.
193 83ec7961 Michael Hanselmann

194 83ec7961 Michael Hanselmann
  """
195 83ec7961 Michael Hanselmann
196 83ec7961 Michael Hanselmann
197 4a265c08 Michael Hanselmann
ARGS_NONE = []
198 4a265c08 Michael Hanselmann
ARGS_MANY_INSTANCES = [ArgInstance()]
199 4a265c08 Michael Hanselmann
ARGS_MANY_NODES = [ArgNode()]
200 4a265c08 Michael Hanselmann
ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
201 4a265c08 Michael Hanselmann
ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
202 4a265c08 Michael Hanselmann
203 4a265c08 Michael Hanselmann
204 73b90123 Michael Hanselmann
205 846baef9 Iustin Pop
def _ExtractTagsObject(opts, args):
206 846baef9 Iustin Pop
  """Extract the tag type object.
207 846baef9 Iustin Pop

208 846baef9 Iustin Pop
  Note that this function will modify its args parameter.
209 846baef9 Iustin Pop

210 846baef9 Iustin Pop
  """
211 846baef9 Iustin Pop
  if not hasattr(opts, "tag_type"):
212 846baef9 Iustin Pop
    raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
213 846baef9 Iustin Pop
  kind = opts.tag_type
214 846baef9 Iustin Pop
  if kind == constants.TAG_CLUSTER:
215 846baef9 Iustin Pop
    retval = kind, kind
216 846baef9 Iustin Pop
  elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
217 846baef9 Iustin Pop
    if not args:
218 0c434948 Iustin Pop
      raise errors.OpPrereqError("no arguments passed to the command")
219 846baef9 Iustin Pop
    name = args.pop(0)
220 846baef9 Iustin Pop
    retval = kind, name
221 846baef9 Iustin Pop
  else:
222 846baef9 Iustin Pop
    raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
223 846baef9 Iustin Pop
  return retval
224 846baef9 Iustin Pop
225 846baef9 Iustin Pop
226 810c50b7 Iustin Pop
def _ExtendTags(opts, args):
227 810c50b7 Iustin Pop
  """Extend the args if a source file has been given.
228 810c50b7 Iustin Pop

229 810c50b7 Iustin Pop
  This function will extend the tags with the contents of the file
230 810c50b7 Iustin Pop
  passed in the 'tags_source' attribute of the opts parameter. A file
231 810c50b7 Iustin Pop
  named '-' will be replaced by stdin.
232 810c50b7 Iustin Pop

233 810c50b7 Iustin Pop
  """
234 810c50b7 Iustin Pop
  fname = opts.tags_source
235 810c50b7 Iustin Pop
  if fname is None:
236 810c50b7 Iustin Pop
    return
237 810c50b7 Iustin Pop
  if fname == "-":
238 810c50b7 Iustin Pop
    new_fh = sys.stdin
239 810c50b7 Iustin Pop
  else:
240 810c50b7 Iustin Pop
    new_fh = open(fname, "r")
241 810c50b7 Iustin Pop
  new_data = []
242 810c50b7 Iustin Pop
  try:
243 810c50b7 Iustin Pop
    # we don't use the nice 'new_data = [line.strip() for line in fh]'
244 810c50b7 Iustin Pop
    # because of python bug 1633941
245 810c50b7 Iustin Pop
    while True:
246 810c50b7 Iustin Pop
      line = new_fh.readline()
247 810c50b7 Iustin Pop
      if not line:
248 810c50b7 Iustin Pop
        break
249 810c50b7 Iustin Pop
      new_data.append(line.strip())
250 810c50b7 Iustin Pop
  finally:
251 810c50b7 Iustin Pop
    new_fh.close()
252 810c50b7 Iustin Pop
  args.extend(new_data)
253 810c50b7 Iustin Pop
254 810c50b7 Iustin Pop
255 846baef9 Iustin Pop
def ListTags(opts, args):
256 846baef9 Iustin Pop
  """List the tags on a given object.
257 846baef9 Iustin Pop

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

263 846baef9 Iustin Pop
  """
264 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
265 846baef9 Iustin Pop
  op = opcodes.OpGetTags(kind=kind, name=name)
266 846baef9 Iustin Pop
  result = SubmitOpCode(op)
267 846baef9 Iustin Pop
  result = list(result)
268 846baef9 Iustin Pop
  result.sort()
269 846baef9 Iustin Pop
  for tag in result:
270 03298ebe Michael Hanselmann
    ToStdout(tag)
271 846baef9 Iustin Pop
272 846baef9 Iustin Pop
273 846baef9 Iustin Pop
def AddTags(opts, args):
274 846baef9 Iustin Pop
  """Add tags on a given object.
275 846baef9 Iustin Pop

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

281 846baef9 Iustin Pop
  """
282 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
283 810c50b7 Iustin Pop
  _ExtendTags(opts, args)
284 846baef9 Iustin Pop
  if not args:
285 846baef9 Iustin Pop
    raise errors.OpPrereqError("No tags to be added")
286 846baef9 Iustin Pop
  op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
287 846baef9 Iustin Pop
  SubmitOpCode(op)
288 846baef9 Iustin Pop
289 846baef9 Iustin Pop
290 846baef9 Iustin Pop
def RemoveTags(opts, args):
291 846baef9 Iustin Pop
  """Remove tags from a given object.
292 846baef9 Iustin Pop

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

298 846baef9 Iustin Pop
  """
299 846baef9 Iustin Pop
  kind, name = _ExtractTagsObject(opts, args)
300 810c50b7 Iustin Pop
  _ExtendTags(opts, args)
301 846baef9 Iustin Pop
  if not args:
302 846baef9 Iustin Pop
    raise errors.OpPrereqError("No tags to be removed")
303 846baef9 Iustin Pop
  op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
304 846baef9 Iustin Pop
  SubmitOpCode(op)
305 846baef9 Iustin Pop
306 a8083063 Iustin Pop
307 a8083063 Iustin Pop
def check_unit(option, opt, value):
308 65fe4693 Iustin Pop
  """OptParsers custom converter for units.
309 65fe4693 Iustin Pop

310 65fe4693 Iustin Pop
  """
311 a8083063 Iustin Pop
  try:
312 a8083063 Iustin Pop
    return utils.ParseUnit(value)
313 a8083063 Iustin Pop
  except errors.UnitParseError, err:
314 3ecf6786 Iustin Pop
    raise OptionValueError("option %s: %s" % (opt, err))
315 a8083063 Iustin Pop
316 a8083063 Iustin Pop
317 a8469393 Iustin Pop
def _SplitKeyVal(opt, data):
318 a8469393 Iustin Pop
  """Convert a KeyVal string into a dict.
319 a8469393 Iustin Pop

320 a8469393 Iustin Pop
  This function will convert a key=val[,...] string into a dict. Empty
321 a8469393 Iustin Pop
  values will be converted specially: keys which have the prefix 'no_'
322 a8469393 Iustin Pop
  will have the value=False and the prefix stripped, the others will
323 a8469393 Iustin Pop
  have value=True.
324 a8469393 Iustin Pop

325 a8469393 Iustin Pop
  @type opt: string
326 a8469393 Iustin Pop
  @param opt: a string holding the option name for which we process the
327 a8469393 Iustin Pop
      data, used in building error messages
328 a8469393 Iustin Pop
  @type data: string
329 a8469393 Iustin Pop
  @param data: a string of the format key=val,key=val,...
330 a8469393 Iustin Pop
  @rtype: dict
331 a8469393 Iustin Pop
  @return: {key=val, key=val}
332 a8469393 Iustin Pop
  @raises errors.ParameterError: if there are duplicate keys
333 a8469393 Iustin Pop

334 a8469393 Iustin Pop
  """
335 a8469393 Iustin Pop
  kv_dict = {}
336 4f31882e Guido Trotter
  if data:
337 4f31882e Guido Trotter
    for elem in data.split(","):
338 4f31882e Guido Trotter
      if "=" in elem:
339 4f31882e Guido Trotter
        key, val = elem.split("=", 1)
340 a8469393 Iustin Pop
      else:
341 4f31882e Guido Trotter
        if elem.startswith(NO_PREFIX):
342 4f31882e Guido Trotter
          key, val = elem[len(NO_PREFIX):], False
343 4f31882e Guido Trotter
        elif elem.startswith(UN_PREFIX):
344 4f31882e Guido Trotter
          key, val = elem[len(UN_PREFIX):], None
345 4f31882e Guido Trotter
        else:
346 4f31882e Guido Trotter
          key, val = elem, True
347 4f31882e Guido Trotter
      if key in kv_dict:
348 4f31882e Guido Trotter
        raise errors.ParameterError("Duplicate key '%s' in option %s" %
349 4f31882e Guido Trotter
                                    (key, opt))
350 4f31882e Guido Trotter
      kv_dict[key] = val
351 a8469393 Iustin Pop
  return kv_dict
352 a8469393 Iustin Pop
353 a8469393 Iustin Pop
354 a8469393 Iustin Pop
def check_ident_key_val(option, opt, value):
355 552c8dff Michael Hanselmann
  """Custom parser for ident:key=val,key=val options.
356 552c8dff Michael Hanselmann

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

360 a8469393 Iustin Pop
  """
361 a8469393 Iustin Pop
  if ":" not in value:
362 8b46606c Guido Trotter
    ident, rest = value, ''
363 a8469393 Iustin Pop
  else:
364 a8469393 Iustin Pop
    ident, rest = value.split(":", 1)
365 8b46606c Guido Trotter
366 8b46606c Guido Trotter
  if ident.startswith(NO_PREFIX):
367 8b46606c Guido Trotter
    if rest:
368 8b46606c Guido Trotter
      msg = "Cannot pass options when removing parameter groups: %s" % value
369 8b46606c Guido Trotter
      raise errors.ParameterError(msg)
370 8b46606c Guido Trotter
    retval = (ident[len(NO_PREFIX):], False)
371 8b46606c Guido Trotter
  elif ident.startswith(UN_PREFIX):
372 8b46606c Guido Trotter
    if rest:
373 8b46606c Guido Trotter
      msg = "Cannot pass options when removing parameter groups: %s" % value
374 8b46606c Guido Trotter
      raise errors.ParameterError(msg)
375 8b46606c Guido Trotter
    retval = (ident[len(UN_PREFIX):], None)
376 8b46606c Guido Trotter
  else:
377 a8469393 Iustin Pop
    kv_dict = _SplitKeyVal(opt, rest)
378 a8469393 Iustin Pop
    retval = (ident, kv_dict)
379 a8469393 Iustin Pop
  return retval
380 a8469393 Iustin Pop
381 a8469393 Iustin Pop
382 a8469393 Iustin Pop
def check_key_val(option, opt, value):
383 552c8dff Michael Hanselmann
  """Custom parser class for key=val,key=val options.
384 552c8dff Michael Hanselmann

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

387 a8469393 Iustin Pop
  """
388 a8469393 Iustin Pop
  return _SplitKeyVal(opt, value)
389 a8469393 Iustin Pop
390 a8469393 Iustin Pop
391 63d44c55 Michael Hanselmann
# completion_suggestion is normally a list. Using numeric values not evaluating
392 63d44c55 Michael Hanselmann
# to False for dynamic completion.
393 63d44c55 Michael Hanselmann
(OPT_COMPL_MANY_NODES,
394 63d44c55 Michael Hanselmann
 OPT_COMPL_ONE_NODE,
395 63d44c55 Michael Hanselmann
 OPT_COMPL_ONE_INSTANCE,
396 63d44c55 Michael Hanselmann
 OPT_COMPL_ONE_OS,
397 2d3ed64b Michael Hanselmann
 OPT_COMPL_ONE_IALLOCATOR,
398 2d3ed64b Michael Hanselmann
 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
399 63d44c55 Michael Hanselmann
400 63d44c55 Michael Hanselmann
OPT_COMPL_ALL = frozenset([
401 63d44c55 Michael Hanselmann
  OPT_COMPL_MANY_NODES,
402 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_NODE,
403 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_INSTANCE,
404 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_OS,
405 63d44c55 Michael Hanselmann
  OPT_COMPL_ONE_IALLOCATOR,
406 2d3ed64b Michael Hanselmann
  OPT_COMPL_INST_ADD_NODES,
407 63d44c55 Michael Hanselmann
  ])
408 63d44c55 Michael Hanselmann
409 63d44c55 Michael Hanselmann
410 552c8dff Michael Hanselmann
class CliOption(Option):
411 552c8dff Michael Hanselmann
  """Custom option class for optparse.
412 a8469393 Iustin Pop

413 a8469393 Iustin Pop
  """
414 863d7f46 Michael Hanselmann
  ATTRS = Option.ATTRS + [
415 863d7f46 Michael Hanselmann
    "completion_suggest",
416 863d7f46 Michael Hanselmann
    ]
417 552c8dff Michael Hanselmann
  TYPES = Option.TYPES + (
418 552c8dff Michael Hanselmann
    "identkeyval",
419 552c8dff Michael Hanselmann
    "keyval",
420 552c8dff Michael Hanselmann
    "unit",
421 552c8dff Michael Hanselmann
    )
422 552c8dff Michael Hanselmann
  TYPE_CHECKER = Option.TYPE_CHECKER.copy()
423 552c8dff Michael Hanselmann
  TYPE_CHECKER["identkeyval"] = check_ident_key_val
424 a8469393 Iustin Pop
  TYPE_CHECKER["keyval"] = check_key_val
425 552c8dff Michael Hanselmann
  TYPE_CHECKER["unit"] = check_unit
426 a8469393 Iustin Pop
427 a8469393 Iustin Pop
428 a8083063 Iustin Pop
# optparse.py sets make_option, so we do it for our own option class, too
429 a8083063 Iustin Pop
cli_option = CliOption
430 a8083063 Iustin Pop
431 a8083063 Iustin Pop
432 c38c44ad Michael Hanselmann
DEBUG_OPT = cli_option("-d", "--debug", default=False,
433 c38c44ad Michael Hanselmann
                       action="store_true",
434 c38c44ad Michael Hanselmann
                       help="Turn debugging on")
435 c38c44ad Michael Hanselmann
436 c38c44ad Michael Hanselmann
NOHDR_OPT = cli_option("--no-headers", default=False,
437 c38c44ad Michael Hanselmann
                       action="store_true", dest="no_headers",
438 c38c44ad Michael Hanselmann
                       help="Don't display column headers")
439 c38c44ad Michael Hanselmann
440 c38c44ad Michael Hanselmann
SEP_OPT = cli_option("--separator", default=None,
441 c38c44ad Michael Hanselmann
                     action="store", dest="separator",
442 c38c44ad Michael Hanselmann
                     help=("Separator between output fields"
443 c38c44ad Michael Hanselmann
                           " (defaults to one space)"))
444 c38c44ad Michael Hanselmann
445 c38c44ad Michael Hanselmann
USEUNITS_OPT = cli_option("--units", default=None,
446 c38c44ad Michael Hanselmann
                          dest="units", choices=('h', 'm', 'g', 't'),
447 c38c44ad Michael Hanselmann
                          help="Specify units for output (one of hmgt)")
448 c38c44ad Michael Hanselmann
449 c38c44ad Michael Hanselmann
FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
450 c38c44ad Michael Hanselmann
                        type="string", metavar="FIELDS",
451 c38c44ad Michael Hanselmann
                        help="Comma separated list of output fields")
452 c38c44ad Michael Hanselmann
453 c38c44ad Michael Hanselmann
FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
454 c38c44ad Michael Hanselmann
                       default=False, help="Force the operation")
455 c38c44ad Michael Hanselmann
456 c38c44ad Michael Hanselmann
CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
457 c38c44ad Michael Hanselmann
                         default=False, help="Do not require confirmation")
458 c38c44ad Michael Hanselmann
459 c38c44ad Michael Hanselmann
TAG_SRC_OPT = cli_option("--from", dest="tags_source",
460 c38c44ad Michael Hanselmann
                         default=None, help="File with tag names")
461 c38c44ad Michael Hanselmann
462 c38c44ad Michael Hanselmann
SUBMIT_OPT = cli_option("--submit", dest="submit_only",
463 c38c44ad Michael Hanselmann
                        default=False, action="store_true",
464 c38c44ad Michael Hanselmann
                        help=("Submit the job and return the job ID, but"
465 c38c44ad Michael Hanselmann
                              " don't wait for the job to finish"))
466 c38c44ad Michael Hanselmann
467 c38c44ad Michael Hanselmann
SYNC_OPT = cli_option("--sync", dest="do_locking",
468 c38c44ad Michael Hanselmann
                      default=False, action="store_true",
469 c38c44ad Michael Hanselmann
                      help=("Grab locks while doing the queries"
470 c38c44ad Michael Hanselmann
                            " in order to ensure more consistent results"))
471 c38c44ad Michael Hanselmann
472 c38c44ad Michael Hanselmann
_DRY_RUN_OPT = cli_option("--dry-run", default=False,
473 c38c44ad Michael Hanselmann
                          action="store_true",
474 c38c44ad Michael Hanselmann
                          help=("Do not execute the operation, just run the"
475 c38c44ad Michael Hanselmann
                                " check steps and verify it it could be"
476 c38c44ad Michael Hanselmann
                                " executed"))
477 c38c44ad Michael Hanselmann
478 9cdb9578 Iustin Pop
VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
479 9cdb9578 Iustin Pop
                         action="store_true",
480 9cdb9578 Iustin Pop
                         help="Increase the verbosity of the operation")
481 9cdb9578 Iustin Pop
482 a0c9776a Iustin Pop
DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
483 a0c9776a Iustin Pop
                              action="store_true", dest="simulate_errors",
484 a0c9776a Iustin Pop
                              help="Debugging option that makes the operation"
485 a0c9776a Iustin Pop
                              " treat most runtime checks as failed")
486 a0c9776a Iustin Pop
487 3f75b4f3 Iustin Pop
NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
488 3f75b4f3 Iustin Pop
                        default=True, action="store_false",
489 3f75b4f3 Iustin Pop
                        help="Don't wait for sync (DANGEROUS!)")
490 3f75b4f3 Iustin Pop
491 4f365444 Iustin Pop
DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
492 4f365444 Iustin Pop
                               help="Custom disk setup (diskless, file,"
493 4f365444 Iustin Pop
                               " plain or drbd)",
494 4f365444 Iustin Pop
                               default=None, metavar="TEMPL",
495 4f365444 Iustin Pop
                               choices=list(constants.DISK_TEMPLATES))
496 4f365444 Iustin Pop
497 26023ecd Iustin Pop
NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
498 26023ecd Iustin Pop
                        help="Do not create any network cards for"
499 26023ecd Iustin Pop
                        " the instance")
500 26023ecd Iustin Pop
501 4a25828c Iustin Pop
FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
502 4a25828c Iustin Pop
                               help="Relative path under default cluster-wide"
503 4a25828c Iustin Pop
                               " file storage dir to store file-based disks",
504 4a25828c Iustin Pop
                               default=None, metavar="<DIR>")
505 4a25828c Iustin Pop
506 0f87c43e Iustin Pop
FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
507 0f87c43e Iustin Pop
                                  help="Driver to use for image files",
508 0f87c43e Iustin Pop
                                  default="loop", metavar="<DRIVER>",
509 0f87c43e Iustin Pop
                                  choices=list(constants.FILE_DRIVER))
510 0f87c43e Iustin Pop
511 4eb62659 Iustin Pop
IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
512 4eb62659 Iustin Pop
                            help="Select nodes for the instance automatically"
513 4eb62659 Iustin Pop
                            " using the <NAME> iallocator plugin",
514 4eb62659 Iustin Pop
                            default=None, type="string",
515 4eb62659 Iustin Pop
                            completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
516 4eb62659 Iustin Pop
517 d3ed23ff Iustin Pop
OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
518 d3ed23ff Iustin Pop
                    metavar="<os>",
519 d3ed23ff Iustin Pop
                    completion_suggest=OPT_COMPL_ONE_OS)
520 d3ed23ff Iustin Pop
521 087ed2ed Iustin Pop
BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
522 087ed2ed Iustin Pop
                         type="keyval", default={},
523 087ed2ed Iustin Pop
                         help="Backend parameters")
524 48f212d7 Iustin Pop
525 48f212d7 Iustin Pop
HVOPTS_OPT =  cli_option("-H", "--hypervisor-parameters", type="keyval",
526 48f212d7 Iustin Pop
                         default={}, dest="hvparams",
527 48f212d7 Iustin Pop
                         help="Hypervisor parameters")
528 087ed2ed Iustin Pop
529 236fd9c4 Iustin Pop
HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
530 236fd9c4 Iustin Pop
                            help="Hypervisor and hypervisor options, in the"
531 236fd9c4 Iustin Pop
                            " format hypervisor:option=value,option=value,...",
532 236fd9c4 Iustin Pop
                            default=None, type="identkeyval")
533 073271f6 Iustin Pop
534 073271f6 Iustin Pop
HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
535 073271f6 Iustin Pop
                        help="Hypervisor and hypervisor options, in the"
536 073271f6 Iustin Pop
                        " format hypervisor:option=value,option=value,...",
537 073271f6 Iustin Pop
                        default=[], action="append", type="identkeyval")
538 236fd9c4 Iustin Pop
539 91e0748c Iustin Pop
NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
540 91e0748c Iustin Pop
                           action="store_false",
541 91e0748c Iustin Pop
                           help="Don't check that the instance's IP"
542 91e0748c Iustin Pop
                           " is alive")
543 91e0748c Iustin Pop
544 7d3a9fab Iustin Pop
NET_OPT = cli_option("--net",
545 7d3a9fab Iustin Pop
                     help="NIC parameters", default=[],
546 7d3a9fab Iustin Pop
                     dest="nics", action="append", type="identkeyval")
547 e3876ccb Iustin Pop
548 e3876ccb Iustin Pop
DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
549 e3876ccb Iustin Pop
                      dest="disks", action="append", type="identkeyval")
550 91e0748c Iustin Pop
551 c38c44ad Michael Hanselmann
552 de47cf8f Guido Trotter
def _ParseArgs(argv, commands, aliases):
553 c41eea6e Iustin Pop
  """Parser for the command line arguments.
554 a8083063 Iustin Pop

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

558 c41eea6e Iustin Pop
  @param argv: the command line
559 c41eea6e Iustin Pop
  @param commands: dictionary with special contents, see the design
560 c41eea6e Iustin Pop
      doc for cmdline handling
561 c41eea6e Iustin Pop
  @param aliases: dictionary with command aliases {'alias': 'target, ...}
562 098c0958 Michael Hanselmann

563 a8083063 Iustin Pop
  """
564 a8083063 Iustin Pop
  if len(argv) == 0:
565 a8083063 Iustin Pop
    binary = "<command>"
566 a8083063 Iustin Pop
  else:
567 a8083063 Iustin Pop
    binary = argv[0].split("/")[-1]
568 a8083063 Iustin Pop
569 a8083063 Iustin Pop
  if len(argv) > 1 and argv[1] == "--version":
570 03298ebe Michael Hanselmann
    ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
571 a8083063 Iustin Pop
    # Quit right away. That way we don't have to care about this special
572 a8083063 Iustin Pop
    # argument. optparse.py does it the same.
573 a8083063 Iustin Pop
    sys.exit(0)
574 a8083063 Iustin Pop
575 de47cf8f Guido Trotter
  if len(argv) < 2 or not (argv[1] in commands or
576 70a35b6f Guido Trotter
                           argv[1] in aliases):
577 a8083063 Iustin Pop
    # let's do a nice thing
578 a8083063 Iustin Pop
    sortedcmds = commands.keys()
579 a8083063 Iustin Pop
    sortedcmds.sort()
580 03298ebe Michael Hanselmann
581 03298ebe Michael Hanselmann
    ToStdout("Usage: %s {command} [options...] [argument...]", binary)
582 03298ebe Michael Hanselmann
    ToStdout("%s <command> --help to see details, or man %s", binary, binary)
583 03298ebe Michael Hanselmann
    ToStdout("")
584 03298ebe Michael Hanselmann
585 a8083063 Iustin Pop
    # compute the max line length for cmd + usage
586 4e713df6 Iustin Pop
    mlen = max([len(" %s" % cmd) for cmd in commands])
587 a8083063 Iustin Pop
    mlen = min(60, mlen) # should not get here...
588 03298ebe Michael Hanselmann
589 a8083063 Iustin Pop
    # and format a nice command list
590 03298ebe Michael Hanselmann
    ToStdout("Commands:")
591 a8083063 Iustin Pop
    for cmd in sortedcmds:
592 4e713df6 Iustin Pop
      cmdstr = " %s" % (cmd,)
593 9a033156 Iustin Pop
      help_text = commands[cmd][4]
594 03298ebe Michael Hanselmann
      help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
595 03298ebe Michael Hanselmann
      ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
596 a8083063 Iustin Pop
      for line in help_lines:
597 03298ebe Michael Hanselmann
        ToStdout("%-*s   %s", mlen, "", line)
598 03298ebe Michael Hanselmann
599 03298ebe Michael Hanselmann
    ToStdout("")
600 03298ebe Michael Hanselmann
601 a8083063 Iustin Pop
    return None, None, None
602 de47cf8f Guido Trotter
603 de47cf8f Guido Trotter
  # get command, unalias it, and look it up in commands
604 a8083063 Iustin Pop
  cmd = argv.pop(1)
605 de47cf8f Guido Trotter
  if cmd in aliases:
606 de47cf8f Guido Trotter
    if cmd in commands:
607 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' overrides an existing"
608 de47cf8f Guido Trotter
                                   " command" % cmd)
609 de47cf8f Guido Trotter
610 de47cf8f Guido Trotter
    if aliases[cmd] not in commands:
611 de47cf8f Guido Trotter
      raise errors.ProgrammerError("Alias '%s' maps to non-existing"
612 de47cf8f Guido Trotter
                                   " command '%s'" % (cmd, aliases[cmd]))
613 de47cf8f Guido Trotter
614 de47cf8f Guido Trotter
    cmd = aliases[cmd]
615 de47cf8f Guido Trotter
616 a8005e17 Michael Hanselmann
  func, args_def, parser_opts, usage, description = commands[cmd]
617 64c65a2a Iustin Pop
  parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT],
618 a8083063 Iustin Pop
                        description=description,
619 a8083063 Iustin Pop
                        formatter=TitledHelpFormatter(),
620 a8083063 Iustin Pop
                        usage="%%prog %s %s" % (cmd, usage))
621 a8083063 Iustin Pop
  parser.disable_interspersed_args()
622 a8083063 Iustin Pop
  options, args = parser.parse_args()
623 a8005e17 Michael Hanselmann
624 a8005e17 Michael Hanselmann
  if not _CheckArguments(cmd, args_def, args):
625 a8083063 Iustin Pop
    return None, None, None
626 a8083063 Iustin Pop
627 a8083063 Iustin Pop
  return func, options, args
628 a8083063 Iustin Pop
629 a8083063 Iustin Pop
630 a8005e17 Michael Hanselmann
def _CheckArguments(cmd, args_def, args):
631 a8005e17 Michael Hanselmann
  """Verifies the arguments using the argument definition.
632 a8005e17 Michael Hanselmann

633 a8005e17 Michael Hanselmann
  Algorithm:
634 a8005e17 Michael Hanselmann

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

637 a8005e17 Michael Hanselmann
    1. For each argument in definition
638 a8005e17 Michael Hanselmann

639 a8005e17 Michael Hanselmann
      1. Keep running count of minimum number of values (min_count)
640 a8005e17 Michael Hanselmann
      1. Keep running count of maximum number of values (max_count)
641 a8005e17 Michael Hanselmann
      1. If it has an unlimited number of values
642 a8005e17 Michael Hanselmann

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

645 a8005e17 Michael Hanselmann
    1. If last argument has limited number of values
646 a8005e17 Michael Hanselmann

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

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

651 a8005e17 Michael Hanselmann
  """
652 a8005e17 Michael Hanselmann
  if args and not args_def:
653 a8005e17 Michael Hanselmann
    ToStderr("Error: Command %s expects no arguments", cmd)
654 a8005e17 Michael Hanselmann
    return False
655 a8005e17 Michael Hanselmann
656 a8005e17 Michael Hanselmann
  min_count = None
657 a8005e17 Michael Hanselmann
  max_count = None
658 a8005e17 Michael Hanselmann
  check_max = None
659 a8005e17 Michael Hanselmann
660 a8005e17 Michael Hanselmann
  last_idx = len(args_def) - 1
661 a8005e17 Michael Hanselmann
662 a8005e17 Michael Hanselmann
  for idx, arg in enumerate(args_def):
663 a8005e17 Michael Hanselmann
    if min_count is None:
664 a8005e17 Michael Hanselmann
      min_count = arg.min
665 a8005e17 Michael Hanselmann
    elif arg.min is not None:
666 a8005e17 Michael Hanselmann
      min_count += arg.min
667 a8005e17 Michael Hanselmann
668 a8005e17 Michael Hanselmann
    if max_count is None:
669 a8005e17 Michael Hanselmann
      max_count = arg.max
670 a8005e17 Michael Hanselmann
    elif arg.max is not None:
671 a8005e17 Michael Hanselmann
      max_count += arg.max
672 a8005e17 Michael Hanselmann
673 a8005e17 Michael Hanselmann
    if idx == last_idx:
674 a8005e17 Michael Hanselmann
      check_max = (arg.max is not None)
675 a8005e17 Michael Hanselmann
676 a8005e17 Michael Hanselmann
    elif arg.max is None:
677 a8005e17 Michael Hanselmann
      raise errors.ProgrammerError("Only the last argument can have max=None")
678 a8005e17 Michael Hanselmann
679 a8005e17 Michael Hanselmann
  if check_max:
680 a8005e17 Michael Hanselmann
    # Command with exact number of arguments
681 a8005e17 Michael Hanselmann
    if (min_count is not None and max_count is not None and
682 a8005e17 Michael Hanselmann
        min_count == max_count and len(args) != min_count):
683 a8005e17 Michael Hanselmann
      ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
684 a8005e17 Michael Hanselmann
      return False
685 a8005e17 Michael Hanselmann
686 a8005e17 Michael Hanselmann
    # Command with limited number of arguments
687 a8005e17 Michael Hanselmann
    if max_count is not None and len(args) > max_count:
688 a8005e17 Michael Hanselmann
      ToStderr("Error: Command %s expects only %d argument(s)",
689 a8005e17 Michael Hanselmann
               cmd, max_count)
690 a8005e17 Michael Hanselmann
      return False
691 a8005e17 Michael Hanselmann
692 a8005e17 Michael Hanselmann
  # Command with some required arguments
693 a8005e17 Michael Hanselmann
  if min_count is not None and len(args) < min_count:
694 a8005e17 Michael Hanselmann
    ToStderr("Error: Command %s expects at least %d argument(s)",
695 a8005e17 Michael Hanselmann
             cmd, min_count)
696 a8005e17 Michael Hanselmann
    return False
697 a8005e17 Michael Hanselmann
698 a8005e17 Michael Hanselmann
  return True
699 a8005e17 Michael Hanselmann
700 a8005e17 Michael Hanselmann
701 60d49723 Michael Hanselmann
def SplitNodeOption(value):
702 60d49723 Michael Hanselmann
  """Splits the value of a --node option.
703 60d49723 Michael Hanselmann

704 60d49723 Michael Hanselmann
  """
705 60d49723 Michael Hanselmann
  if value and ':' in value:
706 60d49723 Michael Hanselmann
    return value.split(':', 1)
707 60d49723 Michael Hanselmann
  else:
708 60d49723 Michael Hanselmann
    return (value, None)
709 60d49723 Michael Hanselmann
710 60d49723 Michael Hanselmann
711 4331f6cd Michael Hanselmann
def UsesRPC(fn):
712 4331f6cd Michael Hanselmann
  def wrapper(*args, **kwargs):
713 4331f6cd Michael Hanselmann
    rpc.Init()
714 4331f6cd Michael Hanselmann
    try:
715 4331f6cd Michael Hanselmann
      return fn(*args, **kwargs)
716 4331f6cd Michael Hanselmann
    finally:
717 4331f6cd Michael Hanselmann
      rpc.Shutdown()
718 4331f6cd Michael Hanselmann
  return wrapper
719 4331f6cd Michael Hanselmann
720 4331f6cd Michael Hanselmann
721 47988778 Iustin Pop
def AskUser(text, choices=None):
722 47988778 Iustin Pop
  """Ask the user a question.
723 a8083063 Iustin Pop

724 c41eea6e Iustin Pop
  @param text: the question to ask
725 a8083063 Iustin Pop

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

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

735 a8083063 Iustin Pop
  """
736 47988778 Iustin Pop
  if choices is None:
737 47988778 Iustin Pop
    choices = [('y', True, 'Perform the operation'),
738 47988778 Iustin Pop
               ('n', False, 'Do not perform the operation')]
739 47988778 Iustin Pop
  if not choices or not isinstance(choices, list):
740 5bbd3f7f Michael Hanselmann
    raise errors.ProgrammerError("Invalid choices argument to AskUser")
741 47988778 Iustin Pop
  for entry in choices:
742 47988778 Iustin Pop
    if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
743 5bbd3f7f Michael Hanselmann
      raise errors.ProgrammerError("Invalid choices element to AskUser")
744 47988778 Iustin Pop
745 47988778 Iustin Pop
  answer = choices[-1][1]
746 47988778 Iustin Pop
  new_text = []
747 47988778 Iustin Pop
  for line in text.splitlines():
748 47988778 Iustin Pop
    new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
749 47988778 Iustin Pop
  text = "\n".join(new_text)
750 a8083063 Iustin Pop
  try:
751 3023170f Iustin Pop
    f = file("/dev/tty", "a+")
752 a8083063 Iustin Pop
  except IOError:
753 47988778 Iustin Pop
    return answer
754 a8083063 Iustin Pop
  try:
755 47988778 Iustin Pop
    chars = [entry[0] for entry in choices]
756 47988778 Iustin Pop
    chars[-1] = "[%s]" % chars[-1]
757 47988778 Iustin Pop
    chars.append('?')
758 47988778 Iustin Pop
    maps = dict([(entry[0], entry[1]) for entry in choices])
759 47988778 Iustin Pop
    while True:
760 47988778 Iustin Pop
      f.write(text)
761 47988778 Iustin Pop
      f.write('\n')
762 47988778 Iustin Pop
      f.write("/".join(chars))
763 47988778 Iustin Pop
      f.write(": ")
764 47988778 Iustin Pop
      line = f.readline(2).strip().lower()
765 47988778 Iustin Pop
      if line in maps:
766 47988778 Iustin Pop
        answer = maps[line]
767 47988778 Iustin Pop
        break
768 47988778 Iustin Pop
      elif line == '?':
769 47988778 Iustin Pop
        for entry in choices:
770 47988778 Iustin Pop
          f.write(" %s - %s\n" % (entry[0], entry[2]))
771 47988778 Iustin Pop
        f.write("\n")
772 47988778 Iustin Pop
        continue
773 a8083063 Iustin Pop
  finally:
774 a8083063 Iustin Pop
    f.close()
775 a8083063 Iustin Pop
  return answer
776 a8083063 Iustin Pop
777 a8083063 Iustin Pop
778 e9d741b6 Iustin Pop
class JobSubmittedException(Exception):
779 e9d741b6 Iustin Pop
  """Job was submitted, client should exit.
780 e9d741b6 Iustin Pop

781 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
782 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
783 e9d741b6 Iustin Pop

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

786 e9d741b6 Iustin Pop
  """
787 e9d741b6 Iustin Pop
788 e9d741b6 Iustin Pop
789 0a1e74d9 Iustin Pop
def SendJob(ops, cl=None):
790 0a1e74d9 Iustin Pop
  """Function to submit an opcode without waiting for the results.
791 a8083063 Iustin Pop

792 0a1e74d9 Iustin Pop
  @type ops: list
793 0a1e74d9 Iustin Pop
  @param ops: list of opcodes
794 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
795 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
796 0a1e74d9 Iustin Pop
             if None, a new client will be created
797 a8083063 Iustin Pop

798 a8083063 Iustin Pop
  """
799 e2212007 Iustin Pop
  if cl is None:
800 b33e986b Iustin Pop
    cl = GetClient()
801 685ee993 Iustin Pop
802 0a1e74d9 Iustin Pop
  job_id = cl.SubmitJob(ops)
803 0a1e74d9 Iustin Pop
804 0a1e74d9 Iustin Pop
  return job_id
805 0a1e74d9 Iustin Pop
806 0a1e74d9 Iustin Pop
807 281606c1 Michael Hanselmann
def PollJob(job_id, cl=None, feedback_fn=None):
808 0a1e74d9 Iustin Pop
  """Function to poll for the result of a job.
809 0a1e74d9 Iustin Pop

810 0a1e74d9 Iustin Pop
  @type job_id: job identified
811 0a1e74d9 Iustin Pop
  @param job_id: the job to poll for results
812 0a1e74d9 Iustin Pop
  @type cl: luxi.Client
813 0a1e74d9 Iustin Pop
  @param cl: the luxi client to use for communicating with the master;
814 0a1e74d9 Iustin Pop
             if None, a new client will be created
815 0a1e74d9 Iustin Pop

816 0a1e74d9 Iustin Pop
  """
817 0a1e74d9 Iustin Pop
  if cl is None:
818 0a1e74d9 Iustin Pop
    cl = GetClient()
819 685ee993 Iustin Pop
820 6c5a7090 Michael Hanselmann
  prev_job_info = None
821 6c5a7090 Michael Hanselmann
  prev_logmsg_serial = None
822 6c5a7090 Michael Hanselmann
823 685ee993 Iustin Pop
  while True:
824 6c5a7090 Michael Hanselmann
    result = cl.WaitForJobChange(job_id, ["status"], prev_job_info,
825 6c5a7090 Michael Hanselmann
                                 prev_logmsg_serial)
826 6c5a7090 Michael Hanselmann
    if not result:
827 685ee993 Iustin Pop
      # job not found, go away!
828 0bbe448c Michael Hanselmann
      raise errors.JobLost("Job with id %s lost" % job_id)
829 685ee993 Iustin Pop
830 6c5a7090 Michael Hanselmann
    # Split result, a tuple of (field values, log entries)
831 6c5a7090 Michael Hanselmann
    (job_info, log_entries) = result
832 6c5a7090 Michael Hanselmann
    (status, ) = job_info
833 6c5a7090 Michael Hanselmann
834 6c5a7090 Michael Hanselmann
    if log_entries:
835 6c5a7090 Michael Hanselmann
      for log_entry in log_entries:
836 6c5a7090 Michael Hanselmann
        (serial, timestamp, _, message) = log_entry
837 6c5a7090 Michael Hanselmann
        if callable(feedback_fn):
838 6c5a7090 Michael Hanselmann
          feedback_fn(log_entry[1:])
839 6c5a7090 Michael Hanselmann
        else:
840 26f15862 Iustin Pop
          encoded = utils.SafeEncode(message)
841 03298ebe Michael Hanselmann
          ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), encoded)
842 6c5a7090 Michael Hanselmann
        prev_logmsg_serial = max(prev_logmsg_serial, serial)
843 6c5a7090 Michael Hanselmann
844 0bbe448c Michael Hanselmann
    # TODO: Handle canceled and archived jobs
845 fbf0262f Michael Hanselmann
    elif status in (constants.JOB_STATUS_SUCCESS,
846 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_ERROR,
847 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_CANCELING,
848 fbf0262f Michael Hanselmann
                    constants.JOB_STATUS_CANCELED):
849 685ee993 Iustin Pop
      break
850 6c5a7090 Michael Hanselmann
851 6c5a7090 Michael Hanselmann
    prev_job_info = job_info
852 685ee993 Iustin Pop
853 0e050889 Iustin Pop
  jobs = cl.QueryJobs([job_id], ["status", "opstatus", "opresult"])
854 0bbe448c Michael Hanselmann
  if not jobs:
855 0bbe448c Michael Hanselmann
    raise errors.JobLost("Job with id %s lost" % job_id)
856 685ee993 Iustin Pop
857 0e050889 Iustin Pop
  status, opstatus, result = jobs[0]
858 0bbe448c Michael Hanselmann
  if status == constants.JOB_STATUS_SUCCESS:
859 53c04d04 Iustin Pop
    return result
860 fbf0262f Michael Hanselmann
  elif status in (constants.JOB_STATUS_CANCELING,
861 fbf0262f Michael Hanselmann
                  constants.JOB_STATUS_CANCELED):
862 fbf0262f Michael Hanselmann
    raise errors.OpExecError("Job was canceled")
863 0bbe448c Michael Hanselmann
  else:
864 0e050889 Iustin Pop
    has_ok = False
865 0e050889 Iustin Pop
    for idx, (status, msg) in enumerate(zip(opstatus, result)):
866 0e050889 Iustin Pop
      if status == constants.OP_STATUS_SUCCESS:
867 0e050889 Iustin Pop
        has_ok = True
868 0e050889 Iustin Pop
      elif status == constants.OP_STATUS_ERROR:
869 bcb66fca Iustin Pop
        errors.MaybeRaise(msg)
870 0e050889 Iustin Pop
        if has_ok:
871 0e050889 Iustin Pop
          raise errors.OpExecError("partial failure (opcode %d): %s" %
872 0e050889 Iustin Pop
                                   (idx, msg))
873 0e050889 Iustin Pop
        else:
874 0e050889 Iustin Pop
          raise errors.OpExecError(str(msg))
875 0e050889 Iustin Pop
    # default failure mode
876 0bbe448c Michael Hanselmann
    raise errors.OpExecError(result)
877 ceab32dd Iustin Pop
878 ceab32dd Iustin Pop
879 0a1e74d9 Iustin Pop
def SubmitOpCode(op, cl=None, feedback_fn=None):
880 0a1e74d9 Iustin Pop
  """Legacy function to submit an opcode.
881 0a1e74d9 Iustin Pop

882 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
883 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
884 0a1e74d9 Iustin Pop
  interaction functions.
885 0a1e74d9 Iustin Pop

886 0a1e74d9 Iustin Pop
  """
887 0a1e74d9 Iustin Pop
  if cl is None:
888 0a1e74d9 Iustin Pop
    cl = GetClient()
889 0a1e74d9 Iustin Pop
890 0a1e74d9 Iustin Pop
  job_id = SendJob([op], cl)
891 0a1e74d9 Iustin Pop
892 53c04d04 Iustin Pop
  op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
893 53c04d04 Iustin Pop
894 53c04d04 Iustin Pop
  return op_results[0]
895 0a1e74d9 Iustin Pop
896 0a1e74d9 Iustin Pop
897 94428652 Iustin Pop
def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
898 94428652 Iustin Pop
  """Wrapper around SubmitOpCode or SendJob.
899 94428652 Iustin Pop

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

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

907 94428652 Iustin Pop
  """
908 64c65a2a Iustin Pop
  if opts and opts.dry_run:
909 64c65a2a Iustin Pop
    op.dry_run = opts.dry_run
910 94428652 Iustin Pop
  if opts and opts.submit_only:
911 e9d741b6 Iustin Pop
    job_id = SendJob([op], cl=cl)
912 e9d741b6 Iustin Pop
    raise JobSubmittedException(job_id)
913 94428652 Iustin Pop
  else:
914 94428652 Iustin Pop
    return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn)
915 94428652 Iustin Pop
916 94428652 Iustin Pop
917 af30b2fd Michael Hanselmann
def GetClient():
918 af30b2fd Michael Hanselmann
  # TODO: Cache object?
919 b33e986b Iustin Pop
  try:
920 b33e986b Iustin Pop
    client = luxi.Client()
921 b33e986b Iustin Pop
  except luxi.NoMasterError:
922 b33e986b Iustin Pop
    master, myself = ssconf.GetMasterAndMyself()
923 b33e986b Iustin Pop
    if master != myself:
924 b33e986b Iustin Pop
      raise errors.OpPrereqError("This is not the master node, please connect"
925 b33e986b Iustin Pop
                                 " to node '%s' and rerun the command" %
926 b33e986b Iustin Pop
                                 master)
927 b33e986b Iustin Pop
    else:
928 b33e986b Iustin Pop
      raise
929 b33e986b Iustin Pop
  return client
930 af30b2fd Michael Hanselmann
931 af30b2fd Michael Hanselmann
932 73702ee7 Iustin Pop
def FormatError(err):
933 73702ee7 Iustin Pop
  """Return a formatted error message for a given error.
934 73702ee7 Iustin Pop

935 73702ee7 Iustin Pop
  This function takes an exception instance and returns a tuple
936 73702ee7 Iustin Pop
  consisting of two values: first, the recommended exit code, and
937 73702ee7 Iustin Pop
  second, a string describing the error message (not
938 73702ee7 Iustin Pop
  newline-terminated).
939 73702ee7 Iustin Pop

940 73702ee7 Iustin Pop
  """
941 73702ee7 Iustin Pop
  retcode = 1
942 73702ee7 Iustin Pop
  obuf = StringIO()
943 e2e521d0 Iustin Pop
  msg = str(err)
944 73702ee7 Iustin Pop
  if isinstance(err, errors.ConfigurationError):
945 e2e521d0 Iustin Pop
    txt = "Corrupt configuration file: %s" % msg
946 46fbdd04 Iustin Pop
    logging.error(txt)
947 e2e521d0 Iustin Pop
    obuf.write(txt + "\n")
948 73702ee7 Iustin Pop
    obuf.write("Aborting.")
949 73702ee7 Iustin Pop
    retcode = 2
950 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksAbort):
951 73702ee7 Iustin Pop
    obuf.write("Failure: hooks execution failed:\n")
952 73702ee7 Iustin Pop
    for node, script, out in err.args[0]:
953 73702ee7 Iustin Pop
      if out:
954 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s, output: %s\n" %
955 73702ee7 Iustin Pop
                   (node, script, out))
956 73702ee7 Iustin Pop
      else:
957 73702ee7 Iustin Pop
        obuf.write("  node: %s, script: %s (no output)\n" %
958 73702ee7 Iustin Pop
                   (node, script))
959 73702ee7 Iustin Pop
  elif isinstance(err, errors.HooksFailure):
960 e2e521d0 Iustin Pop
    obuf.write("Failure: hooks general failure: %s" % msg)
961 73702ee7 Iustin Pop
  elif isinstance(err, errors.ResolverError):
962 73702ee7 Iustin Pop
    this_host = utils.HostInfo.SysName()
963 73702ee7 Iustin Pop
    if err.args[0] == this_host:
964 73702ee7 Iustin Pop
      msg = "Failure: can't resolve my own hostname ('%s')"
965 73702ee7 Iustin Pop
    else:
966 73702ee7 Iustin Pop
      msg = "Failure: can't resolve hostname '%s'"
967 73702ee7 Iustin Pop
    obuf.write(msg % err.args[0])
968 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpPrereqError):
969 73702ee7 Iustin Pop
    obuf.write("Failure: prerequisites not met for this"
970 e2e521d0 Iustin Pop
               " operation:\n%s" % msg)
971 73702ee7 Iustin Pop
  elif isinstance(err, errors.OpExecError):
972 e2e521d0 Iustin Pop
    obuf.write("Failure: command execution error:\n%s" % msg)
973 73702ee7 Iustin Pop
  elif isinstance(err, errors.TagError):
974 e2e521d0 Iustin Pop
    obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
975 686d7433 Iustin Pop
  elif isinstance(err, errors.JobQueueDrainError):
976 686d7433 Iustin Pop
    obuf.write("Failure: the job queue is marked for drain and doesn't"
977 686d7433 Iustin Pop
               " accept new requests\n")
978 f87b405e Michael Hanselmann
  elif isinstance(err, errors.JobQueueFull):
979 f87b405e Michael Hanselmann
    obuf.write("Failure: the job queue is full and doesn't accept new"
980 f87b405e Michael Hanselmann
               " job submissions until old jobs are archived\n")
981 a5728081 Guido Trotter
  elif isinstance(err, errors.TypeEnforcementError):
982 a5728081 Guido Trotter
    obuf.write("Parameter Error: %s" % msg)
983 c1ce76bb Iustin Pop
  elif isinstance(err, errors.ParameterError):
984 c1ce76bb Iustin Pop
    obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
985 73702ee7 Iustin Pop
  elif isinstance(err, errors.GenericError):
986 e2e521d0 Iustin Pop
    obuf.write("Unhandled Ganeti error: %s" % msg)
987 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.NoMasterError):
988 03a8dbdc Iustin Pop
    obuf.write("Cannot communicate with the master daemon.\nIs it running"
989 082c5adb Michael Hanselmann
               " and listening for connections?")
990 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.TimeoutError):
991 03a8dbdc Iustin Pop
    obuf.write("Timeout while talking to the master daemon. Error:\n"
992 03a8dbdc Iustin Pop
               "%s" % msg)
993 03a8dbdc Iustin Pop
  elif isinstance(err, luxi.ProtocolError):
994 03a8dbdc Iustin Pop
    obuf.write("Unhandled protocol error while talking to the master daemon:\n"
995 03a8dbdc Iustin Pop
               "%s" % msg)
996 e9d741b6 Iustin Pop
  elif isinstance(err, JobSubmittedException):
997 e9d741b6 Iustin Pop
    obuf.write("JobID: %s\n" % err.args[0])
998 e9d741b6 Iustin Pop
    retcode = 0
999 73702ee7 Iustin Pop
  else:
1000 e2e521d0 Iustin Pop
    obuf.write("Unhandled exception: %s" % msg)
1001 73702ee7 Iustin Pop
  return retcode, obuf.getvalue().rstrip('\n')
1002 73702ee7 Iustin Pop
1003 73702ee7 Iustin Pop
1004 de47cf8f Guido Trotter
def GenericMain(commands, override=None, aliases=None):
1005 a8083063 Iustin Pop
  """Generic main function for all the gnt-* commands.
1006 a8083063 Iustin Pop

1007 334d1483 Iustin Pop
  Arguments:
1008 334d1483 Iustin Pop
    - commands: a dictionary with a special structure, see the design doc
1009 334d1483 Iustin Pop
                for command line handling.
1010 334d1483 Iustin Pop
    - override: if not None, we expect a dictionary with keys that will
1011 334d1483 Iustin Pop
                override command line options; this can be used to pass
1012 334d1483 Iustin Pop
                options from the scripts to generic functions
1013 de47cf8f Guido Trotter
    - aliases: dictionary with command aliases {'alias': 'target, ...}
1014 a8083063 Iustin Pop

1015 a8083063 Iustin Pop
  """
1016 a8083063 Iustin Pop
  # save the program name and the entire command line for later logging
1017 a8083063 Iustin Pop
  if sys.argv:
1018 a8083063 Iustin Pop
    binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1019 a8083063 Iustin Pop
    if len(sys.argv) >= 2:
1020 a8083063 Iustin Pop
      binary += " " + sys.argv[1]
1021 a8083063 Iustin Pop
      old_cmdline = " ".join(sys.argv[2:])
1022 a8083063 Iustin Pop
    else:
1023 a8083063 Iustin Pop
      old_cmdline = ""
1024 a8083063 Iustin Pop
  else:
1025 a8083063 Iustin Pop
    binary = "<unknown program>"
1026 a8083063 Iustin Pop
    old_cmdline = ""
1027 a8083063 Iustin Pop
1028 de47cf8f Guido Trotter
  if aliases is None:
1029 de47cf8f Guido Trotter
    aliases = {}
1030 de47cf8f Guido Trotter
1031 3126878d Guido Trotter
  try:
1032 3126878d Guido Trotter
    func, options, args = _ParseArgs(sys.argv, commands, aliases)
1033 3126878d Guido Trotter
  except errors.ParameterError, err:
1034 3126878d Guido Trotter
    result, err_msg = FormatError(err)
1035 3126878d Guido Trotter
    ToStderr(err_msg)
1036 3126878d Guido Trotter
    return 1
1037 3126878d Guido Trotter
1038 a8083063 Iustin Pop
  if func is None: # parse error
1039 a8083063 Iustin Pop
    return 1
1040 a8083063 Iustin Pop
1041 334d1483 Iustin Pop
  if override is not None:
1042 334d1483 Iustin Pop
    for key, val in override.iteritems():
1043 334d1483 Iustin Pop
      setattr(options, key, val)
1044 334d1483 Iustin Pop
1045 82d9caef Iustin Pop
  utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1046 82d9caef Iustin Pop
                     stderr_logging=True, program=binary)
1047 a8083063 Iustin Pop
1048 a8083063 Iustin Pop
  if old_cmdline:
1049 46fbdd04 Iustin Pop
    logging.info("run with arguments '%s'", old_cmdline)
1050 a8083063 Iustin Pop
  else:
1051 46fbdd04 Iustin Pop
    logging.info("run with no arguments")
1052 a8083063 Iustin Pop
1053 a8083063 Iustin Pop
  try:
1054 a4af651e Iustin Pop
    result = func(options, args)
1055 d8353c3a Iustin Pop
  except (errors.GenericError, luxi.ProtocolError,
1056 d8353c3a Iustin Pop
          JobSubmittedException), err:
1057 a4af651e Iustin Pop
    result, err_msg = FormatError(err)
1058 5bbd3f7f Michael Hanselmann
    logging.exception("Error during command processing")
1059 46fbdd04 Iustin Pop
    ToStderr(err_msg)
1060 a8083063 Iustin Pop
1061 a8083063 Iustin Pop
  return result
1062 137161c9 Michael Hanselmann
1063 137161c9 Michael Hanselmann
1064 16be8703 Iustin Pop
def GenerateTable(headers, fields, separator, data,
1065 9fbfbb7b Iustin Pop
                  numfields=None, unitfields=None,
1066 9fbfbb7b Iustin Pop
                  units=None):
1067 137161c9 Michael Hanselmann
  """Prints a table with headers and different fields.
1068 137161c9 Michael Hanselmann

1069 9fbfbb7b Iustin Pop
  @type headers: dict
1070 9fbfbb7b Iustin Pop
  @param headers: dictionary mapping field names to headers for
1071 9fbfbb7b Iustin Pop
      the table
1072 9fbfbb7b Iustin Pop
  @type fields: list
1073 9fbfbb7b Iustin Pop
  @param fields: the field names corresponding to each row in
1074 9fbfbb7b Iustin Pop
      the data field
1075 9fbfbb7b Iustin Pop
  @param separator: the separator to be used; if this is None,
1076 9fbfbb7b Iustin Pop
      the default 'smart' algorithm is used which computes optimal
1077 9fbfbb7b Iustin Pop
      field width, otherwise just the separator is used between
1078 9fbfbb7b Iustin Pop
      each field
1079 9fbfbb7b Iustin Pop
  @type data: list
1080 9fbfbb7b Iustin Pop
  @param data: a list of lists, each sublist being one row to be output
1081 9fbfbb7b Iustin Pop
  @type numfields: list
1082 9fbfbb7b Iustin Pop
  @param numfields: a list with the fields that hold numeric
1083 9fbfbb7b Iustin Pop
      values and thus should be right-aligned
1084 9fbfbb7b Iustin Pop
  @type unitfields: list
1085 9fbfbb7b Iustin Pop
  @param unitfields: a list with the fields that hold numeric
1086 9fbfbb7b Iustin Pop
      values that should be formatted with the units field
1087 9fbfbb7b Iustin Pop
  @type units: string or None
1088 9fbfbb7b Iustin Pop
  @param units: the units we should use for formatting, or None for
1089 9fbfbb7b Iustin Pop
      automatic choice (human-readable for non-separator usage, otherwise
1090 9fbfbb7b Iustin Pop
      megabytes); this is a one-letter string
1091 137161c9 Michael Hanselmann

1092 137161c9 Michael Hanselmann
  """
1093 9fbfbb7b Iustin Pop
  if units is None:
1094 9fbfbb7b Iustin Pop
    if separator:
1095 9fbfbb7b Iustin Pop
      units = "m"
1096 9fbfbb7b Iustin Pop
    else:
1097 9fbfbb7b Iustin Pop
      units = "h"
1098 9fbfbb7b Iustin Pop
1099 137161c9 Michael Hanselmann
  if numfields is None:
1100 137161c9 Michael Hanselmann
    numfields = []
1101 137161c9 Michael Hanselmann
  if unitfields is None:
1102 137161c9 Michael Hanselmann
    unitfields = []
1103 137161c9 Michael Hanselmann
1104 00430f8e Iustin Pop
  numfields = utils.FieldSet(*numfields)
1105 00430f8e Iustin Pop
  unitfields = utils.FieldSet(*unitfields)
1106 00430f8e Iustin Pop
1107 137161c9 Michael Hanselmann
  format_fields = []
1108 137161c9 Michael Hanselmann
  for field in fields:
1109 01ca31ae Iustin Pop
    if headers and field not in headers:
1110 ea5a5b74 Guido Trotter
      # TODO: handle better unknown fields (either revert to old
1111 71c1af58 Iustin Pop
      # style of raising exception, or deal more intelligently with
1112 71c1af58 Iustin Pop
      # variable fields)
1113 71c1af58 Iustin Pop
      headers[field] = field
1114 137161c9 Michael Hanselmann
    if separator is not None:
1115 137161c9 Michael Hanselmann
      format_fields.append("%s")
1116 00430f8e Iustin Pop
    elif numfields.Matches(field):
1117 137161c9 Michael Hanselmann
      format_fields.append("%*s")
1118 137161c9 Michael Hanselmann
    else:
1119 137161c9 Michael Hanselmann
      format_fields.append("%-*s")
1120 137161c9 Michael Hanselmann
1121 137161c9 Michael Hanselmann
  if separator is None:
1122 137161c9 Michael Hanselmann
    mlens = [0 for name in fields]
1123 137161c9 Michael Hanselmann
    format = ' '.join(format_fields)
1124 137161c9 Michael Hanselmann
  else:
1125 137161c9 Michael Hanselmann
    format = separator.replace("%", "%%").join(format_fields)
1126 137161c9 Michael Hanselmann
1127 137161c9 Michael Hanselmann
  for row in data:
1128 dcbd6288 Guido Trotter
    if row is None:
1129 dcbd6288 Guido Trotter
      continue
1130 137161c9 Michael Hanselmann
    for idx, val in enumerate(row):
1131 00430f8e Iustin Pop
      if unitfields.Matches(fields[idx]):
1132 137161c9 Michael Hanselmann
        try:
1133 137161c9 Michael Hanselmann
          val = int(val)
1134 137161c9 Michael Hanselmann
        except ValueError:
1135 137161c9 Michael Hanselmann
          pass
1136 137161c9 Michael Hanselmann
        else:
1137 9fbfbb7b Iustin Pop
          val = row[idx] = utils.FormatUnit(val, units)
1138 01ca31ae Iustin Pop
      val = row[idx] = str(val)
1139 137161c9 Michael Hanselmann
      if separator is None:
1140 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(val))
1141 137161c9 Michael Hanselmann
1142 16be8703 Iustin Pop
  result = []
1143 137161c9 Michael Hanselmann
  if headers:
1144 137161c9 Michael Hanselmann
    args = []
1145 137161c9 Michael Hanselmann
    for idx, name in enumerate(fields):
1146 137161c9 Michael Hanselmann
      hdr = headers[name]
1147 137161c9 Michael Hanselmann
      if separator is None:
1148 137161c9 Michael Hanselmann
        mlens[idx] = max(mlens[idx], len(hdr))
1149 137161c9 Michael Hanselmann
        args.append(mlens[idx])
1150 137161c9 Michael Hanselmann
      args.append(hdr)
1151 16be8703 Iustin Pop
    result.append(format % tuple(args))
1152 137161c9 Michael Hanselmann
1153 137161c9 Michael Hanselmann
  for line in data:
1154 137161c9 Michael Hanselmann
    args = []
1155 dcbd6288 Guido Trotter
    if line is None:
1156 dcbd6288 Guido Trotter
      line = ['-' for _ in fields]
1157 137161c9 Michael Hanselmann
    for idx in xrange(len(fields)):
1158 137161c9 Michael Hanselmann
      if separator is None:
1159 137161c9 Michael Hanselmann
        args.append(mlens[idx])
1160 137161c9 Michael Hanselmann
      args.append(line[idx])
1161 16be8703 Iustin Pop
    result.append(format % tuple(args))
1162 16be8703 Iustin Pop
1163 16be8703 Iustin Pop
  return result
1164 3386e7a9 Iustin Pop
1165 3386e7a9 Iustin Pop
1166 3386e7a9 Iustin Pop
def FormatTimestamp(ts):
1167 3386e7a9 Iustin Pop
  """Formats a given timestamp.
1168 3386e7a9 Iustin Pop

1169 3386e7a9 Iustin Pop
  @type ts: timestamp
1170 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1171 3386e7a9 Iustin Pop

1172 3386e7a9 Iustin Pop
  @rtype: string
1173 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
1174 3386e7a9 Iustin Pop

1175 3386e7a9 Iustin Pop
  """
1176 e0ec0ff6 Iustin Pop
  if not isinstance (ts, (tuple, list)) or len(ts) != 2:
1177 e0ec0ff6 Iustin Pop
    return '?'
1178 3386e7a9 Iustin Pop
  sec, usec = ts
1179 3386e7a9 Iustin Pop
  return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
1180 2241e2b9 Iustin Pop
1181 2241e2b9 Iustin Pop
1182 2241e2b9 Iustin Pop
def ParseTimespec(value):
1183 2241e2b9 Iustin Pop
  """Parse a time specification.
1184 2241e2b9 Iustin Pop

1185 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
1186 2241e2b9 Iustin Pop

1187 2241e2b9 Iustin Pop
    - s: seconds
1188 2241e2b9 Iustin Pop
    - m: minutes
1189 2241e2b9 Iustin Pop
    - h: hours
1190 2241e2b9 Iustin Pop
    - d: day
1191 2241e2b9 Iustin Pop
    - w: weeks
1192 2241e2b9 Iustin Pop

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

1195 2241e2b9 Iustin Pop
  """
1196 2241e2b9 Iustin Pop
  value = str(value)
1197 2241e2b9 Iustin Pop
  if not value:
1198 2241e2b9 Iustin Pop
    raise errors.OpPrereqError("Empty time specification passed")
1199 2241e2b9 Iustin Pop
  suffix_map = {
1200 2241e2b9 Iustin Pop
    's': 1,
1201 2241e2b9 Iustin Pop
    'm': 60,
1202 2241e2b9 Iustin Pop
    'h': 3600,
1203 2241e2b9 Iustin Pop
    'd': 86400,
1204 2241e2b9 Iustin Pop
    'w': 604800,
1205 2241e2b9 Iustin Pop
    }
1206 2241e2b9 Iustin Pop
  if value[-1] not in suffix_map:
1207 2241e2b9 Iustin Pop
    try:
1208 2241e2b9 Iustin Pop
      value = int(value)
1209 2241e2b9 Iustin Pop
    except ValueError:
1210 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1211 2241e2b9 Iustin Pop
  else:
1212 2241e2b9 Iustin Pop
    multiplier = suffix_map[value[-1]]
1213 2241e2b9 Iustin Pop
    value = value[:-1]
1214 2241e2b9 Iustin Pop
    if not value: # no data left after stripping the suffix
1215 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification (only"
1216 2241e2b9 Iustin Pop
                                 " suffix passed)")
1217 2241e2b9 Iustin Pop
    try:
1218 2241e2b9 Iustin Pop
      value = int(value) * multiplier
1219 2241e2b9 Iustin Pop
    except ValueError:
1220 2241e2b9 Iustin Pop
      raise errors.OpPrereqError("Invalid time specification '%s'" % value)
1221 2241e2b9 Iustin Pop
  return value
1222 46fbdd04 Iustin Pop
1223 46fbdd04 Iustin Pop
1224 4040a784 Iustin Pop
def GetOnlineNodes(nodes, cl=None, nowarn=False):
1225 4040a784 Iustin Pop
  """Returns the names of online nodes.
1226 4040a784 Iustin Pop

1227 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
1228 4040a784 Iustin Pop
  the online nodes.
1229 4040a784 Iustin Pop

1230 4040a784 Iustin Pop
  @param nodes: if not empty, use only this subset of nodes (minus the
1231 4040a784 Iustin Pop
      offline ones)
1232 4040a784 Iustin Pop
  @param cl: if not None, luxi client to use
1233 4040a784 Iustin Pop
  @type nowarn: boolean
1234 4040a784 Iustin Pop
  @param nowarn: by default, this function will output a note with the
1235 4040a784 Iustin Pop
      offline nodes that are skipped; if this parameter is True the
1236 4040a784 Iustin Pop
      note is not displayed
1237 4040a784 Iustin Pop

1238 4040a784 Iustin Pop
  """
1239 4040a784 Iustin Pop
  if cl is None:
1240 4040a784 Iustin Pop
    cl = GetClient()
1241 4040a784 Iustin Pop
1242 2e7b8369 Iustin Pop
  result = cl.QueryNodes(names=nodes, fields=["name", "offline"],
1243 2e7b8369 Iustin Pop
                         use_locking=False)
1244 4040a784 Iustin Pop
  offline = [row[0] for row in result if row[1]]
1245 4040a784 Iustin Pop
  if offline and not nowarn:
1246 4040a784 Iustin Pop
    ToStderr("Note: skipping offline node(s): %s" % ", ".join(offline))
1247 4040a784 Iustin Pop
  return [row[0] for row in result if not row[1]]
1248 4040a784 Iustin Pop
1249 4040a784 Iustin Pop
1250 46fbdd04 Iustin Pop
def _ToStream(stream, txt, *args):
1251 46fbdd04 Iustin Pop
  """Write a message to a stream, bypassing the logging system
1252 46fbdd04 Iustin Pop

1253 46fbdd04 Iustin Pop
  @type stream: file object
1254 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
1255 46fbdd04 Iustin Pop
  @type txt: str
1256 46fbdd04 Iustin Pop
  @param txt: the message
1257 46fbdd04 Iustin Pop

1258 46fbdd04 Iustin Pop
  """
1259 46fbdd04 Iustin Pop
  if args:
1260 46fbdd04 Iustin Pop
    args = tuple(args)
1261 46fbdd04 Iustin Pop
    stream.write(txt % args)
1262 46fbdd04 Iustin Pop
  else:
1263 46fbdd04 Iustin Pop
    stream.write(txt)
1264 46fbdd04 Iustin Pop
  stream.write('\n')
1265 46fbdd04 Iustin Pop
  stream.flush()
1266 46fbdd04 Iustin Pop
1267 46fbdd04 Iustin Pop
1268 46fbdd04 Iustin Pop
def ToStdout(txt, *args):
1269 46fbdd04 Iustin Pop
  """Write a message to stdout only, bypassing the logging system
1270 46fbdd04 Iustin Pop

1271 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1272 46fbdd04 Iustin Pop

1273 46fbdd04 Iustin Pop
  @type txt: str
1274 46fbdd04 Iustin Pop
  @param txt: the message
1275 46fbdd04 Iustin Pop

1276 46fbdd04 Iustin Pop
  """
1277 46fbdd04 Iustin Pop
  _ToStream(sys.stdout, txt, *args)
1278 46fbdd04 Iustin Pop
1279 46fbdd04 Iustin Pop
1280 46fbdd04 Iustin Pop
def ToStderr(txt, *args):
1281 46fbdd04 Iustin Pop
  """Write a message to stderr only, bypassing the logging system
1282 46fbdd04 Iustin Pop

1283 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1284 46fbdd04 Iustin Pop

1285 46fbdd04 Iustin Pop
  @type txt: str
1286 46fbdd04 Iustin Pop
  @param txt: the message
1287 46fbdd04 Iustin Pop

1288 46fbdd04 Iustin Pop
  """
1289 46fbdd04 Iustin Pop
  _ToStream(sys.stderr, txt, *args)
1290 479636a3 Iustin Pop
1291 479636a3 Iustin Pop
1292 479636a3 Iustin Pop
class JobExecutor(object):
1293 479636a3 Iustin Pop
  """Class which manages the submission and execution of multiple jobs.
1294 479636a3 Iustin Pop

1295 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
1296 479636a3 Iustin Pop
  GetResults() calls.
1297 479636a3 Iustin Pop

1298 479636a3 Iustin Pop
  """
1299 479636a3 Iustin Pop
  def __init__(self, cl=None, verbose=True):
1300 479636a3 Iustin Pop
    self.queue = []
1301 479636a3 Iustin Pop
    if cl is None:
1302 479636a3 Iustin Pop
      cl = GetClient()
1303 479636a3 Iustin Pop
    self.cl = cl
1304 479636a3 Iustin Pop
    self.verbose = verbose
1305 23b4b983 Iustin Pop
    self.jobs = []
1306 479636a3 Iustin Pop
1307 479636a3 Iustin Pop
  def QueueJob(self, name, *ops):
1308 23b4b983 Iustin Pop
    """Record a job for later submit.
1309 479636a3 Iustin Pop

1310 479636a3 Iustin Pop
    @type name: string
1311 479636a3 Iustin Pop
    @param name: a description of the job, will be used in WaitJobSet
1312 479636a3 Iustin Pop
    """
1313 23b4b983 Iustin Pop
    self.queue.append((name, ops))
1314 23b4b983 Iustin Pop
1315 23b4b983 Iustin Pop
  def SubmitPending(self):
1316 23b4b983 Iustin Pop
    """Submit all pending jobs.
1317 23b4b983 Iustin Pop

1318 23b4b983 Iustin Pop
    """
1319 23b4b983 Iustin Pop
    results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
1320 23b4b983 Iustin Pop
    for ((status, data), (name, _)) in zip(results, self.queue):
1321 23b4b983 Iustin Pop
      self.jobs.append((status, data, name))
1322 479636a3 Iustin Pop
1323 479636a3 Iustin Pop
  def GetResults(self):
1324 479636a3 Iustin Pop
    """Wait for and return the results of all jobs.
1325 479636a3 Iustin Pop

1326 479636a3 Iustin Pop
    @rtype: list
1327 479636a3 Iustin Pop
    @return: list of tuples (success, job results), in the same order
1328 479636a3 Iustin Pop
        as the submitted jobs; if a job has failed, instead of the result
1329 479636a3 Iustin Pop
        there will be the error message
1330 479636a3 Iustin Pop

1331 479636a3 Iustin Pop
    """
1332 23b4b983 Iustin Pop
    if not self.jobs:
1333 23b4b983 Iustin Pop
      self.SubmitPending()
1334 479636a3 Iustin Pop
    results = []
1335 479636a3 Iustin Pop
    if self.verbose:
1336 23b4b983 Iustin Pop
      ok_jobs = [row[1] for row in self.jobs if row[0]]
1337 23b4b983 Iustin Pop
      if ok_jobs:
1338 23b4b983 Iustin Pop
        ToStdout("Submitted jobs %s", ", ".join(ok_jobs))
1339 23b4b983 Iustin Pop
    for submit_status, jid, name in self.jobs:
1340 23b4b983 Iustin Pop
      if not submit_status:
1341 23b4b983 Iustin Pop
        ToStderr("Failed to submit job for %s: %s", name, jid)
1342 23b4b983 Iustin Pop
        results.append((False, jid))
1343 23b4b983 Iustin Pop
        continue
1344 479636a3 Iustin Pop
      if self.verbose:
1345 479636a3 Iustin Pop
        ToStdout("Waiting for job %s for %s...", jid, name)
1346 479636a3 Iustin Pop
      try:
1347 479636a3 Iustin Pop
        job_result = PollJob(jid, cl=self.cl)
1348 479636a3 Iustin Pop
        success = True
1349 479636a3 Iustin Pop
      except (errors.GenericError, luxi.ProtocolError), err:
1350 479636a3 Iustin Pop
        _, job_result = FormatError(err)
1351 479636a3 Iustin Pop
        success = False
1352 479636a3 Iustin Pop
        # the error message will always be shown, verbose or not
1353 479636a3 Iustin Pop
        ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
1354 479636a3 Iustin Pop
1355 479636a3 Iustin Pop
      results.append((success, job_result))
1356 479636a3 Iustin Pop
    return results
1357 479636a3 Iustin Pop
1358 479636a3 Iustin Pop
  def WaitOrShow(self, wait):
1359 479636a3 Iustin Pop
    """Wait for job results or only print the job IDs.
1360 479636a3 Iustin Pop

1361 479636a3 Iustin Pop
    @type wait: boolean
1362 479636a3 Iustin Pop
    @param wait: whether to wait or not
1363 479636a3 Iustin Pop

1364 479636a3 Iustin Pop
    """
1365 479636a3 Iustin Pop
    if wait:
1366 479636a3 Iustin Pop
      return self.GetResults()
1367 479636a3 Iustin Pop
    else:
1368 23b4b983 Iustin Pop
      if not self.jobs:
1369 23b4b983 Iustin Pop
        self.SubmitPending()
1370 23b4b983 Iustin Pop
      for status, result, name in self.jobs:
1371 23b4b983 Iustin Pop
        if status:
1372 23b4b983 Iustin Pop
          ToStdout("%s: %s", result, name)
1373 23b4b983 Iustin Pop
        else:
1374 23b4b983 Iustin Pop
          ToStderr("Failure for %s: %s", name, result)