Statistics
| Branch: | Tag: | Revision:

root / lib / cli.py @ 7d3a9fab

History | View | Annotate | Download (39.7 kB)

1 2f31098c Iustin Pop
#
2 a8083063 Iustin Pop
#
3 a8083063 Iustin Pop
4 a8083063 Iustin Pop
# Copyright (C) 2006, 2007 Google Inc.
5 a8083063 Iustin Pop
#
6 a8083063 Iustin Pop
# This program is free software; you can redistribute it and/or modify
7 a8083063 Iustin Pop
# it under the terms of the GNU General Public License as published by
8 a8083063 Iustin Pop
# the Free Software Foundation; either version 2 of the License, or
9 a8083063 Iustin Pop
# (at your option) any later version.
10 a8083063 Iustin Pop
#
11 a8083063 Iustin Pop
# This program is distributed in the hope that it will be useful, but
12 a8083063 Iustin Pop
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 a8083063 Iustin Pop
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 a8083063 Iustin Pop
# General Public License for more details.
15 a8083063 Iustin Pop
#
16 a8083063 Iustin Pop
# You should have received a copy of the GNU General Public License
17 a8083063 Iustin Pop
# along with this program; if not, write to the Free Software
18 a8083063 Iustin Pop
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 a8083063 Iustin Pop
# 02110-1301, USA.
20 a8083063 Iustin Pop
21 a8083063 Iustin Pop
22 a8083063 Iustin Pop
"""Module dealing with command line parsing"""
23 a8083063 Iustin Pop
24 a8083063 Iustin Pop
25 a8083063 Iustin Pop
import sys
26 a8083063 Iustin Pop
import textwrap
27 a8083063 Iustin Pop
import os.path
28 a8083063 Iustin Pop
import copy
29 685ee993 Iustin Pop
import time
30 46fbdd04 Iustin Pop
import logging
31 73702ee7 Iustin Pop
from cStringIO import StringIO
32 a8083063 Iustin Pop
33 a8083063 Iustin Pop
from ganeti import utils
34 a8083063 Iustin Pop
from ganeti import errors
35 a8083063 Iustin Pop
from ganeti import constants
36 846baef9 Iustin Pop
from ganeti import opcodes
37 ceab32dd Iustin Pop
from ganeti import luxi
38 b33e986b Iustin Pop
from ganeti import ssconf
39 4331f6cd Michael Hanselmann
from ganeti import rpc
40 a8083063 Iustin Pop
41 c38c44ad Michael Hanselmann
from optparse import (OptionParser, TitledHelpFormatter,
42 38206f3c Iustin Pop
                      Option, OptionValueError)
43 a8083063 Iustin Pop
44 03298ebe Michael Hanselmann
45 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 4abc4f1e Iustin Pop
  "FIELDS_OPT",
53 4a25828c Iustin Pop
  "FILESTORE_DIR_OPT",
54 0f87c43e Iustin Pop
  "FILESTORE_DRIVER_OPT",
55 073271f6 Iustin Pop
  "HVLIST_OPT",
56 48f212d7 Iustin Pop
  "HVOPTS_OPT",
57 236fd9c4 Iustin Pop
  "HYPERVISOR_OPT",
58 4eb62659 Iustin Pop
  "IALLOCATOR_OPT",
59 4abc4f1e Iustin Pop
  "FORCE_OPT",
60 7d3a9fab Iustin Pop
  "NET_OPT",
61 4abc4f1e Iustin Pop
  "NOHDR_OPT",
62 91e0748c Iustin Pop
  "NOIPCHECK_OPT",
63 26023ecd Iustin Pop
  "NONICS_OPT",
64 3f75b4f3 Iustin Pop
  "NWSYNC_OPT",
65 d3ed23ff Iustin Pop
  "OS_OPT",
66 4abc4f1e Iustin Pop
  "SEP_OPT",
67 4abc4f1e Iustin Pop
  "SUBMIT_OPT",
68 4abc4f1e Iustin Pop
  "SYNC_OPT",
69 4abc4f1e Iustin Pop
  "TAG_SRC_OPT",
70 4abc4f1e Iustin Pop
  "USEUNITS_OPT",
71 9cdb9578 Iustin Pop
  "VERBOSE_OPT",
72 4abc4f1e Iustin Pop
  # Generic functions for CLI programs
