Package ganeti :: Module cli
[hide private]
[frames] | no frames]

Source Code for Module ganeti.cli

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc. 
   5  # 
   6  # This program is free software; you can redistribute it and/or modify 
   7  # it under the terms of the GNU General Public License as published by 
   8  # the Free Software Foundation; either version 2 of the License, or 
   9  # (at your option) any later version. 
  10  # 
  11  # This program is distributed in the hope that it will be useful, but 
  12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
  14  # General Public License for more details. 
  15  # 
  16  # You should have received a copy of the GNU General Public License 
  17  # along with this program; if not, write to the Free Software 
  18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
  19  # 02110-1301, USA. 
  20   
  21   
  22  """Module dealing with command line parsing""" 
  23   
  24   
  25  import sys 
  26  import textwrap 
  27  import os.path 
  28  import time 
  29  import logging 
  30  import errno 
  31  import itertools 
  32  from cStringIO import StringIO 
  33   
  34  from ganeti import utils 
  35  from ganeti import errors 
  36  from ganeti import constants 
  37  from ganeti import opcodes 
  38  from ganeti import luxi 
  39  from ganeti import ssconf 
  40  from ganeti import rpc 
  41  from ganeti import ssh 
  42  from ganeti import compat 
  43  from ganeti import netutils 
  44  from ganeti import qlang 
  45   
  46  from optparse import (OptionParser, TitledHelpFormatter, 
  47                        Option, OptionValueError) 
  48   
  49   
  50  __all__ = [ 
  51    # Command line options 
  52    "ADD_UIDS_OPT", 
  53    "ALLOCATABLE_OPT", 
  54    "ALLOC_POLICY_OPT", 
  55    "ALL_OPT", 
  56    "ALLOW_FAILOVER_OPT", 
  57    "AUTO_PROMOTE_OPT", 
  58    "AUTO_REPLACE_OPT", 
  59    "BACKEND_OPT", 
  60    "BLK_OS_OPT", 
  61    "CAPAB_MASTER_OPT", 
  62    "CAPAB_VM_OPT", 
  63    "CLEANUP_OPT", 
  64    "CLUSTER_DOMAIN_SECRET_OPT", 
  65    "CONFIRM_OPT", 
  66    "CP_SIZE_OPT", 
  67    "DEBUG_OPT", 
  68    "DEBUG_SIMERR_OPT", 
  69    "DISKIDX_OPT", 
  70    "DISK_OPT", 
  71    "DISK_TEMPLATE_OPT", 
  72    "DRAINED_OPT", 
  73    "DRY_RUN_OPT", 
  74    "DRBD_HELPER_OPT", 
  75    "DST_NODE_OPT", 
  76    "EARLY_RELEASE_OPT", 
  77    "ENABLED_HV_OPT", 
  78    "ERROR_CODES_OPT", 
  79    "FIELDS_OPT", 
  80    "FILESTORE_DIR_OPT", 
  81    "FILESTORE_DRIVER_OPT", 
  82    "FORCE_FILTER_OPT", 
  83    "FORCE_OPT", 
  84    "FORCE_VARIANT_OPT", 
  85    "GLOBAL_FILEDIR_OPT", 
  86    "HID_OS_OPT", 
  87    "GLOBAL_SHARED_FILEDIR_OPT", 
  88    "HVLIST_OPT", 
  89    "HVOPTS_OPT", 
  90    "HYPERVISOR_OPT", 
  91    "IALLOCATOR_OPT", 
  92    "DEFAULT_IALLOCATOR_OPT", 
  93    "IDENTIFY_DEFAULTS_OPT", 
  94    "IGNORE_CONSIST_OPT", 
  95    "IGNORE_FAILURES_OPT", 
  96    "IGNORE_OFFLINE_OPT", 
  97    "IGNORE_REMOVE_FAILURES_OPT", 
  98    "IGNORE_SECONDARIES_OPT", 
  99    "IGNORE_SIZE_OPT", 
 100    "INTERVAL_OPT", 
 101    "MAC_PREFIX_OPT", 
 102    "MAINTAIN_NODE_HEALTH_OPT", 
 103    "MASTER_NETDEV_OPT", 
 104    "MC_OPT", 
 105    "MIGRATION_MODE_OPT", 
 106    "NET_OPT", 
 107    "NEW_CLUSTER_CERT_OPT", 
 108    "NEW_CLUSTER_DOMAIN_SECRET_OPT", 
 109    "NEW_CONFD_HMAC_KEY_OPT", 
 110    "NEW_RAPI_CERT_OPT", 
 111    "NEW_SECONDARY_OPT", 
 112    "NIC_PARAMS_OPT", 
 113    "NODE_FORCE_JOIN_OPT", 
 114    "NODE_LIST_OPT", 
 115    "NODE_PLACEMENT_OPT", 
 116    "NODEGROUP_OPT", 
 117    "NODE_PARAMS_OPT", 
 118    "NODE_POWERED_OPT", 
 119    "NODRBD_STORAGE_OPT", 
 120    "NOHDR_OPT", 
 121    "NOIPCHECK_OPT", 
 122    "NO_INSTALL_OPT", 
 123    "NONAMECHECK_OPT", 
 124    "NOLVM_STORAGE_OPT", 
 125    "NOMODIFY_ETCHOSTS_OPT", 
 126    "NOMODIFY_SSH_SETUP_OPT", 
 127    "NONICS_OPT", 
 128    "NONLIVE_OPT", 
 129    "NONPLUS1_OPT", 
 130    "NOSHUTDOWN_OPT", 
 131    "NOSTART_OPT", 
 132    "NOSSH_KEYCHECK_OPT", 
 133    "NOVOTING_OPT", 
 134    "NO_REMEMBER_OPT", 
 135    "NWSYNC_OPT", 
 136    "ON_PRIMARY_OPT", 
 137    "ON_SECONDARY_OPT", 
 138    "OFFLINE_OPT", 
 139    "OSPARAMS_OPT", 
 140    "OS_OPT", 
 141    "OS_SIZE_OPT", 
 142    "OOB_TIMEOUT_OPT", 
 143    "POWER_DELAY_OPT", 
 144    "PREALLOC_WIPE_DISKS_OPT", 
 145    "PRIMARY_IP_VERSION_OPT", 
 146    "PRIMARY_ONLY_OPT", 
 147    "PRIORITY_OPT", 
 148    "RAPI_CERT_OPT", 
 149    "READD_OPT", 
 150    "REBOOT_TYPE_OPT", 
 151    "REMOVE_INSTANCE_OPT", 
 152    "REMOVE_UIDS_OPT", 
 153    "RESERVED_LVS_OPT", 
 154    "ROMAN_OPT", 
 155    "SECONDARY_IP_OPT", 
 156    "SECONDARY_ONLY_OPT", 
 157    "SELECT_OS_OPT", 
 158    "SEP_OPT", 
 159    "SHOWCMD_OPT", 
 160    "SHUTDOWN_TIMEOUT_OPT", 
 161    "SINGLE_NODE_OPT", 
 162    "SRC_DIR_OPT", 
 163    "SRC_NODE_OPT", 
 164    "SUBMIT_OPT", 
 165    "STARTUP_PAUSED_OPT", 
 166    "STATIC_OPT", 
 167    "SYNC_OPT", 
 168    "TAG_ADD_OPT", 
 169    "TAG_SRC_OPT", 
 170    "TIMEOUT_OPT", 
 171    "TO_GROUP_OPT", 
 172    "UIDPOOL_OPT", 
 173    "USEUNITS_OPT", 
 174    "USE_REPL_NET_OPT", 
 175    "VERBOSE_OPT", 
 176    "VG_NAME_OPT", 
 177    "YES_DOIT_OPT", 
 178    # Generic functions for CLI programs 
 179    "ConfirmOperation", 
 180    "GenericMain", 
 181    "GenericInstanceCreate", 
 182    "GenericList", 
 183    "GenericListFields", 
 184    "GetClient", 
 185    "GetOnlineNodes", 
 186    "JobExecutor", 
 187    "JobSubmittedException", 
 188    "ParseTimespec", 
 189    "RunWhileClusterStopped", 
 190    "SubmitOpCode", 
 191    "SubmitOrSend", 
 192    "UsesRPC", 
 193    # Formatting functions 
 194    "ToStderr", "ToStdout", 
 195    "FormatError", 
 196    "FormatQueryResult", 
 197    "FormatParameterDict", 
 198    "GenerateTable", 
 199    "AskUser", 
 200    "FormatTimestamp", 
 201    "FormatLogMessage", 
 202    # Tags functions 
 203    "ListTags", 
 204    "AddTags", 
 205    "RemoveTags", 
 206    # command line options support infrastructure 
 207    "ARGS_MANY_INSTANCES", 
 208    "ARGS_MANY_NODES", 
 209    "ARGS_MANY_GROUPS", 
 210    "ARGS_NONE", 
 211    "ARGS_ONE_INSTANCE", 
 212    "ARGS_ONE_NODE", 
 213    "ARGS_ONE_GROUP", 
 214    "ARGS_ONE_OS", 
 215    "ArgChoice", 
 216    "ArgCommand", 
 217    "ArgFile", 
 218    "ArgGroup", 
 219    "ArgHost", 
 220    "ArgInstance", 
 221    "ArgJobId", 
 222    "ArgNode", 
 223    "ArgOs", 
 224    "ArgSuggest", 
 225    "ArgUnknown", 
 226    "OPT_COMPL_INST_ADD_NODES", 
 227    "OPT_COMPL_MANY_NODES", 
 228    "OPT_COMPL_ONE_IALLOCATOR", 
 229    "OPT_COMPL_ONE_INSTANCE", 
 230    "OPT_COMPL_ONE_NODE", 
 231    "OPT_COMPL_ONE_NODEGROUP", 
 232    "OPT_COMPL_ONE_OS", 
 233    "cli_option", 
 234    "SplitNodeOption", 
 235    "CalculateOSNames", 
 236    "ParseFields", 
 237    "COMMON_CREATE_OPTS", 
 238    ] 
 239   
 240  NO_PREFIX = "no_" 
 241  UN_PREFIX = "-" 
 242   
 243  #: Priorities (sorted) 
 244  _PRIORITY_NAMES = [ 
 245    ("low", constants.OP_PRIO_LOW), 
 246    ("normal", constants.OP_PRIO_NORMAL), 
 247    ("high", constants.OP_PRIO_HIGH), 
 248    ] 
 249   
 250  #: Priority dictionary for easier lookup 
 251  # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once 
 252  # we migrate to Python 2.6 
 253  _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES) 
 254   
 255  # Query result status for clients 
 256  (QR_NORMAL, 
 257   QR_UNKNOWN, 
 258   QR_INCOMPLETE) = range(3) 
 259   
 260  #: Maximum batch size for ChooseJob 
 261  _CHOOSE_BATCH = 25 
