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
59 "CLUSTER_DOMAIN_SECRET_OPT",
75 "FILESTORE_DRIVER_OPT",
84 "DEFAULT_IALLOCATOR_OPT",
85 "IDENTIFY_DEFAULTS_OPT",
87 "IGNORE_FAILURES_OPT",
89 "IGNORE_REMOVE_FAILURES_OPT",
90 "IGNORE_SECONDARIES_OPT",
94 "MAINTAIN_NODE_HEALTH_OPT",
99 "NEW_CLUSTER_CERT_OPT",
100 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
101 "NEW_CONFD_HMAC_KEY_OPT",
106 "NODE_PLACEMENT_OPT",
109 "NODRBD_STORAGE_OPT",
115 "NOMODIFY_ETCHOSTS_OPT",
116 "NOMODIFY_SSH_SETUP_OPT",
122 "NOSSH_KEYCHECK_OPT",
131 "PREALLOC_WIPE_DISKS_OPT",
132 "PRIMARY_IP_VERSION_OPT",
137 "REMOVE_INSTANCE_OPT",
145 "SHUTDOWN_TIMEOUT_OPT",
160 # Generic functions for CLI programs
162 "GenericInstanceCreate",
166 "JobSubmittedException",
168 "RunWhileClusterStopped",
172 # Formatting functions
173 "ToStderr", "ToStdout",
183 # command line options support infrastructure
184 "ARGS_MANY_INSTANCES",
203 "OPT_COMPL_INST_ADD_NODES",
204 "OPT_COMPL_MANY_NODES",
205 "OPT_COMPL_ONE_IALLOCATOR",
206 "OPT_COMPL_ONE_INSTANCE",
207 "OPT_COMPL_ONE_NODE",
208 "OPT_COMPL_ONE_NODEGROUP",
214 "COMMON_CREATE_OPTS",
220 #: Priorities (sorted)
222 ("low", constants.OP_PRIO_LOW),
223 ("normal", constants.OP_PRIO_NORMAL),
224 ("high", constants.OP_PRIO_HIGH),
227 #: Priority dictionary for easier lookup
228 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
229 # we migrate to Python 2.6
230 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
234 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
239 return ("<%s min=%s max=%s>" %
240 (self.__class__.__name__, self.min, self.max))
243 class ArgSuggest(_Argument):
244 """Suggesting argument.
246 Value can be any of the ones passed to the constructor.
249 # pylint: disable-msg=W0622
250 def __init__(self, min=0, max=None, choices=None):
251 _Argument.__init__(self, min=min, max=max)
252 self.choices = choices
255 return ("<%s min=%s max=%s choices=%r>" %
256 (self.__class__.__name__, self.min, self.max, self.choices))
259 class ArgChoice(ArgSuggest):
262 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
263 but value must be one of the choices.
268 class ArgUnknown(_Argument):
269 """Unknown argument to program (e.g. determined at runtime).
274 class ArgInstance(_Argument):
275 """Instances argument.
280 class ArgNode(_Argument):
286 class ArgGroup(_Argument):
287 """Node group argument.
292 class ArgJobId(_Argument):
298 class ArgFile(_Argument):
299 """File path argument.
304 class ArgCommand(_Argument):
310 class ArgHost(_Argument):
316 class ArgOs(_Argument):
323 ARGS_MANY_INSTANCES = [ArgInstance()]
324 ARGS_MANY_NODES = [ArgNode()]
325 ARGS_MANY_GROUPS = [ArgGroup()]
326 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
327 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
328 ARGS_ONE_GROUP = [ArgInstance(min=1, max=1)]
329 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
332 def _ExtractTagsObject(opts, args):
333 """Extract the tag type object.
335 Note that this function will modify its args parameter.
338 if not hasattr(opts, "tag_type"):
339 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
341 if kind == constants.TAG_CLUSTER:
343 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
345 raise errors.OpPrereqError("no arguments passed to the command")
349 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
353 def _ExtendTags(opts, args):
354 """Extend the args if a source file has been given.
356 This function will extend the tags with the contents of the file
357 passed in the 'tags_source' attribute of the opts parameter. A file
358 named '-' will be replaced by stdin.
361 fname = opts.tags_source
367 new_fh = open(fname, "r")
370 # we don't use the nice 'new_data = [line.strip() for line in fh]'
371 # because of python bug 1633941
373 line = new_fh.readline()
376 new_data.append(line.strip())
379 args.extend(new_data)
382 def ListTags(opts, args):
383 """List the tags on a given object.
385 This is a generic implementation that knows how to deal with all
386 three cases of tag objects (cluster, node, instance). The opts
387 argument is expected to contain a tag_type field denoting what
388 object type we work on.
391 kind, name = _ExtractTagsObject(opts, args)
393 result = cl.QueryTags(kind, name)
394 result = list(result)
400 def AddTags(opts, args):
401 """Add tags on a given object.
403 This is a generic implementation that knows how to deal with all
404 three cases of tag objects (cluster, node, instance). The opts
405 argument is expected to contain a tag_type field denoting what
406 object type we work on.
409 kind, name = _ExtractTagsObject(opts, args)
410 _ExtendTags(opts, args)
412 raise errors.OpPrereqError("No tags to be added")
413 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
414 SubmitOpCode(op, opts=opts)
417 def RemoveTags(opts, args):
418 """Remove tags from a given object.
420 This is a generic implementation that knows how to deal with all
421 three cases of tag objects (cluster, node, instance). The opts
422 argument is expected to contain a tag_type field denoting what
423 object type we work on.
426 kind, name = _ExtractTagsObject(opts, args)
427 _ExtendTags(opts, args)
429 raise errors.OpPrereqError("No tags to be removed")
430 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
431 SubmitOpCode(op, opts=opts)
434 def check_unit(option, opt, value): # pylint: disable-msg=W0613
435 """OptParsers custom converter for units.
439 return utils.ParseUnit(value)
440 except errors.UnitParseError, err:
441 raise OptionValueError("option %s: %s" % (opt, err))
444 def _SplitKeyVal(opt, data):
445 """Convert a KeyVal string into a dict.
447 This function will convert a key=val[,...] string into a dict. Empty
448 values will be converted specially: keys which have the prefix 'no_'
449 will have the value=False and the prefix stripped, the others will
453 @param opt: a string holding the option name for which we process the
454 data, used in building error messages
456 @param data: a string of the format key=val,key=val,...
458 @return: {key=val, key=val}
459 @raises errors.ParameterError: if there are duplicate keys
464 for elem in utils.UnescapeAndSplit(data, sep=","):
466 key, val = elem.split("=", 1)
468 if elem.startswith(NO_PREFIX):
469 key, val = elem[len(NO_PREFIX):], False
470 elif elem.startswith(UN_PREFIX):
471 key, val = elem[len(UN_PREFIX):], None
473 key, val = elem, True
475 raise errors.ParameterError("Duplicate key '%s' in option %s" %
481 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
482 """Custom parser for ident:key=val,key=val options.
484 This will store the parsed values as a tuple (ident, {key: val}). As such,
485 multiple uses of this option via action=append is possible.
489 ident, rest = value, ''
491 ident, rest = value.split(":", 1)
493 if ident.startswith(NO_PREFIX):
495 msg = "Cannot pass options when removing parameter groups: %s" % value
496 raise errors.ParameterError(msg)
497 retval = (ident[len(NO_PREFIX):], False)
498 elif ident.startswith(UN_PREFIX):
500 msg = "Cannot pass options when removing parameter groups: %s" % value
501 raise errors.ParameterError(msg)
502 retval = (ident[len(UN_PREFIX):], None)
504 kv_dict = _SplitKeyVal(opt, rest)
505 retval = (ident, kv_dict)
509 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
510 """Custom parser class for key=val,key=val options.
512 This will store the parsed values as a dict {key: val}.
515 return _SplitKeyVal(opt, value)
518 def check_bool(option, opt, value): # pylint: disable-msg=W0613
519 """Custom parser for yes/no options.
521 This will store the parsed value as either True or False.
524 value = value.lower()
525 if value == constants.VALUE_FALSE or value == "no":
527 elif value == constants.VALUE_TRUE or value == "yes":
530 raise errors.ParameterError("Invalid boolean value '%s'" % value)
533 # completion_suggestion is normally a list. Using numeric values not evaluating
534 # to False for dynamic completion.
535 (OPT_COMPL_MANY_NODES,
537 OPT_COMPL_ONE_INSTANCE,
539 OPT_COMPL_ONE_IALLOCATOR,
540 OPT_COMPL_INST_ADD_NODES,
541 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
543 OPT_COMPL_ALL = frozenset([
544 OPT_COMPL_MANY_NODES,
546 OPT_COMPL_ONE_INSTANCE,
548 OPT_COMPL_ONE_IALLOCATOR,
549 OPT_COMPL_INST_ADD_NODES,
550 OPT_COMPL_ONE_NODEGROUP,
554 class CliOption(Option):
555 """Custom option class for optparse.
558 ATTRS = Option.ATTRS + [
559 "completion_suggest",
561 TYPES = Option.TYPES + (
567 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
568 TYPE_CHECKER["identkeyval"] = check_ident_key_val
569 TYPE_CHECKER["keyval"] = check_key_val
570 TYPE_CHECKER["unit"] = check_unit
571 TYPE_CHECKER["bool"] = check_bool
574 # optparse.py sets make_option, so we do it for our own option class, too
575 cli_option = CliOption
580 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
581 help="Increase debugging level")
583 NOHDR_OPT = cli_option("--no-headers", default=False,
584 action="store_true", dest="no_headers",
585 help="Don't display column headers")
587 SEP_OPT = cli_option("--separator", default=None,
588 action="store", dest="separator",
589 help=("Separator between output fields"
590 " (defaults to one space)"))
592 USEUNITS_OPT = cli_option("--units", default=None,
593 dest="units", choices=('h', 'm', 'g', 't'),
594 help="Specify units for output (one of hmgt)")
596 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
597 type="string", metavar="FIELDS",
598 help="Comma separated list of output fields")
600 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
601 default=False, help="Force the operation")
603 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
604 default=False, help="Do not require confirmation")
606 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
607 action="store_true", default=False,
608 help=("Ignore offline nodes and do as much"
611 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
612 default=None, help="File with tag names")
614 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
615 default=False, action="store_true",
616 help=("Submit the job and return the job ID, but"
617 " don't wait for the job to finish"))
619 SYNC_OPT = cli_option("--sync", dest="do_locking",
620 default=False, action="store_true",
621 help=("Grab locks while doing the queries"
622 " in order to ensure more consistent results"))
624 DRY_RUN_OPT = cli_option("--dry-run", default=False,
626 help=("Do not execute the operation, just run the"
627 " check steps and verify it it could be"
630 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
632 help="Increase the verbosity of the operation")
634 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
635 action="store_true", dest="simulate_errors",
636 help="Debugging option that makes the operation"
637 " treat most runtime checks as failed")
639 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
640 default=True, action="store_false",
641 help="Don't wait for sync (DANGEROUS!)")
643 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
644 help="Custom disk setup (diskless, file,"
646 default=None, metavar="TEMPL",
647 choices=list(constants.DISK_TEMPLATES))
649 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
650 help="Do not create any network cards for"
653 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
654 help="Relative path under default cluster-wide"
655 " file storage dir to store file-based disks",
656 default=None, metavar="<DIR>")
658 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
659 help="Driver to use for image files",
660 default="loop", metavar="<DRIVER>",
661 choices=list(constants.FILE_DRIVER))
663 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
664 help="Select nodes for the instance automatically"
665 " using the <NAME> iallocator plugin",
666 default=None, type="string",
667 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
669 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
671 help="Set the default instance allocator plugin",
672 default=None, type="string",
673 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
675 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
677 completion_suggest=OPT_COMPL_ONE_OS)
679 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
680 type="keyval", default={},
681 help="OS parameters")
683 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
684 action="store_true", default=False,
685 help="Force an unknown variant")
687 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
688 action="store_true", default=False,
689 help="Do not install the OS (will"
692 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
693 type="keyval", default={},
694 help="Backend parameters")
696 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
697 default={}, dest="hvparams",
698 help="Hypervisor parameters")
700 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
701 help="Hypervisor and hypervisor options, in the"
702 " format hypervisor:option=value,option=value,...",
703 default=None, type="identkeyval")
705 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
706 help="Hypervisor and hypervisor options, in the"
707 " format hypervisor:option=value,option=value,...",
708 default=[], action="append", type="identkeyval")
710 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
711 action="store_false",
712 help="Don't check that the instance's IP"
715 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
716 default=True, action="store_false",
717 help="Don't check that the instance's name"
720 NET_OPT = cli_option("--net",
721 help="NIC parameters", default=[],
722 dest="nics", action="append", type="identkeyval")
724 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
725 dest="disks", action="append", type="identkeyval")
727 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
728 help="Comma-separated list of disks"
729 " indices to act on (e.g. 0,2) (optional,"
730 " defaults to all disks)")
732 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
733 help="Enforces a single-disk configuration using the"
734 " given disk size, in MiB unless a suffix is used",
735 default=None, type="unit", metavar="<size>")
737 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
738 dest="ignore_consistency",
739 action="store_true", default=False,
740 help="Ignore the consistency of the disks on"
743 NONLIVE_OPT = cli_option("--non-live", dest="live",
744 default=True, action="store_false",
745 help="Do a non-live migration (this usually means"
746 " freeze the instance, save the state, transfer and"
747 " only then resume running on the secondary node)")
749 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
751 choices=list(constants.HT_MIGRATION_MODES),
752 help="Override default migration mode (choose"
753 " either live or non-live")
755 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
756 help="Target node and optional secondary node",
757 metavar="<pnode>[:<snode>]",
758 completion_suggest=OPT_COMPL_INST_ADD_NODES)
760 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
761 action="append", metavar="<node>",
762 help="Use only this node (can be used multiple"
763 " times, if not given defaults to all nodes)",
764 completion_suggest=OPT_COMPL_ONE_NODE)
766 NODEGROUP_OPT = cli_option("-g", "--node-group",
768 help="Node group (name or uuid)",
769 metavar="<nodegroup>",
770 default=None, type="string",
771 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
773 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
775 completion_suggest=OPT_COMPL_ONE_NODE)
777 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
778 action="store_false",
779 help="Don't start the instance after creation")
781 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
782 action="store_true", default=False,
783 help="Show command instead of executing it")
785 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
786 default=False, action="store_true",
787 help="Instead of performing the migration, try to"
788 " recover from a failed cleanup. This is safe"
789 " to run even if the instance is healthy, but it"
790 " will create extra replication traffic and "
791 " disrupt briefly the replication (like during the"
794 STATIC_OPT = cli_option("-s", "--static", dest="static",
795 action="store_true", default=False,
796 help="Only show configuration data, not runtime data")
798 ALL_OPT = cli_option("--all", dest="show_all",
799 default=False, action="store_true",
800 help="Show info on all instances on the cluster."
801 " This can take a long time to run, use wisely")
803 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
804 action="store_true", default=False,
805 help="Interactive OS reinstall, lists available"
806 " OS templates for selection")
808 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
809 action="store_true", default=False,
810 help="Remove the instance from the cluster"
811 " configuration even if there are failures"
812 " during the removal process")
814 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
815 dest="ignore_remove_failures",
816 action="store_true", default=False,
817 help="Remove the instance from the"
818 " cluster configuration even if there"
819 " are failures during the removal"
822 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
823 action="store_true", default=False,
824 help="Remove the instance from the cluster")
826 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
827 help="Specifies the new secondary node",
828 metavar="NODE", default=None,
829 completion_suggest=OPT_COMPL_ONE_NODE)
831 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
832 default=False, action="store_true",
833 help="Replace the disk(s) on the primary"
834 " node (only for the drbd template)")
836 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
837 default=False, action="store_true",
838 help="Replace the disk(s) on the secondary"
839 " node (only for the drbd template)")
841 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
842 default=False, action="store_true",
843 help="Lock all nodes and auto-promote as needed"
846 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
847 default=False, action="store_true",
848 help="Automatically replace faulty disks"
849 " (only for the drbd template)")
851 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
852 default=False, action="store_true",
853 help="Ignore current recorded size"
854 " (useful for forcing activation when"
855 " the recorded size is wrong)")
857 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
859 completion_suggest=OPT_COMPL_ONE_NODE)
861 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
864 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
865 help="Specify the secondary ip for the node",
866 metavar="ADDRESS", default=None)
868 READD_OPT = cli_option("--readd", dest="readd",
869 default=False, action="store_true",
870 help="Readd old node after replacing it")
872 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
873 default=True, action="store_false",
874 help="Disable SSH key fingerprint checking")
877 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
878 type="bool", default=None, metavar=_YORNO,
879 help="Set the master_candidate flag on the node")
881 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
882 type="bool", default=None,
883 help="Set the offline flag on the node")
885 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
886 type="bool", default=None,
887 help="Set the drained flag on the node")
889 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
890 type="bool", default=None, metavar=_YORNO,
891 help="Set the master_capable flag on the node")
893 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
894 type="bool", default=None, metavar=_YORNO,
895 help="Set the vm_capable flag on the node")
897 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
898 type="bool", default=None, metavar=_YORNO,
899 help="Set the allocatable flag on a volume")
901 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
902 help="Disable support for lvm based instances"
904 action="store_false", default=True)
906 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
907 dest="enabled_hypervisors",
908 help="Comma-separated list of hypervisors",
909 type="string", default=None)
911 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
912 type="keyval", default={},
913 help="NIC parameters")
915 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
916 dest="candidate_pool_size", type="int",
917 help="Set the candidate pool size")
919 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
920 help="Enables LVM and specifies the volume group"
921 " name (cluster-wide) for disk allocation [xenvg]",
922 metavar="VG", default=None)
924 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
925 help="Destroy cluster", action="store_true")
927 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
928 help="Skip node agreement check (dangerous)",
929 action="store_true", default=False)
931 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
932 help="Specify the mac prefix for the instance IP"
933 " addresses, in the format XX:XX:XX",
937 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
938 help="Specify the node interface (cluster-wide)"
939 " on which the master IP address will be added "
940 " [%s]" % constants.DEFAULT_BRIDGE,
942 default=constants.DEFAULT_BRIDGE)
944 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
945 help="Specify the default directory (cluster-"
946 "wide) for storing the file-based disks [%s]" %
947 constants.DEFAULT_FILE_STORAGE_DIR,
949 default=constants.DEFAULT_FILE_STORAGE_DIR)
951 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
952 help="Don't modify /etc/hosts",
953 action="store_false", default=True)
955 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
956 help="Don't initialize SSH keys",
957 action="store_false", default=True)
959 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
960 help="Enable parseable error messages",
961 action="store_true", default=False)
963 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
964 help="Skip N+1 memory redundancy tests",
965 action="store_true", default=False)
967 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
968 help="Type of reboot: soft/hard/full",
969 default=constants.INSTANCE_REBOOT_HARD,
971 choices=list(constants.REBOOT_TYPES))
973 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
974 dest="ignore_secondaries",
975 default=False, action="store_true",
976 help="Ignore errors from secondaries")
978 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
979 action="store_false", default=True,
980 help="Don't shutdown the instance (unsafe)")
982 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
983 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
984 help="Maximum time to wait")
986 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
987 dest="shutdown_timeout", type="int",
988 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
989 help="Maximum time to wait for instance shutdown")
991 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
993 help=("Number of seconds between repetions of the"
996 EARLY_RELEASE_OPT = cli_option("--early-release",
997 dest="early_release", default=False,
999 help="Release the locks on the secondary"
1002 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1003 dest="new_cluster_cert",
1004 default=False, action="store_true",
1005 help="Generate a new cluster certificate")
1007 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1009 help="File containing new RAPI certificate")
1011 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1012 default=None, action="store_true",
1013 help=("Generate a new self-signed RAPI"
1016 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1017 dest="new_confd_hmac_key",
1018 default=False, action="store_true",
1019 help=("Create a new HMAC key for %s" %
1022 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1023 dest="cluster_domain_secret",
1025 help=("Load new new cluster domain"
1026 " secret from file"))
1028 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1029 dest="new_cluster_domain_secret",
1030 default=False, action="store_true",
1031 help=("Create a new cluster domain"
1034 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1035 dest="use_replication_network",
1036 help="Whether to use the replication network"
1037 " for talking to the nodes",
1038 action="store_true", default=False)
1040 MAINTAIN_NODE_HEALTH_OPT = \
1041 cli_option("--maintain-node-health", dest="maintain_node_health",
1042 metavar=_YORNO, default=None, type="bool",
1043 help="Configure the cluster to automatically maintain node"
1044 " health, by shutting down unknown instances, shutting down"
1045 " unknown DRBD devices, etc.")
1047 IDENTIFY_DEFAULTS_OPT = \
1048 cli_option("--identify-defaults", dest="identify_defaults",
1049 default=False, action="store_true",
1050 help="Identify which saved instance parameters are equal to"
1051 " the current cluster defaults and set them as such, instead"
1052 " of marking them as overridden")
1054 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1055 action="store", dest="uid_pool",
1056 help=("A list of user-ids or user-id"
1057 " ranges separated by commas"))
1059 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1060 action="store", dest="add_uids",
1061 help=("A list of user-ids or user-id"
1062 " ranges separated by commas, to be"
1063 " added to the user-id pool"))
1065 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1066 action="store", dest="remove_uids",
1067 help=("A list of user-ids or user-id"
1068 " ranges separated by commas, to be"
1069 " removed from the user-id pool"))
1071 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1072 action="store", dest="reserved_lvs",
1073 help=("A comma-separated list of reserved"
1074 " logical volumes names, that will be"
1075 " ignored by cluster verify"))
1077 ROMAN_OPT = cli_option("--roman",
1078 dest="roman_integers", default=False,
1079 action="store_true",
1080 help="Use roman numbers for positive integers")
1082 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1083 action="store", default=None,
1084 help="Specifies usermode helper for DRBD")
1086 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1087 action="store_false", default=True,
1088 help="Disable support for DRBD")
1090 PRIMARY_IP_VERSION_OPT = \
1091 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1092 action="store", dest="primary_ip_version",
1093 metavar="%d|%d" % (constants.IP4_VERSION,
1094 constants.IP6_VERSION),
1095 help="Cluster-wide IP version for primary IP")
1097 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1098 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1099 choices=_PRIONAME_TO_VALUE.keys(),
1100 help="Priority for opcode processing")
1102 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1103 type="bool", default=None, metavar=_YORNO,
1104 help="Sets the hidden flag on the OS")
1106 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1107 type="bool", default=None, metavar=_YORNO,
1108 help="Sets the blacklisted flag on the OS")
1110 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1111 type="bool", metavar=_YORNO,
1112 dest="prealloc_wipe_disks",
1113 help=("Wipe disks prior to instance"
1116 NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1117 type="keyval", default=None,
1118 help="Node parameters")
1121 #: Options provided by all commands
1122 COMMON_OPTS = [DEBUG_OPT]
1124 # common options for creating instances. add and import then add their own
1126 COMMON_CREATE_OPTS = [
1131 FILESTORE_DRIVER_OPT,
1148 def _ParseArgs(argv, commands, aliases):
1149 """Parser for the command line arguments.
1151 This function parses the arguments and returns the function which
1152 must be executed together with its (modified) arguments.
1154 @param argv: the command line
1155 @param commands: dictionary with special contents, see the design
1156 doc for cmdline handling
1157 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1161 binary = "<command>"
1163 binary = argv[0].split("/")[-1]
1165 if len(argv) > 1 and argv[1] == "--version":
1166 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1167 constants.RELEASE_VERSION)
1168 # Quit right away. That way we don't have to care about this special
1169 # argument. optparse.py does it the same.
1172 if len(argv) < 2 or not (argv[1] in commands or
1173 argv[1] in aliases):
1174 # let's do a nice thing
1175 sortedcmds = commands.keys()
1178 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1179 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1182 # compute the max line length for cmd + usage
1183 mlen = max([len(" %s" % cmd) for cmd in commands])
1184 mlen = min(60, mlen) # should not get here...
1186 # and format a nice command list
1187 ToStdout("Commands:")
1188 for cmd in sortedcmds:
1189 cmdstr = " %s" % (cmd,)
1190 help_text = commands[cmd][4]
1191 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1192 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1193 for line in help_lines:
1194 ToStdout("%-*s %s", mlen, "", line)
1198 return None, None, None
1200 # get command, unalias it, and look it up in commands
1204 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1207 if aliases[cmd] not in commands:
1208 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1209 " command '%s'" % (cmd, aliases[cmd]))
1213 func, args_def, parser_opts, usage, description = commands[cmd]
1214 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1215 description=description,
1216 formatter=TitledHelpFormatter(),
1217 usage="%%prog %s %s" % (cmd, usage))
1218 parser.disable_interspersed_args()
1219 options, args = parser.parse_args()
1221 if not _CheckArguments(cmd, args_def, args):
1222 return None, None, None
1224 return func, options, args
1227 def _CheckArguments(cmd, args_def, args):
1228 """Verifies the arguments using the argument definition.
1232 1. Abort with error if values specified by user but none expected.
1234 1. For each argument in definition
1236 1. Keep running count of minimum number of values (min_count)
1237 1. Keep running count of maximum number of values (max_count)
1238 1. If it has an unlimited number of values
1240 1. Abort with error if it's not the last argument in the definition
1242 1. If last argument has limited number of values
1244 1. Abort with error if number of values doesn't match or is too large
1246 1. Abort with error if user didn't pass enough values (min_count)
1249 if args and not args_def:
1250 ToStderr("Error: Command %s expects no arguments", cmd)
1257 last_idx = len(args_def) - 1
1259 for idx, arg in enumerate(args_def):
1260 if min_count is None:
1262 elif arg.min is not None:
1263 min_count += arg.min
1265 if max_count is None:
1267 elif arg.max is not None:
1268 max_count += arg.max
1271 check_max = (arg.max is not None)
1273 elif arg.max is None:
1274 raise errors.ProgrammerError("Only the last argument can have max=None")
1277 # Command with exact number of arguments
1278 if (min_count is not None and max_count is not None and
1279 min_count == max_count and len(args) != min_count):
1280 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1283 # Command with limited number of arguments
1284 if max_count is not None and len(args) > max_count:
1285 ToStderr("Error: Command %s expects only %d argument(s)",
1289 # Command with some required arguments
1290 if min_count is not None and len(args) < min_count:
1291 ToStderr("Error: Command %s expects at least %d argument(s)",
1298 def SplitNodeOption(value):
1299 """Splits the value of a --node option.
1302 if value and ':' in value:
1303 return value.split(':', 1)
1305 return (value, None)
1308 def CalculateOSNames(os_name, os_variants):
1309 """Calculates all the names an OS can be called, according to its variants.
1311 @type os_name: string
1312 @param os_name: base name of the os
1313 @type os_variants: list or None
1314 @param os_variants: list of supported variants
1316 @return: list of valid names
1320 return ['%s+%s' % (os_name, v) for v in os_variants]
1325 def ParseFields(selected, default):
1326 """Parses the values of "--field"-like options.
1328 @type selected: string or None
1329 @param selected: User-selected options
1331 @param default: Default fields
1334 if selected is None:
1337 if selected.startswith("+"):
1338 return default + selected[1:].split(",")
1340 return selected.split(",")
1343 UsesRPC = rpc.RunWithRPC
1346 def AskUser(text, choices=None):
1347 """Ask the user a question.
1349 @param text: the question to ask
1351 @param choices: list with elements tuples (input_char, return_value,
1352 description); if not given, it will default to: [('y', True,
1353 'Perform the operation'), ('n', False, 'Do no do the operation')];
1354 note that the '?' char is reserved for help
1356 @return: one of the return values from the choices list; if input is
1357 not possible (i.e. not running with a tty, we return the last
1362 choices = [('y', True, 'Perform the operation'),
1363 ('n', False, 'Do not perform the operation')]
1364 if not choices or not isinstance(choices, list):
1365 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1366 for entry in choices:
1367 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1368 raise errors.ProgrammerError("Invalid choices element to AskUser")
1370 answer = choices[-1][1]
1372 for line in text.splitlines():
1373 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1374 text = "\n".join(new_text)
1376 f = file("/dev/tty", "a+")
1380 chars = [entry[0] for entry in choices]
1381 chars[-1] = "[%s]" % chars[-1]
1383 maps = dict([(entry[0], entry[1]) for entry in choices])
1387 f.write("/".join(chars))
1389 line = f.readline(2).strip().lower()
1394 for entry in choices:
1395 f.write(" %s - %s\n" % (entry[0], entry[2]))
1403 class JobSubmittedException(Exception):
1404 """Job was submitted, client should exit.
1406 This exception has one argument, the ID of the job that was
1407 submitted. The handler should print this ID.
1409 This is not an error, just a structured way to exit from clients.
1414 def SendJob(ops, cl=None):
1415 """Function to submit an opcode without waiting for the results.
1418 @param ops: list of opcodes
1419 @type cl: luxi.Client
1420 @param cl: the luxi client to use for communicating with the master;
1421 if None, a new client will be created
1427 job_id = cl.SubmitJob(ops)
1432 def GenericPollJob(job_id, cbs, report_cbs):
1433 """Generic job-polling function.
1435 @type job_id: number
1436 @param job_id: Job ID
1437 @type cbs: Instance of L{JobPollCbBase}
1438 @param cbs: Data callbacks
1439 @type report_cbs: Instance of L{JobPollReportCbBase}
1440 @param report_cbs: Reporting callbacks
1443 prev_job_info = None
1444 prev_logmsg_serial = None
1449 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1452 # job not found, go away!
1453 raise errors.JobLost("Job with id %s lost" % job_id)
1455 if result == constants.JOB_NOTCHANGED:
1456 report_cbs.ReportNotChanged(job_id, status)
1461 # Split result, a tuple of (field values, log entries)
1462 (job_info, log_entries) = result
1463 (status, ) = job_info
1466 for log_entry in log_entries:
1467 (serial, timestamp, log_type, message) = log_entry
1468 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1470 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1472 # TODO: Handle canceled and archived jobs
1473 elif status in (constants.JOB_STATUS_SUCCESS,
1474 constants.JOB_STATUS_ERROR,
1475 constants.JOB_STATUS_CANCELING,
1476 constants.JOB_STATUS_CANCELED):
1479 prev_job_info = job_info
1481 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1483 raise errors.JobLost("Job with id %s lost" % job_id)
1485 status, opstatus, result = jobs[0]
1487 if status == constants.JOB_STATUS_SUCCESS:
1490 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1491 raise errors.OpExecError("Job was canceled")
1494 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1495 if status == constants.OP_STATUS_SUCCESS:
1497 elif status == constants.OP_STATUS_ERROR:
1498 errors.MaybeRaise(msg)
1501 raise errors.OpExecError("partial failure (opcode %d): %s" %
1504 raise errors.OpExecError(str(msg))
1506 # default failure mode
1507 raise errors.OpExecError(result)
1510 class JobPollCbBase:
1511 """Base class for L{GenericPollJob} callbacks.
1515 """Initializes this class.
1519 def WaitForJobChangeOnce(self, job_id, fields,
1520 prev_job_info, prev_log_serial):
1521 """Waits for changes on a job.
1524 raise NotImplementedError()
1526 def QueryJobs(self, job_ids, fields):
1527 """Returns the selected fields for the selected job IDs.
1529 @type job_ids: list of numbers
1530 @param job_ids: Job IDs
1531 @type fields: list of strings
1532 @param fields: Fields
1535 raise NotImplementedError()
1538 class JobPollReportCbBase:
1539 """Base class for L{GenericPollJob} reporting callbacks.
1543 """Initializes this class.
1547 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1548 """Handles a log message.
1551 raise NotImplementedError()
1553 def ReportNotChanged(self, job_id, status):
1554 """Called for if a job hasn't changed in a while.
1556 @type job_id: number
1557 @param job_id: Job ID
1558 @type status: string or None
1559 @param status: Job status if available
1562 raise NotImplementedError()
1565 class _LuxiJobPollCb(JobPollCbBase):
1566 def __init__(self, cl):
1567 """Initializes this class.
1570 JobPollCbBase.__init__(self)
1573 def WaitForJobChangeOnce(self, job_id, fields,
1574 prev_job_info, prev_log_serial):
1575 """Waits for changes on a job.
1578 return self.cl.WaitForJobChangeOnce(job_id, fields,
1579 prev_job_info, prev_log_serial)
1581 def QueryJobs(self, job_ids, fields):
1582 """Returns the selected fields for the selected job IDs.
1585 return self.cl.QueryJobs(job_ids, fields)
1588 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1589 def __init__(self, feedback_fn):
1590 """Initializes this class.
1593 JobPollReportCbBase.__init__(self)
1595 self.feedback_fn = feedback_fn
1597 assert callable(feedback_fn)
1599 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1600 """Handles a log message.
1603 self.feedback_fn((timestamp, log_type, log_msg))
1605 def ReportNotChanged(self, job_id, status):
1606 """Called if a job hasn't changed in a while.
1612 class StdioJobPollReportCb(JobPollReportCbBase):
1614 """Initializes this class.
1617 JobPollReportCbBase.__init__(self)
1619 self.notified_queued = False
1620 self.notified_waitlock = False
1622 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1623 """Handles a log message.
1626 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1627 FormatLogMessage(log_type, log_msg))
1629 def ReportNotChanged(self, job_id, status):
1630 """Called if a job hasn't changed in a while.
1636 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1637 ToStderr("Job %s is waiting in queue", job_id)
1638 self.notified_queued = True
1640 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1641 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1642 self.notified_waitlock = True
1645 def FormatLogMessage(log_type, log_msg):
1646 """Formats a job message according to its type.
1649 if log_type != constants.ELOG_MESSAGE:
1650 log_msg = str(log_msg)
1652 return utils.SafeEncode(log_msg)
1655 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1656 """Function to poll for the result of a job.
1658 @type job_id: job identified
1659 @param job_id: the job to poll for results
1660 @type cl: luxi.Client
1661 @param cl: the luxi client to use for communicating with the master;
1662 if None, a new client will be created
1668 if reporter is None:
1670 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1672 reporter = StdioJobPollReportCb()
1674 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1676 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1679 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1680 """Legacy function to submit an opcode.
1682 This is just a simple wrapper over the construction of the processor
1683 instance. It should be extended to better handle feedback and
1684 interaction functions.
1690 SetGenericOpcodeOpts([op], opts)
1692 job_id = SendJob([op], cl=cl)
1694 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1697 return op_results[0]
1700 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1701 """Wrapper around SubmitOpCode or SendJob.
1703 This function will decide, based on the 'opts' parameter, whether to
1704 submit and wait for the result of the opcode (and return it), or
1705 whether to just send the job and print its identifier. It is used in
1706 order to simplify the implementation of the '--submit' option.
1708 It will also process the opcodes if we're sending the via SendJob
1709 (otherwise SubmitOpCode does it).
1712 if opts and opts.submit_only:
1714 SetGenericOpcodeOpts(job, opts)
1715 job_id = SendJob(job, cl=cl)
1716 raise JobSubmittedException(job_id)
1718 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1721 def SetGenericOpcodeOpts(opcode_list, options):
1722 """Processor for generic options.
1724 This function updates the given opcodes based on generic command
1725 line options (like debug, dry-run, etc.).
1727 @param opcode_list: list of opcodes
1728 @param options: command line options or None
1729 @return: None (in-place modification)
1734 for op in opcode_list:
1735 op.debug_level = options.debug
1736 if hasattr(options, "dry_run"):
1737 op.dry_run = options.dry_run
1738 if getattr(options, "priority", None) is not None:
1739 op.priority = _PRIONAME_TO_VALUE[options.priority]
1743 # TODO: Cache object?
1745 client = luxi.Client()
1746 except luxi.NoMasterError:
1747 ss = ssconf.SimpleStore()
1749 # Try to read ssconf file
1752 except errors.ConfigurationError:
1753 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1754 " not part of a cluster")
1756 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1757 if master != myself:
1758 raise errors.OpPrereqError("This is not the master node, please connect"
1759 " to node '%s' and rerun the command" %
1765 def FormatError(err):
1766 """Return a formatted error message for a given error.
1768 This function takes an exception instance and returns a tuple
1769 consisting of two values: first, the recommended exit code, and
1770 second, a string describing the error message (not
1771 newline-terminated).
1777 if isinstance(err, errors.ConfigurationError):
1778 txt = "Corrupt configuration file: %s" % msg
1780 obuf.write(txt + "\n")
1781 obuf.write("Aborting.")
1783 elif isinstance(err, errors.HooksAbort):
1784 obuf.write("Failure: hooks execution failed:\n")
1785 for node, script, out in err.args[0]:
1787 obuf.write(" node: %s, script: %s, output: %s\n" %
1788 (node, script, out))
1790 obuf.write(" node: %s, script: %s (no output)\n" %
1792 elif isinstance(err, errors.HooksFailure):
1793 obuf.write("Failure: hooks general failure: %s" % msg)
1794 elif isinstance(err, errors.ResolverError):
1795 this_host = netutils.Hostname.GetSysName()
1796 if err.args[0] == this_host:
1797 msg = "Failure: can't resolve my own hostname ('%s')"
1799 msg = "Failure: can't resolve hostname '%s'"
1800 obuf.write(msg % err.args[0])
1801 elif isinstance(err, errors.OpPrereqError):
1802 if len(err.args) == 2:
1803 obuf.write("Failure: prerequisites not met for this"
1804 " operation:\nerror type: %s, error details:\n%s" %
1805 (err.args[1], err.args[0]))
1807 obuf.write("Failure: prerequisites not met for this"
1808 " operation:\n%s" % msg)
1809 elif isinstance(err, errors.OpExecError):
1810 obuf.write("Failure: command execution error:\n%s" % msg)
1811 elif isinstance(err, errors.TagError):
1812 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1813 elif isinstance(err, errors.JobQueueDrainError):
1814 obuf.write("Failure: the job queue is marked for drain and doesn't"
1815 " accept new requests\n")
1816 elif isinstance(err, errors.JobQueueFull):
1817 obuf.write("Failure: the job queue is full and doesn't accept new"
1818 " job submissions until old jobs are archived\n")
1819 elif isinstance(err, errors.TypeEnforcementError):
1820 obuf.write("Parameter Error: %s" % msg)
1821 elif isinstance(err, errors.ParameterError):
1822 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1823 elif isinstance(err, luxi.NoMasterError):
1824 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1825 " and listening for connections?")
1826 elif isinstance(err, luxi.TimeoutError):
1827 obuf.write("Timeout while talking to the master daemon. Error:\n"
1829 elif isinstance(err, luxi.PermissionError):
1830 obuf.write("It seems you don't have permissions to connect to the"
1831 " master daemon.\nPlease retry as a different user.")
1832 elif isinstance(err, luxi.ProtocolError):
1833 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1835 elif isinstance(err, errors.JobLost):
1836 obuf.write("Error checking job status: %s" % msg)
1837 elif isinstance(err, errors.GenericError):
1838 obuf.write("Unhandled Ganeti error: %s" % msg)
1839 elif isinstance(err, JobSubmittedException):
1840 obuf.write("JobID: %s\n" % err.args[0])
1843 obuf.write("Unhandled exception: %s" % msg)
1844 return retcode, obuf.getvalue().rstrip('\n')
1847 def GenericMain(commands, override=None, aliases=None):
1848 """Generic main function for all the gnt-* commands.
1851 - commands: a dictionary with a special structure, see the design doc
1852 for command line handling.
1853 - override: if not None, we expect a dictionary with keys that will
1854 override command line options; this can be used to pass
1855 options from the scripts to generic functions
1856 - aliases: dictionary with command aliases {'alias': 'target, ...}
1859 # save the program name and the entire command line for later logging
1861 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1862 if len(sys.argv) >= 2:
1863 binary += " " + sys.argv[1]
1864 old_cmdline = " ".join(sys.argv[2:])
1868 binary = "<unknown program>"
1875 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1876 except errors.ParameterError, err:
1877 result, err_msg = FormatError(err)
1881 if func is None: # parse error
1884 if override is not None:
1885 for key, val in override.iteritems():
1886 setattr(options, key, val)
1888 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1889 stderr_logging=True, program=binary)
1892 logging.info("run with arguments '%s'", old_cmdline)
1894 logging.info("run with no arguments")
1897 result = func(options, args)
1898 except (errors.GenericError, luxi.ProtocolError,
1899 JobSubmittedException), err:
1900 result, err_msg = FormatError(err)
1901 logging.exception("Error during command processing")
1907 def ParseNicOption(optvalue):
1908 """Parses the value of the --net option(s).
1912 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1913 except (TypeError, ValueError), err:
1914 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1916 nics = [{}] * nic_max
1917 for nidx, ndict in optvalue:
1920 if not isinstance(ndict, dict):
1921 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1922 " got %s" % (nidx, ndict))
1924 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1931 def GenericInstanceCreate(mode, opts, args):
1932 """Add an instance to the cluster via either creation or import.
1934 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1935 @param opts: the command line options selected by the user
1937 @param args: should contain only one element, the new instance name
1939 @return: the desired exit code
1944 (pnode, snode) = SplitNodeOption(opts.node)
1949 hypervisor, hvparams = opts.hypervisor
1952 nics = ParseNicOption(opts.nics)
1956 elif mode == constants.INSTANCE_CREATE:
1957 # default of one nic, all auto
1963 if opts.disk_template == constants.DT_DISKLESS:
1964 if opts.disks or opts.sd_size is not None:
1965 raise errors.OpPrereqError("Diskless instance but disk"
1966 " information passed")
1969 if (not opts.disks and not opts.sd_size
1970 and mode == constants.INSTANCE_CREATE):
1971 raise errors.OpPrereqError("No disk information specified")
1972 if opts.disks and opts.sd_size is not None:
1973 raise errors.OpPrereqError("Please use either the '--disk' or"
1975 if opts.sd_size is not None:
1976 opts.disks = [(0, {"size": opts.sd_size})]
1980 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1981 except ValueError, err:
1982 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1983 disks = [{}] * disk_max
1986 for didx, ddict in opts.disks:
1988 if not isinstance(ddict, dict):
1989 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1990 raise errors.OpPrereqError(msg)
1991 elif "size" in ddict:
1992 if "adopt" in ddict:
1993 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1994 " (disk %d)" % didx)
1996 ddict["size"] = utils.ParseUnit(ddict["size"])
1997 except ValueError, err:
1998 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2000 elif "adopt" in ddict:
2001 if mode == constants.INSTANCE_IMPORT:
2002 raise errors.OpPrereqError("Disk adoption not allowed for instance"
2006 raise errors.OpPrereqError("Missing size or adoption source for"
2010 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2011 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2013 if mode == constants.INSTANCE_CREATE:
2016 force_variant = opts.force_variant
2019 no_install = opts.no_install
2020 identify_defaults = False
2021 elif mode == constants.INSTANCE_IMPORT:
2024 force_variant = False
2025 src_node = opts.src_node
2026 src_path = opts.src_dir
2028 identify_defaults = opts.identify_defaults
2030 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2032 op = opcodes.OpCreateInstance(instance_name=instance,
2034 disk_template=opts.disk_template,
2036 pnode=pnode, snode=snode,
2037 ip_check=opts.ip_check,
2038 name_check=opts.name_check,
2039 wait_for_sync=opts.wait_for_sync,
2040 file_storage_dir=opts.file_storage_dir,
2041 file_driver=opts.file_driver,
2042 iallocator=opts.iallocator,
2043 hypervisor=hypervisor,
2045 beparams=opts.beparams,
2046 osparams=opts.osparams,
2050 force_variant=force_variant,
2053 no_install=no_install,
2054 identify_defaults=identify_defaults)
2056 SubmitOrSend(op, opts)
2060 class _RunWhileClusterStoppedHelper:
2061 """Helper class for L{RunWhileClusterStopped} to simplify state management
2064 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2065 """Initializes this class.
2067 @type feedback_fn: callable
2068 @param feedback_fn: Feedback function
2069 @type cluster_name: string
2070 @param cluster_name: Cluster name
2071 @type master_node: string
2072 @param master_node Master node name
2073 @type online_nodes: list
2074 @param online_nodes: List of names of online nodes
2077 self.feedback_fn = feedback_fn
2078 self.cluster_name = cluster_name
2079 self.master_node = master_node
2080 self.online_nodes = online_nodes
2082 self.ssh = ssh.SshRunner(self.cluster_name)
2084 self.nonmaster_nodes = [name for name in online_nodes
2085 if name != master_node]
2087 assert self.master_node not in self.nonmaster_nodes
2089 def _RunCmd(self, node_name, cmd):
2090 """Runs a command on the local or a remote machine.
2092 @type node_name: string
2093 @param node_name: Machine name
2098 if node_name is None or node_name == self.master_node:
2099 # No need to use SSH
2100 result = utils.RunCmd(cmd)
2102 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2105 errmsg = ["Failed to run command %s" % result.cmd]
2107 errmsg.append("on node %s" % node_name)
2108 errmsg.append(": exitcode %s and error %s" %
2109 (result.exit_code, result.output))
2110 raise errors.OpExecError(" ".join(errmsg))
2112 def Call(self, fn, *args):
2113 """Call function while all daemons are stopped.
2116 @param fn: Function to be called
2119 # Pause watcher by acquiring an exclusive lock on watcher state file
2120 self.feedback_fn("Blocking watcher")
2121 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2123 # TODO: Currently, this just blocks. There's no timeout.
2124 # TODO: Should it be a shared lock?
2125 watcher_block.Exclusive(blocking=True)
2127 # Stop master daemons, so that no new jobs can come in and all running
2129 self.feedback_fn("Stopping master daemons")
2130 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2132 # Stop daemons on all nodes
2133 for node_name in self.online_nodes:
2134 self.feedback_fn("Stopping daemons on %s" % node_name)
2135 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2137 # All daemons are shut down now
2139 return fn(self, *args)
2140 except Exception, err:
2141 _, errmsg = FormatError(err)
2142 logging.exception("Caught exception")
2143 self.feedback_fn(errmsg)
2146 # Start cluster again, master node last
2147 for node_name in self.nonmaster_nodes + [self.master_node]:
2148 self.feedback_fn("Starting daemons on %s" % node_name)
2149 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2152 watcher_block.Close()
2155 def RunWhileClusterStopped(feedback_fn, fn, *args):
2156 """Calls a function while all cluster daemons are stopped.
2158 @type feedback_fn: callable
2159 @param feedback_fn: Feedback function
2161 @param fn: Function to be called when daemons are stopped
2164 feedback_fn("Gathering cluster information")
2166 # This ensures we're running on the master daemon
2169 (cluster_name, master_node) = \
2170 cl.QueryConfigValues(["cluster_name", "master_node"])
2172 online_nodes = GetOnlineNodes([], cl=cl)
2174 # Don't keep a reference to the client. The master daemon will go away.
2177 assert master_node in online_nodes
2179 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2180 online_nodes).Call(fn, *args)
2183 def GenerateTable(headers, fields, separator, data,
2184 numfields=None, unitfields=None,
2186 """Prints a table with headers and different fields.
2189 @param headers: dictionary mapping field names to headers for
2192 @param fields: the field names corresponding to each row in
2194 @param separator: the separator to be used; if this is None,
2195 the default 'smart' algorithm is used which computes optimal
2196 field width, otherwise just the separator is used between
2199 @param data: a list of lists, each sublist being one row to be output
2200 @type numfields: list
2201 @param numfields: a list with the fields that hold numeric
2202 values and thus should be right-aligned
2203 @type unitfields: list
2204 @param unitfields: a list with the fields that hold numeric
2205 values that should be formatted with the units field
2206 @type units: string or None
2207 @param units: the units we should use for formatting, or None for
2208 automatic choice (human-readable for non-separator usage, otherwise
2209 megabytes); this is a one-letter string
2218 if numfields is None:
2220 if unitfields is None:
2223 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2224 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2227 for field in fields:
2228 if headers and field not in headers:
2229 # TODO: handle better unknown fields (either revert to old
2230 # style of raising exception, or deal more intelligently with
2232 headers[field] = field
2233 if separator is not None:
2234 format_fields.append("%s")
2235 elif numfields.Matches(field):
2236 format_fields.append("%*s")
2238 format_fields.append("%-*s")
2240 if separator is None:
2241 mlens = [0 for name in fields]
2242 format_str = ' '.join(format_fields)
2244 format_str = separator.replace("%", "%%").join(format_fields)
2249 for idx, val in enumerate(row):
2250 if unitfields.Matches(fields[idx]):
2253 except (TypeError, ValueError):
2256 val = row[idx] = utils.FormatUnit(val, units)
2257 val = row[idx] = str(val)
2258 if separator is None:
2259 mlens[idx] = max(mlens[idx], len(val))
2264 for idx, name in enumerate(fields):
2266 if separator is None:
2267 mlens[idx] = max(mlens[idx], len(hdr))
2268 args.append(mlens[idx])
2270 result.append(format_str % tuple(args))
2272 if separator is None:
2273 assert len(mlens) == len(fields)
2275 if fields and not numfields.Matches(fields[-1]):
2281 line = ['-' for _ in fields]
2282 for idx in range(len(fields)):
2283 if separator is None:
2284 args.append(mlens[idx])
2285 args.append(line[idx])
2286 result.append(format_str % tuple(args))
2291 def FormatTimestamp(ts):
2292 """Formats a given timestamp.
2295 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2298 @return: a string with the formatted timestamp
2301 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2304 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2307 def ParseTimespec(value):
2308 """Parse a time specification.
2310 The following suffixed will be recognized:
2318 Without any suffix, the value will be taken to be in seconds.
2323 raise errors.OpPrereqError("Empty time specification passed")
2331 if value[-1] not in suffix_map:
2334 except (TypeError, ValueError):
2335 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2337 multiplier = suffix_map[value[-1]]
2339 if not value: # no data left after stripping the suffix
2340 raise errors.OpPrereqError("Invalid time specification (only"
2343 value = int(value) * multiplier
2344 except (TypeError, ValueError):
2345 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2349 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2350 filter_master=False):
2351 """Returns the names of online nodes.
2353 This function will also log a warning on stderr with the names of
2356 @param nodes: if not empty, use only this subset of nodes (minus the
2358 @param cl: if not None, luxi client to use
2359 @type nowarn: boolean
2360 @param nowarn: by default, this function will output a note with the
2361 offline nodes that are skipped; if this parameter is True the
2362 note is not displayed
2363 @type secondary_ips: boolean
2364 @param secondary_ips: if True, return the secondary IPs instead of the
2365 names, useful for doing network traffic over the replication interface
2367 @type filter_master: boolean
2368 @param filter_master: if True, do not return the master node in the list
2369 (useful in coordination with secondary_ips where we cannot check our
2370 node name against the list)
2382 master_node = cl.QueryConfigValues(["master_node"])[0]
2383 filter_fn = lambda x: x != master_node
2385 filter_fn = lambda _: True
2387 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2389 offline = [row[0] for row in result if row[1]]
2390 if offline and not nowarn:
2391 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2392 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2395 def _ToStream(stream, txt, *args):
2396 """Write a message to a stream, bypassing the logging system
2398 @type stream: file object
2399 @param stream: the file to which we should write
2401 @param txt: the message
2406 stream.write(txt % args)
2413 def ToStdout(txt, *args):
2414 """Write a message to stdout only, bypassing the logging system
2416 This is just a wrapper over _ToStream.
2419 @param txt: the message
2422 _ToStream(sys.stdout, txt, *args)
2425 def ToStderr(txt, *args):
2426 """Write a message to stderr only, bypassing the logging system
2428 This is just a wrapper over _ToStream.
2431 @param txt: the message
2434 _ToStream(sys.stderr, txt, *args)
2437 class JobExecutor(object):
2438 """Class which manages the submission and execution of multiple jobs.
2440 Note that instances of this class should not be reused between
2444 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2449 self.verbose = verbose
2452 self.feedback_fn = feedback_fn
2454 def QueueJob(self, name, *ops):
2455 """Record a job for later submit.
2458 @param name: a description of the job, will be used in WaitJobSet
2460 SetGenericOpcodeOpts(ops, self.opts)
2461 self.queue.append((name, ops))
2463 def SubmitPending(self, each=False):
2464 """Submit all pending jobs.
2469 for row in self.queue:
2470 # SubmitJob will remove the success status, but raise an exception if
2471 # the submission fails, so we'll notice that anyway.
2472 results.append([True, self.cl.SubmitJob(row[1])])
2474 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2475 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2477 self.jobs.append((idx, status, data, name))
2479 def _ChooseJob(self):
2480 """Choose a non-waiting/queued job to poll next.
2483 assert self.jobs, "_ChooseJob called with empty job list"
2485 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2488 for job_data, status in zip(self.jobs, result):
2489 if (isinstance(status, list) and status and
2490 status[0] in (constants.JOB_STATUS_QUEUED,
2491 constants.JOB_STATUS_WAITLOCK,
2492 constants.JOB_STATUS_CANCELING)):
2493 # job is still present and waiting
2495 # good candidate found (either running job or lost job)
2496 self.jobs.remove(job_data)
2500 return self.jobs.pop(0)
2502 def GetResults(self):
2503 """Wait for and return the results of all jobs.
2506 @return: list of tuples (success, job results), in the same order
2507 as the submitted jobs; if a job has failed, instead of the result
2508 there will be the error message
2512 self.SubmitPending()
2515 ok_jobs = [row[2] for row in self.jobs if row[1]]
2517 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2519 # first, remove any non-submitted jobs
2520 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2521 for idx, _, jid, name in failures:
2522 ToStderr("Failed to submit job for %s: %s", name, jid)
2523 results.append((idx, False, jid))
2526 (idx, _, jid, name) = self._ChooseJob()
2527 ToStdout("Waiting for job %s for %s...", jid, name)
2529 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2531 except errors.JobLost, err:
2532 _, job_result = FormatError(err)
2533 ToStderr("Job %s for %s has been archived, cannot check its result",
2536 except (errors.GenericError, luxi.ProtocolError), err:
2537 _, job_result = FormatError(err)
2539 # the error message will always be shown, verbose or not
2540 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2542 results.append((idx, success, job_result))
2544 # sort based on the index, then drop it
2546 results = [i[1:] for i in results]
2550 def WaitOrShow(self, wait):
2551 """Wait for job results or only print the job IDs.
2554 @param wait: whether to wait or not
2558 return self.GetResults()
2561 self.SubmitPending()
2562 for _, status, result, name in self.jobs:
2564 ToStdout("%s: %s", result, name)
2566 ToStderr("Failure for %s: %s", name, result)
2567 return [row[1:3] for row in self.jobs]