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