73 4abc4f1e Iustin Pop
  "GenericMain",
74 4abc4f1e Iustin Pop
  "GetClient",
75 4abc4f1e Iustin Pop
  "GetOnlineNodes",
76 4abc4f1e Iustin Pop
  "JobExecutor",
77 4abc4f1e Iustin Pop
  "JobSubmittedException",
78 4abc4f1e Iustin Pop
  "ParseTimespec",
79 4abc4f1e Iustin Pop
  "SubmitOpCode",
80 4abc4f1e Iustin Pop
  "SubmitOrSend",
81 4abc4f1e Iustin Pop
  "UsesRPC",
82 4abc4f1e Iustin Pop
  # Formatting functions
83 4abc4f1e Iustin Pop
  "ToStderr", "ToStdout",
84 4abc4f1e Iustin Pop
  "FormatError",
85 4abc4f1e Iustin Pop
  "GenerateTable",
86 4abc4f1e Iustin Pop
  "AskUser",
87 4abc4f1e Iustin Pop
  "FormatTimestamp",
88 4abc4f1e Iustin Pop
  # Tags functions
89 4abc4f1e Iustin Pop
  "ListTags",
90 4abc4f1e Iustin Pop
  "AddTags",
91 4abc4f1e Iustin Pop
  "RemoveTags",
92 4abc4f1e Iustin Pop
  # command line options support infrastructure
93 4abc4f1e Iustin Pop
  "ARGS_MANY_INSTANCES",
94 4abc4f1e Iustin Pop
  "ARGS_MANY_NODES",
95 4abc4f1e Iustin Pop
  "ARGS_NONE",
96 4abc4f1e Iustin Pop
  "ARGS_ONE_INSTANCE",
97 4abc4f1e Iustin Pop
  "ARGS_ONE_NODE",
98 4abc4f1e Iustin Pop
  "ArgChoice",
99 4abc4f1e Iustin Pop
  "ArgCommand",
100 4abc4f1e Iustin Pop
  "ArgFile",
101 4abc4f1e Iustin Pop
  "ArgHost",
102 4abc4f1e Iustin Pop
  "ArgInstance",
103 4abc4f1e Iustin Pop
  "ArgJobId",
104 4abc4f1e Iustin Pop
  "ArgNode",
105 4abc4f1e Iustin Pop
  "ArgSuggest",
106 4abc4f1e Iustin Pop
  "ArgUnknown",
107 4abc4f1e Iustin Pop
  "OPT_COMPL_INST_ADD_NODES",
108 4abc4f1e Iustin Pop
  "OPT_COMPL_MANY_NODES",
109 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_IALLOCATOR",
110 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_INSTANCE",
111 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_NODE",
112 4abc4f1e Iustin Pop
  "OPT_COMPL_ONE_OS",
113 4abc4f1e Iustin Pop
  "cli_option",
114 4abc4f1e Iustin Pop
  "SplitNodeOption",
115 4abc4f1e Iustin Pop
  ]
116 846baef9 Iustin Pop
117 8b46606c Guido Trotter
NO_PREFIX = "no_"
118 8b46606c Guido Trotter
UN_PREFIX = "-"
119 846baef9 Iustin Pop
120 03298ebe Michael Hanselmann
121 863d7f46 Michael Hanselmann
class _Argument:
122 dff85078 Michael Hanselmann
  def __init__(self, min=0, max=None):
123 863d7f46 Michael Hanselmann
    self.min = min
124 863d7f46 Michael Hanselmann
    self.max = max
125 863d7f46 Michael Hanselmann
126 863d7f46 Michael Hanselmann
  def __repr__(self):
127 863d7f46 Michael Hanselmann
    return ("<%s min=%s max=%s>" %
128 863d7f46 Michael Hanselmann
            (self.__class__.__name__, self.min, self.max))
