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",
108 "NODRBD_STORAGE_OPT",
114 "NOMODIFY_ETCHOSTS_OPT",
115 "NOMODIFY_SSH_SETUP_OPT",
121 "NOSSH_KEYCHECK_OPT",
130 "PREALLOC_WIPE_DISKS_OPT",
131 "PRIMARY_IP_VERSION_OPT",
136 "REMOVE_INSTANCE_OPT",
144 "SHUTDOWN_TIMEOUT_OPT",
159 # Generic functions for CLI programs
161 "GenericInstanceCreate",
165 "JobSubmittedException",
167 "RunWhileClusterStopped",
171 # Formatting functions
172 "ToStderr", "ToStdout",
182 # command line options support infrastructure
183 "ARGS_MANY_INSTANCES",
199 "OPT_COMPL_INST_ADD_NODES",
200 "OPT_COMPL_MANY_NODES",
201 "OPT_COMPL_ONE_IALLOCATOR",
202 "OPT_COMPL_ONE_INSTANCE",
203 "OPT_COMPL_ONE_NODE",
204 "OPT_COMPL_ONE_NODEGROUP",
210 "COMMON_CREATE_OPTS",
216 #: Priorities (sorted)
218 ("low", constants.OP_PRIO_LOW),
219 ("normal", constants.OP_PRIO_NORMAL),
220 ("high", constants.OP_PRIO_HIGH),
223 #: Priority dictionary for easier lookup
224 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
225 # we migrate to Python 2.6
226 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
230 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
235 return ("<%s min=%s max=%s>" %
236 (self.__class__.__name__, self.min, self.max))
239 class ArgSuggest(_Argument):
240 """Suggesting argument.
242 Value can be any of the ones passed to the constructor.
245 # pylint: disable-msg=W0622
246 def __init__(self, min=0, max=None, choices=None):
247 _Argument.__init__(self, min=min, max=max)
248 self.choices = choices
251 return ("<%s min=%s max=%s choices=%r>" %
252 (self.__class__.__name__, self.min, self.max, self.choices))
255 class ArgChoice(ArgSuggest):
258 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
259 but value must be one of the choices.
264 class ArgUnknown(_Argument):
265 """Unknown argument to program (e.g. determined at runtime).
270 class ArgInstance(_Argument):
271 """Instances argument.
276 class ArgNode(_Argument):
281 class ArgJobId(_Argument):
287 class ArgFile(_Argument):
288 """File path argument.
293 class ArgCommand(_Argument):
299 class ArgHost(_Argument):
305 class ArgOs(_Argument):
312 ARGS_MANY_INSTANCES = [ArgInstance()]
313 ARGS_MANY_NODES = [ArgNode()]
314 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
315 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
316 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
319 def _ExtractTagsObject(opts, args):
320 """Extract the tag type object.
322 Note that this function will modify its args parameter.
325 if not hasattr(opts, "tag_type"):
326 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
328 if kind == constants.TAG_CLUSTER:
330 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
332 raise errors.OpPrereqError("no arguments passed to the command")
336 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
340 def _ExtendTags(opts, args):
341 """Extend the args if a source file has been given.
343 This function will extend the tags with the contents of the file
344 passed in the 'tags_source' attribute of the opts parameter. A file
345 named '-' will be replaced by stdin.
348 fname = opts.tags_source
354 new_fh = open(fname, "r")
357 # we don't use the nice 'new_data = [line.strip() for line in fh]'
358 # because of python bug 1633941
360 line = new_fh.readline()
363 new_data.append(line.strip())
366 args.extend(new_data)
369 def ListTags(opts, args):
370 """List the tags on a given object.
372 This is a generic implementation that knows how to deal with all
373 three cases of tag objects (cluster, node, instance). The opts
374 argument is expected to contain a tag_type field denoting what
375 object type we work on.
378 kind, name = _ExtractTagsObject(opts, args)
380 result = cl.QueryTags(kind, name)
381 result = list(result)
387 def AddTags(opts, args):
388 """Add tags on a given object.
390 This is a generic implementation that knows how to deal with all
391 three cases of tag objects (cluster, node, instance). The opts
392 argument is expected to contain a tag_type field denoting what
393 object type we work on.
396 kind, name = _ExtractTagsObject(opts, args)
397 _ExtendTags(opts, args)
399 raise errors.OpPrereqError("No tags to be added")
400 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
401 SubmitOpCode(op, opts=opts)
404 def RemoveTags(opts, args):
405 """Remove tags from a given object.
407 This is a generic implementation that knows how to deal with all
408 three cases of tag objects (cluster, node, instance). The opts
409 argument is expected to contain a tag_type field denoting what
410 object type we work on.
413 kind, name = _ExtractTagsObject(opts, args)
414 _ExtendTags(opts, args)
416 raise errors.OpPrereqError("No tags to be removed")
417 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
418 SubmitOpCode(op, opts=opts)
421 def check_unit(option, opt, value): # pylint: disable-msg=W0613
422 """OptParsers custom converter for units.
426 return utils.ParseUnit(value)
427 except errors.UnitParseError, err:
428 raise OptionValueError("option %s: %s" % (opt, err))
431 def _SplitKeyVal(opt, data):
432 """Convert a KeyVal string into a dict.
434 This function will convert a key=val[,...] string into a dict. Empty
435 values will be converted specially: keys which have the prefix 'no_'
436 will have the value=False and the prefix stripped, the others will
440 @param opt: a string holding the option name for which we process the
441 data, used in building error messages
443 @param data: a string of the format key=val,key=val,...
445 @return: {key=val, key=val}
446 @raises errors.ParameterError: if there are duplicate keys
451 for elem in utils.UnescapeAndSplit(data, sep=","):
453 key, val = elem.split("=", 1)
455 if elem.startswith(NO_PREFIX):
456 key, val = elem[len(NO_PREFIX):], False
457 elif elem.startswith(UN_PREFIX):
458 key, val = elem[len(UN_PREFIX):], None
460 key, val = elem, True
462 raise errors.ParameterError("Duplicate key '%s' in option %s" %
468 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
469 """Custom parser for ident:key=val,key=val options.
471 This will store the parsed values as a tuple (ident, {key: val}). As such,
472 multiple uses of this option via action=append is possible.
476 ident, rest = value, ''
478 ident, rest = value.split(":", 1)
480 if ident.startswith(NO_PREFIX):
482 msg = "Cannot pass options when removing parameter groups: %s" % value
483 raise errors.ParameterError(msg)
484 retval = (ident[len(NO_PREFIX):], False)
485 elif ident.startswith(UN_PREFIX):
487 msg = "Cannot pass options when removing parameter groups: %s" % value
488 raise errors.ParameterError(msg)
489 retval = (ident[len(UN_PREFIX):], None)
491 kv_dict = _SplitKeyVal(opt, rest)
492 retval = (ident, kv_dict)
496 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
497 """Custom parser class for key=val,key=val options.
499 This will store the parsed values as a dict {key: val}.
502 return _SplitKeyVal(opt, value)
505 def check_bool(option, opt, value): # pylint: disable-msg=W0613
506 """Custom parser for yes/no options.
508 This will store the parsed value as either True or False.
511 value = value.lower()
512 if value == constants.VALUE_FALSE or value == "no":
514 elif value == constants.VALUE_TRUE or value == "yes":
517 raise errors.ParameterError("Invalid boolean value '%s'" % value)
520 # completion_suggestion is normally a list. Using numeric values not evaluating
521 # to False for dynamic completion.
522 (OPT_COMPL_MANY_NODES,
524 OPT_COMPL_ONE_INSTANCE,
526 OPT_COMPL_ONE_IALLOCATOR,
527 OPT_COMPL_INST_ADD_NODES,
528 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
530 OPT_COMPL_ALL = frozenset([
531 OPT_COMPL_MANY_NODES,
533 OPT_COMPL_ONE_INSTANCE,
535 OPT_COMPL_ONE_IALLOCATOR,
536 OPT_COMPL_INST_ADD_NODES,
537 OPT_COMPL_ONE_NODEGROUP,
541 class CliOption(Option):
542 """Custom option class for optparse.
545 ATTRS = Option.ATTRS + [
546 "completion_suggest",
548 TYPES = Option.TYPES + (
554 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
555 TYPE_CHECKER["identkeyval"] = check_ident_key_val
556 TYPE_CHECKER["keyval"] = check_key_val
557 TYPE_CHECKER["unit"] = check_unit
558 TYPE_CHECKER["bool"] = check_bool
561 # optparse.py sets make_option, so we do it for our own option class, too
562 cli_option = CliOption
567 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
568 help="Increase debugging level")
570 NOHDR_OPT = cli_option("--no-headers", default=False,
571 action="store_true", dest="no_headers",
572 help="Don't display column headers")
574 SEP_OPT = cli_option("--separator", default=None,
575 action="store", dest="separator",
576 help=("Separator between output fields"
577 " (defaults to one space)"))
579 USEUNITS_OPT = cli_option("--units", default=None,
580 dest="units", choices=('h', 'm', 'g', 't'),
581 help="Specify units for output (one of hmgt)")
583 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
584 type="string", metavar="FIELDS",
585 help="Comma separated list of output fields")
587 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
588 default=False, help="Force the operation")
590 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
591 default=False, help="Do not require confirmation")
593 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
594 action="store_true", default=False,
595 help=("Ignore offline nodes and do as much"
598 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
599 default=None, help="File with tag names")
601 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
602 default=False, action="store_true",
603 help=("Submit the job and return the job ID, but"
604 " don't wait for the job to finish"))
606 SYNC_OPT = cli_option("--sync", dest="do_locking",
607 default=False, action="store_true",
608 help=("Grab locks while doing the queries"
609 " in order to ensure more consistent results"))
611 DRY_RUN_OPT = cli_option("--dry-run", default=False,
613 help=("Do not execute the operation, just run the"
614 " check steps and verify it it could be"
617 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
619 help="Increase the verbosity of the operation")
621 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
622 action="store_true", dest="simulate_errors",
623 help="Debugging option that makes the operation"
624 " treat most runtime checks as failed")
626 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
627 default=True, action="store_false",
628 help="Don't wait for sync (DANGEROUS!)")
630 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
631 help="Custom disk setup (diskless, file,"
633 default=None, metavar="TEMPL",
634 choices=list(constants.DISK_TEMPLATES))
636 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
637 help="Do not create any network cards for"
640 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
641 help="Relative path under default cluster-wide"
642 " file storage dir to store file-based disks",
643 default=None, metavar="<DIR>")
645 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
646 help="Driver to use for image files",
647 default="loop", metavar="<DRIVER>",
648 choices=list(constants.FILE_DRIVER))
650 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
651 help="Select nodes for the instance automatically"
652 " using the <NAME> iallocator plugin",
653 default=None, type="string",
654 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
656 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
658 help="Set the default instance allocator plugin",
659 default=None, type="string",
660 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
662 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
664 completion_suggest=OPT_COMPL_ONE_OS)
666 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
667 type="keyval", default={},
668 help="OS parameters")
670 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
671 action="store_true", default=False,
672 help="Force an unknown variant")
674 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
675 action="store_true", default=False,
676 help="Do not install the OS (will"
679 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
680 type="keyval", default={},
681 help="Backend parameters")
683 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
684 default={}, dest="hvparams",
685 help="Hypervisor parameters")
687 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
688 help="Hypervisor and hypervisor options, in the"
689 " format hypervisor:option=value,option=value,...",
690 default=None, type="identkeyval")
692 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
693 help="Hypervisor and hypervisor options, in the"
694 " format hypervisor:option=value,option=value,...",
695 default=[], action="append", type="identkeyval")
697 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
698 action="store_false",
699 help="Don't check that the instance's IP"
702 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
703 default=True, action="store_false",
704 help="Don't check that the instance's name"
707 NET_OPT = cli_option("--net",
708 help="NIC parameters", default=[],
709 dest="nics", action="append", type="identkeyval")
711 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
712 dest="disks", action="append", type="identkeyval")
714 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
715 help="Comma-separated list of disks"
716 " indices to act on (e.g. 0,2) (optional,"
717 " defaults to all disks)")
719 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
720 help="Enforces a single-disk configuration using the"
721 " given disk size, in MiB unless a suffix is used",
722 default=None, type="unit", metavar="<size>")
724 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
725 dest="ignore_consistency",
726 action="store_true", default=False,
727 help="Ignore the consistency of the disks on"
730 NONLIVE_OPT = cli_option("--non-live", dest="live",
731 default=True, action="store_false",
732 help="Do a non-live migration (this usually means"
733 " freeze the instance, save the state, transfer and"
734 " only then resume running on the secondary node)")
736 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
738 choices=list(constants.HT_MIGRATION_MODES),
739 help="Override default migration mode (choose"
740 " either live or non-live")
742 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
743 help="Target node and optional secondary node",
744 metavar="<pnode>[:<snode>]",
745 completion_suggest=OPT_COMPL_INST_ADD_NODES)
747 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
748 action="append", metavar="<node>",
749 help="Use only this node (can be used multiple"
750 " times, if not given defaults to all nodes)",
751 completion_suggest=OPT_COMPL_ONE_NODE)
753 NODEGROUP_OPT = cli_option("-g", "--node-group",
755 help="Node group (name or uuid)",
756 metavar="<nodegroup>",
757 default=None, type="string",
758 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
760 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
762 completion_suggest=OPT_COMPL_ONE_NODE)
764 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
765 action="store_false",
766 help="Don't start the instance after creation")
768 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
769 action="store_true", default=False,
770 help="Show command instead of executing it")
772 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
773 default=False, action="store_true",
774 help="Instead of performing the migration, try to"
775 " recover from a failed cleanup. This is safe"
776 " to run even if the instance is healthy, but it"
777 " will create extra replication traffic and "
778 " disrupt briefly the replication (like during the"
781 STATIC_OPT = cli_option("-s", "--static", dest="static",
782 action="store_true", default=False,
783 help="Only show configuration data, not runtime data")
785 ALL_OPT = cli_option("--all", dest="show_all",
786 default=False, action="store_true",
787 help="Show info on all instances on the cluster."
788 " This can take a long time to run, use wisely")
790 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
791 action="store_true", default=False,
792 help="Interactive OS reinstall, lists available"
793 " OS templates for selection")
795 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
796 action="store_true", default=False,
797 help="Remove the instance from the cluster"
798 " configuration even if there are failures"
799 " during the removal process")
801 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
802 dest="ignore_remove_failures",
803 action="store_true", default=False,
804 help="Remove the instance from the"
805 " cluster configuration even if there"
806 " are failures during the removal"
809 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
810 action="store_true", default=False,
811 help="Remove the instance from the cluster")
813 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
814 help="Specifies the new secondary node",
815 metavar="NODE", default=None,
816 completion_suggest=OPT_COMPL_ONE_NODE)
818 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
819 default=False, action="store_true",
820 help="Replace the disk(s) on the primary"
821 " node (only for the drbd template)")
823 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
824 default=False, action="store_true",
825 help="Replace the disk(s) on the secondary"
826 " node (only for the drbd template)")
828 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
829 default=False, action="store_true",
830 help="Lock all nodes and auto-promote as needed"
833 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
834 default=False, action="store_true",
835 help="Automatically replace faulty disks"
836 " (only for the drbd template)")
838 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
839 default=False, action="store_true",
840 help="Ignore current recorded size"
841 " (useful for forcing activation when"
842 " the recorded size is wrong)")
844 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
846 completion_suggest=OPT_COMPL_ONE_NODE)
848 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
851 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
852 help="Specify the secondary ip for the node",
853 metavar="ADDRESS", default=None)
855 READD_OPT = cli_option("--readd", dest="readd",
856 default=False, action="store_true",
857 help="Readd old node after replacing it")
859 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
860 default=True, action="store_false",
861 help="Disable SSH key fingerprint checking")
863 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
864 type="bool", default=None, metavar=_YORNO,
865 help="Set the master_candidate flag on the node")
867 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
868 type="bool", default=None,
869 help=("Set the offline flag on the node"
870 " (cluster does not communicate with offline"
873 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
874 type="bool", default=None,
875 help=("Set the drained flag on the node"
876 " (excluded from allocation operations)"))
878 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
879 type="bool", default=None, metavar=_YORNO,
880 help="Set the master_capable flag on the node")
882 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
883 type="bool", default=None, metavar=_YORNO,
884 help="Set the vm_capable flag on the node")
886 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
887 type="bool", default=None, metavar=_YORNO,
888 help="Set the allocatable flag on a volume")
890 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
891 help="Disable support for lvm based instances"
893 action="store_false", default=True)
895 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
896 dest="enabled_hypervisors",
897 help="Comma-separated list of hypervisors",
898 type="string", default=None)
900 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
901 type="keyval", default={},
902 help="NIC parameters")
904 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
905 dest="candidate_pool_size", type="int",
906 help="Set the candidate pool size")
908 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
909 help=("Enables LVM and specifies the volume group"
910 " name (cluster-wide) for disk allocation"
911 " [%s]" % constants.DEFAULT_VG),
912 metavar="VG", default=None)
914 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
915 help="Destroy cluster", action="store_true")
917 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
918 help="Skip node agreement check (dangerous)",
919 action="store_true", default=False)
921 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
922 help="Specify the mac prefix for the instance IP"
923 " addresses, in the format XX:XX:XX",
927 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
928 help="Specify the node interface (cluster-wide)"
929 " on which the master IP address will be added "
930 " [%s]" % constants.DEFAULT_BRIDGE,
932 default=constants.DEFAULT_BRIDGE)
934 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
935 help="Specify the default directory (cluster-"
936 "wide) for storing the file-based disks [%s]" %
937 constants.DEFAULT_FILE_STORAGE_DIR,
939 default=constants.DEFAULT_FILE_STORAGE_DIR)
941 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
942 help="Don't modify /etc/hosts",
943 action="store_false", default=True)
945 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
946 help="Don't initialize SSH keys",
947 action="store_false", default=True)
949 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
950 help="Enable parseable error messages",
951 action="store_true", default=False)
953 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
954 help="Skip N+1 memory redundancy tests",
955 action="store_true", default=False)
957 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
958 help="Type of reboot: soft/hard/full",
959 default=constants.INSTANCE_REBOOT_HARD,
961 choices=list(constants.REBOOT_TYPES))
963 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
964 dest="ignore_secondaries",
965 default=False, action="store_true",
966 help="Ignore errors from secondaries")
968 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
969 action="store_false", default=True,
970 help="Don't shutdown the instance (unsafe)")
972 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
973 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
974 help="Maximum time to wait")
976 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
977 dest="shutdown_timeout", type="int",
978 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
979 help="Maximum time to wait for instance shutdown")
981 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
983 help=("Number of seconds between repetions of the"
986 EARLY_RELEASE_OPT = cli_option("--early-release",
987 dest="early_release", default=False,
989 help="Release the locks on the secondary"
992 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
993 dest="new_cluster_cert",
994 default=False, action="store_true",
995 help="Generate a new cluster certificate")
997 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
999 help="File containing new RAPI certificate")
1001 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1002 default=None, action="store_true",
1003 help=("Generate a new self-signed RAPI"
1006 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1007 dest="new_confd_hmac_key",
1008 default=False, action="store_true",
1009 help=("Create a new HMAC key for %s" %
1012 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1013 dest="cluster_domain_secret",
1015 help=("Load new new cluster domain"
1016 " secret from file"))
1018 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1019 dest="new_cluster_domain_secret",
1020 default=False, action="store_true",
1021 help=("Create a new cluster domain"
1024 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1025 dest="use_replication_network",
1026 help="Whether to use the replication network"
1027 " for talking to the nodes",
1028 action="store_true", default=False)
1030 MAINTAIN_NODE_HEALTH_OPT = \
1031 cli_option("--maintain-node-health", dest="maintain_node_health",
1032 metavar=_YORNO, default=None, type="bool",
1033 help="Configure the cluster to automatically maintain node"
1034 " health, by shutting down unknown instances, shutting down"
1035 " unknown DRBD devices, etc.")
1037 IDENTIFY_DEFAULTS_OPT = \
1038 cli_option("--identify-defaults", dest="identify_defaults",
1039 default=False, action="store_true",
1040 help="Identify which saved instance parameters are equal to"
1041 " the current cluster defaults and set them as such, instead"
1042 " of marking them as overridden")
1044 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1045 action="store", dest="uid_pool",
1046 help=("A list of user-ids or user-id"
1047 " ranges separated by commas"))
1049 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1050 action="store", dest="add_uids",
1051 help=("A list of user-ids or user-id"
1052 " ranges separated by commas, to be"
1053 " added to the user-id pool"))
1055 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1056 action="store", dest="remove_uids",
1057 help=("A list of user-ids or user-id"
1058 " ranges separated by commas, to be"
1059 " removed from the user-id pool"))
1061 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1062 action="store", dest="reserved_lvs",
1063 help=("A comma-separated list of reserved"
1064 " logical volumes names, that will be"
1065 " ignored by cluster verify"))
1067 ROMAN_OPT = cli_option("--roman",
1068 dest="roman_integers", default=False,
1069 action="store_true",
1070 help="Use roman numbers for positive integers")
1072 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1073 action="store", default=None,
1074 help="Specifies usermode helper for DRBD")
1076 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1077 action="store_false", default=True,
1078 help="Disable support for DRBD")
1080 PRIMARY_IP_VERSION_OPT = \
1081 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1082 action="store", dest="primary_ip_version",
1083 metavar="%d|%d" % (constants.IP4_VERSION,
1084 constants.IP6_VERSION),
1085 help="Cluster-wide IP version for primary IP")
1087 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1088 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1089 choices=_PRIONAME_TO_VALUE.keys(),
1090 help="Priority for opcode processing")
1092 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1093 type="bool", default=None, metavar=_YORNO,
1094 help="Sets the hidden flag on the OS")
1096 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1097 type="bool", default=None, metavar=_YORNO,
1098 help="Sets the blacklisted flag on the OS")
1100 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1101 type="bool", metavar=_YORNO,
1102 dest="prealloc_wipe_disks",
1103 help=("Wipe disks prior to instance"
1107 #: Options provided by all commands
1108 COMMON_OPTS = [DEBUG_OPT]
1110 # common options for creating instances. add and import then add their own
1112 COMMON_CREATE_OPTS = [
1117 FILESTORE_DRIVER_OPT,
1134 def _ParseArgs(argv, commands, aliases):
1135 """Parser for the command line arguments.
1137 This function parses the arguments and returns the function which
1138 must be executed together with its (modified) arguments.
1140 @param argv: the command line
1141 @param commands: dictionary with special contents, see the design
1142 doc for cmdline handling
1143 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1147 binary = "<command>"
1149 binary = argv[0].split("/")[-1]
1151 if len(argv) > 1 and argv[1] == "--version":
1152 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1153 constants.RELEASE_VERSION)
1154 # Quit right away. That way we don't have to care about this special
1155 # argument. optparse.py does it the same.
1158 if len(argv) < 2 or not (argv[1] in commands or
1159 argv[1] in aliases):
1160 # let's do a nice thing
1161 sortedcmds = commands.keys()
1164 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1165 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1168 # compute the max line length for cmd + usage
1169 mlen = max([len(" %s" % cmd) for cmd in commands])
1170 mlen = min(60, mlen) # should not get here...
1172 # and format a nice command list
1173 ToStdout("Commands:")
1174 for cmd in sortedcmds:
1175 cmdstr = " %s" % (cmd,)
1176 help_text = commands[cmd][4]
1177 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1178 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1179 for line in help_lines:
1180 ToStdout("%-*s %s", mlen, "", line)
1184 return None, None, None
1186 # get command, unalias it, and look it up in commands
1190 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1193 if aliases[cmd] not in commands:
1194 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1195 " command '%s'" % (cmd, aliases[cmd]))
1199 func, args_def, parser_opts, usage, description = commands[cmd]
1200 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1201 description=description,
1202 formatter=TitledHelpFormatter(),
1203 usage="%%prog %s %s" % (cmd, usage))
1204 parser.disable_interspersed_args()
1205 options, args = parser.parse_args()
1207 if not _CheckArguments(cmd, args_def, args):
1208 return None, None, None
1210 return func, options, args
1213 def _CheckArguments(cmd, args_def, args):
1214 """Verifies the arguments using the argument definition.
1218 1. Abort with error if values specified by user but none expected.
1220 1. For each argument in definition
1222 1. Keep running count of minimum number of values (min_count)
1223 1. Keep running count of maximum number of values (max_count)
1224 1. If it has an unlimited number of values
1226 1. Abort with error if it's not the last argument in the definition
1228 1. If last argument has limited number of values
1230 1. Abort with error if number of values doesn't match or is too large
1232 1. Abort with error if user didn't pass enough values (min_count)
1235 if args and not args_def:
1236 ToStderr("Error: Command %s expects no arguments", cmd)
1243 last_idx = len(args_def) - 1
1245 for idx, arg in enumerate(args_def):
1246 if min_count is None:
1248 elif arg.min is not None:
1249 min_count += arg.min
1251 if max_count is None:
1253 elif arg.max is not None:
1254 max_count += arg.max
1257 check_max = (arg.max is not None)
1259 elif arg.max is None:
1260 raise errors.ProgrammerError("Only the last argument can have max=None")
1263 # Command with exact number of arguments
1264 if (min_count is not None and max_count is not None and
1265 min_count == max_count and len(args) != min_count):
1266 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1269 # Command with limited number of arguments
1270 if max_count is not None and len(args) > max_count:
1271 ToStderr("Error: Command %s expects only %d argument(s)",
1275 # Command with some required arguments
1276 if min_count is not None and len(args) < min_count:
1277 ToStderr("Error: Command %s expects at least %d argument(s)",
1284 def SplitNodeOption(value):
1285 """Splits the value of a --node option.
1288 if value and ':' in value:
1289 return value.split(':', 1)
1291 return (value, None)
1294 def CalculateOSNames(os_name, os_variants):
1295 """Calculates all the names an OS can be called, according to its variants.
1297 @type os_name: string
1298 @param os_name: base name of the os
1299 @type os_variants: list or None
1300 @param os_variants: list of supported variants
1302 @return: list of valid names
1306 return ['%s+%s' % (os_name, v) for v in os_variants]
1311 def ParseFields(selected, default):
1312 """Parses the values of "--field"-like options.
1314 @type selected: string or None
1315 @param selected: User-selected options
1317 @param default: Default fields
1320 if selected is None:
1323 if selected.startswith("+"):
1324 return default + selected[1:].split(",")
1326 return selected.split(",")
1329 UsesRPC = rpc.RunWithRPC
1332 def AskUser(text, choices=None):
1333 """Ask the user a question.
1335 @param text: the question to ask
1337 @param choices: list with elements tuples (input_char, return_value,
1338 description); if not given, it will default to: [('y', True,
1339 'Perform the operation'), ('n', False, 'Do no do the operation')];
1340 note that the '?' char is reserved for help
1342 @return: one of the return values from the choices list; if input is
1343 not possible (i.e. not running with a tty, we return the last
1348 choices = [('y', True, 'Perform the operation'),
1349 ('n', False, 'Do not perform the operation')]
1350 if not choices or not isinstance(choices, list):
1351 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1352 for entry in choices:
1353 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1354 raise errors.ProgrammerError("Invalid choices element to AskUser")
1356 answer = choices[-1][1]
1358 for line in text.splitlines():
1359 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1360 text = "\n".join(new_text)
1362 f = file("/dev/tty", "a+")
1366 chars = [entry[0] for entry in choices]
1367 chars[-1] = "[%s]" % chars[-1]
1369 maps = dict([(entry[0], entry[1]) for entry in choices])
1373 f.write("/".join(chars))
1375 line = f.readline(2).strip().lower()
1380 for entry in choices:
1381 f.write(" %s - %s\n" % (entry[0], entry[2]))
1389 class JobSubmittedException(Exception):
1390 """Job was submitted, client should exit.
1392 This exception has one argument, the ID of the job that was
1393 submitted. The handler should print this ID.
1395 This is not an error, just a structured way to exit from clients.
1400 def SendJob(ops, cl=None):
1401 """Function to submit an opcode without waiting for the results.
1404 @param ops: list of opcodes
1405 @type cl: luxi.Client
1406 @param cl: the luxi client to use for communicating with the master;
1407 if None, a new client will be created
1413 job_id = cl.SubmitJob(ops)
1418 def GenericPollJob(job_id, cbs, report_cbs):
1419 """Generic job-polling function.
1421 @type job_id: number
1422 @param job_id: Job ID
1423 @type cbs: Instance of L{JobPollCbBase}
1424 @param cbs: Data callbacks
1425 @type report_cbs: Instance of L{JobPollReportCbBase}
1426 @param report_cbs: Reporting callbacks
1429 prev_job_info = None
1430 prev_logmsg_serial = None
1435 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1438 # job not found, go away!
1439 raise errors.JobLost("Job with id %s lost" % job_id)
1441 if result == constants.JOB_NOTCHANGED:
1442 report_cbs.ReportNotChanged(job_id, status)
1447 # Split result, a tuple of (field values, log entries)
1448 (job_info, log_entries) = result
1449 (status, ) = job_info
1452 for log_entry in log_entries:
1453 (serial, timestamp, log_type, message) = log_entry
1454 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1456 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1458 # TODO: Handle canceled and archived jobs
1459 elif status in (constants.JOB_STATUS_SUCCESS,
1460 constants.JOB_STATUS_ERROR,
1461 constants.JOB_STATUS_CANCELING,
1462 constants.JOB_STATUS_CANCELED):
1465 prev_job_info = job_info
1467 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1469 raise errors.JobLost("Job with id %s lost" % job_id)
1471 status, opstatus, result = jobs[0]
1473 if status == constants.JOB_STATUS_SUCCESS:
1476 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1477 raise errors.OpExecError("Job was canceled")
1480 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1481 if status == constants.OP_STATUS_SUCCESS:
1483 elif status == constants.OP_STATUS_ERROR:
1484 errors.MaybeRaise(msg)
1487 raise errors.OpExecError("partial failure (opcode %d): %s" %
1490 raise errors.OpExecError(str(msg))
1492 # default failure mode
1493 raise errors.OpExecError(result)
1496 class JobPollCbBase:
1497 """Base class for L{GenericPollJob} callbacks.
1501 """Initializes this class.
1505 def WaitForJobChangeOnce(self, job_id, fields,
1506 prev_job_info, prev_log_serial):
1507 """Waits for changes on a job.
1510 raise NotImplementedError()
1512 def QueryJobs(self, job_ids, fields):
1513 """Returns the selected fields for the selected job IDs.
1515 @type job_ids: list of numbers
1516 @param job_ids: Job IDs
1517 @type fields: list of strings
1518 @param fields: Fields
1521 raise NotImplementedError()
1524 class JobPollReportCbBase:
1525 """Base class for L{GenericPollJob} reporting callbacks.
1529 """Initializes this class.
1533 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1534 """Handles a log message.
1537 raise NotImplementedError()
1539 def ReportNotChanged(self, job_id, status):
1540 """Called for if a job hasn't changed in a while.
1542 @type job_id: number
1543 @param job_id: Job ID
1544 @type status: string or None
1545 @param status: Job status if available
1548 raise NotImplementedError()
1551 class _LuxiJobPollCb(JobPollCbBase):
1552 def __init__(self, cl):
1553 """Initializes this class.
1556 JobPollCbBase.__init__(self)
1559 def WaitForJobChangeOnce(self, job_id, fields,
1560 prev_job_info, prev_log_serial):
1561 """Waits for changes on a job.
1564 return self.cl.WaitForJobChangeOnce(job_id, fields,
1565 prev_job_info, prev_log_serial)
1567 def QueryJobs(self, job_ids, fields):
1568 """Returns the selected fields for the selected job IDs.
1571 return self.cl.QueryJobs(job_ids, fields)
1574 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1575 def __init__(self, feedback_fn):
1576 """Initializes this class.
1579 JobPollReportCbBase.__init__(self)
1581 self.feedback_fn = feedback_fn
1583 assert callable(feedback_fn)
1585 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1586 """Handles a log message.
1589 self.feedback_fn((timestamp, log_type, log_msg))
1591 def ReportNotChanged(self, job_id, status):
1592 """Called if a job hasn't changed in a while.
1598 class StdioJobPollReportCb(JobPollReportCbBase):
1600 """Initializes this class.
1603 JobPollReportCbBase.__init__(self)
1605 self.notified_queued = False
1606 self.notified_waitlock = False
1608 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1609 """Handles a log message.
1612 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1613 FormatLogMessage(log_type, log_msg))
1615 def ReportNotChanged(self, job_id, status):
1616 """Called if a job hasn't changed in a while.
1622 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1623 ToStderr("Job %s is waiting in queue", job_id)
1624 self.notified_queued = True
1626 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1627 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1628 self.notified_waitlock = True
1631 def FormatLogMessage(log_type, log_msg):
1632 """Formats a job message according to its type.
1635 if log_type != constants.ELOG_MESSAGE:
1636 log_msg = str(log_msg)
1638 return utils.SafeEncode(log_msg)
1641 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1642 """Function to poll for the result of a job.
1644 @type job_id: job identified
1645 @param job_id: the job to poll for results
1646 @type cl: luxi.Client
1647 @param cl: the luxi client to use for communicating with the master;
1648 if None, a new client will be created
1654 if reporter is None:
1656 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1658 reporter = StdioJobPollReportCb()
1660 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1662 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1665 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1666 """Legacy function to submit an opcode.
1668 This is just a simple wrapper over the construction of the processor
1669 instance. It should be extended to better handle feedback and
1670 interaction functions.
1676 SetGenericOpcodeOpts([op], opts)
1678 job_id = SendJob([op], cl=cl)
1680 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1683 return op_results[0]
1686 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1687 """Wrapper around SubmitOpCode or SendJob.
1689 This function will decide, based on the 'opts' parameter, whether to
1690 submit and wait for the result of the opcode (and return it), or
1691 whether to just send the job and print its identifier. It is used in
1692 order to simplify the implementation of the '--submit' option.
1694 It will also process the opcodes if we're sending the via SendJob
1695 (otherwise SubmitOpCode does it).
1698 if opts and opts.submit_only:
1700 SetGenericOpcodeOpts(job, opts)
1701 job_id = SendJob(job, cl=cl)
1702 raise JobSubmittedException(job_id)
1704 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1707 def SetGenericOpcodeOpts(opcode_list, options):
1708 """Processor for generic options.
1710 This function updates the given opcodes based on generic command
1711 line options (like debug, dry-run, etc.).
1713 @param opcode_list: list of opcodes
1714 @param options: command line options or None
1715 @return: None (in-place modification)
1720 for op in opcode_list:
1721 op.debug_level = options.debug
1722 if hasattr(options, "dry_run"):
1723 op.dry_run = options.dry_run
1724 if getattr(options, "priority", None) is not None:
1725 op.priority = _PRIONAME_TO_VALUE[options.priority]
1729 # TODO: Cache object?
1731 client = luxi.Client()
1732 except luxi.NoMasterError:
1733 ss = ssconf.SimpleStore()
1735 # Try to read ssconf file
1738 except errors.ConfigurationError:
1739 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1740 " not part of a cluster")
1742 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1743 if master != myself:
1744 raise errors.OpPrereqError("This is not the master node, please connect"
1745 " to node '%s' and rerun the command" %
1751 def FormatError(err):
1752 """Return a formatted error message for a given error.
1754 This function takes an exception instance and returns a tuple
1755 consisting of two values: first, the recommended exit code, and
1756 second, a string describing the error message (not
1757 newline-terminated).
1763 if isinstance(err, errors.ConfigurationError):
1764 txt = "Corrupt configuration file: %s" % msg
1766 obuf.write(txt + "\n")
1767 obuf.write("Aborting.")
1769 elif isinstance(err, errors.HooksAbort):
1770 obuf.write("Failure: hooks execution failed:\n")
1771 for node, script, out in err.args[0]:
1773 obuf.write(" node: %s, script: %s, output: %s\n" %
1774 (node, script, out))
1776 obuf.write(" node: %s, script: %s (no output)\n" %
1778 elif isinstance(err, errors.HooksFailure):
1779 obuf.write("Failure: hooks general failure: %s" % msg)
1780 elif isinstance(err, errors.ResolverError):
1781 this_host = netutils.Hostname.GetSysName()
1782 if err.args[0] == this_host:
1783 msg = "Failure: can't resolve my own hostname ('%s')"
1785 msg = "Failure: can't resolve hostname '%s'"
1786 obuf.write(msg % err.args[0])
1787 elif isinstance(err, errors.OpPrereqError):
1788 if len(err.args) == 2:
1789 obuf.write("Failure: prerequisites not met for this"
1790 " operation:\nerror type: %s, error details:\n%s" %
1791 (err.args[1], err.args[0]))
1793 obuf.write("Failure: prerequisites not met for this"
1794 " operation:\n%s" % msg)
1795 elif isinstance(err, errors.OpExecError):
1796 obuf.write("Failure: command execution error:\n%s" % msg)
1797 elif isinstance(err, errors.TagError):
1798 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1799 elif isinstance(err, errors.JobQueueDrainError):
1800 obuf.write("Failure: the job queue is marked for drain and doesn't"
1801 " accept new requests\n")
1802 elif isinstance(err, errors.JobQueueFull):
1803 obuf.write("Failure: the job queue is full and doesn't accept new"
1804 " job submissions until old jobs are archived\n")
1805 elif isinstance(err, errors.TypeEnforcementError):
1806 obuf.write("Parameter Error: %s" % msg)
1807 elif isinstance(err, errors.ParameterError):
1808 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1809 elif isinstance(err, luxi.NoMasterError):
1810 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1811 " and listening for connections?")
1812 elif isinstance(err, luxi.TimeoutError):
1813 obuf.write("Timeout while talking to the master daemon. Jobs might have"
1814 " been submitted and will continue to run even if the call"
1815 " timed out. Useful commands in this situation are \"gnt-job"
1816 " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
1818 elif isinstance(err, luxi.PermissionError):
1819 obuf.write("It seems you don't have permissions to connect to the"
1820 " master daemon.\nPlease retry as a different user.")
1821 elif isinstance(err, luxi.ProtocolError):
1822 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1824 elif isinstance(err, errors.JobLost):
1825 obuf.write("Error checking job status: %s" % msg)
1826 elif isinstance(err, errors.GenericError):
1827 obuf.write("Unhandled Ganeti error: %s" % msg)
1828 elif isinstance(err, JobSubmittedException):
1829 obuf.write("JobID: %s\n" % err.args[0])
1832 obuf.write("Unhandled exception: %s" % msg)
1833 return retcode, obuf.getvalue().rstrip('\n')
1836 def GenericMain(commands, override=None, aliases=None):
1837 """Generic main function for all the gnt-* commands.
1840 - commands: a dictionary with a special structure, see the design doc
1841 for command line handling.
1842 - override: if not None, we expect a dictionary with keys that will
1843 override command line options; this can be used to pass
1844 options from the scripts to generic functions
1845 - aliases: dictionary with command aliases {'alias': 'target, ...}
1848 # save the program name and the entire command line for later logging
1850 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1851 if len(sys.argv) >= 2:
1852 binary += " " + sys.argv[1]
1853 old_cmdline = " ".join(sys.argv[2:])
1857 binary = "<unknown program>"
1864 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1865 except errors.ParameterError, err:
1866 result, err_msg = FormatError(err)
1870 if func is None: # parse error
1873 if override is not None:
1874 for key, val in override.iteritems():
1875 setattr(options, key, val)
1877 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1878 stderr_logging=True, program=binary)
1881 logging.info("run with arguments '%s'", old_cmdline)
1883 logging.info("run with no arguments")
1886 result = func(options, args)
1887 except (errors.GenericError, luxi.ProtocolError,
1888 JobSubmittedException), err:
1889 result, err_msg = FormatError(err)
1890 logging.exception("Error during command processing")
1896 def ParseNicOption(optvalue):
1897 """Parses the value of the --net option(s).
1901 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1902 except (TypeError, ValueError), err:
1903 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1905 nics = [{}] * nic_max
1906 for nidx, ndict in optvalue:
1909 if not isinstance(ndict, dict):
1910 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1911 " got %s" % (nidx, ndict))
1913 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1920 def GenericInstanceCreate(mode, opts, args):
1921 """Add an instance to the cluster via either creation or import.
1923 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1924 @param opts: the command line options selected by the user
1926 @param args: should contain only one element, the new instance name
1928 @return: the desired exit code
1933 (pnode, snode) = SplitNodeOption(opts.node)
1938 hypervisor, hvparams = opts.hypervisor
1941 nics = ParseNicOption(opts.nics)
1945 elif mode == constants.INSTANCE_CREATE:
1946 # default of one nic, all auto
1952 if opts.disk_template == constants.DT_DISKLESS:
1953 if opts.disks or opts.sd_size is not None:
1954 raise errors.OpPrereqError("Diskless instance but disk"
1955 " information passed")
1958 if (not opts.disks and not opts.sd_size
1959 and mode == constants.INSTANCE_CREATE):
1960 raise errors.OpPrereqError("No disk information specified")
1961 if opts.disks and opts.sd_size is not None:
1962 raise errors.OpPrereqError("Please use either the '--disk' or"
1964 if opts.sd_size is not None:
1965 opts.disks = [(0, {"size": opts.sd_size})]
1969 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1970 except ValueError, err:
1971 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1972 disks = [{}] * disk_max
1975 for didx, ddict in opts.disks:
1977 if not isinstance(ddict, dict):
1978 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1979 raise errors.OpPrereqError(msg)
1980 elif "size" in ddict:
1981 if "adopt" in ddict:
1982 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1983 " (disk %d)" % didx)
1985 ddict["size"] = utils.ParseUnit(ddict["size"])
1986 except ValueError, err:
1987 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1989 elif "adopt" in ddict:
1990 if mode == constants.INSTANCE_IMPORT:
1991 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1995 raise errors.OpPrereqError("Missing size or adoption source for"
1999 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
2000 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2002 if mode == constants.INSTANCE_CREATE:
2005 force_variant = opts.force_variant
2008 no_install = opts.no_install
2009 identify_defaults = False
2010 elif mode == constants.INSTANCE_IMPORT:
2013 force_variant = False
2014 src_node = opts.src_node
2015 src_path = opts.src_dir
2017 identify_defaults = opts.identify_defaults
2019 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2021 op = opcodes.OpCreateInstance(instance_name=instance,
2023 disk_template=opts.disk_template,
2025 pnode=pnode, snode=snode,
2026 ip_check=opts.ip_check,
2027 name_check=opts.name_check,
2028 wait_for_sync=opts.wait_for_sync,
2029 file_storage_dir=opts.file_storage_dir,
2030 file_driver=opts.file_driver,
2031 iallocator=opts.iallocator,
2032 hypervisor=hypervisor,
2034 beparams=opts.beparams,
2035 osparams=opts.osparams,
2039 force_variant=force_variant,
2042 no_install=no_install,
2043 identify_defaults=identify_defaults)
2045 SubmitOrSend(op, opts)
2049 class _RunWhileClusterStoppedHelper:
2050 """Helper class for L{RunWhileClusterStopped} to simplify state management
2053 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2054 """Initializes this class.
2056 @type feedback_fn: callable
2057 @param feedback_fn: Feedback function
2058 @type cluster_name: string
2059 @param cluster_name: Cluster name
2060 @type master_node: string
2061 @param master_node Master node name
2062 @type online_nodes: list
2063 @param online_nodes: List of names of online nodes
2066 self.feedback_fn = feedback_fn
2067 self.cluster_name = cluster_name
2068 self.master_node = master_node
2069 self.online_nodes = online_nodes
2071 self.ssh = ssh.SshRunner(self.cluster_name)
2073 self.nonmaster_nodes = [name for name in online_nodes
2074 if name != master_node]
2076 assert self.master_node not in self.nonmaster_nodes
2078 def _RunCmd(self, node_name, cmd):
2079 """Runs a command on the local or a remote machine.
2081 @type node_name: string
2082 @param node_name: Machine name
2087 if node_name is None or node_name == self.master_node:
2088 # No need to use SSH
2089 result = utils.RunCmd(cmd)
2091 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2094 errmsg = ["Failed to run command %s" % result.cmd]
2096 errmsg.append("on node %s" % node_name)
2097 errmsg.append(": exitcode %s and error %s" %
2098 (result.exit_code, result.output))
2099 raise errors.OpExecError(" ".join(errmsg))
2101 def Call(self, fn, *args):
2102 """Call function while all daemons are stopped.
2105 @param fn: Function to be called
2108 # Pause watcher by acquiring an exclusive lock on watcher state file
2109 self.feedback_fn("Blocking watcher")
2110 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2112 # TODO: Currently, this just blocks. There's no timeout.
2113 # TODO: Should it be a shared lock?
2114 watcher_block.Exclusive(blocking=True)
2116 # Stop master daemons, so that no new jobs can come in and all running
2118 self.feedback_fn("Stopping master daemons")
2119 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2121 # Stop daemons on all nodes
2122 for node_name in self.online_nodes:
2123 self.feedback_fn("Stopping daemons on %s" % node_name)
2124 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2126 # All daemons are shut down now
2128 return fn(self, *args)
2129 except Exception, err:
2130 _, errmsg = FormatError(err)
2131 logging.exception("Caught exception")
2132 self.feedback_fn(errmsg)
2135 # Start cluster again, master node last
2136 for node_name in self.nonmaster_nodes + [self.master_node]:
2137 self.feedback_fn("Starting daemons on %s" % node_name)
2138 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2141 watcher_block.Close()
2144 def RunWhileClusterStopped(feedback_fn, fn, *args):
2145 """Calls a function while all cluster daemons are stopped.
2147 @type feedback_fn: callable
2148 @param feedback_fn: Feedback function
2150 @param fn: Function to be called when daemons are stopped
2153 feedback_fn("Gathering cluster information")
2155 # This ensures we're running on the master daemon
2158 (cluster_name, master_node) = \
2159 cl.QueryConfigValues(["cluster_name", "master_node"])
2161 online_nodes = GetOnlineNodes([], cl=cl)
2163 # Don't keep a reference to the client. The master daemon will go away.
2166 assert master_node in online_nodes
2168 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2169 online_nodes).Call(fn, *args)
2172 def GenerateTable(headers, fields, separator, data,
2173 numfields=None, unitfields=None,
2175 """Prints a table with headers and different fields.
2178 @param headers: dictionary mapping field names to headers for
2181 @param fields: the field names corresponding to each row in
2183 @param separator: the separator to be used; if this is None,
2184 the default 'smart' algorithm is used which computes optimal
2185 field width, otherwise just the separator is used between
2188 @param data: a list of lists, each sublist being one row to be output
2189 @type numfields: list
2190 @param numfields: a list with the fields that hold numeric
2191 values and thus should be right-aligned
2192 @type unitfields: list
2193 @param unitfields: a list with the fields that hold numeric
2194 values that should be formatted with the units field
2195 @type units: string or None
2196 @param units: the units we should use for formatting, or None for
2197 automatic choice (human-readable for non-separator usage, otherwise
2198 megabytes); this is a one-letter string
2207 if numfields is None:
2209 if unitfields is None:
2212 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2213 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2216 for field in fields:
2217 if headers and field not in headers:
2218 # TODO: handle better unknown fields (either revert to old
2219 # style of raising exception, or deal more intelligently with
2221 headers[field] = field
2222 if separator is not None:
2223 format_fields.append("%s")
2224 elif numfields.Matches(field):
2225 format_fields.append("%*s")
2227 format_fields.append("%-*s")
2229 if separator is None:
2230 mlens = [0 for name in fields]
2231 format_str = ' '.join(format_fields)
2233 format_str = separator.replace("%", "%%").join(format_fields)
2238 for idx, val in enumerate(row):
2239 if unitfields.Matches(fields[idx]):
2242 except (TypeError, ValueError):
2245 val = row[idx] = utils.FormatUnit(val, units)
2246 val = row[idx] = str(val)
2247 if separator is None:
2248 mlens[idx] = max(mlens[idx], len(val))
2253 for idx, name in enumerate(fields):
2255 if separator is None:
2256 mlens[idx] = max(mlens[idx], len(hdr))
2257 args.append(mlens[idx])
2259 result.append(format_str % tuple(args))
2261 if separator is None:
2262 assert len(mlens) == len(fields)
2264 if fields and not numfields.Matches(fields[-1]):
2270 line = ['-' for _ in fields]
2271 for idx in range(len(fields)):
2272 if separator is None:
2273 args.append(mlens[idx])
2274 args.append(line[idx])
2275 result.append(format_str % tuple(args))
2280 def FormatTimestamp(ts):
2281 """Formats a given timestamp.
2284 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2287 @return: a string with the formatted timestamp
2290 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2293 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2296 def ParseTimespec(value):
2297 """Parse a time specification.
2299 The following suffixed will be recognized:
2307 Without any suffix, the value will be taken to be in seconds.
2312 raise errors.OpPrereqError("Empty time specification passed")
2320 if value[-1] not in suffix_map:
2323 except (TypeError, ValueError):
2324 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2326 multiplier = suffix_map[value[-1]]
2328 if not value: # no data left after stripping the suffix
2329 raise errors.OpPrereqError("Invalid time specification (only"
2332 value = int(value) * multiplier
2333 except (TypeError, ValueError):
2334 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2338 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2339 filter_master=False):
2340 """Returns the names of online nodes.
2342 This function will also log a warning on stderr with the names of
2345 @param nodes: if not empty, use only this subset of nodes (minus the
2347 @param cl: if not None, luxi client to use
2348 @type nowarn: boolean
2349 @param nowarn: by default, this function will output a note with the
2350 offline nodes that are skipped; if this parameter is True the
2351 note is not displayed
2352 @type secondary_ips: boolean
2353 @param secondary_ips: if True, return the secondary IPs instead of the
2354 names, useful for doing network traffic over the replication interface
2356 @type filter_master: boolean
2357 @param filter_master: if True, do not return the master node in the list
2358 (useful in coordination with secondary_ips where we cannot check our
2359 node name against the list)
2371 master_node = cl.QueryConfigValues(["master_node"])[0]
2372 filter_fn = lambda x: x != master_node
2374 filter_fn = lambda _: True
2376 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2378 offline = [row[0] for row in result if row[1]]
2379 if offline and not nowarn:
2380 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2381 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2384 def _ToStream(stream, txt, *args):
2385 """Write a message to a stream, bypassing the logging system
2387 @type stream: file object
2388 @param stream: the file to which we should write
2390 @param txt: the message
2395 stream.write(txt % args)
2402 def ToStdout(txt, *args):
2403 """Write a message to stdout only, bypassing the logging system
2405 This is just a wrapper over _ToStream.
2408 @param txt: the message
2411 _ToStream(sys.stdout, txt, *args)
2414 def ToStderr(txt, *args):
2415 """Write a message to stderr only, bypassing the logging system
2417 This is just a wrapper over _ToStream.
2420 @param txt: the message
2423 _ToStream(sys.stderr, txt, *args)
2426 class JobExecutor(object):
2427 """Class which manages the submission and execution of multiple jobs.
2429 Note that instances of this class should not be reused between
2433 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2438 self.verbose = verbose
2441 self.feedback_fn = feedback_fn
2443 def QueueJob(self, name, *ops):
2444 """Record a job for later submit.
2447 @param name: a description of the job, will be used in WaitJobSet
2449 SetGenericOpcodeOpts(ops, self.opts)
2450 self.queue.append((name, ops))
2452 def SubmitPending(self, each=False):
2453 """Submit all pending jobs.
2458 for row in self.queue:
2459 # SubmitJob will remove the success status, but raise an exception if
2460 # the submission fails, so we'll notice that anyway.
2461 results.append([True, self.cl.SubmitJob(row[1])])
2463 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2464 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2466 self.jobs.append((idx, status, data, name))
2468 def _ChooseJob(self):
2469 """Choose a non-waiting/queued job to poll next.
2472 assert self.jobs, "_ChooseJob called with empty job list"
2474 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2477 for job_data, status in zip(self.jobs, result):
2478 if (isinstance(status, list) and status and
2479 status[0] in (constants.JOB_STATUS_QUEUED,
2480 constants.JOB_STATUS_WAITLOCK,
2481 constants.JOB_STATUS_CANCELING)):
2482 # job is still present and waiting
2484 # good candidate found (either running job or lost job)
2485 self.jobs.remove(job_data)
2489 return self.jobs.pop(0)
2491 def GetResults(self):
2492 """Wait for and return the results of all jobs.
2495 @return: list of tuples (success, job results), in the same order
2496 as the submitted jobs; if a job has failed, instead of the result
2497 there will be the error message
2501 self.SubmitPending()
2504 ok_jobs = [row[2] for row in self.jobs if row[1]]
2506 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2508 # first, remove any non-submitted jobs
2509 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2510 for idx, _, jid, name in failures:
2511 ToStderr("Failed to submit job for %s: %s", name, jid)
2512 results.append((idx, False, jid))
2515 (idx, _, jid, name) = self._ChooseJob()
2516 ToStdout("Waiting for job %s for %s...", jid, name)
2518 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2520 except errors.JobLost, err:
2521 _, job_result = FormatError(err)
2522 ToStderr("Job %s for %s has been archived, cannot check its result",
2525 except (errors.GenericError, luxi.ProtocolError), err:
2526 _, job_result = FormatError(err)
2528 # the error message will always be shown, verbose or not
2529 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2531 results.append((idx, success, job_result))
2533 # sort based on the index, then drop it
2535 results = [i[1:] for i in results]
2539 def WaitOrShow(self, wait):
2540 """Wait for job results or only print the job IDs.
2543 @param wait: whether to wait or not
2547 return self.GetResults()
2550 self.SubmitPending()
2551 for _, status, result, name in self.jobs:
2553 ToStdout("%s: %s", result, name)
2555 ToStderr("Failure for %s: %s", name, result)
2556 return [row[1:3] for row in self.jobs]