262 263 264 -class _Argument:
265 - def __init__(self, min=0, max=None): # pylint: disable=W0622
266 self.min = min 267 self.max = max
268
269 - def __repr__(self):
270 return ("<%s min=%s max=%s>" % 271 (self.__class__.__name__, self.min, self.max))
272
273 274 -class ArgSuggest(_Argument):
275 """Suggesting argument. 276 277 Value can be any of the ones passed to the constructor. 278 279 """ 280 # pylint: disable=W0622
281 - def __init__(self, min=0, max=None, choices=None):
282 _Argument.__init__(self, min=min, max=max) 283 self.choices = choices
284
285 - def __repr__(self):
286 return ("<%s min=%s max=%s choices=%r>" % 287 (self.__class__.__name__, self.min, self.max, self.choices))
288
289 290 -class ArgChoice(ArgSuggest):
291 """Choice argument. 292 293 Value can be any of the ones passed to the constructor. Like L{ArgSuggest}, 294 but value must be one of the choices. 295 296 """
297
298 299 -class ArgUnknown(_Argument):
300 """Unknown argument to program (e.g. determined at runtime). 301 302 """
303
304 305 -class ArgInstance(_Argument):
306 """Instances argument. 307 308 """
309
310 311 -class ArgNode(_Argument):
312 """Node argument. 313 314 """
315
316 317 -class ArgGroup(_Argument):
318 """Node group argument. 319 320 """
321
322 323 -class ArgJobId(_Argument):
324 """Job ID argument. 325 326 """
327
328 329 -class ArgFile(_Argument):
330 """File path argument. 331 332 """
333
334 335 -class ArgCommand(_Argument):
336 """Command argument. 337 338 """
339
340 341 -class ArgHost(_Argument):
342 """Host argument. 343 344 """
345
346 347 -class ArgOs(_Argument):
348 """OS argument. 349 350 """
351 352 353 ARGS_NONE = [] 354 ARGS_MANY_INSTANCES = [ArgInstance()] 355 ARGS_MANY_NODES = [ArgNode()] 356 ARGS_MANY_GROUPS = [ArgGroup()] 357 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)] 358 ARGS_ONE_NODE = [ArgNode(min=1, max=1)] 359 # TODO 360 ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)] 361 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
362 363 364 -def _ExtractTagsObject(opts, args):
365 """Extract the tag type object. 366 367 Note that this function will modify its args parameter. 368 369 """ 370 if not hasattr(opts, "tag_type"): 371 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject") 372 kind = opts.tag_type 373 if kind == constants.TAG_CLUSTER: 374 retval = kind, kind 375 elif kind in (constants.TAG_NODEGROUP, 376 constants.TAG_NODE, 377 constants.TAG_INSTANCE): 378 if not args: 379 raise errors.OpPrereqError("no arguments passed to the command") 380 name = args.pop(0) 381 retval = kind, name 382 else: 383 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind) 384 return retval
385
386 387 -def _ExtendTags(opts, args):
388 """Extend the args if a source file has been given. 389 390 This function will extend the tags with the contents of the file 391 passed in the 'tags_source' attribute of the opts parameter. A file 392 named '-' will be replaced by stdin. 393 394 """ 395 fname = opts.tags_source 396 if fname is None: 397 return 398 if fname == "-": 399 new_fh = sys.stdin 400 else: 401 new_fh = open(fname, "r") 402 new_data = [] 403 try: 404 # we don't use the nice 'new_data = [line.strip() for line in fh]' 405 # because of python bug 1633941 406 while True: 407 line = new_fh.readline() 408 if not line: 409 break 410 new_data.append(line.strip()) 411 finally: 412 new_fh.close() 413 args.extend(new_data)
414
415 416 -def ListTags(opts, args):
417 """List the tags on a given object. 418 419 This is a generic implementation that knows how to deal with all 420 three cases of tag objects (cluster, node, instance). The opts 421 argument is expected to contain a tag_type field denoting what 422 object type we work on. 423 424 """ 425 kind, name = _ExtractTagsObject(opts, args) 426 cl = GetClient() 427 result = cl.QueryTags(kind, name) 428 result = list(result) 429 result.sort() 430 for tag in result: 431 ToStdout(tag)
432
433 434 -def AddTags(opts, args):
435 """Add tags on a given object. 436 437 This is a generic implementation that knows how to deal with all 438 three cases of tag objects (cluster, node, instance). The opts 439 argument is expected to contain a tag_type field denoting what 440 object type we work on. 441 442 """ 443 kind, name = _ExtractTagsObject(opts, args) 444 _ExtendTags(opts, args) 445 if not args: 446 raise errors.OpPrereqError("No tags to be added") 447 op = opcodes.OpTagsSet(kind=kind, name=name, tags=args) 448 SubmitOpCode(op, opts=opts)
449
450 451 -def RemoveTags(opts, args):
452 """Remove tags from a given object. 453 454 This is a generic implementation that knows how to deal with all 455 three cases of tag objects (cluster, node, instance). The opts 456 argument is expected to contain a tag_type field denoting what 457 object type we work on. 458 459 """ 460 kind, name = _ExtractTagsObject(opts, args) 461 _ExtendTags(opts, args) 462 if not args: 463 raise errors.OpPrereqError("No tags to be removed") 464 op = opcodes.OpTagsDel(kind=kind, name=name, tags=args) 465 SubmitOpCode(op, opts=opts)
466
467 468 -def check_unit(option, opt, value): # pylint: disable=W0613
469 """OptParsers custom converter for units. 470 471 """ 472 try: 473 return utils.ParseUnit(value) 474 except errors.UnitParseError, err: 475 raise OptionValueError("option %s: %s" % (opt, err)) 476
477 478 -def _SplitKeyVal(opt, data):
479 """Convert a KeyVal string into a dict. 480 481 This function will convert a key=val[,...] string into a dict. Empty 482 values will be converted specially: keys which have the prefix 'no_' 483 will have the value=False and the prefix stripped, the others will 484 have value=True. 485 486 @type opt: string 487 @param opt: a string holding the option name for which we process the 488 data, used in building error messages 489 @type data: string 490 @param data: a string of the format key=val,key=val,... 491 @rtype: dict 492 @return: {key=val, key=val} 493 @raises errors.ParameterError: if there are duplicate keys 494 495 """ 496 kv_dict = {} 497 if data: 498 for elem in utils.UnescapeAndSplit(data, sep=","): 499 if "=" in elem: 500 key, val = elem.split("=", 1) 501 else: 502 if elem.startswith(NO_PREFIX): 503 key, val = elem[len(NO_PREFIX):], False 504 elif elem.startswith(UN_PREFIX): 505 key, val = elem[len(UN_PREFIX):], None 506 else: 507 key, val = elem, True 508 if key in kv_dict: 509 raise errors.ParameterError("Duplicate key '%s' in option %s" % 510 (key, opt)) 511 kv_dict[key] = val 512 return kv_dict
513
514 515 -def check_ident_key_val(option, opt, value): # pylint: disable=W0613
516 """Custom parser for ident:key=val,key=val options. 517 518 This will store the parsed values as a tuple (ident, {key: val}). As such, 519 multiple uses of this option via action=append is possible. 520 521 """ 522 if ":" not in value: 523 ident, rest = value, "" 524 else: 525 ident, rest = value.split(":", 1) 526 527 if ident.startswith(NO_PREFIX): 528 if rest: 529 msg = "Cannot pass options when removing parameter groups: %s" % value 530 raise errors.ParameterError(msg) 531 retval = (ident[len(NO_PREFIX):], False) 532 elif ident.startswith(UN_PREFIX): 533 if rest: 534 msg = "Cannot pass options when removing parameter groups: %s" % value 535 raise errors.ParameterError(msg) 536 retval = (ident[len(UN_PREFIX):], None) 537 else: 538 kv_dict = _SplitKeyVal(opt, rest) 539 retval = (ident, kv_dict) 540 return retval 541
542 543 -def check_key_val(option, opt, value): # pylint: disable=W0613
544 """Custom parser class for key=val,key=val options. 545 546 This will store the parsed values as a dict {key: val}. 547 548 """ 549 return _SplitKeyVal(opt, value) 550
551 552 -def check_bool(option, opt, value): # pylint: disable=W0613
553 """Custom parser for yes/no options. 554 555 This will store the parsed value as either True or False. 556 557 """ 558 value = value.lower() 559 if value == constants.VALUE_FALSE or value == "no": 560 return False 561 elif value == constants.VALUE_TRUE or value == "yes": 562 return True 563 else: 564 raise errors.ParameterError("Invalid boolean value '%s'" % value) 565 566 567 # completion_suggestion is normally a list. Using numeric values not evaluating 568 # to False for dynamic completion. 569 (OPT_COMPL_MANY_NODES, 570 OPT_COMPL_ONE_NODE, 571 OPT_COMPL_ONE_INSTANCE, 572 OPT_COMPL_ONE_OS, 573 OPT_COMPL_ONE_IALLOCATOR, 574 OPT_COMPL_INST_ADD_NODES, 575 OPT_COMPL_ONE_NODEGROUP) = range(100, 107) 576 577 OPT_COMPL_ALL = frozenset([ 578 OPT_COMPL_MANY_NODES, 579 OPT_COMPL_ONE_NODE, 580 OPT_COMPL_ONE_INSTANCE, 581 OPT_COMPL_ONE_OS, 582 OPT_COMPL_ONE_IALLOCATOR, 583 OPT_COMPL_INST_ADD_NODES, 584 OPT_COMPL_ONE_NODEGROUP, 585 ])
586 587 588 -class CliOption(Option):
589 """Custom option class for optparse. 590 591 """ 592 ATTRS = Option.ATTRS + [ 593 "completion_suggest", 594 ] 595 TYPES = Option.TYPES + ( 596 "identkeyval", 597 "keyval", 598 "unit", 599 "bool", 600 ) 601 TYPE_CHECKER = Option.TYPE_CHECKER.copy() 602 TYPE_CHECKER["identkeyval"] = check_ident_key_val 603 TYPE_CHECKER["keyval"] = check_key_val 604 TYPE_CHECKER["unit"] = check_unit 605 TYPE_CHECKER["bool"] = check_bool
606 607 608 # optparse.py sets make_option, so we do it for our own option class, too 609 cli_option = CliOption 610 611 612 _YORNO = "yes|no" 613 614 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count", 615 help="Increase debugging level") 616 617 NOHDR_OPT = cli_option("--no-headers", default=False, 618 action="store_true", dest="no_headers", 619 help="Don't display column headers") 620 621 SEP_OPT = cli_option("--separator", default=None, 622 action="store", dest="separator", 623 help=("Separator between output fields" 624 " (defaults to one space)")) 625 626 USEUNITS_OPT = cli_option("--units", default=None, 627 dest="units", choices=("h", "m", "g", "t"), 628 help="Specify units for output (one of h/m/g/t)") 629 630 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store", 631 type="string", metavar="FIELDS", 632 help="Comma separated list of output fields") 633 634 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true", 635 default=False, help="Force the operation") 636 637 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true", 638 default=False, help="Do not require confirmation") 639 640 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline", 641 action="store_true", default=False, 642 help=("Ignore offline nodes and do as much" 643 " as possible")) 644 645 TAG_ADD_OPT = cli_option("--tags", dest="tags", 646 default=None, help="Comma-separated list of instance" 647 " tags") 648 649 TAG_SRC_OPT = cli_option("--from", dest="tags_source", 650 default=None, help="File with tag names") 651 652 SUBMIT_OPT = cli_option("--submit", dest="submit_only", 653 default=False, action="store_true", 654 help=("Submit the job and return the job ID, but" 655 " don't wait for the job to finish")) 656 657 SYNC_OPT = cli_option("--sync", dest="do_locking", 658 default=False, action="store_true", 659 help=("Grab locks while doing the queries" 660 " in order to ensure more consistent results")) 661 662 DRY_RUN_OPT = cli_option("--dry-run", default=False, 663 action="store_true", 664 help=("Do not execute the operation, just run the" 665 " check steps and verify it it could be" 666 " executed")) 667 668 VERBOSE_OPT = cli_option("-v", "--verbose", default=False, 669 action="store_true", 670 help="Increase the verbosity of the operation") 671 672 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False, 673 action="store_true", dest="simulate_errors", 674 help="Debugging option that makes the operation" 675 " treat most runtime checks as failed") 676 677 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync", 678 default=True, action="store_false", 679 help="Don't wait for sync (DANGEROUS!)") 680 681 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template", 682 help=("Custom disk setup (%s)" % 683 utils.CommaJoin(constants.DISK_TEMPLATES)), 684 default=None, metavar="TEMPL", 685 choices=list(constants.DISK_TEMPLATES)) 686 687 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true", 688 help="Do not create any network cards for" 689 " the instance") 690 691 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir", 692 help="Relative path under default cluster-wide" 693 " file storage dir to store file-based disks", 694 default=None, metavar="<DIR>") 695 696 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver", 697 help="Driver to use for image files", 698 default="loop", metavar="<DRIVER>", 699 choices=list(constants.FILE_DRIVER)) 700 701 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>", 702 help="Select nodes for the instance automatically" 703 " using the <NAME> iallocator plugin", 704 default=None, type="string", 705 completion_suggest=OPT_COMPL_ONE_IALLOCATOR) 706 707 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator", 708 metavar="<NAME>", 709 help="Set the default instance allocator plugin", 710 default=None, type="string", 711 completion_suggest=OPT_COMPL_ONE_IALLOCATOR) 712 713 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run", 714 metavar="<os>", 715 completion_suggest=OPT_COMPL_ONE_OS) 716 717 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams", 718 type="keyval", default={}, 719 help="OS parameters") 720 721 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant", 722 action="store_true", default=False, 723 help="Force an unknown variant") 724 725 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install", 726 action="store_true", default=False, 727 help="Do not install the OS (will" 728 " enable no-start)") 729 730 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams", 731 type="keyval", default={}, 732 help="Backend parameters") 733 734 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval", 735 default={}, dest="hvparams", 736 help="Hypervisor parameters") 737 738 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor", 739 help="Hypervisor and hypervisor options, in the" 740 " format hypervisor:option=value,option=value,...", 741 default=None, type="identkeyval") 742 743 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams", 744 help="Hypervisor and hypervisor options, in the" 745 " format hypervisor:option=value,option=value,...", 746 default=[], action="append", type="identkeyval") 747 748 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True, 749 action="store_false", 750 help="Don't check that the instance's IP" 751 " is alive") 752 753 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check", 754 default=True, action="store_false", 755 help="Don't check that the instance's name" 756 " is resolvable") 757 758 NET_OPT = cli_option("--net", 759 help="NIC parameters", default=[], 760 dest="nics", action="append", type="identkeyval") 761 762 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[], 763 dest="disks", action="append", type="identkeyval") 764 765 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None, 766 help="Comma-separated list of disks" 767 " indices to act on (e.g. 0,2) (optional," 768 " defaults to all disks)") 769 770 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size", 771 help="Enforces a single-disk configuration using the" 772 " given disk size, in MiB unless a suffix is used", 773 default=None, type="unit", metavar="<size>") 774 775 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency", 776 dest="ignore_consistency", 777 action="store_true", default=False, 778 help="Ignore the consistency of the disks on" 779 " the secondary") 780 781 ALLOW_FAILOVER_OPT = cli_option("--allow-failover", 782 dest="allow_failover", 783 action="store_true", default=False, 784 help="If migration is not possible fallback to" 785 " failover") 786 787 NONLIVE_OPT = cli_option("--non-live", dest="live", 788 default=True, action="store_false", 789 help="Do a non-live migration (this usually means" 790 " freeze the instance, save the state, transfer and" 791 " only then resume running on the secondary node)") 792 793 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode", 794 default=None, 795 choices=list(constants.HT_MIGRATION_MODES), 796 help="Override default migration mode (choose" 797 " either live or non-live") 798 799 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node", 800 help="Target node and optional secondary node", 801 metavar="<pnode>[:<snode>]", 802 completion_suggest=OPT_COMPL_INST_ADD_NODES) 803 804 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[], 805 action="append", metavar="<node>", 806 help="Use only this node (can be used multiple" 807 " times, if not given defaults to all nodes)", 808 completion_suggest=OPT_COMPL_ONE_NODE) 809 810 NODEGROUP_OPT_NAME = "--node-group" 811 NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME, 812 dest="nodegroup", 813 help="Node group (name or uuid)", 814 metavar="<nodegroup>", 815 default=None, type="string", 816 completion_suggest=OPT_COMPL_ONE_NODEGROUP) 817 818 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node", 819 metavar="<node>", 820 completion_suggest=OPT_COMPL_ONE_NODE) 821 822 NOSTART_OPT = cli_option("--no-start", dest="start", default=True, 823 action="store_false", 824 help="Don't start the instance after creation") 825 826 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command", 827 action="store_true", default=False, 828 help="Show command instead of executing it") 829 830 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup", 831 default=False, action="store_true", 832 help="Instead of performing the migration, try to" 833 " recover from a failed cleanup. This is safe" 834 " to run even if the instance is healthy, but it" 835 " will create extra replication traffic and " 836 " disrupt briefly the replication (like during the" 837 " migration") 838 839 STATIC_OPT = cli_option("-s", "--static", dest="static", 840 action="store_true", default=False, 841 help="Only show configuration data, not runtime data") 842 843 ALL_OPT = cli_option("--all", dest="show_all", 844 default=False, action="store_true", 845 help="Show info on all instances on the cluster." 846 " This can take a long time to run, use wisely") 847 848 SELECT_OS_OPT = cli_option("--select-os", dest="select_os", 849 action="store_true", default=False, 850 help="Interactive OS reinstall, lists available" 851 " OS templates for selection") 852 853 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures", 854 action="store_true", default=False, 855 help="Remove the instance from the cluster" 856 " configuration even if there are failures" 857 " during the removal process") 858 859 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures", 860 dest="ignore_remove_failures", 861 action="store_true", default=False, 862 help="Remove the instance from the" 863 " cluster configuration even if there" 864 " are failures during the removal" 865 " process") 866 867 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance", 868 action="store_true", default=False, 869 help="Remove the instance from the cluster") 870 871 DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node", 872 help="Specifies the new node for the instance", 873 metavar="NODE", default=None, 874 completion_suggest=OPT_COMPL_ONE_NODE) 875 876 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node", 877 help="Specifies the new secondary node", 878 metavar="NODE", default=None, 879 completion_suggest=OPT_COMPL_ONE_NODE) 880 881 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary", 882 default=False, action="store_true", 883 help="Replace the disk(s) on the primary" 884 " node (applies only to internally mirrored" 885 " disk templates, e.g. %s)" % 886 utils.CommaJoin(constants.DTS_INT_MIRROR)) 887 888 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary", 889 default=False, action="store_true", 890 help="Replace the disk(s) on the secondary" 891 " node (applies only to internally mirrored" 892 " disk templates, e.g. %s)" % 893 utils.CommaJoin(constants.DTS_INT_MIRROR)) 894 895 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote", 896 default=False, action="store_true", 897 help="Lock all nodes and auto-promote as needed" 898 " to MC status") 899 900 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto", 901 default=False, action="store_true", 902 help="Automatically replace faulty disks" 903 " (applies only to internally mirrored" 904 " disk templates, e.g. %s)" % 905 utils.CommaJoin(constants.DTS_INT_MIRROR)) 906 907 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size", 908 default=False, action="store_true", 909 help="Ignore current recorded size" 910 " (useful for forcing activation when" 911 " the recorded size is wrong)") 912 913 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node", 914 metavar="<node>", 915 completion_suggest=OPT_COMPL_ONE_NODE) 916 917 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory", 918 metavar="<dir>") 919 920 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip", 921 help="Specify the secondary ip for the node", 922 metavar="ADDRESS", default=None) 923 924 READD_OPT = cli_option("--readd", dest="readd", 925 default=False, action="store_true", 926 help="Readd old node after replacing it") 927 928 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check", 929 default=True, action="store_false", 930 help="Disable SSH key fingerprint checking") 931 932 NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join", 933 default=False, action="store_true", 934 help="Force the joining of a node") 935 936 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate", 937 type="bool", default=None, metavar=_YORNO, 938 help="Set the master_candidate flag on the node") 939 940 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO, 941 type="bool", default=None, 942 help=("Set the offline flag on the node" 943 " (cluster does not communicate with offline" 944 " nodes)")) 945 946 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO, 947 type="bool", default=None, 948 help=("Set the drained flag on the node" 949 " (excluded from allocation operations)")) 950 951 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable", 952 type="bool", default=None, metavar=_YORNO, 953 help="Set the master_capable flag on the node") 954 955 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable", 956 type="bool", default=None, metavar=_YORNO, 957 help="Set the vm_capable flag on the node") 958 959 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable", 960 type="bool", default=None, metavar=_YORNO, 961 help="Set the allocatable flag on a volume") 962 963 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage", 964 help="Disable support for lvm based instances" 965 " (cluster-wide)", 966 action="store_false", default=True) 967 968 ENABLED_HV_OPT = cli_option("--enabled-hypervisors", 969 dest="enabled_hypervisors", 970 help="Comma-separated list of hypervisors", 971 type="string", default=None) 972 973 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams", 974 type="keyval", default={}, 975 help="NIC parameters") 976 977 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None, 978 dest="candidate_pool_size", type="int", 979 help="Set the candidate pool size") 980 981 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name", 982 help=("Enables LVM and specifies the volume group" 983 " name (cluster-wide) for disk allocation" 984 " [%s]" % constants.DEFAULT_VG), 985 metavar="VG", default=None) 986 987 YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it", 988 help="Destroy cluster", action="store_true") 989 990 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting", 991 help="Skip node agreement check (dangerous)", 992 action="store_true", default=False) 993 994 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix", 995 help="Specify the mac prefix for the instance IP" 996 " addresses, in the format XX:XX:XX", 997 metavar="PREFIX", 998 default=None) 999 1000 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev", 1001 help="Specify the node interface (cluster-wide)" 1002 " on which the master IP address will be added" 1003 " (cluster init default: %s)" % 1004 constants.DEFAULT_BRIDGE, 1005 metavar="NETDEV", 1006 default=None) 1007 1008 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir", 1009 help="Specify the default directory (cluster-" 1010 "wide) for storing the file-based disks [%s]" % 1011 constants.DEFAULT_FILE_STORAGE_DIR, 1012 metavar="DIR", 1013 default=constants.DEFAULT_FILE_STORAGE_DIR) 1014 1015 GLOBAL_SHARED_FILEDIR_OPT = cli_option("--shared-file-storage-dir", 1016 dest="shared_file_storage_dir", 1017 help="Specify the default directory (cluster-" 1018 "wide) for storing the shared file-based" 1019 " disks [%s]" % 1020 constants.DEFAULT_SHARED_FILE_STORAGE_DIR, 1021 metavar="SHAREDDIR", 1022 default=constants.DEFAULT_SHARED_FILE_STORAGE_DIR) 1023 1024 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts", 1025 help="Don't modify /etc/hosts", 1026 action="store_false", default=True) 1027 1028 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup", 1029 help="Don't initialize SSH keys", 1030 action="store_false", default=True) 1031 1032 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes", 1033 help="Enable parseable error messages", 1034 action="store_true", default=False) 1035 1036 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem", 1037 help="Skip N+1 memory redundancy tests", 1038 action="store_true", default=False) 1039 1040 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type", 1041 help="Type of reboot: soft/hard/full", 1042 default=constants.INSTANCE_REBOOT_HARD, 1043 metavar="<REBOOT>", 1044 choices=list(constants.REBOOT_TYPES)) 1045 1046 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries", 1047 dest="ignore_secondaries", 1048 default=False, action="store_true", 1049 help="Ignore errors from secondaries") 1050 1051 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown", 1052 action="store_false", default=True, 1053 help="Don't shutdown the instance (unsafe)") 1054 1055 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int", 1056 default=constants.DEFAULT_SHUTDOWN_TIMEOUT, 1057 help="Maximum time to wait") 1058 1059 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout", 1060 dest="shutdown_timeout", type="int", 1061 default=constants.DEFAULT_SHUTDOWN_TIMEOUT, 1062 help="Maximum time to wait for instance shutdown") 1063 1064 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int", 1065 default=None, 1066 help=("Number of seconds between repetions of the" 1067 " command")) 1068 1069 EARLY_RELEASE_OPT = cli_option("--early-release", 1070 dest="early_release", default=False, 1071 action="store_true", 1072 help="Release the locks on the secondary" 1073 " node(s) early") 1074 1075 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate", 1076 dest="new_cluster_cert", 1077 default=False, action="store_true", 1078 help="Generate a new cluster certificate") 1079 1080 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert", 1081 default=None, 1082 help="File containing new RAPI certificate") 1083 1084 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert", 1085 default=None, action="store_true", 1086 help=("Generate a new self-signed RAPI" 1087 " certificate")) 1088 1089 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key", 1090 dest="new_confd_hmac_key", 1091 default=False, action="store_true", 1092 help=("Create a new HMAC key for %s" % 1093 constants.CONFD)) 1094 1095 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret", 1096 dest="cluster_domain_secret", 1097 default=None, 1098 help=("Load new new cluster domain" 1099 " secret from file")) 1100 1101 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret", 1102 dest="new_cluster_domain_secret", 1103 default=False, action="store_true", 1104 help=("Create a new cluster domain" 1105 " secret")) 1106 1107 USE_REPL_NET_OPT = cli_option("--use-replication-network", 1108 dest="use_replication_network", 1109 help="Whether to use the replication network" 1110 " for talking to the nodes", 1111 action="store_true", default=False) 1112 1113 MAINTAIN_NODE_HEALTH_OPT = \ 1114 cli_option("--maintain-node-health", dest="maintain_node_health", 1115 metavar=_YORNO, default=None, type="bool", 1116 help="Configure the cluster to automatically maintain node" 1117 " health, by shutting down unknown instances, shutting down" 1118 " unknown DRBD devices, etc.") 1119 1120 IDENTIFY_DEFAULTS_OPT = \ 1121 cli_option("--identify-defaults", dest="identify_defaults", 1122 default=False, action="store_true", 1123 help="Identify which saved instance parameters are equal to" 1124 " the current cluster defaults and set them as such, instead" 1125 " of marking them as overridden") 1126 1127 UIDPOOL_OPT = cli_option("--uid-pool", default=None, 1128 action="store", dest="uid_pool", 1129 help=("A list of user-ids or user-id" 1130 " ranges separated by commas")) 1131 1132 ADD_UIDS_OPT = cli_option("--add-uids", default=None, 1133 action="store", dest="add_uids", 1134 help=("A list of user-ids or user-id" 1135 " ranges separated by commas, to be" 1136 " added to the user-id pool")) 1137 1138 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None, 1139 action="store", dest="remove_uids", 1140 help=("A list of user-ids or user-id" 1141 " ranges separated by commas, to be" 1142 " removed from the user-id pool")) 1143 1144 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None, 1145 action="store", dest="reserved_lvs", 1146 help=("A comma-separated list of reserved" 1147 " logical volumes names, that will be" 1148 " ignored by cluster verify")) 1149 1150 ROMAN_OPT = cli_option("--roman", 1151 dest="roman_integers", default=False, 1152 action="store_true", 1153 help="Use roman numbers for positive integers") 1154 1155 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper", 1156 action="store", default=None, 1157 help="Specifies usermode helper for DRBD") 1158 1159 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage", 1160 action="store_false", default=True, 1161 help="Disable support for DRBD") 1162 1163 PRIMARY_IP_VERSION_OPT = \ 1164 cli_option("--primary-ip-version", default=constants.IP4_VERSION, 1165 action="store", dest="primary_ip_version", 1166 metavar="%d|%d" % (constants.IP4_VERSION, 1167 constants.IP6_VERSION), 1168 help="Cluster-wide IP version for primary IP") 1169 1170 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority", 1171 metavar="|".join(name for name, _ in _PRIORITY_NAMES), 1172 choices=_PRIONAME_TO_VALUE.keys(), 1173 help="Priority for opcode processing") 1174 1175 HID_OS_OPT = cli_option("--hidden", dest="hidden", 1176 type="bool", default=None, metavar=_YORNO, 1177 help="Sets the hidden flag on the OS") 1178 1179 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted", 1180 type="bool", default=None, metavar=_YORNO, 1181 help="Sets the blacklisted flag on the OS") 1182 1183 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None, 1184 type="bool", metavar=_YORNO, 1185 dest="prealloc_wipe_disks", 1186 help=("Wipe disks prior to instance" 1187 " creation")) 1188 1189 NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams", 1190 type="keyval", default=None, 1191 help="Node parameters") 1192 1193 ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy", 1194 action="store", metavar="POLICY", default=None, 1195 help="Allocation policy for the node group") 1196 1197 NODE_POWERED_OPT = cli_option("--node-powered", default=None, 1198 type="bool", metavar=_YORNO, 1199 dest="node_powered", 1200 help="Specify if the SoR for node is powered") 1201 1202 OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int", 1203 default=constants.OOB_TIMEOUT, 1204 help="Maximum time to wait for out-of-band helper") 1205 1206 POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float", 1207 default=constants.OOB_POWER_DELAY, 1208 help="Time in seconds to wait between power-ons") 1209 1210 FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter", 1211 action="store_true", default=False, 1212 help=("Whether command argument should be treated" 1213 " as filter")) 1214 1215 NO_REMEMBER_OPT = cli_option("--no-remember", 1216 dest="no_remember", 1217 action="store_true", default=False, 1218 help="Perform but do not record the change" 1219 " in the configuration") 1220 1221 PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only", 1222 default=False, action="store_true", 1223 help="Evacuate primary instances only") 1224 1225 SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only", 1226 default=False, action="store_true", 1227 help="Evacuate secondary instances only" 1228 " (applies only to internally mirrored" 1229 " disk templates, e.g. %s)" % 1230 utils.CommaJoin(constants.DTS_INT_MIRROR)) 1231 1232 STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused", 1233 action="store_true", default=False, 1234 help="Pause instance at startup") 1235 1236 TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>", 1237 help="Destination node group (name or uuid)", 1238 default=None, action="append", 1239 completion_suggest=OPT_COMPL_ONE_NODEGROUP) 1240 1241 1242 #: Options provided by all commands 1243 COMMON_OPTS = [DEBUG_OPT] 1244 1245 # common options for creating instances. add and import then add their own 1246 # specific ones. 1247 COMMON_CREATE_OPTS = [ 1248 BACKEND_OPT, 1249 DISK_OPT, 1250 DISK_TEMPLATE_OPT, 1251 FILESTORE_DIR_OPT, 1252 FILESTORE_DRIVER_OPT, 1253 HYPERVISOR_OPT, 1254 IALLOCATOR_OPT, 1255 NET_OPT, 1256 NODE_PLACEMENT_OPT, 1257 NOIPCHECK_OPT, 1258 NONAMECHECK_OPT, 1259 NONICS_OPT, 1260 NWSYNC_OPT, 1261 OSPARAMS_OPT, 1262 OS_SIZE_OPT, 1263 SUBMIT_OPT, 1264 TAG_ADD_OPT, 1265 DRY_RUN_OPT, 1266 PRIORITY_OPT, 1267 ]
1268 1269 1270 -def _ParseArgs(argv, commands, aliases):
1271 """Parser for the command line arguments. 1272 1273 This function parses the arguments and returns the function which 1274 must be executed together with its (modified) arguments. 1275 1276 @param argv: the command line 1277 @param commands: dictionary with special contents, see the design 1278 doc for cmdline handling 1279 @param aliases: dictionary with command aliases {'alias': 'target, ...} 1280 1281 """ 1282 if len(argv) == 0: 1283 binary = "<command>" 1284 else: 1285 binary = argv[0].split("/")[-1] 1286 1287 if len(argv) > 1 and argv[1] == "--version": 1288 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION, 1289 constants.RELEASE_VERSION) 1290 # Quit right away. That way we don't have to care about this special 1291 # argument. optparse.py does it the same. 1292 sys.exit(0) 1293 1294 if len(argv) < 2 or not (argv[1] in commands or 1295 argv[1] in aliases): 1296 # let's do a nice thing 1297 sortedcmds = commands.keys() 1298 sortedcmds.sort() 1299 1300 ToStdout("Usage: %s {command} [options...] [argument...]", binary) 1301 ToStdout("%s <command> --help to see details, or man %s", binary, binary) 1302 ToStdout("") 1303 1304 # compute the max line length for cmd + usage 1305 mlen = max([len(" %s" % cmd) for cmd in commands]) 1306 mlen = min(60, mlen) # should not get here... 1307 1308 # and format a nice command list 1309 ToStdout("Commands:") 1310 for cmd in sortedcmds: 1311 cmdstr = " %s" % (cmd,) 1312 help_text = commands[cmd][4] 1313 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen) 1314 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0)) 1315 for line in help_lines: 1316 ToStdout("%-*s %s", mlen, "", line) 1317 1318 ToStdout("") 1319 1320 return None, None, None 1321 1322 # get command, unalias it, and look it up in commands 1323 cmd = argv.pop(1) 1324 if cmd in aliases: 1325 if cmd in commands: 1326 raise errors.ProgrammerError("Alias '%s' overrides an existing" 1327 " command" % cmd) 1328 1329 if aliases[cmd] not in commands: 1330 raise errors.ProgrammerError("Alias '%s' maps to non-existing" 1331 " command '%s'" % (cmd, aliases[cmd])) 1332 1333 cmd = aliases[cmd] 1334 1335 func, args_def, parser_opts, usage, description = commands[cmd] 1336 parser = OptionParser(option_list=parser_opts + COMMON_OPTS, 1337 description=description, 1338 formatter=TitledHelpFormatter(), 1339 usage="%%prog %s %s" % (cmd, usage)) 1340 parser.disable_interspersed_args() 1341 options, args = parser.parse_args() 1342 1343 if not _CheckArguments(cmd, args_def, args): 1344 return None, None, None 1345 1346 return func, options, args
1347
1348 1349 -def _CheckArguments(cmd, args_def, args):
1350 """Verifies the arguments using the argument definition. 1351 1352 Algorithm: 1353 1354 1. Abort with error if values specified by user but none expected. 1355 1356 1. For each argument in definition 1357 1358 1. Keep running count of minimum number of values (min_count) 1359 1. Keep running count of maximum number of values (max_count) 1360 1. If it has an unlimited number of values 1361 1362 1. Abort with error if it's not the last argument in the definition 1363 1364 1. If last argument has limited number of values 1365 1366 1. Abort with error if number of values doesn't match or is too large 1367 1368 1. Abort with error if user didn't pass enough values (min_count) 1369 1370 """ 1371 if args and not args_def: 1372 ToStderr("Error: Command %s expects no arguments", cmd) 1373 return False 1374 1375 min_count = None 1376 max_count = None 1377 check_max = None 1378 1379 last_idx = len(args_def) - 1 1380 1381 for idx, arg in enumerate(args_def): 1382 if min_count is None: 1383 min_count = arg.min 1384 elif arg.min is not None: 1385 min_count += arg.min 1386 1387 if max_count is None: 1388 max_count = arg.max 1389 elif arg.max is not None: 1390 max_count += arg.max 1391 1392 if idx == last_idx: 1393 check_max = (arg.max is not None) 1394 1395 elif arg.max is None: 1396 raise errors.ProgrammerError("Only the last argument can have max=None") 1397 1398 if check_max: 1399 # Command with exact number of arguments 1400 if (min_count is not None and max_count is not None and 1401 min_count == max_count and len(args) != min_count): 1402 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count) 1403 return False 1404 1405 # Command with limited number of arguments 1406 if max_count is not None and len(args) > max_count: 1407 ToStderr("Error: Command %s expects only %d argument(s)", 1408 cmd, max_count) 1409 return False 1410 1411 # Command with some required arguments 1412 if min_count is not None and len(args) < min_count: 1413 ToStderr("Error: Command %s expects at least %d argument(s)", 1414 cmd, min_count) 1415 return False 1416 1417 return True
1418
1419 1420 -def SplitNodeOption(value):
1421 """Splits the value of a --node option. 1422 1423 """ 1424 if value and ":" in value: 1425 return value.split(":", 1) 1426 else: 1427 return (value, None)
1428
1429 1430 -def CalculateOSNames(os_name, os_variants):
1431 """Calculates all the names an OS can be called, according to its variants. 1432 1433 @type os_name: string 1434 @param os_name: base name of the os 1435 @type os_variants: list or None 1436 @param os_variants: list of supported variants 1437 @rtype: list 1438 @return: list of valid names 1439 1440 """ 1441 if os_variants: 1442 return ["%s+%s" % (os_name, v) for v in os_variants] 1443 else: 1444 return [os_name]
1445
1446 1447 -def ParseFields(selected, default):
1448 """Parses the values of "--field"-like options. 1449 1450 @type selected: string or None 1451 @param selected: User-selected options 1452 @type default: list 1453 @param default: Default fields 1454 1455 """ 1456 if selected is None: 1457 return default 1458 1459 if selected.startswith("+"): 1460 return default + selected[1:].split(",") 1461 1462 return selected.split(",")
1463 1464 1465 UsesRPC = rpc.RunWithRPC
1466 1467 1468 -def AskUser(text, choices=None):
1469 """Ask the user a question. 1470 1471 @param text: the question to ask 1472 1473 @param choices: list with elements tuples (input_char, return_value, 1474 description); if not given, it will default to: [('y', True, 1475 'Perform the operation'), ('n', False, 'Do no do the operation')]; 1476 note that the '?' char is reserved for help 1477 1478 @return: one of the return values from the choices list; if input is 1479 not possible (i.e. not running with a tty, we return the last 1480 entry from the list 1481 1482 """ 1483 if choices is None: 1484 choices = [("y", True, "Perform the operation"), 1485 ("n", False, "Do not perform the operation")] 1486 if not choices or not isinstance(choices, list): 1487 raise errors.ProgrammerError("Invalid choices argument to AskUser") 1488 for entry in choices: 1489 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?": 1490 raise errors.ProgrammerError("Invalid choices element to AskUser") 1491 1492 answer = choices[-1][1] 1493 new_text = [] 1494 for line in text.splitlines(): 1495 new_text.append(textwrap.fill(line, 70, replace_whitespace=False)) 1496 text = "\n".join(new_text) 1497 try: 1498 f = file("/dev/tty", "a+") 1499 except IOError: 1500 return answer 1501 try: 1502 chars = [entry[0] for entry in choices] 1503 chars[-1] = "[%s]" % chars[-1] 1504 chars.append("?") 1505 maps = dict([(entry[0], entry[1]) for entry in choices]) 1506 while True: 1507 f.write(text) 1508 f.write("\n") 1509 f.write("/".join(chars)) 1510 f.write(": ") 1511 line = f.readline(2).strip().lower() 1512 if line in maps: 1513 answer = maps[line] 1514 break 1515 elif line == "?": 1516 for entry in choices: 1517 f.write(" %s - %s\n" % (entry[0], entry[2])) 1518 f.write("\n") 1519 continue 1520 finally: 1521 f.close() 1522 return answer
1523
1524 1525 -class JobSubmittedException(Exception):
1526 """Job was submitted, client should exit. 1527 1528 This exception has one argument, the ID of the job that was 1529 submitted. The handler should print this ID. 1530 1531 This is not an error, just a structured way to exit from clients. 1532 1533 """
1534
1535 1536 -def SendJob(ops, cl=None):
1537 """Function to submit an opcode without waiting for the results. 1538 1539 @type ops: list 1540 @param ops: list of opcodes 1541 @type cl: luxi.Client 1542 @param cl: the luxi client to use for communicating with the master; 1543 if None, a new client will be created 1544 1545 """ 1546 if cl is None: 1547 cl = GetClient() 1548 1549 job_id = cl.SubmitJob(ops) 1550 1551 return job_id
1552
1553 1554 -def GenericPollJob(job_id, cbs, report_cbs):
1555 """Generic job-polling function. 1556 1557 @type job_id: number 1558 @param job_id: Job ID 1559 @type cbs: Instance of L{JobPollCbBase} 1560 @param cbs: Data callbacks 1561 @type report_cbs: Instance of L{JobPollReportCbBase} 1562 @param report_cbs: Reporting callbacks 1563 1564 """ 1565 prev_job_info = None 1566 prev_logmsg_serial = None 1567 1568 status = None 1569 1570 while True: 1571 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info, 1572 prev_logmsg_serial) 1573 if not result: 1574 # job not found, go away! 1575 raise errors.JobLost("Job with id %s lost" % job_id) 1576 1577 if result == constants.JOB_NOTCHANGED: 1578 report_cbs.ReportNotChanged(job_id, status) 1579 1580 # Wait again 1581 continue 1582 1583 # Split result, a tuple of (field values, log entries) 1584 (job_info, log_entries) = result 1585 (status, ) = job_info 1586 1587 if log_entries: 1588 for log_entry in log_entries: 1589 (serial, timestamp, log_type, message) = log_entry 1590 report_cbs.ReportLogMessage(job_id, serial, timestamp, 1591 log_type, message) 1592 prev_logmsg_serial = max(prev_logmsg_serial, serial) 1593 1594 # TODO: Handle canceled and archived jobs 1595 elif status in (constants.JOB_STATUS_SUCCESS, 1596 constants.JOB_STATUS_ERROR, 1597 constants.JOB_STATUS_CANCELING, 1598 constants.JOB_STATUS_CANCELED): 1599 break 1600 1601 prev_job_info = job_info 1602 1603 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"]) 1604 if not jobs: 1605 raise errors.JobLost("Job with id %s lost" % job_id) 1606 1607 status, opstatus, result = jobs[0] 1608 1609 if status == constants.JOB_STATUS_SUCCESS: 1610 return result 1611 1612 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED): 1613 raise errors.OpExecError("Job was canceled") 1614 1615 has_ok = False 1616 for idx, (status, msg) in enumerate(zip(opstatus, result)): 1617 if status == constants.OP_STATUS_SUCCESS: 1618 has_ok = True 1619 elif status == constants.OP_STATUS_ERROR: 1620 errors.MaybeRaise(msg) 1621 1622 if has_ok: 1623 raise errors.OpExecError("partial failure (opcode %d): %s" % 1624 (idx, msg)) 1625 1626 raise errors.OpExecError(str(msg)) 1627 1628 # default failure mode 1629 raise errors.OpExecError(result)
1630
1631 1632 -class JobPollCbBase:
1633 """Base class for L{GenericPollJob} callbacks. 1634 1635 """
1636 - def __init__(self):
1637 """Initializes this class. 1638 1639 """
1640
1641 - def WaitForJobChangeOnce(self, job_id, fields, 1642 prev_job_info, prev_log_serial):
1643 """Waits for changes on a job. 1644 1645 """ 1646 raise NotImplementedError()
1647
1648 - def QueryJobs(self, job_ids, fields):
1649 """Returns the selected fields for the selected job IDs. 1650 1651 @type job_ids: list of numbers 1652 @param job_ids: Job IDs 1653 @type fields: list of strings 1654 @param fields: Fields 1655 1656 """ 1657 raise NotImplementedError()
1658
1659 1660 -class JobPollReportCbBase:
1661 """Base class for L{GenericPollJob} reporting callbacks. 1662 1663 """
1664 - def __init__(self):
1665 """Initializes this class. 1666 1667 """
1668
1669 - def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1670 """Handles a log message. 1671 1672 """ 1673 raise NotImplementedError()
1674
1675 - def ReportNotChanged(self, job_id, status):
1676 """Called for if a job hasn't changed in a while. 1677 1678 @type job_id: number 1679 @param job_id: Job ID 1680 @type status: string or None 1681 @param status: Job status if available 1682 1683 """ 1684 raise NotImplementedError()
1685
1686 1687 -class _LuxiJobPollCb(JobPollCbBase):
1688 - def __init__(self, cl):
1689 """Initializes this class. 1690 1691 """ 1692 JobPollCbBase.__init__(self) 1693 self.cl = cl
1694
1695 - def WaitForJobChangeOnce(self, job_id, fields, 1696 prev_job_info, prev_log_serial):
1697 """Waits for changes on a job. 1698 1699 """ 1700 return self.cl.WaitForJobChangeOnce(job_id, fields, 1701 prev_job_info, prev_log_serial)
1702
1703 - def QueryJobs(self, job_ids, fields):
1704 """Returns the selected fields for the selected job IDs. 1705 1706 """ 1707 return self.cl.QueryJobs(job_ids, fields)
1708
1709 1710 -class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1711 - def __init__(self, feedback_fn):
1712 """Initializes this class. 1713 1714 """ 1715 JobPollReportCbBase.__init__(self) 1716 1717 self.feedback_fn = feedback_fn 1718 1719 assert callable(feedback_fn)
1720
1721 - def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1722 """Handles a log message. 1723 1724 """ 1725 self.feedback_fn((timestamp, log_type, log_msg))
1726
1727 - def ReportNotChanged(self, job_id, status):
1728 """Called if a job hasn't changed in a while. 1729 1730 """
1731 # Ignore
1732 1733 1734 -class StdioJobPollReportCb(JobPollReportCbBase):
1735 - def __init__(self):
1736 """Initializes this class. 1737 1738 """ 1739 JobPollReportCbBase.__init__(self) 1740 1741 self.notified_queued = False 1742 self.notified_waitlock = False
1743
1744 - def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1745 """Handles a log message. 1746 1747 """ 1748 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)), 1749 FormatLogMessage(log_type, log_msg))
1750
1751 - def ReportNotChanged(self, job_id, status):
1752 """Called if a job hasn't changed in a while. 1753 1754 """ 1755 if status is None: 1756 return 1757 1758 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued: 1759 ToStderr("Job %s is waiting in queue", job_id) 1760 self.notified_queued = True 1761 1762 elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock: 1763 ToStderr("Job %s is trying to acquire all necessary locks", job_id) 1764 self.notified_waitlock = True
1765
1766 1767 -def FormatLogMessage(log_type, log_msg):
1768 """Formats a job message according to its type. 1769 1770 """ 1771 if log_type != constants.ELOG_MESSAGE: 1772 log_msg = str(log_msg) 1773 1774 return utils.SafeEncode(log_msg)
1775
1776 1777 -def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1778 """Function to poll for the result of a job. 1779 1780 @type job_id: job identified 1781 @param job_id: the job to poll for results 1782 @type cl: luxi.Client 1783 @param cl: the luxi client to use for communicating with the master; 1784 if None, a new client will be created 1785 1786 """ 1787 if cl is None: 1788 cl = GetClient() 1789 1790 if reporter is None: 1791 if feedback_fn: 1792 reporter = FeedbackFnJobPollReportCb(feedback_fn) 1793 else: 1794 reporter = StdioJobPollReportCb() 1795 elif feedback_fn: 1796 raise errors.ProgrammerError("Can't specify reporter and feedback function") 1797 1798 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1799
1800 1801 -def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1802 """Legacy function to submit an opcode. 1803 1804 This is just a simple wrapper over the construction of the processor 1805 instance. It should be extended to better handle feedback and 1806 interaction functions. 1807 1808 """ 1809 if cl is None: 1810 cl = GetClient() 1811 1812 SetGenericOpcodeOpts([op], opts) 1813 1814 job_id = SendJob([op], cl=cl) 1815 1816 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn, 1817 reporter=reporter) 1818 1819 return op_results[0]
1820
1821 1822 -def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1823 """Wrapper around SubmitOpCode or SendJob. 1824 1825 This function will decide, based on the 'opts' parameter, whether to 1826 submit and wait for the result of the opcode (and return it), or 1827 whether to just send the job and print its identifier. It is used in 1828 order to simplify the implementation of the '--submit' option. 1829 1830 It will also process the opcodes if we're sending the via SendJob 1831 (otherwise SubmitOpCode does it). 1832 1833 """ 1834 if opts and opts.submit_only: 1835 job = [op] 1836 SetGenericOpcodeOpts(job, opts) 1837 job_id = SendJob(job, cl=cl) 1838 raise JobSubmittedException(job_id) 1839 else: 1840 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1841
1842 1843 -def SetGenericOpcodeOpts(opcode_list, options):
1844 """Processor for generic options. 1845 1846 This function updates the given opcodes based on generic command 1847 line options (like debug, dry-run, etc.). 1848 1849 @param opcode_list: list of opcodes 1850 @param options: command line options or None 1851 @return: None (in-place modification) 1852 1853 """ 1854 if not options: 1855 return 1856 for op in opcode_list: 1857 op.debug_level = options.debug 1858 if hasattr(options, "dry_run"): 1859 op.dry_run = options.dry_run 1860 if getattr(options, "priority", None) is not None: 1861 op.priority = _PRIONAME_TO_VALUE[options.priority]
1862
1863 1864 -def GetClient():
1865 # TODO: Cache object? 1866 try: 1867 client = luxi.Client() 1868 except luxi.NoMasterError: 1869 ss = ssconf.SimpleStore() 1870 1871 # Try to read ssconf file 1872 try: 1873 ss.GetMasterNode() 1874 except errors.ConfigurationError: 1875 raise errors.OpPrereqError("Cluster not initialized or this machine is" 1876 " not part of a cluster") 1877 1878 master, myself = ssconf.GetMasterAndMyself(ss=ss) 1879 if master != myself: 1880 raise errors.OpPrereqError("This is not the master node, please connect" 1881 " to node '%s' and rerun the command" % 1882 master) 1883 raise 1884 return client
1885
1886 1887 -def FormatError(err):
1888 """Return a formatted error message for a given error. 1889 1890 This function takes an exception instance and returns a tuple 1891 consisting of two values: first, the recommended exit code, and 1892 second, a string describing the error message (not 1893 newline-terminated). 1894 1895 """ 1896 retcode = 1 1897 obuf = StringIO() 1898 msg = str(err) 1899 if isinstance(err, errors.ConfigurationError): 1900 txt = "Corrupt configuration file: %s" % msg 1901 logging.error(txt) 1902 obuf.write(txt + "\n") 1903 obuf.write("Aborting.") 1904 retcode = 2 1905 elif isinstance(err, errors.HooksAbort): 1906 obuf.write("Failure: hooks execution failed:\n") 1907 for node, script, out in err.args[0]: 1908 if out: 1909 obuf.write(" node: %s, script: %s, output: %s\n" % 1910 (node, script, out)) 1911 else: 1912 obuf.write(" node: %s, script: %s (no output)\n" % 1913 (node, script)) 1914 elif isinstance(err, errors.HooksFailure): 1915 obuf.write("Failure: hooks general failure: %s" % msg) 1916 elif isinstance(err, errors.ResolverError): 1917 this_host = netutils.Hostname.GetSysName() 1918 if err.args[0] == this_host: 1919 msg = "Failure: can't resolve my own hostname ('%s')" 1920 else: 1921 msg = "Failure: can't resolve hostname '%s'" 1922 obuf.write(msg % err.args[0]) 1923 elif isinstance(err, errors.OpPrereqError): 1924 if len(err.args) == 2: 1925 obuf.write("Failure: prerequisites not met for this" 1926 " operation:\nerror type: %s, error details:\n%s" % 1927 (err.args[1], err.args[0])) 1928 else: 1929 obuf.write("Failure: prerequisites not met for this" 1930 " operation:\n%s" % msg) 1931 elif isinstance(err, errors.OpExecError): 1932 obuf.write("Failure: command execution error:\n%s" % msg) 1933 elif isinstance(err, errors.TagError): 1934 obuf.write("Failure: invalid tag(s) given:\n%s" % msg) 1935 elif isinstance(err, errors.JobQueueDrainError): 1936 obuf.write("Failure: the job queue is marked for drain and doesn't" 1937 " accept new requests\n") 1938 elif isinstance(err, errors.JobQueueFull): 1939 obuf.write("Failure: the job queue is full and doesn't accept new" 1940 " job submissions until old jobs are archived\n") 1941 elif isinstance(err, errors.TypeEnforcementError): 1942 obuf.write("Parameter Error: %s" % msg) 1943 elif isinstance(err, errors.ParameterError): 1944 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg) 1945 elif isinstance(err, luxi.NoMasterError): 1946 obuf.write("Cannot communicate with the master daemon.\nIs it running" 1947 " and listening for connections?") 1948 elif isinstance(err, luxi.TimeoutError): 1949 obuf.write("Timeout while talking to the master daemon. Jobs might have" 1950 " been submitted and will continue to run even if the call" 1951 " timed out. Useful commands in this situation are \"gnt-job" 1952 " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n") 1953 obuf.write(msg) 1954 elif isinstance(err, luxi.PermissionError): 1955 obuf.write("It seems you don't have permissions to connect to the" 1956 " master daemon.\nPlease retry as a different user.") 1957 elif isinstance(err, luxi.ProtocolError): 1958 obuf.write("Unhandled protocol error while talking to the master daemon:\n" 1959 "%s" % msg) 1960 elif isinstance(err, errors.JobLost): 1961 obuf.write("Error checking job status: %s" % msg) 1962 elif isinstance(err, errors.QueryFilterParseError): 1963 obuf.write("Error while parsing query filter: %s\n" % err.args[0]) 1964 obuf.write("\n".join(err.GetDetails())) 1965 elif isinstance(err, errors.GenericError): 1966 obuf.write("Unhandled Ganeti error: %s" % msg) 1967 elif isinstance(err, JobSubmittedException): 1968 obuf.write("JobID: %s\n" % err.args[0]) 1969 retcode = 0 1970 else: 1971 obuf.write("Unhandled exception: %s" % msg) 1972 return retcode, obuf.getvalue().rstrip("\n")
1973
1974 1975 -def GenericMain(commands, override=None, aliases=None):
1976 """Generic main function for all the gnt-* commands. 1977 1978 Arguments: 1979 - commands: a dictionary with a special structure, see the design doc 1980 for command line handling. 1981 - override: if not None, we expect a dictionary with keys that will 1982 override command line options; this can be used to pass 1983 options from the scripts to generic functions 1984 - aliases: dictionary with command aliases {'alias': 'target, ...} 1985 1986 """ 1987 # save the program name and the entire command line for later logging 1988 if sys.argv: 1989 binary = os.path.basename(sys.argv[0]) or sys.argv[0] 1990 if len(sys.argv) >= 2: 1991 binary += " " + sys.argv[1] 1992 old_cmdline = " ".join(sys.argv[2:]) 1993 else: 1994 old_cmdline = "" 1995 else: 1996 binary = "<unknown program>" 1997 old_cmdline = "" 1998 1999 if aliases is None: 2000 aliases = {} 2001 2002 try: 2003 func, options, args = _ParseArgs(sys.argv, commands, aliases) 2004 except errors.ParameterError, err: 2005 result, err_msg = FormatError(err) 2006 ToStderr(err_msg) 2007 return 1 2008 2009 if func is None: # parse error 2010 return 1 2011 2012 if override is not None: 2013 for key, val in override.iteritems(): 2014 setattr(options, key, val) 2015 2016 utils.SetupLogging(constants.LOG_COMMANDS, binary, debug=options.debug, 2017 stderr_logging=True) 2018 2019 if old_cmdline: 2020 logging.info("run with arguments '%s'", old_cmdline) 2021 else: 2022 logging.info("run with no arguments") 2023 2024 try: 2025 result = func(options, args) 2026 except (errors.GenericError, luxi.ProtocolError, 2027 JobSubmittedException), err: 2028 result, err_msg = FormatError(err) 2029 logging.exception("Error during command processing") 2030 ToStderr(err_msg) 2031 except KeyboardInterrupt: 2032 result = constants.EXIT_FAILURE 2033 ToStderr("Aborted. Note that if the operation created any jobs, they" 2034 " might have been submitted and" 2035 " will continue to run in the background.") 2036 except IOError, err: 2037 if err.errno == errno.EPIPE: 2038 # our terminal went away, we'll exit 2039 sys.exit(constants.EXIT_FAILURE) 2040 else: 2041 raise 2042 2043 return result
2044
2045 2046 -def ParseNicOption(optvalue):
2047 """Parses the value of the --net option(s). 2048 2049 """ 2050 try: 2051 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue) 2052 except (TypeError, ValueError), err: 2053 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err)) 2054 2055 nics = [{}] * nic_max 2056 for nidx, ndict in optvalue: 2057 nidx = int(nidx) 2058 2059 if not isinstance(ndict, dict): 2060 raise errors.OpPrereqError("Invalid nic/%d value: expected dict," 2061 " got %s" % (nidx, ndict)) 2062 2063 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES) 2064 2065 nics[nidx] = ndict 2066 2067 return nics
2068
2069 2070 -def GenericInstanceCreate(mode, opts, args):
2071 """Add an instance to the cluster via either creation or import. 2072 2073 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT 2074 @param opts: the command line options selected by the user 2075 @type args: list 2076 @param args: should contain only one element, the new instance name 2077 @rtype: int 2078 @return: the desired exit code 2079 2080 """ 2081 instance = args[0] 2082 2083 (pnode, snode) = SplitNodeOption(opts.node) 2084 2085 hypervisor = None 2086 hvparams = {} 2087 if opts.hypervisor: 2088 hypervisor, hvparams = opts.hypervisor 2089 2090 if opts.nics: 2091 nics = ParseNicOption(opts.nics) 2092 elif opts.no_nics: 2093 # no nics 2094 nics = [] 2095 elif mode == constants.INSTANCE_CREATE: 2096 # default of one nic, all auto 2097 nics = [{}] 2098 else: 2099 # mode == import 2100 nics = [] 2101 2102 if opts.disk_template == constants.DT_DISKLESS: 2103 if opts.disks or opts.sd_size is not None: 2104 raise errors.OpPrereqError("Diskless instance but disk" 2105 " information passed") 2106 disks = [] 2107 else: 2108 if (not opts.disks and not opts.sd_size 2109 and mode == constants.INSTANCE_CREATE): 2110 raise errors.OpPrereqError("No disk information specified") 2111 if opts.disks and opts.sd_size is not None: 2112 raise errors.OpPrereqError("Please use either the '--disk' or" 2113 " '-s' option") 2114 if opts.sd_size is not None: 2115 opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})] 2116 2117 if opts.disks: 2118 try: 2119 disk_max = max(int(didx[0]) + 1 for didx in opts.disks) 2120 except ValueError, err: 2121 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err)) 2122 disks = [{}] * disk_max 2123 else: 2124 disks = [] 2125 for didx, ddict in opts.disks: 2126 didx = int(didx) 2127 if not isinstance(ddict, dict): 2128 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict) 2129 raise errors.OpPrereqError(msg) 2130 elif constants.IDISK_SIZE in ddict: 2131 if constants.IDISK_ADOPT in ddict: 2132 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed" 2133 " (disk %d)" % didx) 2134 try: 2135 ddict[constants.IDISK_SIZE] = \ 2136 utils.ParseUnit(ddict[constants.IDISK_SIZE]) 2137 except ValueError, err: 2138 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" % 2139 (didx, err)) 2140 elif constants.IDISK_ADOPT in ddict: 2141 if mode == constants.INSTANCE_IMPORT: 2142 raise errors.OpPrereqError("Disk adoption not allowed for instance" 2143 " import") 2144 ddict[constants.IDISK_SIZE] = 0 2145 else: 2146 raise errors.OpPrereqError("Missing size or adoption source for" 2147 " disk %d" % didx) 2148 disks[didx] = ddict 2149 2150 if opts.tags is not None: 2151 tags = opts.tags.split(",") 2152 else: 2153 tags = [] 2154 2155 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES) 2156 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES) 2157 2158 if mode == constants.INSTANCE_CREATE: 2159 start = opts.start 2160 os_type = opts.os 2161 force_variant = opts.force_variant 2162 src_node = None 2163 src_path = None 2164 no_install = opts.no_install 2165 identify_defaults = False 2166 elif mode == constants.INSTANCE_IMPORT: 2167 start = False 2168 os_type = None 2169 force_variant = False 2170 src_node = opts.src_node 2171 src_path = opts.src_dir 2172 no_install = None 2173 identify_defaults = opts.identify_defaults 2174 else: 2175 raise errors.ProgrammerError("Invalid creation mode %s" % mode) 2176 2177 op = opcodes.OpInstanceCreate(instance_name=instance, 2178 disks=disks, 2179 disk_template=opts.disk_template, 2180 nics=nics, 2181 pnode=pnode, snode=snode, 2182 ip_check=opts.ip_check, 2183 name_check=opts.name_check, 2184 wait_for_sync=opts.wait_for_sync, 2185 file_storage_dir=opts.file_storage_dir, 2186 file_driver=opts.file_driver, 2187 iallocator=opts.iallocator, 2188 hypervisor=hypervisor, 2189 hvparams=hvparams, 2190 beparams=opts.beparams, 2191 osparams=opts.osparams, 2192 mode=mode, 2193 start=start, 2194 os_type=os_type, 2195 force_variant=force_variant, 2196 src_node=src_node, 2197 src_path=src_path, 2198 tags=tags, 2199 no_install=no_install, 2200 identify_defaults=identify_defaults) 2201 2202 SubmitOrSend(op, opts) 2203 return 0
2204
2205 2206 -class _RunWhileClusterStoppedHelper:
2207 """Helper class for L{RunWhileClusterStopped} to simplify state management 2208 2209 """
2210 - def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2211 """Initializes this class. 2212 2213 @type feedback_fn: callable 2214 @param feedback_fn: Feedback function 2215 @type cluster_name: string 2216 @param cluster_name: Cluster name 2217 @type master_node: string 2218 @param master_node Master node name 2219 @type online_nodes: list 2220 @param online_nodes: List of names of online nodes 2221 2222 """ 2223 self.feedback_fn = feedback_fn 2224 self.cluster_name = cluster_name 2225 self.master_node = master_node 2226 self.online_nodes = online_nodes 2227 2228 self.ssh = ssh.SshRunner(self.cluster_name) 2229 2230 self.nonmaster_nodes = [name for name in online_nodes 2231 if name != master_node] 2232 2233 assert self.master_node not in self.nonmaster_nodes
2234
2235 - def _RunCmd(self, node_name, cmd):
2236 """Runs a command on the local or a remote machine. 2237 2238 @type node_name: string 2239 @param node_name: Machine name 2240 @type cmd: list 2241 @param cmd: Command 2242 2243 """ 2244 if node_name is None or node_name == self.master_node: 2245 # No need to use SSH 2246 result = utils.RunCmd(cmd) 2247 else: 2248 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd)) 2249 2250 if result.failed: 2251 errmsg = ["Failed to run command %s" % result.cmd] 2252 if node_name: 2253 errmsg.append("on node %s" % node_name) 2254 errmsg.append(": exitcode %s and error %s" % 2255 (result.exit_code, result.output)) 2256 raise errors.OpExecError(" ".join(errmsg))
2257
2258 - def Call(self, fn, *args):
2259 """Call function while all daemons are stopped. 2260 2261 @type fn: callable 2262 @param fn: Function to be called 2263 2264 """ 2265 # Pause watcher by acquiring an exclusive lock on watcher state file 2266 self.feedback_fn("Blocking watcher") 2267 watcher_block = utils.FileLock.Open(constants.WATCHER_LOCK_FILE) 2268 try: 2269 # TODO: Currently, this just blocks. There's no timeout. 2270 # TODO: Should it be a shared lock? 2271 watcher_block.Exclusive(blocking=True) 2272 2273 # Stop master daemons, so that no new jobs can come in and all running 2274 # ones are finished 2275 self.feedback_fn("Stopping master daemons") 2276 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"]) 2277 try: 2278 # Stop daemons on all nodes 2279 for node_name in self.online_nodes: 2280 self.feedback_fn("Stopping daemons on %s" % node_name) 2281 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"]) 2282 2283 # All daemons are shut down now 2284 try: 2285 return fn(self, *args) 2286 except Exception, err: 2287 _, errmsg = FormatError(err) 2288 logging.exception("Caught exception") 2289 self.feedback_fn(errmsg) 2290 raise 2291 finally: 2292 # Start cluster again, master node last 2293 for node_name in self.nonmaster_nodes + [self.master_node]: 2294 self.feedback_fn("Starting daemons on %s" % node_name) 2295 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"]) 2296 finally: 2297 # Resume watcher 2298 watcher_block.Close()
2299
2300 2301 -def RunWhileClusterStopped(feedback_fn, fn, *args):
2302 """Calls a function while all cluster daemons are stopped. 2303 2304 @type feedback_fn: callable 2305 @param feedback_fn: Feedback function 2306 @type fn: callable 2307 @param fn: Function to be called when daemons are stopped 2308 2309 """ 2310 feedback_fn("Gathering cluster information") 2311 2312 # This ensures we're running on the master daemon 2313 cl = GetClient() 2314 2315 (cluster_name, master_node) = \ 2316 cl.QueryConfigValues(["cluster_name", "master_node"]) 2317 2318 online_nodes = GetOnlineNodes([], cl=cl) 2319 2320 # Don't keep a reference to the client. The master daemon will go away. 2321 del cl 2322 2323 assert master_node in online_nodes 2324 2325 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node, 2326 online_nodes).Call(fn, *args)
2327
2328 2329 -def GenerateTable(headers, fields, separator, data, 2330 numfields=None, unitfields=None, 2331 units=None):
2332 """Prints a table with headers and different fields. 2333 2334 @type headers: dict 2335 @param headers: dictionary mapping field names to headers for 2336 the table 2337 @type fields: list 2338 @param fields: the field names corresponding to each row in 2339 the data field 2340 @param separator: the separator to be used; if this is None, 2341 the default 'smart' algorithm is used which computes optimal 2342 field width, otherwise just the separator is used between 2343 each field 2344 @type data: list 2345 @param data: a list of lists, each sublist being one row to be output 2346 @type numfields: list 2347 @param numfields: a list with the fields that hold numeric 2348 values and thus should be right-aligned 2349 @type unitfields: list 2350 @param unitfields: a list with the fields that hold numeric 2351 values that should be formatted with the units field 2352 @type units: string or None 2353 @param units: the units we should use for formatting, or None for 2354 automatic choice (human-readable for non-separator usage, otherwise 2355 megabytes); this is a one-letter string 2356 2357 """ 2358 if units is None: 2359 if separator: 2360 units = "m" 2361 else: 2362 units = "h" 2363 2364 if numfields is None: 2365 numfields = [] 2366 if unitfields is None: 2367 unitfields = [] 2368 2369 numfields = utils.FieldSet(*numfields) # pylint: disable=W0142 2370 unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142 2371 2372 format_fields = [] 2373 for field in fields: 2374 if headers and field not in headers: 2375 # TODO: handle better unknown fields (either revert to old 2376 # style of raising exception, or deal more intelligently with 2377 # variable fields) 2378 headers[field] = field 2379 if separator is not None: 2380 format_fields.append("%s") 2381 elif numfields.Matches(field): 2382 format_fields.append("%*s") 2383 else: 2384 format_fields.append("%-*s") 2385 2386 if separator is None: 2387 mlens = [0 for name in fields] 2388 format_str = " ".join(format_fields) 2389 else: 2390 format_str = separator.replace("%", "%%").join(format_fields) 2391 2392 for row in data: 2393 if row is None: 2394 continue 2395 for idx, val in enumerate(row): 2396 if unitfields.Matches(fields[idx]): 2397 try: 2398 val = int(val) 2399 except (TypeError, ValueError): 2400 pass 2401 else: 2402 val = row[idx] = utils.FormatUnit(val, units) 2403 val = row[idx] = str(val) 2404 if separator is None: 2405 mlens[idx] = max(mlens[idx], len(val)) 2406 2407 result = [] 2408 if headers: 2409 args = [] 2410 for idx, name in enumerate(fields): 2411 hdr = headers[name] 2412 if separator is None: 2413 mlens[idx] = max(mlens[idx], len(hdr)) 2414 args.append(mlens[idx]) 2415 args.append(hdr) 2416 result.append(format_str % tuple(args)) 2417 2418 if separator is None: 2419 assert len(mlens) == len(fields) 2420 2421 if fields and not numfields.Matches(fields[-1]): 2422 mlens[-1] = 0 2423 2424 for line in data: 2425 args = [] 2426 if line is None: 2427 line = ["-" for _ in fields] 2428 for idx in range(len(fields)): 2429 if separator is None: 2430 args.append(mlens[idx]) 2431 args.append(line[idx]) 2432 result.append(format_str % tuple(args)) 2433 2434 return result
2435
2436 2437 -def _FormatBool(value):
2438 """Formats a boolean value as a string. 2439 2440 """ 2441 if value: 2442 return "Y" 2443 return "N"
2444 2445 2446 #: Default formatting for query results; (callback, align right) 2447 _DEFAULT_FORMAT_QUERY = { 2448 constants.QFT_TEXT: (str, False), 2449 constants.QFT_BOOL: (_FormatBool, False), 2450 constants.QFT_NUMBER: (str, True), 2451 constants.QFT_TIMESTAMP: (utils.FormatTime, False), 2452 constants.QFT_OTHER: (str, False), 2453 constants.QFT_UNKNOWN: (str, False), 2454 }
2455 2456 2457 -def _GetColumnFormatter(fdef, override, unit):
2458 """Returns formatting function for a field. 2459 2460 @type fdef: L{objects.QueryFieldDefinition} 2461 @type override: dict 2462 @param override: Dictionary for overriding field formatting functions, 2463 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY} 2464 @type unit: string 2465 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} 2466 @rtype: tuple; (callable, bool) 2467 @return: Returns the function to format a value (takes one parameter) and a 2468 boolean for aligning the value on the right-hand side 2469 2470 """ 2471 fmt = override.get(fdef.name, None) 2472 if fmt is not None: 2473 return fmt 2474 2475 assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY 2476 2477 if fdef.kind == constants.QFT_UNIT: 2478 # Can't keep this information in the static dictionary 2479 return (lambda value: utils.FormatUnit(value, unit), True) 2480 2481 fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None) 2482 if fmt is not None: 2483 return fmt 2484 2485 raise NotImplementedError("Can't format column type '%s'" % fdef.kind)
2486
2487 2488 -class _QueryColumnFormatter:
2489 """Callable class for formatting fields of a query. 2490 2491 """
2492 - def __init__(self, fn, status_fn, verbose):
2493 """Initializes this class. 2494 2495 @type fn: callable 2496 @param fn: Formatting function 2497 @type status_fn: callable 2498 @param status_fn: Function to report fields' status 2499 @type verbose: boolean 2500 @param verbose: whether to use verbose field descriptions or not 2501 2502 """ 2503 self._fn = fn 2504 self._status_fn = status_fn 2505 self._verbose = verbose
2506
2507 - def __call__(self, data):
2508 """Returns a field's string representation. 2509 2510 """ 2511 (status, value) = data 2512 2513 # Report status 2514 self._status_fn(status) 2515 2516 if status == constants.RS_NORMAL: 2517 return self._fn(value) 2518 2519 assert value is None, \ 2520 "Found value %r for abnormal status %s" % (value, status) 2521 2522 return FormatResultError(status, self._verbose)
2523
2524 2525 -def FormatResultError(status, verbose):
2526 """Formats result status other than L{constants.RS_NORMAL}. 2527 2528 @param status: The result status 2529 @type verbose: boolean 2530 @param verbose: Whether to return the verbose text 2531 @return: Text of result status 2532 2533 """ 2534 assert status != constants.RS_NORMAL, \ 2535 "FormatResultError called with status equal to constants.RS_NORMAL" 2536 try: 2537 (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status] 2538 except KeyError: 2539 raise NotImplementedError("Unknown status %s" % status) 2540 else: 2541 if verbose: 2542 return verbose_text 2543 return normal_text
2544
2545 2546 -def FormatQueryResult(result, unit=None, format_override=None, separator=None, 2547 header=False, verbose=False):
2548 """Formats data in L{objects.QueryResponse}. 2549 2550 @type result: L{objects.QueryResponse} 2551 @param result: result of query operation 2552 @type unit: string 2553 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}, 2554 see L{utils.text.FormatUnit} 2555 @type format_override: dict 2556 @param format_override: Dictionary for overriding field formatting functions, 2557 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY} 2558 @type separator: string or None 2559 @param separator: String used to separate fields 2560 @type header: bool 2561 @param header: Whether to output header row 2562 @type verbose: boolean 2563 @param verbose: whether to use verbose field descriptions or not 2564 2565 """ 2566 if unit is None: 2567 if separator: 2568 unit = "m" 2569 else: 2570 unit = "h" 2571 2572 if format_override is None: 2573 format_override = {} 2574 2575 stats = dict.fromkeys(constants.RS_ALL, 0) 2576 2577 def _RecordStatus(status): 2578 if status in stats: 2579 stats[status] += 1
2580 2581 columns = [] 2582 for fdef in result.fields: 2583 assert fdef.title and fdef.name 2584 (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit) 2585 columns.append(TableColumn(fdef.title, 2586 _QueryColumnFormatter(fn, _RecordStatus, 2587 verbose), 2588 align_right)) 2589 2590 table = FormatTable(result.data, columns, header, separator) 2591 2592 # Collect statistics 2593 assert len(stats) == len(constants.RS_ALL) 2594 assert compat.all(count >= 0 for count in stats.values()) 2595 2596 # Determine overall status. If there was no data, unknown fields must be 2597 # detected via the field definitions. 2598 if (stats[constants.RS_UNKNOWN] or 2599 (not result.data and _GetUnknownFields(result.fields))): 2600 status = QR_UNKNOWN 2601 elif compat.any(count > 0 for key, count in stats.items() 2602 if key != constants.RS_NORMAL): 2603 status = QR_INCOMPLETE 2604 else: 2605 status = QR_NORMAL 2606 2607 return (status, table) 2608
2609 2610 -def _GetUnknownFields(fdefs):
2611 """Returns list of unknown fields included in C{fdefs}. 2612 2613 @type fdefs: list of L{objects.QueryFieldDefinition} 2614 2615 """ 2616 return [fdef for fdef in fdefs 2617 if fdef.kind == constants.QFT_UNKNOWN]
2618
2619 2620 -def _WarnUnknownFields(fdefs):
2621 """Prints a warning to stderr if a query included unknown fields. 2622 2623 @type fdefs: list of L{objects.QueryFieldDefinition} 2624 2625 """ 2626 unknown = _GetUnknownFields(fdefs) 2627 if unknown: 2628 ToStderr("Warning: Queried for unknown fields %s", 2629 utils.CommaJoin(fdef.name for fdef in unknown)) 2630 return True 2631 2632 return False
2633
2634 2635 -def GenericList(resource, fields, names, unit, separator, header, cl=None, 2636 format_override=None, verbose=False, force_filter=False):
2637 """Generic implementation for listing all items of a resource. 2638 2639 @param resource: One of L{constants.QR_VIA_LUXI} 2640 @type fields: list of strings 2641 @param fields: List of fields to query for 2642 @type names: list of strings 2643 @param names: Names of items to query for 2644 @type unit: string or None 2645 @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or 2646 None for automatic choice (human-readable for non-separator usage, 2647 otherwise megabytes); this is a one-letter string 2648 @type separator: string or None 2649 @param separator: String used to separate fields 2650 @type header: bool 2651 @param header: Whether to show header row 2652 @type force_filter: bool 2653 @param force_filter: Whether to always treat names as filter 2654 @type format_override: dict 2655 @param format_override: Dictionary for overriding field formatting functions, 2656 indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY} 2657 @type verbose: boolean 2658 @param verbose: whether to use verbose field descriptions or not 2659 2660 """ 2661 if cl is None: 2662 cl = GetClient() 2663 2664 if not names: 2665 names = None 2666 2667 filter_ = qlang.MakeFilter(names, force_filter) 2668 2669 response = cl.Query(resource, fields, filter_) 2670 2671 found_unknown = _WarnUnknownFields(response.fields) 2672 2673 (status, data) = FormatQueryResult(response, unit=unit, separator=separator, 2674 header=header, 2675 format_override=format_override, 2676 verbose=verbose) 2677 2678 for line in data: 2679 ToStdout(line) 2680 2681 assert ((found_unknown and status == QR_UNKNOWN) or 2682 (not found_unknown and status != QR_UNKNOWN)) 2683 2684 if status == QR_UNKNOWN: 2685 return constants.EXIT_UNKNOWN_FIELD 2686 2687 # TODO: Should the list command fail if not all data could be collected? 2688 return constants.EXIT_SUCCESS
2689
2690 2691 -def GenericListFields(resource, fields, separator, header, cl=None):
2692 """Generic implementation for listing fields for a resource. 2693 2694 @param resource: One of L{constants.QR_VIA_LUXI} 2695 @type fields: list of strings 2696 @param fields: List of fields to query for 2697 @type separator: string or None 2698 @param separator: String used to separate fields 2699 @type header: bool 2700 @param header: Whether to show header row 2701 2702 """ 2703 if cl is None: 2704 cl = GetClient() 2705 2706 if not fields: 2707 fields = None 2708 2709 response = cl.QueryFields(resource, fields) 2710 2711 found_unknown = _WarnUnknownFields(response.fields) 2712 2713 columns = [ 2714 TableColumn("Name", str, False), 2715 TableColumn("Title", str, False), 2716 TableColumn("Description", str, False), 2717 ] 2718 2719 rows = [[fdef.name, fdef.title, fdef.doc] for fdef in response.fields] 2720 2721 for line in FormatTable(rows, columns, header, separator): 2722 ToStdout(line) 2723 2724 if found_unknown: 2725 return constants.EXIT_UNKNOWN_FIELD 2726 2727 return constants.EXIT_SUCCESS
2728
2729 2730 -class TableColumn:
2731 """Describes a column for L{FormatTable}. 2732 2733 """
2734 - def __init__(self, title, fn, align_right):
2735 """Initializes this class. 2736 2737 @type title: string 2738 @param title: Column title 2739 @type fn: callable 2740 @param fn: Formatting function 2741 @type align_right: bool 2742 @param align_right: Whether to align values on the right-hand side 2743 2744 """ 2745 self.title = title 2746 self.format = fn 2747 self.align_right = align_right
2748
2749 2750 -def _GetColFormatString(width, align_right):
2751 """Returns the format string for a field. 2752 2753 """ 2754 if align_right: 2755 sign = "" 2756 else: 2757 sign = "-" 2758 2759 return "%%%s%ss" % (sign, width)
2760
2761 2762 -def FormatTable(rows, columns, header, separator):
2763 """Formats data as a table. 2764 2765 @type rows: list of lists 2766 @param rows: Row data, one list per row 2767 @type columns: list of L{TableColumn} 2768 @param columns: Column descriptions 2769 @type header: bool 2770 @param header: Whether to show header row 2771 @type separator: string or None 2772 @param separator: String used to separate columns 2773 2774 """ 2775 if header: 2776 data = [[col.title for col in columns]] 2777 colwidth = [len(col.title) for col in columns] 2778 else: 2779 data = [] 2780 colwidth = [0 for _ in columns] 2781 2782 # Format row data 2783 for row in rows: 2784 assert len(row) == len(columns) 2785 2786 formatted = [col.format(value) for value, col in zip(row, columns)] 2787 2788 if separator is None: 2789 # Update column widths 2790 for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)): 2791 # Modifying a list's items while iterating is fine 2792 colwidth[idx] = max(oldwidth, len(value)) 2793 2794 data.append(formatted) 2795 2796 if separator is not None: 2797 # Return early if a separator is used 2798 return [separator.join(row) for row in data] 2799 2800 if columns and not columns[-1].align_right: 2801 # Avoid unnecessary spaces at end of line 2802 colwidth[-1] = 0 2803 2804 # Build format string 2805 fmt = " ".join([_GetColFormatString(width, col.align_right) 2806 for col, width in zip(columns, colwidth)]) 2807 2808 return [fmt % tuple(row) for row in data]
2809
2810 2811 -def FormatTimestamp(ts):
2812 """Formats a given timestamp. 2813 2814 @type ts: timestamp 2815 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds 2816 2817 @rtype: string 2818 @return: a string with the formatted timestamp 2819 2820 """ 2821 if not isinstance(ts, (tuple, list)) or len(ts) != 2: 2822 return "?" 2823 sec, usec = ts 2824 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2825
2826 2827 -def ParseTimespec(value):
2828 """Parse a time specification. 2829 2830 The following suffixed will be recognized: 2831 2832 - s: seconds 2833 - m: minutes 2834 - h: hours 2835 - d: day 2836 - w: weeks 2837 2838 Without any suffix, the value will be taken to be in seconds. 2839 2840 """ 2841 value = str(value) 2842 if not value: 2843 raise errors.OpPrereqError("Empty time specification passed") 2844 suffix_map = { 2845 "s": 1, 2846 "m": 60, 2847 "h": 3600, 2848 "d": 86400, 2849 "w": 604800, 2850 } 2851 if value[-1] not in suffix_map: 2852 try: 2853 value = int(value) 2854 except (TypeError, ValueError): 2855 raise errors.OpPrereqError("Invalid time specification '%s'" % value) 2856 else: 2857 multiplier = suffix_map[value[-1]] 2858 value = value[:-1] 2859 if not value: # no data left after stripping the suffix 2860 raise errors.OpPrereqError("Invalid time specification (only" 2861 " suffix passed)") 2862 try: 2863 value = int(value) * multiplier 2864 except (TypeError, ValueError): 2865 raise errors.OpPrereqError("Invalid time specification '%s'" % value) 2866 return value
2867
2868 2869 -def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False, 2870 filter_master=False, nodegroup=None):
2871 """Returns the names of online nodes. 2872 2873 This function will also log a warning on stderr with the names of 2874 the online nodes. 2875 2876 @param nodes: if not empty, use only this subset of nodes (minus the 2877 offline ones) 2878 @param cl: if not None, luxi client to use 2879 @type nowarn: boolean 2880 @param nowarn: by default, this function will output a note with the 2881 offline nodes that are skipped; if this parameter is True the 2882 note is not displayed 2883 @type secondary_ips: boolean 2884 @param secondary_ips: if True, return the secondary IPs instead of the 2885 names, useful for doing network traffic over the replication interface 2886 (if any) 2887 @type filter_master: boolean 2888 @param filter_master: if True, do not return the master node in the list 2889 (useful in coordination with secondary_ips where we cannot check our 2890 node name against the list) 2891 @type nodegroup: string 2892 @param nodegroup: If set, only return nodes in this node group 2893 2894 """ 2895 if cl is None: 2896 cl = GetClient() 2897 2898 filter_ = [] 2899 2900 if nodes: 2901 filter_.append(qlang.MakeSimpleFilter("name", nodes)) 2902 2903 if nodegroup is not None: 2904 filter_.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup], 2905 [qlang.OP_EQUAL, "group.uuid", nodegroup]]) 2906 2907 if filter_master: 2908 filter_.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]]) 2909 2910 if filter_: 2911 if len(filter_) > 1: 2912 final_filter = [qlang.OP_AND] + filter_ 2913 else: 2914 assert len(filter_) == 1 2915 final_filter = filter_[0] 2916 else: 2917 final_filter = None 2918 2919 result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter) 2920 2921 def _IsOffline(row): 2922 (_, (_, offline), _) = row 2923 return offline
2924 2925 def _GetName(row): 2926 ((_, name), _, _) = row 2927 return name 2928 2929 def _GetSip(row): 2930 (_, _, (_, sip)) = row 2931 return sip 2932 2933 (offline, online) = compat.partition(result.data, _IsOffline) 2934 2935 if offline and not nowarn: 2936 ToStderr("Note: skipping offline node(s): %s" % 2937 utils.CommaJoin(map(_GetName, offline))) 2938 2939 if secondary_ips: 2940 fn = _GetSip 2941 else: 2942 fn = _GetName 2943 2944 return map(fn, online) 2945
2946 2947 -def _ToStream(stream, txt, *args):
2948 """Write a message to a stream, bypassing the logging system 2949 2950 @type stream: file object 2951 @param stream: the file to which we should write 2952 @type txt: str 2953 @param txt: the message 2954 2955 """ 2956 try: 2957 if args: 2958 args = tuple(args) 2959 stream.write(txt % args) 2960 else: 2961 stream.write(txt) 2962 stream.write("\n") 2963 stream.flush() 2964 except IOError, err: 2965 if err.errno == errno.EPIPE: 2966 # our terminal went away, we'll exit 2967 sys.exit(constants.EXIT_FAILURE) 2968 else: 2969 raise
2970
2971 2972 -def ToStdout(txt, *args):
2973 """Write a message to stdout only, bypassing the logging system 2974 2975 This is just a wrapper over _ToStream. 2976 2977 @type txt: str 2978 @param txt: the message 2979 2980 """ 2981 _ToStream(sys.stdout, txt, *args)
2982
2983 2984 -def ToStderr(txt, *args):
2985 """Write a message to stderr only, bypassing the logging system 2986 2987 This is just a wrapper over _ToStream. 2988 2989 @type txt: str 2990 @param txt: the message 2991 2992 """ 2993 _ToStream(sys.stderr, txt, *args)
2994
2995 2996 -class JobExecutor(object):
2997 """Class which manages the submission and execution of multiple jobs. 2998 2999 Note that instances of this class should not be reused between 3000 GetResults() calls. 3001 3002 """
3003 - def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3004 self.queue = [] 3005 if cl is None: 3006 cl = GetClient() 3007 self.cl = cl 3008 self.verbose = verbose 3009 self.jobs = [] 3010 self.opts = opts 3011 self.feedback_fn = feedback_fn 3012 self._counter = itertools.count()
3013 3014 @staticmethod
3015 - def _IfName(name, fmt):
3016 """Helper function for formatting name. 3017 3018 """ 3019 if name: 3020 return fmt % name 3021 3022 return ""
3023
3024 - def QueueJob(self, name, *ops):
3025 """Record a job for later submit. 3026 3027 @type name: string 3028 @param name: a description of the job, will be used in WaitJobSet 3029 3030 """ 3031 SetGenericOpcodeOpts(ops, self.opts) 3032 self.queue.append((self._counter.next(), name, ops))
3033
3034 - def AddJobId(self, name, status, job_id):
3035 """Adds a job ID to the internal queue. 3036 3037 """ 3038 self.jobs.append((self._counter.next(), status, job_id, name))
3039
3040 - def SubmitPending(self, each=False):
3041 """Submit all pending jobs. 3042 3043 """ 3044 if each: 3045 results = [] 3046 for (_, _, ops) in self.queue: 3047 # SubmitJob will remove the success status, but raise an exception if 3048 # the submission fails, so we'll notice that anyway. 3049 results.append([True, self.cl.SubmitJob(ops)]) 3050 else: 3051 results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue]) 3052 for ((status, data), (idx, name, _)) in zip(results, self.queue): 3053 self.jobs.append((idx, status, data, name))
3054
3055 - def _ChooseJob(self):
3056 """Choose a non-waiting/queued job to poll next. 3057 3058 """ 3059 assert self.jobs, "_ChooseJob called with empty job list" 3060 3061 result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]], 3062 ["status"]) 3063 assert result 3064 3065 for job_data, status in zip(self.jobs, result): 3066 if (isinstance(status, list) and status and 3067 status[0] in (constants.JOB_STATUS_QUEUED, 3068 constants.JOB_STATUS_WAITING, 3069 constants.JOB_STATUS_CANCELING)): 3070 # job is still present and waiting 3071 continue 3072 # good candidate found (either running job or lost job) 3073 self.jobs.remove(job_data) 3074 return job_data 3075 3076 # no job found 3077 return self.jobs.pop(0)
3078
3079 - def GetResults(self):
3080 """Wait for and return the results of all jobs. 3081 3082 @rtype: list 3083 @return: list of tuples (success, job results), in the same order 3084 as the submitted jobs; if a job has failed, instead of the result 3085 there will be the error message 3086 3087 """ 3088 if not self.jobs: 3089 self.SubmitPending() 3090 results = [] 3091 if self.verbose: 3092 ok_jobs = [row[2] for row in self.jobs if row[1]] 3093 if ok_jobs: 3094 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs)) 3095 3096 # first, remove any non-submitted jobs 3097 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1]) 3098 for idx, _, jid, name in failures: 3099 ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid) 3100 results.append((idx, False, jid)) 3101 3102 while self.jobs: 3103 (idx, _, jid, name) = self._ChooseJob() 3104 ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s")) 3105 try: 3106 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn) 3107 success = True 3108 except errors.JobLost, err: 3109 _, job_result = FormatError(err) 3110 ToStderr("Job %s%s has been archived, cannot check its result", 3111 jid, self._IfName(name, " for %s")) 3112 success = False 3113 except (errors.GenericError, luxi.ProtocolError), err: 3114 _, job_result = FormatError(err) 3115 success = False 3116 # the error message will always be shown, verbose or not 3117 ToStderr("Job %s%s has failed: %s", 3118 jid, self._IfName(name, " for %s"), job_result) 3119 3120 results.append((idx, success, job_result)) 3121 3122 # sort based on the index, then drop it 3123 results.sort() 3124 results = [i[1:] for i in results] 3125 3126 return results
3127
3128 - def WaitOrShow(self, wait):
3129 """Wait for job results or only print the job IDs. 3130 3131 @type wait: boolean 3132 @param wait: whether to wait or not 3133 3134 """ 3135 if wait: 3136 return self.GetResults() 3137 else: 3138 if not self.jobs: 3139 self.SubmitPending() 3140 for _, status, result, name in self.jobs: 3141 if status: 3142 ToStdout("%s: %s", result, name) 3143 else: 3144 ToStderr("Failure for %s: %s", name, result) 3145 return [row[1:3] for row in self.jobs]
3146
3147 3148 -def FormatParameterDict(buf, param_dict, actual, level=1):
3149 """Formats a parameter dictionary. 3150 3151 @type buf: L{StringIO} 3152 @param buf: the buffer into which to write 3153 @type param_dict: dict 3154 @param param_dict: the own parameters 3155 @type actual: dict 3156 @param actual: the current parameter set (including defaults) 3157 @param level: Level of indent 3158 3159 """ 3160 indent = " " * level 3161 for key in sorted(actual): 3162 val = param_dict.get(key, "default (%s)" % actual[key]) 3163 buf.write("%s- %s: %s\n" % (indent, key, val))
3164
3165 3166 -def ConfirmOperation(names, list_type, text, extra=""):
3167 """Ask the user to confirm an operation on a list of list_type. 3168 3169 This function is used to request confirmation for doing an operation 3170 on a given list of list_type. 3171 3172 @type names: list 3173 @param names: the list of names that we display when 3174 we ask for confirmation 3175 @type list_type: str 3176 @param list_type: Human readable name for elements in the list (e.g. nodes) 3177 @type text: str 3178 @param text: the operation that the user should confirm 3179 @rtype: boolean 3180 @return: True or False depending on user's confirmation. 3181 3182 """ 3183 count = len(names) 3184 msg = ("The %s will operate on %d %s.\n%s" 3185 "Do you want to continue?" % (text, count, list_type, extra)) 3186 affected = (("\nAffected %s:\n" % list_type) + 3187 "\n".join([" %s" % name for name in names])) 3188 3189 choices = [("y", True, "Yes, execute the %s" % text), 3190 ("n", False, "No, abort the %s" % text)] 3191 3192 if count > 20: 3193 choices.insert(1, ("v", "v", "View the list of affected %s" % list_type)) 3194 question = msg 3195 else: 3196 question = msg + affected 3197 3198 choice = AskUser(question, choices) 3199 if choice == "v": 3200 choices.pop(1) 3201 choice = AskUser(msg + affected, choices) 3202 return choice
3203