129 863d7f46 Michael Hanselmann
130 863d7f46 Michael Hanselmann
131 863d7f46 Michael Hanselmann
class ArgSuggest(_Argument):
132 863d7f46 Michael Hanselmann
  """Suggesting argument.
133 863d7f46 Michael Hanselmann

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

629 a8005e17 Michael Hanselmann
  Algorithm:
630 a8005e17 Michael Hanselmann

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

633 a8005e17 Michael Hanselmann
    1. For each argument in definition
634 a8005e17 Michael Hanselmann

635 a8005e17 Michael Hanselmann
      1. Keep running count of minimum number of values (min_count)
636 a8005e17 Michael Hanselmann
      1. Keep running count of maximum number of values (max_count)
637 a8005e17 Michael Hanselmann
      1. If it has an unlimited number of values
638 a8005e17 Michael Hanselmann

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

641 a8005e17 Michael Hanselmann
    1. If last argument has limited number of values
642 a8005e17 Michael Hanselmann

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

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

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

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

720 c41eea6e Iustin Pop
  @param text: the question to ask
721 a8083063 Iustin Pop

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

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

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

777 e9d741b6 Iustin Pop
  This exception has one argument, the ID of the job that was
778 e9d741b6 Iustin Pop
  submitted. The handler should print this ID.
779 e9d741b6 Iustin Pop

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

782 e9d741b6 Iustin Pop
  """
783 e9d741b6 Iustin Pop
784 e9d741b6 Iustin Pop
785 0a1e74d9 Iustin Pop
def SendJob(ops, cl=None):
786 0a1e74d9 Iustin Pop
  """Function to submit an opcode without waiting for the results.
787 a8083063 Iustin Pop

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

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

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

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

878 0a1e74d9 Iustin Pop
  This is just a simple wrapper over the construction of the processor
879 0a1e74d9 Iustin Pop
  instance. It should be extended to better handle feedback and
880 0a1e74d9 Iustin Pop
  interaction functions.
881 0a1e74d9 Iustin Pop

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

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

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

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

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

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

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

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

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

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

1165 3386e7a9 Iustin Pop
  @type ts: timestamp
1166 3386e7a9 Iustin Pop
  @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
1167 3386e7a9 Iustin Pop

1168 3386e7a9 Iustin Pop
  @rtype: string
1169 5fcc718f Iustin Pop
  @return: a string with the formatted timestamp
1170 3386e7a9 Iustin Pop

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

1181 2241e2b9 Iustin Pop
  The following suffixed will be recognized:
1182 2241e2b9 Iustin Pop

1183 2241e2b9 Iustin Pop
    - s: seconds
1184 2241e2b9 Iustin Pop
    - m: minutes
1185 2241e2b9 Iustin Pop
    - h: hours
1186 2241e2b9 Iustin Pop
    - d: day
1187 2241e2b9 Iustin Pop
    - w: weeks
1188 2241e2b9 Iustin Pop

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

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

1223 4040a784 Iustin Pop
  This function will also log a warning on stderr with the names of
1224 4040a784 Iustin Pop
  the online nodes.
1225 4040a784 Iustin Pop

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

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

1249 46fbdd04 Iustin Pop
  @type stream: file object
1250 46fbdd04 Iustin Pop
  @param stream: the file to which we should write
1251 46fbdd04 Iustin Pop
  @type txt: str
1252 46fbdd04 Iustin Pop
  @param txt: the message
1253 46fbdd04 Iustin Pop

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

1267 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1268 46fbdd04 Iustin Pop

1269 46fbdd04 Iustin Pop
  @type txt: str
1270 46fbdd04 Iustin Pop
  @param txt: the message
1271 46fbdd04 Iustin Pop

1272 46fbdd04 Iustin Pop
  """
1273 46fbdd04 Iustin Pop
  _ToStream(sys.stdout, txt, *args)
1274 46fbdd04 Iustin Pop
1275 46fbdd04 Iustin Pop
1276 46fbdd04 Iustin Pop
def ToStderr(txt, *args):
1277 46fbdd04 Iustin Pop
  """Write a message to stderr only, bypassing the logging system
1278 46fbdd04 Iustin Pop

1279 46fbdd04 Iustin Pop
  This is just a wrapper over _ToStream.
1280 46fbdd04 Iustin Pop

1281 46fbdd04 Iustin Pop
  @type txt: str
1282 46fbdd04 Iustin Pop
  @param txt: the message
1283 46fbdd04 Iustin Pop

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

1291 479636a3 Iustin Pop
  Note that instances of this class should not be reused between
1292 479636a3 Iustin Pop
  GetResults() calls.
1293 479636a3 Iustin Pop

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

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

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

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

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

1357 479636a3 Iustin Pop
    @type wait: boolean
1358 479636a3 Iustin Pop
    @param wait: whether to wait or not
1359 479636a3 Iustin Pop

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