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