4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
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.
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.
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
22 """Module dealing with command line parsing"""
30 from cStringIO import StringIO
32 from ganeti import utils
33 from ganeti import errors
34 from ganeti import constants
35 from ganeti import opcodes
36 from ganeti import luxi
37 from ganeti import ssconf
38 from ganeti import rpc
39 from ganeti import ssh
40 from ganeti import compat
41 from ganeti import netutils
43 from optparse import (OptionParser, TitledHelpFormatter,
44 Option, OptionValueError)
48 # Command line options
56 "CLUSTER_DOMAIN_SECRET_OPT",
72 "FILESTORE_DRIVER_OPT",
80 "DEFAULT_IALLOCATOR_OPT",
81 "IDENTIFY_DEFAULTS_OPT",
83 "IGNORE_FAILURES_OPT",
84 "IGNORE_REMOVE_FAILURES_OPT",
85 "IGNORE_SECONDARIES_OPT",
88 "MAINTAIN_NODE_HEALTH_OPT",
93 "NEW_CLUSTER_CERT_OPT",
94 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
95 "NEW_CONFD_HMAC_KEY_OPT",
100 "NODE_PLACEMENT_OPT",
101 "NODRBD_STORAGE_OPT",
107 "NOMODIFY_ETCHOSTS_OPT",
108 "NOMODIFY_SSH_SETUP_OPT",
114 "NOSSH_KEYCHECK_OPT",
126 "REMOVE_INSTANCE_OPT",
134 "SHUTDOWN_TIMEOUT_OPT",
149 # Generic functions for CLI programs
151 "GenericInstanceCreate",
155 "JobSubmittedException",
157 "RunWhileClusterStopped",
161 # Formatting functions
162 "ToStderr", "ToStdout",
172 # command line options support infrastructure
173 "ARGS_MANY_INSTANCES",
189 "OPT_COMPL_INST_ADD_NODES",
190 "OPT_COMPL_MANY_NODES",
191 "OPT_COMPL_ONE_IALLOCATOR",
192 "OPT_COMPL_ONE_INSTANCE",
193 "OPT_COMPL_ONE_NODE",
206 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
211 return ("<%s min=%s max=%s>" %
212 (self.__class__.__name__, self.min, self.max))
215 class ArgSuggest(_Argument):
216 """Suggesting argument.
218 Value can be any of the ones passed to the constructor.
221 # pylint: disable-msg=W0622
222 def __init__(self, min=0, max=None, choices=None):
223 _Argument.__init__(self, min=min, max=max)
224 self.choices = choices
227 return ("<%s min=%s max=%s choices=%r>" %
228 (self.__class__.__name__, self.min, self.max, self.choices))
231 class ArgChoice(ArgSuggest):
234 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
235 but value must be one of the choices.
240 class ArgUnknown(_Argument):
241 """Unknown argument to program (e.g. determined at runtime).
246 class ArgInstance(_Argument):
247 """Instances argument.
252 class ArgNode(_Argument):
257 class ArgJobId(_Argument):
263 class ArgFile(_Argument):
264 """File path argument.
269 class ArgCommand(_Argument):
275 class ArgHost(_Argument):
281 class ArgOs(_Argument):
288 ARGS_MANY_INSTANCES = [ArgInstance()]
289 ARGS_MANY_NODES = [ArgNode()]
290 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
291 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
292 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
295 def _ExtractTagsObject(opts, args):
296 """Extract the tag type object.
298 Note that this function will modify its args parameter.
301 if not hasattr(opts, "tag_type"):
302 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
304 if kind == constants.TAG_CLUSTER:
306 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
308 raise errors.OpPrereqError("no arguments passed to the command")
312 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
316 def _ExtendTags(opts, args):
317 """Extend the args if a source file has been given.
319 This function will extend the tags with the contents of the file
320 passed in the 'tags_source' attribute of the opts parameter. A file
321 named '-' will be replaced by stdin.
324 fname = opts.tags_source
330 new_fh = open(fname, "r")
333 # we don't use the nice 'new_data = [line.strip() for line in fh]'
334 # because of python bug 1633941
336 line = new_fh.readline()
339 new_data.append(line.strip())
342 args.extend(new_data)
345 def ListTags(opts, args):
346 """List the tags on a given object.
348 This is a generic implementation that knows how to deal with all
349 three cases of tag objects (cluster, node, instance). The opts
350 argument is expected to contain a tag_type field denoting what
351 object type we work on.
354 kind, name = _ExtractTagsObject(opts, args)
356 result = cl.QueryTags(kind, name)
357 result = list(result)
363 def AddTags(opts, args):
364 """Add tags on a given object.
366 This is a generic implementation that knows how to deal with all
367 three cases of tag objects (cluster, node, instance). The opts
368 argument is expected to contain a tag_type field denoting what
369 object type we work on.
372 kind, name = _ExtractTagsObject(opts, args)
373 _ExtendTags(opts, args)
375 raise errors.OpPrereqError("No tags to be added")
376 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
380 def RemoveTags(opts, args):
381 """Remove tags from a given object.
383 This is a generic implementation that knows how to deal with all
384 three cases of tag objects (cluster, node, instance). The opts
385 argument is expected to contain a tag_type field denoting what
386 object type we work on.
389 kind, name = _ExtractTagsObject(opts, args)
390 _ExtendTags(opts, args)
392 raise errors.OpPrereqError("No tags to be removed")
393 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
397 def check_unit(option, opt, value): # pylint: disable-msg=W0613
398 """OptParsers custom converter for units.
402 return utils.ParseUnit(value)
403 except errors.UnitParseError, err:
404 raise OptionValueError("option %s: %s" % (opt, err))
407 def _SplitKeyVal(opt, data):
408 """Convert a KeyVal string into a dict.
410 This function will convert a key=val[,...] string into a dict. Empty
411 values will be converted specially: keys which have the prefix 'no_'
412 will have the value=False and the prefix stripped, the others will
416 @param opt: a string holding the option name for which we process the
417 data, used in building error messages
419 @param data: a string of the format key=val,key=val,...
421 @return: {key=val, key=val}
422 @raises errors.ParameterError: if there are duplicate keys
427 for elem in utils.UnescapeAndSplit(data, sep=","):
429 key, val = elem.split("=", 1)
431 if elem.startswith(NO_PREFIX):
432 key, val = elem[len(NO_PREFIX):], False
433 elif elem.startswith(UN_PREFIX):
434 key, val = elem[len(UN_PREFIX):], None
436 key, val = elem, True
438 raise errors.ParameterError("Duplicate key '%s' in option %s" %
444 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
445 """Custom parser for ident:key=val,key=val options.
447 This will store the parsed values as a tuple (ident, {key: val}). As such,
448 multiple uses of this option via action=append is possible.
452 ident, rest = value, ''
454 ident, rest = value.split(":", 1)
456 if ident.startswith(NO_PREFIX):
458 msg = "Cannot pass options when removing parameter groups: %s" % value
459 raise errors.ParameterError(msg)
460 retval = (ident[len(NO_PREFIX):], False)
461 elif ident.startswith(UN_PREFIX):
463 msg = "Cannot pass options when removing parameter groups: %s" % value
464 raise errors.ParameterError(msg)
465 retval = (ident[len(UN_PREFIX):], None)
467 kv_dict = _SplitKeyVal(opt, rest)
468 retval = (ident, kv_dict)
472 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
473 """Custom parser class for key=val,key=val options.
475 This will store the parsed values as a dict {key: val}.
478 return _SplitKeyVal(opt, value)
481 def check_bool(option, opt, value): # pylint: disable-msg=W0613
482 """Custom parser for yes/no options.
484 This will store the parsed value as either True or False.
487 value = value.lower()
488 if value == constants.VALUE_FALSE or value == "no":
490 elif value == constants.VALUE_TRUE or value == "yes":
493 raise errors.ParameterError("Invalid boolean value '%s'" % value)
496 # completion_suggestion is normally a list. Using numeric values not evaluating
497 # to False for dynamic completion.
498 (OPT_COMPL_MANY_NODES,
500 OPT_COMPL_ONE_INSTANCE,
502 OPT_COMPL_ONE_IALLOCATOR,
503 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
505 OPT_COMPL_ALL = frozenset([
506 OPT_COMPL_MANY_NODES,
508 OPT_COMPL_ONE_INSTANCE,
510 OPT_COMPL_ONE_IALLOCATOR,
511 OPT_COMPL_INST_ADD_NODES,
515 class CliOption(Option):
516 """Custom option class for optparse.
519 ATTRS = Option.ATTRS + [
520 "completion_suggest",
522 TYPES = Option.TYPES + (
528 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
529 TYPE_CHECKER["identkeyval"] = check_ident_key_val
530 TYPE_CHECKER["keyval"] = check_key_val
531 TYPE_CHECKER["unit"] = check_unit
532 TYPE_CHECKER["bool"] = check_bool
535 # optparse.py sets make_option, so we do it for our own option class, too
536 cli_option = CliOption
541 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
542 help="Increase debugging level")
544 NOHDR_OPT = cli_option("--no-headers", default=False,
545 action="store_true", dest="no_headers",
546 help="Don't display column headers")
548 SEP_OPT = cli_option("--separator", default=None,
549 action="store", dest="separator",
550 help=("Separator between output fields"
551 " (defaults to one space)"))
553 USEUNITS_OPT = cli_option("--units", default=None,
554 dest="units", choices=('h', 'm', 'g', 't'),
555 help="Specify units for output (one of hmgt)")
557 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
558 type="string", metavar="FIELDS",
559 help="Comma separated list of output fields")
561 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
562 default=False, help="Force the operation")
564 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
565 default=False, help="Do not require confirmation")
567 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
568 default=None, help="File with tag names")
570 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
571 default=False, action="store_true",
572 help=("Submit the job and return the job ID, but"
573 " don't wait for the job to finish"))
575 SYNC_OPT = cli_option("--sync", dest="do_locking",
576 default=False, action="store_true",
577 help=("Grab locks while doing the queries"
578 " in order to ensure more consistent results"))
580 DRY_RUN_OPT = cli_option("--dry-run", default=False,
582 help=("Do not execute the operation, just run the"
583 " check steps and verify it it could be"
586 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
588 help="Increase the verbosity of the operation")
590 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
591 action="store_true", dest="simulate_errors",
592 help="Debugging option that makes the operation"
593 " treat most runtime checks as failed")
595 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
596 default=True, action="store_false",
597 help="Don't wait for sync (DANGEROUS!)")
599 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
600 help="Custom disk setup (diskless, file,"
602 default=None, metavar="TEMPL",
603 choices=list(constants.DISK_TEMPLATES))
605 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
606 help="Do not create any network cards for"
609 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
610 help="Relative path under default cluster-wide"
611 " file storage dir to store file-based disks",
612 default=None, metavar="<DIR>")
614 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
615 help="Driver to use for image files",
616 default="loop", metavar="<DRIVER>",
617 choices=list(constants.FILE_DRIVER))
619 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
620 help="Select nodes for the instance automatically"
621 " using the <NAME> iallocator plugin",
622 default=None, type="string",
623 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
625 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
627 help="Set the default instance allocator plugin",
628 default=None, type="string",
629 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
631 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
633 completion_suggest=OPT_COMPL_ONE_OS)
635 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
636 type="keyval", default={},
637 help="OS parameters")
639 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
640 action="store_true", default=False,
641 help="Force an unknown variant")
643 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
644 action="store_true", default=False,
645 help="Do not install the OS (will"
648 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
649 type="keyval", default={},
650 help="Backend parameters")
652 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
653 default={}, dest="hvparams",
654 help="Hypervisor parameters")
656 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
657 help="Hypervisor and hypervisor options, in the"
658 " format hypervisor:option=value,option=value,...",
659 default=None, type="identkeyval")
661 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
662 help="Hypervisor and hypervisor options, in the"
663 " format hypervisor:option=value,option=value,...",
664 default=[], action="append", type="identkeyval")
666 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
667 action="store_false",
668 help="Don't check that the instance's IP"
671 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
672 default=True, action="store_false",
673 help="Don't check that the instance's name"
676 NET_OPT = cli_option("--net",
677 help="NIC parameters", default=[],
678 dest="nics", action="append", type="identkeyval")
680 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
681 dest="disks", action="append", type="identkeyval")
683 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
684 help="Comma-separated list of disks"
685 " indices to act on (e.g. 0,2) (optional,"
686 " defaults to all disks)")
688 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
689 help="Enforces a single-disk configuration using the"
690 " given disk size, in MiB unless a suffix is used",
691 default=None, type="unit", metavar="<size>")
693 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
694 dest="ignore_consistency",
695 action="store_true", default=False,
696 help="Ignore the consistency of the disks on"
699 NONLIVE_OPT = cli_option("--non-live", dest="live",
700 default=True, action="store_false",
701 help="Do a non-live migration (this usually means"
702 " freeze the instance, save the state, transfer and"
703 " only then resume running on the secondary node)")
705 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
707 choices=list(constants.HT_MIGRATION_MODES),
708 help="Override default migration mode (choose"
709 " either live or non-live")
711 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
712 help="Target node and optional secondary node",
713 metavar="<pnode>[:<snode>]",
714 completion_suggest=OPT_COMPL_INST_ADD_NODES)
716 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
717 action="append", metavar="<node>",
718 help="Use only this node (can be used multiple"
719 " times, if not given defaults to all nodes)",
720 completion_suggest=OPT_COMPL_ONE_NODE)
722 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
724 completion_suggest=OPT_COMPL_ONE_NODE)
726 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
727 action="store_false",
728 help="Don't start the instance after creation")
730 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
731 action="store_true", default=False,
732 help="Show command instead of executing it")
734 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
735 default=False, action="store_true",
736 help="Instead of performing the migration, try to"
737 " recover from a failed cleanup. This is safe"
738 " to run even if the instance is healthy, but it"
739 " will create extra replication traffic and "
740 " disrupt briefly the replication (like during the"
743 STATIC_OPT = cli_option("-s", "--static", dest="static",
744 action="store_true", default=False,
745 help="Only show configuration data, not runtime data")
747 ALL_OPT = cli_option("--all", dest="show_all",
748 default=False, action="store_true",
749 help="Show info on all instances on the cluster."
750 " This can take a long time to run, use wisely")
752 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
753 action="store_true", default=False,
754 help="Interactive OS reinstall, lists available"
755 " OS templates for selection")
757 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
758 action="store_true", default=False,
759 help="Remove the instance from the cluster"
760 " configuration even if there are failures"
761 " during the removal process")
763 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
764 dest="ignore_remove_failures",
765 action="store_true", default=False,
766 help="Remove the instance from the"
767 " cluster configuration even if there"
768 " are failures during the removal"
771 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
772 action="store_true", default=False,
773 help="Remove the instance from the cluster")
775 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
776 help="Specifies the new secondary node",
777 metavar="NODE", default=None,
778 completion_suggest=OPT_COMPL_ONE_NODE)
780 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
781 default=False, action="store_true",
782 help="Replace the disk(s) on the primary"
783 " node (only for the drbd template)")
785 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
786 default=False, action="store_true",
787 help="Replace the disk(s) on the secondary"
788 " node (only for the drbd template)")
790 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
791 default=False, action="store_true",
792 help="Lock all nodes and auto-promote as needed"
795 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
796 default=False, action="store_true",
797 help="Automatically replace faulty disks"
798 " (only for the drbd template)")
800 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
801 default=False, action="store_true",
802 help="Ignore current recorded size"
803 " (useful for forcing activation when"
804 " the recorded size is wrong)")
806 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
808 completion_suggest=OPT_COMPL_ONE_NODE)
810 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
813 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
814 help="Specify the secondary ip for the node",
815 metavar="ADDRESS", default=None)
817 READD_OPT = cli_option("--readd", dest="readd",
818 default=False, action="store_true",
819 help="Readd old node after replacing it")
821 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
822 default=True, action="store_false",
823 help="Disable SSH key fingerprint checking")
826 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
827 type="bool", default=None, metavar=_YORNO,
828 help="Set the master_candidate flag on the node")
830 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
831 type="bool", default=None,
832 help="Set the offline flag on the node")
834 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
835 type="bool", default=None,
836 help="Set the drained flag on the node")
838 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
839 type="bool", default=None, metavar=_YORNO,
840 help="Set the allocatable flag on a volume")
842 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
843 help="Disable support for lvm based instances"
845 action="store_false", default=True)
847 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
848 dest="enabled_hypervisors",
849 help="Comma-separated list of hypervisors",
850 type="string", default=None)
852 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
853 type="keyval", default={},
854 help="NIC parameters")
856 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
857 dest="candidate_pool_size", type="int",
858 help="Set the candidate pool size")
860 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
861 help="Enables LVM and specifies the volume group"
862 " name (cluster-wide) for disk allocation [xenvg]",
863 metavar="VG", default=None)
865 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
866 help="Destroy cluster", action="store_true")
868 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
869 help="Skip node agreement check (dangerous)",
870 action="store_true", default=False)
872 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
873 help="Specify the mac prefix for the instance IP"
874 " addresses, in the format XX:XX:XX",
878 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
879 help="Specify the node interface (cluster-wide)"
880 " on which the master IP address will be added "
881 " [%s]" % constants.DEFAULT_BRIDGE,
883 default=constants.DEFAULT_BRIDGE)
885 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
886 help="Specify the default directory (cluster-"
887 "wide) for storing the file-based disks [%s]" %
888 constants.DEFAULT_FILE_STORAGE_DIR,
890 default=constants.DEFAULT_FILE_STORAGE_DIR)
892 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
893 help="Don't modify /etc/hosts",
894 action="store_false", default=True)
896 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
897 help="Don't initialize SSH keys",
898 action="store_false", default=True)
900 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
901 help="Enable parseable error messages",
902 action="store_true", default=False)
904 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
905 help="Skip N+1 memory redundancy tests",
906 action="store_true", default=False)
908 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
909 help="Type of reboot: soft/hard/full",
910 default=constants.INSTANCE_REBOOT_HARD,
912 choices=list(constants.REBOOT_TYPES))
914 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
915 dest="ignore_secondaries",
916 default=False, action="store_true",
917 help="Ignore errors from secondaries")
919 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
920 action="store_false", default=True,
921 help="Don't shutdown the instance (unsafe)")
923 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
924 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
925 help="Maximum time to wait")
927 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
928 dest="shutdown_timeout", type="int",
929 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
930 help="Maximum time to wait for instance shutdown")
932 EARLY_RELEASE_OPT = cli_option("--early-release",
933 dest="early_release", default=False,
935 help="Release the locks on the secondary"
938 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
939 dest="new_cluster_cert",
940 default=False, action="store_true",
941 help="Generate a new cluster certificate")
943 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
945 help="File containing new RAPI certificate")
947 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
948 default=None, action="store_true",
949 help=("Generate a new self-signed RAPI"
952 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
953 dest="new_confd_hmac_key",
954 default=False, action="store_true",
955 help=("Create a new HMAC key for %s" %
958 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
959 dest="cluster_domain_secret",
961 help=("Load new new cluster domain"
962 " secret from file"))
964 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
965 dest="new_cluster_domain_secret",
966 default=False, action="store_true",
967 help=("Create a new cluster domain"
970 USE_REPL_NET_OPT = cli_option("--use-replication-network",
971 dest="use_replication_network",
972 help="Whether to use the replication network"
973 " for talking to the nodes",
974 action="store_true", default=False)
976 MAINTAIN_NODE_HEALTH_OPT = \
977 cli_option("--maintain-node-health", dest="maintain_node_health",
978 metavar=_YORNO, default=None, type="bool",
979 help="Configure the cluster to automatically maintain node"
980 " health, by shutting down unknown instances, shutting down"
981 " unknown DRBD devices, etc.")
983 IDENTIFY_DEFAULTS_OPT = \
984 cli_option("--identify-defaults", dest="identify_defaults",
985 default=False, action="store_true",
986 help="Identify which saved instance parameters are equal to"
987 " the current cluster defaults and set them as such, instead"
988 " of marking them as overridden")
990 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
991 action="store", dest="uid_pool",
992 help=("A list of user-ids or user-id"
993 " ranges separated by commas"))
995 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
996 action="store", dest="add_uids",
997 help=("A list of user-ids or user-id"
998 " ranges separated by commas, to be"
999 " added to the user-id pool"))
1001 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1002 action="store", dest="remove_uids",
1003 help=("A list of user-ids or user-id"
1004 " ranges separated by commas, to be"
1005 " removed from the user-id pool"))
1007 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1008 action="store", dest="reserved_lvs",
1009 help=("A comma-separated list of reserved"
1010 " logical volumes names, that will be"
1011 " ignored by cluster verify"))
1013 ROMAN_OPT = cli_option("--roman",
1014 dest="roman_integers", default=False,
1015 action="store_true",
1016 help="Use roman numbers for positive integers")
1018 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1019 action="store", default=None,
1020 help="Specifies usermode helper for DRBD")
1022 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1023 action="store_false", default=True,
1024 help="Disable support for DRBD")
1027 def _ParseArgs(argv, commands, aliases):
1028 """Parser for the command line arguments.
1030 This function parses the arguments and returns the function which
1031 must be executed together with its (modified) arguments.
1033 @param argv: the command line
1034 @param commands: dictionary with special contents, see the design
1035 doc for cmdline handling
1036 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1040 binary = "<command>"
1042 binary = argv[0].split("/")[-1]
1044 if len(argv) > 1 and argv[1] == "--version":
1045 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1046 constants.RELEASE_VERSION)
1047 # Quit right away. That way we don't have to care about this special
1048 # argument. optparse.py does it the same.
1051 if len(argv) < 2 or not (argv[1] in commands or
1052 argv[1] in aliases):
1053 # let's do a nice thing
1054 sortedcmds = commands.keys()
1057 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1058 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1061 # compute the max line length for cmd + usage
1062 mlen = max([len(" %s" % cmd) for cmd in commands])
1063 mlen = min(60, mlen) # should not get here...
1065 # and format a nice command list
1066 ToStdout("Commands:")
1067 for cmd in sortedcmds:
1068 cmdstr = " %s" % (cmd,)
1069 help_text = commands[cmd][4]
1070 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1071 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1072 for line in help_lines:
1073 ToStdout("%-*s %s", mlen, "", line)
1077 return None, None, None
1079 # get command, unalias it, and look it up in commands
1083 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1086 if aliases[cmd] not in commands:
1087 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1088 " command '%s'" % (cmd, aliases[cmd]))
1092 func, args_def, parser_opts, usage, description = commands[cmd]
1093 parser = OptionParser(option_list=parser_opts + [DEBUG_OPT],
1094 description=description,
1095 formatter=TitledHelpFormatter(),
1096 usage="%%prog %s %s" % (cmd, usage))
1097 parser.disable_interspersed_args()
1098 options, args = parser.parse_args()
1100 if not _CheckArguments(cmd, args_def, args):
1101 return None, None, None
1103 return func, options, args
1106 def _CheckArguments(cmd, args_def, args):
1107 """Verifies the arguments using the argument definition.
1111 1. Abort with error if values specified by user but none expected.
1113 1. For each argument in definition
1115 1. Keep running count of minimum number of values (min_count)
1116 1. Keep running count of maximum number of values (max_count)
1117 1. If it has an unlimited number of values
1119 1. Abort with error if it's not the last argument in the definition
1121 1. If last argument has limited number of values
1123 1. Abort with error if number of values doesn't match or is too large
1125 1. Abort with error if user didn't pass enough values (min_count)
1128 if args and not args_def:
1129 ToStderr("Error: Command %s expects no arguments", cmd)
1136 last_idx = len(args_def) - 1
1138 for idx, arg in enumerate(args_def):
1139 if min_count is None:
1141 elif arg.min is not None:
1142 min_count += arg.min
1144 if max_count is None:
1146 elif arg.max is not None:
1147 max_count += arg.max
1150 check_max = (arg.max is not None)
1152 elif arg.max is None:
1153 raise errors.ProgrammerError("Only the last argument can have max=None")
1156 # Command with exact number of arguments
1157 if (min_count is not None and max_count is not None and
1158 min_count == max_count and len(args) != min_count):
1159 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1162 # Command with limited number of arguments
1163 if max_count is not None and len(args) > max_count:
1164 ToStderr("Error: Command %s expects only %d argument(s)",
1168 # Command with some required arguments
1169 if min_count is not None and len(args) < min_count:
1170 ToStderr("Error: Command %s expects at least %d argument(s)",
1177 def SplitNodeOption(value):
1178 """Splits the value of a --node option.
1181 if value and ':' in value:
1182 return value.split(':', 1)
1184 return (value, None)
1187 def CalculateOSNames(os_name, os_variants):
1188 """Calculates all the names an OS can be called, according to its variants.
1190 @type os_name: string
1191 @param os_name: base name of the os
1192 @type os_variants: list or None
1193 @param os_variants: list of supported variants
1195 @return: list of valid names
1199 return ['%s+%s' % (os_name, v) for v in os_variants]
1204 def ParseFields(selected, default):
1205 """Parses the values of "--field"-like options.
1207 @type selected: string or None
1208 @param selected: User-selected options
1210 @param default: Default fields
1213 if selected is None:
1216 if selected.startswith("+"):
1217 return default + selected[1:].split(",")
1219 return selected.split(",")
1222 UsesRPC = rpc.RunWithRPC
1225 def AskUser(text, choices=None):
1226 """Ask the user a question.
1228 @param text: the question to ask
1230 @param choices: list with elements tuples (input_char, return_value,
1231 description); if not given, it will default to: [('y', True,
1232 'Perform the operation'), ('n', False, 'Do no do the operation')];
1233 note that the '?' char is reserved for help
1235 @return: one of the return values from the choices list; if input is
1236 not possible (i.e. not running with a tty, we return the last
1241 choices = [('y', True, 'Perform the operation'),
1242 ('n', False, 'Do not perform the operation')]
1243 if not choices or not isinstance(choices, list):
1244 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1245 for entry in choices:
1246 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1247 raise errors.ProgrammerError("Invalid choices element to AskUser")
1249 answer = choices[-1][1]
1251 for line in text.splitlines():
1252 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1253 text = "\n".join(new_text)
1255 f = file("/dev/tty", "a+")
1259 chars = [entry[0] for entry in choices]
1260 chars[-1] = "[%s]" % chars[-1]
1262 maps = dict([(entry[0], entry[1]) for entry in choices])
1266 f.write("/".join(chars))
1268 line = f.readline(2).strip().lower()
1273 for entry in choices:
1274 f.write(" %s - %s\n" % (entry[0], entry[2]))
1282 class JobSubmittedException(Exception):
1283 """Job was submitted, client should exit.
1285 This exception has one argument, the ID of the job that was
1286 submitted. The handler should print this ID.
1288 This is not an error, just a structured way to exit from clients.
1293 def SendJob(ops, cl=None):
1294 """Function to submit an opcode without waiting for the results.
1297 @param ops: list of opcodes
1298 @type cl: luxi.Client
1299 @param cl: the luxi client to use for communicating with the master;
1300 if None, a new client will be created
1306 job_id = cl.SubmitJob(ops)
1311 def GenericPollJob(job_id, cbs, report_cbs):
1312 """Generic job-polling function.
1314 @type job_id: number
1315 @param job_id: Job ID
1316 @type cbs: Instance of L{JobPollCbBase}
1317 @param cbs: Data callbacks
1318 @type report_cbs: Instance of L{JobPollReportCbBase}
1319 @param report_cbs: Reporting callbacks
1322 prev_job_info = None
1323 prev_logmsg_serial = None
1328 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1331 # job not found, go away!
1332 raise errors.JobLost("Job with id %s lost" % job_id)
1334 if result == constants.JOB_NOTCHANGED:
1335 report_cbs.ReportNotChanged(job_id, status)
1340 # Split result, a tuple of (field values, log entries)
1341 (job_info, log_entries) = result
1342 (status, ) = job_info
1345 for log_entry in log_entries:
1346 (serial, timestamp, log_type, message) = log_entry
1347 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1349 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1351 # TODO: Handle canceled and archived jobs
1352 elif status in (constants.JOB_STATUS_SUCCESS,
1353 constants.JOB_STATUS_ERROR,
1354 constants.JOB_STATUS_CANCELING,
1355 constants.JOB_STATUS_CANCELED):
1358 prev_job_info = job_info
1360 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1362 raise errors.JobLost("Job with id %s lost" % job_id)
1364 status, opstatus, result = jobs[0]
1366 if status == constants.JOB_STATUS_SUCCESS:
1369 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1370 raise errors.OpExecError("Job was canceled")
1373 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1374 if status == constants.OP_STATUS_SUCCESS:
1376 elif status == constants.OP_STATUS_ERROR:
1377 errors.MaybeRaise(msg)
1380 raise errors.OpExecError("partial failure (opcode %d): %s" %
1383 raise errors.OpExecError(str(msg))
1385 # default failure mode
1386 raise errors.OpExecError(result)
1389 class JobPollCbBase:
1390 """Base class for L{GenericPollJob} callbacks.
1394 """Initializes this class.
1398 def WaitForJobChangeOnce(self, job_id, fields,
1399 prev_job_info, prev_log_serial):
1400 """Waits for changes on a job.
1403 raise NotImplementedError()
1405 def QueryJobs(self, job_ids, fields):
1406 """Returns the selected fields for the selected job IDs.
1408 @type job_ids: list of numbers
1409 @param job_ids: Job IDs
1410 @type fields: list of strings
1411 @param fields: Fields
1414 raise NotImplementedError()
1417 class JobPollReportCbBase:
1418 """Base class for L{GenericPollJob} reporting callbacks.
1422 """Initializes this class.
1426 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1427 """Handles a log message.
1430 raise NotImplementedError()
1432 def ReportNotChanged(self, job_id, status):
1433 """Called for if a job hasn't changed in a while.
1435 @type job_id: number
1436 @param job_id: Job ID
1437 @type status: string or None
1438 @param status: Job status if available
1441 raise NotImplementedError()
1444 class _LuxiJobPollCb(JobPollCbBase):
1445 def __init__(self, cl):
1446 """Initializes this class.
1449 JobPollCbBase.__init__(self)
1452 def WaitForJobChangeOnce(self, job_id, fields,
1453 prev_job_info, prev_log_serial):
1454 """Waits for changes on a job.
1457 return self.cl.WaitForJobChangeOnce(job_id, fields,
1458 prev_job_info, prev_log_serial)
1460 def QueryJobs(self, job_ids, fields):
1461 """Returns the selected fields for the selected job IDs.
1464 return self.cl.QueryJobs(job_ids, fields)
1467 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1468 def __init__(self, feedback_fn):
1469 """Initializes this class.
1472 JobPollReportCbBase.__init__(self)
1474 self.feedback_fn = feedback_fn
1476 assert callable(feedback_fn)
1478 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1479 """Handles a log message.
1482 self.feedback_fn((timestamp, log_type, log_msg))
1484 def ReportNotChanged(self, job_id, status):
1485 """Called if a job hasn't changed in a while.
1491 class StdioJobPollReportCb(JobPollReportCbBase):
1493 """Initializes this class.
1496 JobPollReportCbBase.__init__(self)
1498 self.notified_queued = False
1499 self.notified_waitlock = False
1501 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1502 """Handles a log message.
1505 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1506 FormatLogMessage(log_type, log_msg))
1508 def ReportNotChanged(self, job_id, status):
1509 """Called if a job hasn't changed in a while.
1515 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1516 ToStderr("Job %s is waiting in queue", job_id)
1517 self.notified_queued = True
1519 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1520 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1521 self.notified_waitlock = True
1524 def FormatLogMessage(log_type, log_msg):
1525 """Formats a job message according to its type.
1528 if log_type != constants.ELOG_MESSAGE:
1529 log_msg = str(log_msg)
1531 return utils.SafeEncode(log_msg)
1534 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1535 """Function to poll for the result of a job.
1537 @type job_id: job identified
1538 @param job_id: the job to poll for results
1539 @type cl: luxi.Client
1540 @param cl: the luxi client to use for communicating with the master;
1541 if None, a new client will be created
1547 if reporter is None:
1549 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1551 reporter = StdioJobPollReportCb()
1553 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1555 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1558 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1559 """Legacy function to submit an opcode.
1561 This is just a simple wrapper over the construction of the processor
1562 instance. It should be extended to better handle feedback and
1563 interaction functions.
1569 SetGenericOpcodeOpts([op], opts)
1571 job_id = SendJob([op], cl=cl)
1573 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1576 return op_results[0]
1579 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1580 """Wrapper around SubmitOpCode or SendJob.
1582 This function will decide, based on the 'opts' parameter, whether to
1583 submit and wait for the result of the opcode (and return it), or
1584 whether to just send the job and print its identifier. It is used in
1585 order to simplify the implementation of the '--submit' option.
1587 It will also process the opcodes if we're sending the via SendJob
1588 (otherwise SubmitOpCode does it).
1591 if opts and opts.submit_only:
1593 SetGenericOpcodeOpts(job, opts)
1594 job_id = SendJob(job, cl=cl)
1595 raise JobSubmittedException(job_id)
1597 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1600 def SetGenericOpcodeOpts(opcode_list, options):
1601 """Processor for generic options.
1603 This function updates the given opcodes based on generic command
1604 line options (like debug, dry-run, etc.).
1606 @param opcode_list: list of opcodes
1607 @param options: command line options or None
1608 @return: None (in-place modification)
1613 for op in opcode_list:
1614 if hasattr(options, "dry_run"):
1615 op.dry_run = options.dry_run
1616 op.debug_level = options.debug
1620 # TODO: Cache object?
1622 client = luxi.Client()
1623 except luxi.NoMasterError:
1624 ss = ssconf.SimpleStore()
1626 # Try to read ssconf file
1629 except errors.ConfigurationError:
1630 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1631 " not part of a cluster")
1633 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1634 if master != myself:
1635 raise errors.OpPrereqError("This is not the master node, please connect"
1636 " to node '%s' and rerun the command" %
1642 def FormatError(err):
1643 """Return a formatted error message for a given error.
1645 This function takes an exception instance and returns a tuple
1646 consisting of two values: first, the recommended exit code, and
1647 second, a string describing the error message (not
1648 newline-terminated).
1654 if isinstance(err, errors.ConfigurationError):
1655 txt = "Corrupt configuration file: %s" % msg
1657 obuf.write(txt + "\n")
1658 obuf.write("Aborting.")
1660 elif isinstance(err, errors.HooksAbort):
1661 obuf.write("Failure: hooks execution failed:\n")
1662 for node, script, out in err.args[0]:
1664 obuf.write(" node: %s, script: %s, output: %s\n" %
1665 (node, script, out))
1667 obuf.write(" node: %s, script: %s (no output)\n" %
1669 elif isinstance(err, errors.HooksFailure):
1670 obuf.write("Failure: hooks general failure: %s" % msg)
1671 elif isinstance(err, errors.ResolverError):
1672 this_host = netutils.HostInfo.SysName()
1673 if err.args[0] == this_host:
1674 msg = "Failure: can't resolve my own hostname ('%s')"
1676 msg = "Failure: can't resolve hostname '%s'"
1677 obuf.write(msg % err.args[0])
1678 elif isinstance(err, errors.OpPrereqError):
1679 if len(err.args) == 2:
1680 obuf.write("Failure: prerequisites not met for this"
1681 " operation:\nerror type: %s, error details:\n%s" %
1682 (err.args[1], err.args[0]))
1684 obuf.write("Failure: prerequisites not met for this"
1685 " operation:\n%s" % msg)
1686 elif isinstance(err, errors.OpExecError):
1687 obuf.write("Failure: command execution error:\n%s" % msg)
1688 elif isinstance(err, errors.TagError):
1689 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1690 elif isinstance(err, errors.JobQueueDrainError):
1691 obuf.write("Failure: the job queue is marked for drain and doesn't"
1692 " accept new requests\n")
1693 elif isinstance(err, errors.JobQueueFull):
1694 obuf.write("Failure: the job queue is full and doesn't accept new"
1695 " job submissions until old jobs are archived\n")
1696 elif isinstance(err, errors.TypeEnforcementError):
1697 obuf.write("Parameter Error: %s" % msg)
1698 elif isinstance(err, errors.ParameterError):
1699 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1700 elif isinstance(err, luxi.NoMasterError):
1701 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1702 " and listening for connections?")
1703 elif isinstance(err, luxi.TimeoutError):
1704 obuf.write("Timeout while talking to the master daemon. Error:\n"
1706 elif isinstance(err, luxi.PermissionError):
1707 obuf.write("It seems you don't have permissions to connect to the"
1708 " master daemon.\nPlease retry as a different user.")
1709 elif isinstance(err, luxi.ProtocolError):
1710 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1712 elif isinstance(err, errors.JobLost):
1713 obuf.write("Error checking job status: %s" % msg)
1714 elif isinstance(err, errors.GenericError):
1715 obuf.write("Unhandled Ganeti error: %s" % msg)
1716 elif isinstance(err, JobSubmittedException):
1717 obuf.write("JobID: %s\n" % err.args[0])
1720 obuf.write("Unhandled exception: %s" % msg)
1721 return retcode, obuf.getvalue().rstrip('\n')
1724 def GenericMain(commands, override=None, aliases=None):
1725 """Generic main function for all the gnt-* commands.
1728 - commands: a dictionary with a special structure, see the design doc
1729 for command line handling.
1730 - override: if not None, we expect a dictionary with keys that will
1731 override command line options; this can be used to pass
1732 options from the scripts to generic functions
1733 - aliases: dictionary with command aliases {'alias': 'target, ...}
1736 # save the program name and the entire command line for later logging
1738 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1739 if len(sys.argv) >= 2:
1740 binary += " " + sys.argv[1]
1741 old_cmdline = " ".join(sys.argv[2:])
1745 binary = "<unknown program>"
1752 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1753 except errors.ParameterError, err:
1754 result, err_msg = FormatError(err)
1758 if func is None: # parse error
1761 if override is not None:
1762 for key, val in override.iteritems():
1763 setattr(options, key, val)
1765 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1766 stderr_logging=True, program=binary)
1769 logging.info("run with arguments '%s'", old_cmdline)
1771 logging.info("run with no arguments")
1774 result = func(options, args)
1775 except (errors.GenericError, luxi.ProtocolError,
1776 JobSubmittedException), err:
1777 result, err_msg = FormatError(err)
1778 logging.exception("Error during command processing")
1784 def GenericInstanceCreate(mode, opts, args):
1785 """Add an instance to the cluster via either creation or import.
1787 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1788 @param opts: the command line options selected by the user
1790 @param args: should contain only one element, the new instance name
1792 @return: the desired exit code
1797 (pnode, snode) = SplitNodeOption(opts.node)
1802 hypervisor, hvparams = opts.hypervisor
1806 nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1807 except ValueError, err:
1808 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1809 nics = [{}] * nic_max
1810 for nidx, ndict in opts.nics:
1812 if not isinstance(ndict, dict):
1813 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1814 raise errors.OpPrereqError(msg)
1819 elif mode == constants.INSTANCE_CREATE:
1820 # default of one nic, all auto
1826 if opts.disk_template == constants.DT_DISKLESS:
1827 if opts.disks or opts.sd_size is not None:
1828 raise errors.OpPrereqError("Diskless instance but disk"
1829 " information passed")
1832 if (not opts.disks and not opts.sd_size
1833 and mode == constants.INSTANCE_CREATE):
1834 raise errors.OpPrereqError("No disk information specified")
1835 if opts.disks and opts.sd_size is not None:
1836 raise errors.OpPrereqError("Please use either the '--disk' or"
1838 if opts.sd_size is not None:
1839 opts.disks = [(0, {"size": opts.sd_size})]
1843 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1844 except ValueError, err:
1845 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1846 disks = [{}] * disk_max
1849 for didx, ddict in opts.disks:
1851 if not isinstance(ddict, dict):
1852 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1853 raise errors.OpPrereqError(msg)
1854 elif "size" in ddict:
1855 if "adopt" in ddict:
1856 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1857 " (disk %d)" % didx)
1859 ddict["size"] = utils.ParseUnit(ddict["size"])
1860 except ValueError, err:
1861 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1863 elif "adopt" in ddict:
1864 if mode == constants.INSTANCE_IMPORT:
1865 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1869 raise errors.OpPrereqError("Missing size or adoption source for"
1873 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1874 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1876 if mode == constants.INSTANCE_CREATE:
1879 force_variant = opts.force_variant
1882 no_install = opts.no_install
1883 identify_defaults = False
1884 elif mode == constants.INSTANCE_IMPORT:
1887 force_variant = False
1888 src_node = opts.src_node
1889 src_path = opts.src_dir
1891 identify_defaults = opts.identify_defaults
1893 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1895 op = opcodes.OpCreateInstance(instance_name=instance,
1897 disk_template=opts.disk_template,
1899 pnode=pnode, snode=snode,
1900 ip_check=opts.ip_check,
1901 name_check=opts.name_check,
1902 wait_for_sync=opts.wait_for_sync,
1903 file_storage_dir=opts.file_storage_dir,
1904 file_driver=opts.file_driver,
1905 iallocator=opts.iallocator,
1906 hypervisor=hypervisor,
1908 beparams=opts.beparams,
1909 osparams=opts.osparams,
1913 force_variant=force_variant,
1916 no_install=no_install,
1917 identify_defaults=identify_defaults)
1919 SubmitOrSend(op, opts)
1923 class _RunWhileClusterStoppedHelper:
1924 """Helper class for L{RunWhileClusterStopped} to simplify state management
1927 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
1928 """Initializes this class.
1930 @type feedback_fn: callable
1931 @param feedback_fn: Feedback function
1932 @type cluster_name: string
1933 @param cluster_name: Cluster name
1934 @type master_node: string
1935 @param master_node Master node name
1936 @type online_nodes: list
1937 @param online_nodes: List of names of online nodes
1940 self.feedback_fn = feedback_fn
1941 self.cluster_name = cluster_name
1942 self.master_node = master_node
1943 self.online_nodes = online_nodes
1945 self.ssh = ssh.SshRunner(self.cluster_name)
1947 self.nonmaster_nodes = [name for name in online_nodes
1948 if name != master_node]
1950 assert self.master_node not in self.nonmaster_nodes
1952 def _RunCmd(self, node_name, cmd):
1953 """Runs a command on the local or a remote machine.
1955 @type node_name: string
1956 @param node_name: Machine name
1961 if node_name is None or node_name == self.master_node:
1962 # No need to use SSH
1963 result = utils.RunCmd(cmd)
1965 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
1968 errmsg = ["Failed to run command %s" % result.cmd]
1970 errmsg.append("on node %s" % node_name)
1971 errmsg.append(": exitcode %s and error %s" %
1972 (result.exit_code, result.output))
1973 raise errors.OpExecError(" ".join(errmsg))
1975 def Call(self, fn, *args):
1976 """Call function while all daemons are stopped.
1979 @param fn: Function to be called
1982 # Pause watcher by acquiring an exclusive lock on watcher state file
1983 self.feedback_fn("Blocking watcher")
1984 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
1986 # TODO: Currently, this just blocks. There's no timeout.
1987 # TODO: Should it be a shared lock?
1988 watcher_block.Exclusive(blocking=True)
1990 # Stop master daemons, so that no new jobs can come in and all running
1992 self.feedback_fn("Stopping master daemons")
1993 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
1995 # Stop daemons on all nodes
1996 for node_name in self.online_nodes:
1997 self.feedback_fn("Stopping daemons on %s" % node_name)
1998 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2000 # All daemons are shut down now
2002 return fn(self, *args)
2003 except Exception, err:
2004 _, errmsg = FormatError(err)
2005 logging.exception("Caught exception")
2006 self.feedback_fn(errmsg)
2009 # Start cluster again, master node last
2010 for node_name in self.nonmaster_nodes + [self.master_node]:
2011 self.feedback_fn("Starting daemons on %s" % node_name)
2012 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2015 watcher_block.Close()
2018 def RunWhileClusterStopped(feedback_fn, fn, *args):
2019 """Calls a function while all cluster daemons are stopped.
2021 @type feedback_fn: callable
2022 @param feedback_fn: Feedback function
2024 @param fn: Function to be called when daemons are stopped
2027 feedback_fn("Gathering cluster information")
2029 # This ensures we're running on the master daemon
2032 (cluster_name, master_node) = \
2033 cl.QueryConfigValues(["cluster_name", "master_node"])
2035 online_nodes = GetOnlineNodes([], cl=cl)
2037 # Don't keep a reference to the client. The master daemon will go away.
2040 assert master_node in online_nodes
2042 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2043 online_nodes).Call(fn, *args)
2046 def GenerateTable(headers, fields, separator, data,
2047 numfields=None, unitfields=None,
2049 """Prints a table with headers and different fields.
2052 @param headers: dictionary mapping field names to headers for
2055 @param fields: the field names corresponding to each row in
2057 @param separator: the separator to be used; if this is None,
2058 the default 'smart' algorithm is used which computes optimal
2059 field width, otherwise just the separator is used between
2062 @param data: a list of lists, each sublist being one row to be output
2063 @type numfields: list
2064 @param numfields: a list with the fields that hold numeric
2065 values and thus should be right-aligned
2066 @type unitfields: list
2067 @param unitfields: a list with the fields that hold numeric
2068 values that should be formatted with the units field
2069 @type units: string or None
2070 @param units: the units we should use for formatting, or None for
2071 automatic choice (human-readable for non-separator usage, otherwise
2072 megabytes); this is a one-letter string
2081 if numfields is None:
2083 if unitfields is None:
2086 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2087 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2090 for field in fields:
2091 if headers and field not in headers:
2092 # TODO: handle better unknown fields (either revert to old
2093 # style of raising exception, or deal more intelligently with
2095 headers[field] = field
2096 if separator is not None:
2097 format_fields.append("%s")
2098 elif numfields.Matches(field):
2099 format_fields.append("%*s")
2101 format_fields.append("%-*s")
2103 if separator is None:
2104 mlens = [0 for name in fields]
2105 format_str = ' '.join(format_fields)
2107 format_str = separator.replace("%", "%%").join(format_fields)
2112 for idx, val in enumerate(row):
2113 if unitfields.Matches(fields[idx]):
2116 except (TypeError, ValueError):
2119 val = row[idx] = utils.FormatUnit(val, units)
2120 val = row[idx] = str(val)
2121 if separator is None:
2122 mlens[idx] = max(mlens[idx], len(val))
2127 for idx, name in enumerate(fields):
2129 if separator is None:
2130 mlens[idx] = max(mlens[idx], len(hdr))
2131 args.append(mlens[idx])
2133 result.append(format_str % tuple(args))
2135 if separator is None:
2136 assert len(mlens) == len(fields)
2138 if fields and not numfields.Matches(fields[-1]):
2144 line = ['-' for _ in fields]
2145 for idx in range(len(fields)):
2146 if separator is None:
2147 args.append(mlens[idx])
2148 args.append(line[idx])
2149 result.append(format_str % tuple(args))
2154 def FormatTimestamp(ts):
2155 """Formats a given timestamp.
2158 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2161 @return: a string with the formatted timestamp
2164 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2167 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2170 def ParseTimespec(value):
2171 """Parse a time specification.
2173 The following suffixed will be recognized:
2181 Without any suffix, the value will be taken to be in seconds.
2186 raise errors.OpPrereqError("Empty time specification passed")
2194 if value[-1] not in suffix_map:
2197 except (TypeError, ValueError):
2198 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2200 multiplier = suffix_map[value[-1]]
2202 if not value: # no data left after stripping the suffix
2203 raise errors.OpPrereqError("Invalid time specification (only"
2206 value = int(value) * multiplier
2207 except (TypeError, ValueError):
2208 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2212 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2213 filter_master=False):
2214 """Returns the names of online nodes.
2216 This function will also log a warning on stderr with the names of
2219 @param nodes: if not empty, use only this subset of nodes (minus the
2221 @param cl: if not None, luxi client to use
2222 @type nowarn: boolean
2223 @param nowarn: by default, this function will output a note with the
2224 offline nodes that are skipped; if this parameter is True the
2225 note is not displayed
2226 @type secondary_ips: boolean
2227 @param secondary_ips: if True, return the secondary IPs instead of the
2228 names, useful for doing network traffic over the replication interface
2230 @type filter_master: boolean
2231 @param filter_master: if True, do not return the master node in the list
2232 (useful in coordination with secondary_ips where we cannot check our
2233 node name against the list)
2245 master_node = cl.QueryConfigValues(["master_node"])[0]
2246 filter_fn = lambda x: x != master_node
2248 filter_fn = lambda _: True
2250 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2252 offline = [row[0] for row in result if row[1]]
2253 if offline and not nowarn:
2254 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2255 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2258 def _ToStream(stream, txt, *args):
2259 """Write a message to a stream, bypassing the logging system
2261 @type stream: file object
2262 @param stream: the file to which we should write
2264 @param txt: the message
2269 stream.write(txt % args)
2276 def ToStdout(txt, *args):
2277 """Write a message to stdout only, bypassing the logging system
2279 This is just a wrapper over _ToStream.
2282 @param txt: the message
2285 _ToStream(sys.stdout, txt, *args)
2288 def ToStderr(txt, *args):
2289 """Write a message to stderr only, bypassing the logging system
2291 This is just a wrapper over _ToStream.
2294 @param txt: the message
2297 _ToStream(sys.stderr, txt, *args)
2300 class JobExecutor(object):
2301 """Class which manages the submission and execution of multiple jobs.
2303 Note that instances of this class should not be reused between
2307 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2312 self.verbose = verbose
2315 self.feedback_fn = feedback_fn
2317 def QueueJob(self, name, *ops):
2318 """Record a job for later submit.
2321 @param name: a description of the job, will be used in WaitJobSet
2323 SetGenericOpcodeOpts(ops, self.opts)
2324 self.queue.append((name, ops))
2326 def SubmitPending(self, each=False):
2327 """Submit all pending jobs.
2332 for row in self.queue:
2333 # SubmitJob will remove the success status, but raise an exception if
2334 # the submission fails, so we'll notice that anyway.
2335 results.append([True, self.cl.SubmitJob(row[1])])
2337 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2338 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2340 self.jobs.append((idx, status, data, name))
2342 def _ChooseJob(self):
2343 """Choose a non-waiting/queued job to poll next.
2346 assert self.jobs, "_ChooseJob called with empty job list"
2348 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2351 for job_data, status in zip(self.jobs, result):
2352 if (isinstance(status, list) and status and
2353 status[0] in (constants.JOB_STATUS_QUEUED,
2354 constants.JOB_STATUS_WAITLOCK,
2355 constants.JOB_STATUS_CANCELING)):
2356 # job is still present and waiting
2358 # good candidate found (either running job or lost job)
2359 self.jobs.remove(job_data)
2363 return self.jobs.pop(0)
2365 def GetResults(self):
2366 """Wait for and return the results of all jobs.
2369 @return: list of tuples (success, job results), in the same order
2370 as the submitted jobs; if a job has failed, instead of the result
2371 there will be the error message
2375 self.SubmitPending()
2378 ok_jobs = [row[2] for row in self.jobs if row[1]]
2380 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2382 # first, remove any non-submitted jobs
2383 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2384 for idx, _, jid, name in failures:
2385 ToStderr("Failed to submit job for %s: %s", name, jid)
2386 results.append((idx, False, jid))
2389 (idx, _, jid, name) = self._ChooseJob()
2390 ToStdout("Waiting for job %s for %s...", jid, name)
2392 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2394 except errors.JobLost, err:
2395 _, job_result = FormatError(err)
2396 ToStderr("Job %s for %s has been archived, cannot check its result",
2399 except (errors.GenericError, luxi.ProtocolError), err:
2400 _, job_result = FormatError(err)
2402 # the error message will always be shown, verbose or not
2403 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2405 results.append((idx, success, job_result))
2407 # sort based on the index, then drop it
2409 results = [i[1:] for i in results]
2413 def WaitOrShow(self, wait):
2414 """Wait for job results or only print the job IDs.
2417 @param wait: whether to wait or not
2421 return self.GetResults()
2424 self.SubmitPending()
2425 for _, status, result, name in self.jobs:
2427 ToStdout("%s: %s", result, name)
2429 ToStderr("Failure for %s: %s", name, result)
2430 return [row[1:3] for row in self.jobs]