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")
864 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
865 type="bool", default=None, metavar=_YORNO,
866 help="Set the master_candidate flag on the node")
868 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
869 type="bool", default=None,
870 help="Set the offline flag on the node")
872 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
873 type="bool", default=None,
874 help="Set the drained flag on the node")
876 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
877 type="bool", default=None, metavar=_YORNO,
878 help="Set the master_capable flag on the node")
880 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
881 type="bool", default=None, metavar=_YORNO,
882 help="Set the vm_capable flag on the node")
884 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
885 type="bool", default=None, metavar=_YORNO,
886 help="Set the allocatable flag on a volume")
888 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
889 help="Disable support for lvm based instances"
891 action="store_false", default=True)
893 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
894 dest="enabled_hypervisors",
895 help="Comma-separated list of hypervisors",
896 type="string", default=None)
898 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
899 type="keyval", default={},
900 help="NIC parameters")
902 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
903 dest="candidate_pool_size", type="int",
904 help="Set the candidate pool size")
906 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
907 help="Enables LVM and specifies the volume group"
908 " name (cluster-wide) for disk allocation [xenvg]",
909 metavar="VG", default=None)
911 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
912 help="Destroy cluster", action="store_true")
914 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
915 help="Skip node agreement check (dangerous)",
916 action="store_true", default=False)
918 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
919 help="Specify the mac prefix for the instance IP"
920 " addresses, in the format XX:XX:XX",
924 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
925 help="Specify the node interface (cluster-wide)"
926 " on which the master IP address will be added "
927 " [%s]" % constants.DEFAULT_BRIDGE,
929 default=constants.DEFAULT_BRIDGE)
931 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
932 help="Specify the default directory (cluster-"
933 "wide) for storing the file-based disks [%s]" %
934 constants.DEFAULT_FILE_STORAGE_DIR,
936 default=constants.DEFAULT_FILE_STORAGE_DIR)
938 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
939 help="Don't modify /etc/hosts",
940 action="store_false", default=True)
942 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
943 help="Don't initialize SSH keys",
944 action="store_false", default=True)
946 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
947 help="Enable parseable error messages",
948 action="store_true", default=False)
950 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
951 help="Skip N+1 memory redundancy tests",
952 action="store_true", default=False)
954 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
955 help="Type of reboot: soft/hard/full",
956 default=constants.INSTANCE_REBOOT_HARD,
958 choices=list(constants.REBOOT_TYPES))
960 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
961 dest="ignore_secondaries",
962 default=False, action="store_true",
963 help="Ignore errors from secondaries")
965 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
966 action="store_false", default=True,
967 help="Don't shutdown the instance (unsafe)")
969 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
970 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
971 help="Maximum time to wait")
973 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
974 dest="shutdown_timeout", type="int",
975 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
976 help="Maximum time to wait for instance shutdown")
978 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
980 help=("Number of seconds between repetions of the"
983 EARLY_RELEASE_OPT = cli_option("--early-release",
984 dest="early_release", default=False,
986 help="Release the locks on the secondary"
989 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
990 dest="new_cluster_cert",
991 default=False, action="store_true",
992 help="Generate a new cluster certificate")
994 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
996 help="File containing new RAPI certificate")
998 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
999 default=None, action="store_true",
1000 help=("Generate a new self-signed RAPI"
1003 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1004 dest="new_confd_hmac_key",
1005 default=False, action="store_true",
1006 help=("Create a new HMAC key for %s" %
1009 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1010 dest="cluster_domain_secret",
1012 help=("Load new new cluster domain"
1013 " secret from file"))
1015 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1016 dest="new_cluster_domain_secret",
1017 default=False, action="store_true",
1018 help=("Create a new cluster domain"
1021 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1022 dest="use_replication_network",
1023 help="Whether to use the replication network"
1024 " for talking to the nodes",
1025 action="store_true", default=False)
1027 MAINTAIN_NODE_HEALTH_OPT = \
1028 cli_option("--maintain-node-health", dest="maintain_node_health",
1029 metavar=_YORNO, default=None, type="bool",
1030 help="Configure the cluster to automatically maintain node"
1031 " health, by shutting down unknown instances, shutting down"
1032 " unknown DRBD devices, etc.")
1034 IDENTIFY_DEFAULTS_OPT = \
1035 cli_option("--identify-defaults", dest="identify_defaults",
1036 default=False, action="store_true",
1037 help="Identify which saved instance parameters are equal to"
1038 " the current cluster defaults and set them as such, instead"
1039 " of marking them as overridden")
1041 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1042 action="store", dest="uid_pool",
1043 help=("A list of user-ids or user-id"
1044 " ranges separated by commas"))
1046 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1047 action="store", dest="add_uids",
1048 help=("A list of user-ids or user-id"
1049 " ranges separated by commas, to be"
1050 " added to the user-id pool"))
1052 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1053 action="store", dest="remove_uids",
1054 help=("A list of user-ids or user-id"
1055 " ranges separated by commas, to be"
1056 " removed from the user-id pool"))
1058 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1059 action="store", dest="reserved_lvs",
1060 help=("A comma-separated list of reserved"
1061 " logical volumes names, that will be"
1062 " ignored by cluster verify"))
1064 ROMAN_OPT = cli_option("--roman",
1065 dest="roman_integers", default=False,
1066 action="store_true",
1067 help="Use roman numbers for positive integers")
1069 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1070 action="store", default=None,
1071 help="Specifies usermode helper for DRBD")
1073 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1074 action="store_false", default=True,
1075 help="Disable support for DRBD")
1077 PRIMARY_IP_VERSION_OPT = \
1078 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1079 action="store", dest="primary_ip_version",
1080 metavar="%d|%d" % (constants.IP4_VERSION,
1081 constants.IP6_VERSION),
1082 help="Cluster-wide IP version for primary IP")
1084 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1085 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1086 choices=_PRIONAME_TO_VALUE.keys(),
1087 help="Priority for opcode processing")
1089 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1090 type="bool", default=None, metavar=_YORNO,
1091 help="Sets the hidden flag on the OS")
1093 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1094 type="bool", default=None, metavar=_YORNO,
1095 help="Sets the blacklisted flag on the OS")
1097 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1098 type="bool", metavar=_YORNO,
1099 dest="prealloc_wipe_disks",
1100 help=("Wipe disks prior to instance"
1104 #: Options provided by all commands
1105 COMMON_OPTS = [DEBUG_OPT]
1107 # common options for creating instances. add and import then add their own
1109 COMMON_CREATE_OPTS = [
1114 FILESTORE_DRIVER_OPT,
1131 def _ParseArgs(argv, commands, aliases):
1132 """Parser for the command line arguments.
1134 This function parses the arguments and returns the function which
1135 must be executed together with its (modified) arguments.
1137 @param argv: the command line
1138 @param commands: dictionary with special contents, see the design
1139 doc for cmdline handling
1140 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1144 binary = "<command>"
1146 binary = argv[0].split("/")[-1]
1148 if len(argv) > 1 and argv[1] == "--version":
1149 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1150 constants.RELEASE_VERSION)
1151 # Quit right away. That way we don't have to care about this special
1152 # argument. optparse.py does it the same.
1155 if len(argv) < 2 or not (argv[1] in commands or
1156 argv[1] in aliases):
1157 # let's do a nice thing
1158 sortedcmds = commands.keys()
1161 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1162 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1165 # compute the max line length for cmd + usage
1166 mlen = max([len(" %s" % cmd) for cmd in commands])
1167 mlen = min(60, mlen) # should not get here...
1169 # and format a nice command list
1170 ToStdout("Commands:")
1171 for cmd in sortedcmds:
1172 cmdstr = " %s" % (cmd,)
1173 help_text = commands[cmd][4]
1174 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1175 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1176 for line in help_lines:
1177 ToStdout("%-*s %s", mlen, "", line)
1181 return None, None, None
1183 # get command, unalias it, and look it up in commands
1187 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1190 if aliases[cmd] not in commands:
1191 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1192 " command '%s'" % (cmd, aliases[cmd]))
1196 func, args_def, parser_opts, usage, description = commands[cmd]
1197 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1198 description=description,
1199 formatter=TitledHelpFormatter(),
1200 usage="%%prog %s %s" % (cmd, usage))
1201 parser.disable_interspersed_args()
1202 options, args = parser.parse_args()
1204 if not _CheckArguments(cmd, args_def, args):
1205 return None, None, None
1207 return func, options, args
1210 def _CheckArguments(cmd, args_def, args):
1211 """Verifies the arguments using the argument definition.
1215 1. Abort with error if values specified by user but none expected.
1217 1. For each argument in definition
1219 1. Keep running count of minimum number of values (min_count)
1220 1. Keep running count of maximum number of values (max_count)
1221 1. If it has an unlimited number of values
1223 1. Abort with error if it's not the last argument in the definition
1225 1. If last argument has limited number of values
1227 1. Abort with error if number of values doesn't match or is too large
1229 1. Abort with error if user didn't pass enough values (min_count)
1232 if args and not args_def:
1233 ToStderr("Error: Command %s expects no arguments", cmd)
1240 last_idx = len(args_def) - 1
1242 for idx, arg in enumerate(args_def):
1243 if min_count is None:
1245 elif arg.min is not None:
1246 min_count += arg.min
1248 if max_count is None:
1250 elif arg.max is not None:
1251 max_count += arg.max
1254 check_max = (arg.max is not None)
1256 elif arg.max is None:
1257 raise errors.ProgrammerError("Only the last argument can have max=None")
1260 # Command with exact number of arguments
1261 if (min_count is not None and max_count is not None and
1262 min_count == max_count and len(args) != min_count):
1263 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1266 # Command with limited number of arguments
1267 if max_count is not None and len(args) > max_count:
1268 ToStderr("Error: Command %s expects only %d argument(s)",
1272 # Command with some required arguments
1273 if min_count is not None and len(args) < min_count:
1274 ToStderr("Error: Command %s expects at least %d argument(s)",
1281 def SplitNodeOption(value):
1282 """Splits the value of a --node option.
1285 if value and ':' in value:
1286 return value.split(':', 1)
1288 return (value, None)
1291 def CalculateOSNames(os_name, os_variants):
1292 """Calculates all the names an OS can be called, according to its variants.
1294 @type os_name: string
1295 @param os_name: base name of the os
1296 @type os_variants: list or None
1297 @param os_variants: list of supported variants
1299 @return: list of valid names
1303 return ['%s+%s' % (os_name, v) for v in os_variants]
1308 def ParseFields(selected, default):
1309 """Parses the values of "--field"-like options.
1311 @type selected: string or None
1312 @param selected: User-selected options
1314 @param default: Default fields
1317 if selected is None:
1320 if selected.startswith("+"):
1321 return default + selected[1:].split(",")
1323 return selected.split(",")
1326 UsesRPC = rpc.RunWithRPC
1329 def AskUser(text, choices=None):
1330 """Ask the user a question.
1332 @param text: the question to ask
1334 @param choices: list with elements tuples (input_char, return_value,
1335 description); if not given, it will default to: [('y', True,
1336 'Perform the operation'), ('n', False, 'Do no do the operation')];
1337 note that the '?' char is reserved for help
1339 @return: one of the return values from the choices list; if input is
1340 not possible (i.e. not running with a tty, we return the last
1345 choices = [('y', True, 'Perform the operation'),
1346 ('n', False, 'Do not perform the operation')]
1347 if not choices or not isinstance(choices, list):
1348 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1349 for entry in choices:
1350 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1351 raise errors.ProgrammerError("Invalid choices element to AskUser")
1353 answer = choices[-1][1]
1355 for line in text.splitlines():
1356 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1357 text = "\n".join(new_text)
1359 f = file("/dev/tty", "a+")
1363 chars = [entry[0] for entry in choices]
1364 chars[-1] = "[%s]" % chars[-1]
1366 maps = dict([(entry[0], entry[1]) for entry in choices])
1370 f.write("/".join(chars))
1372 line = f.readline(2).strip().lower()
1377 for entry in choices:
1378 f.write(" %s - %s\n" % (entry[0], entry[2]))
1386 class JobSubmittedException(Exception):
1387 """Job was submitted, client should exit.
1389 This exception has one argument, the ID of the job that was
1390 submitted. The handler should print this ID.
1392 This is not an error, just a structured way to exit from clients.
1397 def SendJob(ops, cl=None):
1398 """Function to submit an opcode without waiting for the results.
1401 @param ops: list of opcodes
1402 @type cl: luxi.Client
1403 @param cl: the luxi client to use for communicating with the master;
1404 if None, a new client will be created
1410 job_id = cl.SubmitJob(ops)
1415 def GenericPollJob(job_id, cbs, report_cbs):
1416 """Generic job-polling function.
1418 @type job_id: number
1419 @param job_id: Job ID
1420 @type cbs: Instance of L{JobPollCbBase}
1421 @param cbs: Data callbacks
1422 @type report_cbs: Instance of L{JobPollReportCbBase}
1423 @param report_cbs: Reporting callbacks
1426 prev_job_info = None
1427 prev_logmsg_serial = None
1432 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1435 # job not found, go away!
1436 raise errors.JobLost("Job with id %s lost" % job_id)
1438 if result == constants.JOB_NOTCHANGED:
1439 report_cbs.ReportNotChanged(job_id, status)
1444 # Split result, a tuple of (field values, log entries)
1445 (job_info, log_entries) = result
1446 (status, ) = job_info
1449 for log_entry in log_entries:
1450 (serial, timestamp, log_type, message) = log_entry
1451 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1453 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1455 # TODO: Handle canceled and archived jobs
1456 elif status in (constants.JOB_STATUS_SUCCESS,
1457 constants.JOB_STATUS_ERROR,
1458 constants.JOB_STATUS_CANCELING,
1459 constants.JOB_STATUS_CANCELED):
1462 prev_job_info = job_info
1464 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1466 raise errors.JobLost("Job with id %s lost" % job_id)
1468 status, opstatus, result = jobs[0]
1470 if status == constants.JOB_STATUS_SUCCESS:
1473 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1474 raise errors.OpExecError("Job was canceled")
1477 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1478 if status == constants.OP_STATUS_SUCCESS:
1480 elif status == constants.OP_STATUS_ERROR:
1481 errors.MaybeRaise(msg)
1484 raise errors.OpExecError("partial failure (opcode %d): %s" %
1487 raise errors.OpExecError(str(msg))
1489 # default failure mode
1490 raise errors.OpExecError(result)
1493 class JobPollCbBase:
1494 """Base class for L{GenericPollJob} callbacks.
1498 """Initializes this class.
1502 def WaitForJobChangeOnce(self, job_id, fields,
1503 prev_job_info, prev_log_serial):
1504 """Waits for changes on a job.
1507 raise NotImplementedError()
1509 def QueryJobs(self, job_ids, fields):
1510 """Returns the selected fields for the selected job IDs.
1512 @type job_ids: list of numbers
1513 @param job_ids: Job IDs
1514 @type fields: list of strings
1515 @param fields: Fields
1518 raise NotImplementedError()
1521 class JobPollReportCbBase:
1522 """Base class for L{GenericPollJob} reporting callbacks.
1526 """Initializes this class.
1530 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1531 """Handles a log message.
1534 raise NotImplementedError()
1536 def ReportNotChanged(self, job_id, status):
1537 """Called for if a job hasn't changed in a while.
1539 @type job_id: number
1540 @param job_id: Job ID
1541 @type status: string or None
1542 @param status: Job status if available
1545 raise NotImplementedError()
1548 class _LuxiJobPollCb(JobPollCbBase):
1549 def __init__(self, cl):
1550 """Initializes this class.
1553 JobPollCbBase.__init__(self)
1556 def WaitForJobChangeOnce(self, job_id, fields,
1557 prev_job_info, prev_log_serial):
1558 """Waits for changes on a job.
1561 return self.cl.WaitForJobChangeOnce(job_id, fields,
1562 prev_job_info, prev_log_serial)
1564 def QueryJobs(self, job_ids, fields):
1565 """Returns the selected fields for the selected job IDs.
1568 return self.cl.QueryJobs(job_ids, fields)
1571 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1572 def __init__(self, feedback_fn):
1573 """Initializes this class.
1576 JobPollReportCbBase.__init__(self)
1578 self.feedback_fn = feedback_fn
1580 assert callable(feedback_fn)
1582 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1583 """Handles a log message.
1586 self.feedback_fn((timestamp, log_type, log_msg))
1588 def ReportNotChanged(self, job_id, status):
1589 """Called if a job hasn't changed in a while.
1595 class StdioJobPollReportCb(JobPollReportCbBase):
1597 """Initializes this class.
1600 JobPollReportCbBase.__init__(self)
1602 self.notified_queued = False
1603 self.notified_waitlock = False
1605 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1606 """Handles a log message.
1609 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1610 FormatLogMessage(log_type, log_msg))
1612 def ReportNotChanged(self, job_id, status):
1613 """Called if a job hasn't changed in a while.
1619 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1620 ToStderr("Job %s is waiting in queue", job_id)
1621 self.notified_queued = True
1623 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1624 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1625 self.notified_waitlock = True
1628 def FormatLogMessage(log_type, log_msg):
1629 """Formats a job message according to its type.
1632 if log_type != constants.ELOG_MESSAGE:
1633 log_msg = str(log_msg)
1635 return utils.SafeEncode(log_msg)
1638 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1639 """Function to poll for the result of a job.
1641 @type job_id: job identified
1642 @param job_id: the job to poll for results
1643 @type cl: luxi.Client
1644 @param cl: the luxi client to use for communicating with the master;
1645 if None, a new client will be created
1651 if reporter is None:
1653 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1655 reporter = StdioJobPollReportCb()
1657 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1659 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1662 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1663 """Legacy function to submit an opcode.
1665 This is just a simple wrapper over the construction of the processor
1666 instance. It should be extended to better handle feedback and
1667 interaction functions.
1673 SetGenericOpcodeOpts([op], opts)
1675 job_id = SendJob([op], cl=cl)
1677 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1680 return op_results[0]
1683 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1684 """Wrapper around SubmitOpCode or SendJob.
1686 This function will decide, based on the 'opts' parameter, whether to
1687 submit and wait for the result of the opcode (and return it), or
1688 whether to just send the job and print its identifier. It is used in
1689 order to simplify the implementation of the '--submit' option.
1691 It will also process the opcodes if we're sending the via SendJob
1692 (otherwise SubmitOpCode does it).
1695 if opts and opts.submit_only:
1697 SetGenericOpcodeOpts(job, opts)
1698 job_id = SendJob(job, cl=cl)
1699 raise JobSubmittedException(job_id)
1701 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1704 def SetGenericOpcodeOpts(opcode_list, options):
1705 """Processor for generic options.
1707 This function updates the given opcodes based on generic command
1708 line options (like debug, dry-run, etc.).
1710 @param opcode_list: list of opcodes
1711 @param options: command line options or None
1712 @return: None (in-place modification)
1717 for op in opcode_list:
1718 op.debug_level = options.debug
1719 if hasattr(options, "dry_run"):
1720 op.dry_run = options.dry_run
1721 if getattr(options, "priority", None) is not None:
1722 op.priority = _PRIONAME_TO_VALUE[options.priority]
1726 # TODO: Cache object?
1728 client = luxi.Client()
1729 except luxi.NoMasterError:
1730 ss = ssconf.SimpleStore()
1732 # Try to read ssconf file
1735 except errors.ConfigurationError:
1736 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1737 " not part of a cluster")
1739 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1740 if master != myself:
1741 raise errors.OpPrereqError("This is not the master node, please connect"
1742 " to node '%s' and rerun the command" %
1748 def FormatError(err):
1749 """Return a formatted error message for a given error.
1751 This function takes an exception instance and returns a tuple
1752 consisting of two values: first, the recommended exit code, and
1753 second, a string describing the error message (not
1754 newline-terminated).
1760 if isinstance(err, errors.ConfigurationError):
1761 txt = "Corrupt configuration file: %s" % msg
1763 obuf.write(txt + "\n")
1764 obuf.write("Aborting.")
1766 elif isinstance(err, errors.HooksAbort):
1767 obuf.write("Failure: hooks execution failed:\n")
1768 for node, script, out in err.args[0]:
1770 obuf.write(" node: %s, script: %s, output: %s\n" %
1771 (node, script, out))
1773 obuf.write(" node: %s, script: %s (no output)\n" %
1775 elif isinstance(err, errors.HooksFailure):
1776 obuf.write("Failure: hooks general failure: %s" % msg)
1777 elif isinstance(err, errors.ResolverError):
1778 this_host = netutils.Hostname.GetSysName()
1779 if err.args[0] == this_host:
1780 msg = "Failure: can't resolve my own hostname ('%s')"
1782 msg = "Failure: can't resolve hostname '%s'"
1783 obuf.write(msg % err.args[0])
1784 elif isinstance(err, errors.OpPrereqError):
1785 if len(err.args) == 2:
1786 obuf.write("Failure: prerequisites not met for this"
1787 " operation:\nerror type: %s, error details:\n%s" %
1788 (err.args[1], err.args[0]))
1790 obuf.write("Failure: prerequisites not met for this"
1791 " operation:\n%s" % msg)
1792 elif isinstance(err, errors.OpExecError):
1793 obuf.write("Failure: command execution error:\n%s" % msg)
1794 elif isinstance(err, errors.TagError):
1795 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1796 elif isinstance(err, errors.JobQueueDrainError):
1797 obuf.write("Failure: the job queue is marked for drain and doesn't"
1798 " accept new requests\n")
1799 elif isinstance(err, errors.JobQueueFull):
1800 obuf.write("Failure: the job queue is full and doesn't accept new"
1801 " job submissions until old jobs are archived\n")
1802 elif isinstance(err, errors.TypeEnforcementError):
1803 obuf.write("Parameter Error: %s" % msg)
1804 elif isinstance(err, errors.ParameterError):
1805 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1806 elif isinstance(err, luxi.NoMasterError):
1807 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1808 " and listening for connections?")
1809 elif isinstance(err, luxi.TimeoutError):
1810 obuf.write("Timeout while talking to the master daemon. Jobs might have"
1811 " been submitted and will continue to run even if the call"
1812 " timed out. Useful commands in this situation are \"gnt-job"
1813 " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
1815 elif isinstance(err, luxi.PermissionError):
1816 obuf.write("It seems you don't have permissions to connect to the"
1817 " master daemon.\nPlease retry as a different user.")
1818 elif isinstance(err, luxi.ProtocolError):
1819 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1821 elif isinstance(err, errors.JobLost):
1822 obuf.write("Error checking job status: %s" % msg)
1823 elif isinstance(err, errors.GenericError):
1824 obuf.write("Unhandled Ganeti error: %s" % msg)
1825 elif isinstance(err, JobSubmittedException):
1826 obuf.write("JobID: %s\n" % err.args[0])
1829 obuf.write("Unhandled exception: %s" % msg)
1830 return retcode, obuf.getvalue().rstrip('\n')
1833 def GenericMain(commands, override=None, aliases=None):
1834 """Generic main function for all the gnt-* commands.
1837 - commands: a dictionary with a special structure, see the design doc
1838 for command line handling.
1839 - override: if not None, we expect a dictionary with keys that will
1840 override command line options; this can be used to pass
1841 options from the scripts to generic functions
1842 - aliases: dictionary with command aliases {'alias': 'target, ...}
1845 # save the program name and the entire command line for later logging
1847 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1848 if len(sys.argv) >= 2:
1849 binary += " " + sys.argv[1]
1850 old_cmdline = " ".join(sys.argv[2:])
1854 binary = "<unknown program>"
1861 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1862 except errors.ParameterError, err:
1863 result, err_msg = FormatError(err)
1867 if func is None: # parse error
1870 if override is not None:
1871 for key, val in override.iteritems():
1872 setattr(options, key, val)
1874 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1875 stderr_logging=True, program=binary)
1878 logging.info("run with arguments '%s'", old_cmdline)
1880 logging.info("run with no arguments")
1883 result = func(options, args)
1884 except (errors.GenericError, luxi.ProtocolError,
1885 JobSubmittedException), err:
1886 result, err_msg = FormatError(err)
1887 logging.exception("Error during command processing")
1893 def ParseNicOption(optvalue):
1894 """Parses the value of the --net option(s).
1898 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1899 except (TypeError, ValueError), err:
1900 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1902 nics = [{}] * nic_max
1903 for nidx, ndict in optvalue:
1906 if not isinstance(ndict, dict):
1907 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1908 " got %s" % (nidx, ndict))
1910 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1917 def GenericInstanceCreate(mode, opts, args):
1918 """Add an instance to the cluster via either creation or import.
1920 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1921 @param opts: the command line options selected by the user
1923 @param args: should contain only one element, the new instance name
1925 @return: the desired exit code
1930 (pnode, snode) = SplitNodeOption(opts.node)
1935 hypervisor, hvparams = opts.hypervisor
1938 nics = ParseNicOption(opts.nics)
1942 elif mode == constants.INSTANCE_CREATE:
1943 # default of one nic, all auto
1949 if opts.disk_template == constants.DT_DISKLESS:
1950 if opts.disks or opts.sd_size is not None:
1951 raise errors.OpPrereqError("Diskless instance but disk"
1952 " information passed")
1955 if (not opts.disks and not opts.sd_size
1956 and mode == constants.INSTANCE_CREATE):
1957 raise errors.OpPrereqError("No disk information specified")
1958 if opts.disks and opts.sd_size is not None:
1959 raise errors.OpPrereqError("Please use either the '--disk' or"
1961 if opts.sd_size is not None:
1962 opts.disks = [(0, {"size": opts.sd_size})]
1966 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1967 except ValueError, err:
1968 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1969 disks = [{}] * disk_max
1972 for didx, ddict in opts.disks:
1974 if not isinstance(ddict, dict):
1975 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1976 raise errors.OpPrereqError(msg)
1977 elif "size" in ddict:
1978 if "adopt" in ddict:
1979 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1980 " (disk %d)" % didx)
1982 ddict["size"] = utils.ParseUnit(ddict["size"])
1983 except ValueError, err:
1984 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1986 elif "adopt" in ddict:
1987 if mode == constants.INSTANCE_IMPORT:
1988 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1992 raise errors.OpPrereqError("Missing size or adoption source for"
1996 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1997 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1999 if mode == constants.INSTANCE_CREATE:
2002 force_variant = opts.force_variant
2005 no_install = opts.no_install
2006 identify_defaults = False
2007 elif mode == constants.INSTANCE_IMPORT:
2010 force_variant = False
2011 src_node = opts.src_node
2012 src_path = opts.src_dir
2014 identify_defaults = opts.identify_defaults
2016 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2018 op = opcodes.OpCreateInstance(instance_name=instance,
2020 disk_template=opts.disk_template,
2022 pnode=pnode, snode=snode,
2023 ip_check=opts.ip_check,
2024 name_check=opts.name_check,
2025 wait_for_sync=opts.wait_for_sync,
2026 file_storage_dir=opts.file_storage_dir,
2027 file_driver=opts.file_driver,
2028 iallocator=opts.iallocator,
2029 hypervisor=hypervisor,
2031 beparams=opts.beparams,
2032 osparams=opts.osparams,
2036 force_variant=force_variant,
2039 no_install=no_install,
2040 identify_defaults=identify_defaults)
2042 SubmitOrSend(op, opts)
2046 class _RunWhileClusterStoppedHelper:
2047 """Helper class for L{RunWhileClusterStopped} to simplify state management
2050 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2051 """Initializes this class.
2053 @type feedback_fn: callable
2054 @param feedback_fn: Feedback function
2055 @type cluster_name: string
2056 @param cluster_name: Cluster name
2057 @type master_node: string
2058 @param master_node Master node name
2059 @type online_nodes: list
2060 @param online_nodes: List of names of online nodes
2063 self.feedback_fn = feedback_fn
2064 self.cluster_name = cluster_name
2065 self.master_node = master_node
2066 self.online_nodes = online_nodes
2068 self.ssh = ssh.SshRunner(self.cluster_name)
2070 self.nonmaster_nodes = [name for name in online_nodes
2071 if name != master_node]
2073 assert self.master_node not in self.nonmaster_nodes
2075 def _RunCmd(self, node_name, cmd):
2076 """Runs a command on the local or a remote machine.
2078 @type node_name: string
2079 @param node_name: Machine name
2084 if node_name is None or node_name == self.master_node:
2085 # No need to use SSH
2086 result = utils.RunCmd(cmd)
2088 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2091 errmsg = ["Failed to run command %s" % result.cmd]
2093 errmsg.append("on node %s" % node_name)
2094 errmsg.append(": exitcode %s and error %s" %
2095 (result.exit_code, result.output))
2096 raise errors.OpExecError(" ".join(errmsg))
2098 def Call(self, fn, *args):
2099 """Call function while all daemons are stopped.
2102 @param fn: Function to be called
2105 # Pause watcher by acquiring an exclusive lock on watcher state file
2106 self.feedback_fn("Blocking watcher")
2107 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2109 # TODO: Currently, this just blocks. There's no timeout.
2110 # TODO: Should it be a shared lock?
2111 watcher_block.Exclusive(blocking=True)
2113 # Stop master daemons, so that no new jobs can come in and all running
2115 self.feedback_fn("Stopping master daemons")
2116 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2118 # Stop daemons on all nodes
2119 for node_name in self.online_nodes:
2120 self.feedback_fn("Stopping daemons on %s" % node_name)
2121 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2123 # All daemons are shut down now
2125 return fn(self, *args)
2126 except Exception, err:
2127 _, errmsg = FormatError(err)
2128 logging.exception("Caught exception")
2129 self.feedback_fn(errmsg)
2132 # Start cluster again, master node last
2133 for node_name in self.nonmaster_nodes + [self.master_node]:
2134 self.feedback_fn("Starting daemons on %s" % node_name)
2135 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2138 watcher_block.Close()
2141 def RunWhileClusterStopped(feedback_fn, fn, *args):
2142 """Calls a function while all cluster daemons are stopped.
2144 @type feedback_fn: callable
2145 @param feedback_fn: Feedback function
2147 @param fn: Function to be called when daemons are stopped
2150 feedback_fn("Gathering cluster information")
2152 # This ensures we're running on the master daemon
2155 (cluster_name, master_node) = \
2156 cl.QueryConfigValues(["cluster_name", "master_node"])
2158 online_nodes = GetOnlineNodes([], cl=cl)
2160 # Don't keep a reference to the client. The master daemon will go away.
2163 assert master_node in online_nodes
2165 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2166 online_nodes).Call(fn, *args)
2169 def GenerateTable(headers, fields, separator, data,
2170 numfields=None, unitfields=None,
2172 """Prints a table with headers and different fields.
2175 @param headers: dictionary mapping field names to headers for
2178 @param fields: the field names corresponding to each row in
2180 @param separator: the separator to be used; if this is None,
2181 the default 'smart' algorithm is used which computes optimal
2182 field width, otherwise just the separator is used between
2185 @param data: a list of lists, each sublist being one row to be output
2186 @type numfields: list
2187 @param numfields: a list with the fields that hold numeric
2188 values and thus should be right-aligned
2189 @type unitfields: list
2190 @param unitfields: a list with the fields that hold numeric
2191 values that should be formatted with the units field
2192 @type units: string or None
2193 @param units: the units we should use for formatting, or None for
2194 automatic choice (human-readable for non-separator usage, otherwise
2195 megabytes); this is a one-letter string
2204 if numfields is None:
2206 if unitfields is None:
2209 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2210 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2213 for field in fields:
2214 if headers and field not in headers:
2215 # TODO: handle better unknown fields (either revert to old
2216 # style of raising exception, or deal more intelligently with
2218 headers[field] = field
2219 if separator is not None:
2220 format_fields.append("%s")
2221 elif numfields.Matches(field):
2222 format_fields.append("%*s")
2224 format_fields.append("%-*s")
2226 if separator is None:
2227 mlens = [0 for name in fields]
2228 format_str = ' '.join(format_fields)
2230 format_str = separator.replace("%", "%%").join(format_fields)
2235 for idx, val in enumerate(row):
2236 if unitfields.Matches(fields[idx]):
2239 except (TypeError, ValueError):
2242 val = row[idx] = utils.FormatUnit(val, units)
2243 val = row[idx] = str(val)
2244 if separator is None:
2245 mlens[idx] = max(mlens[idx], len(val))
2250 for idx, name in enumerate(fields):
2252 if separator is None:
2253 mlens[idx] = max(mlens[idx], len(hdr))
2254 args.append(mlens[idx])
2256 result.append(format_str % tuple(args))
2258 if separator is None:
2259 assert len(mlens) == len(fields)
2261 if fields and not numfields.Matches(fields[-1]):
2267 line = ['-' for _ in fields]
2268 for idx in range(len(fields)):
2269 if separator is None:
2270 args.append(mlens[idx])
2271 args.append(line[idx])
2272 result.append(format_str % tuple(args))
2277 def FormatTimestamp(ts):
2278 """Formats a given timestamp.
2281 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2284 @return: a string with the formatted timestamp
2287 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2290 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2293 def ParseTimespec(value):
2294 """Parse a time specification.
2296 The following suffixed will be recognized:
2304 Without any suffix, the value will be taken to be in seconds.
2309 raise errors.OpPrereqError("Empty time specification passed")
2317 if value[-1] not in suffix_map:
2320 except (TypeError, ValueError):
2321 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2323 multiplier = suffix_map[value[-1]]
2325 if not value: # no data left after stripping the suffix
2326 raise errors.OpPrereqError("Invalid time specification (only"
2329 value = int(value) * multiplier
2330 except (TypeError, ValueError):
2331 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2335 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2336 filter_master=False):
2337 """Returns the names of online nodes.
2339 This function will also log a warning on stderr with the names of
2342 @param nodes: if not empty, use only this subset of nodes (minus the
2344 @param cl: if not None, luxi client to use
2345 @type nowarn: boolean
2346 @param nowarn: by default, this function will output a note with the
2347 offline nodes that are skipped; if this parameter is True the
2348 note is not displayed
2349 @type secondary_ips: boolean
2350 @param secondary_ips: if True, return the secondary IPs instead of the
2351 names, useful for doing network traffic over the replication interface
2353 @type filter_master: boolean
2354 @param filter_master: if True, do not return the master node in the list
2355 (useful in coordination with secondary_ips where we cannot check our
2356 node name against the list)
2368 master_node = cl.QueryConfigValues(["master_node"])[0]
2369 filter_fn = lambda x: x != master_node
2371 filter_fn = lambda _: True
2373 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2375 offline = [row[0] for row in result if row[1]]
2376 if offline and not nowarn:
2377 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2378 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2381 def _ToStream(stream, txt, *args):
2382 """Write a message to a stream, bypassing the logging system
2384 @type stream: file object
2385 @param stream: the file to which we should write
2387 @param txt: the message
2392 stream.write(txt % args)
2399 def ToStdout(txt, *args):
2400 """Write a message to stdout only, bypassing the logging system
2402 This is just a wrapper over _ToStream.
2405 @param txt: the message
2408 _ToStream(sys.stdout, txt, *args)
2411 def ToStderr(txt, *args):
2412 """Write a message to stderr only, bypassing the logging system
2414 This is just a wrapper over _ToStream.
2417 @param txt: the message
2420 _ToStream(sys.stderr, txt, *args)
2423 class JobExecutor(object):
2424 """Class which manages the submission and execution of multiple jobs.
2426 Note that instances of this class should not be reused between
2430 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2435 self.verbose = verbose
2438 self.feedback_fn = feedback_fn
2440 def QueueJob(self, name, *ops):
2441 """Record a job for later submit.
2444 @param name: a description of the job, will be used in WaitJobSet
2446 SetGenericOpcodeOpts(ops, self.opts)
2447 self.queue.append((name, ops))
2449 def SubmitPending(self, each=False):
2450 """Submit all pending jobs.
2455 for row in self.queue:
2456 # SubmitJob will remove the success status, but raise an exception if
2457 # the submission fails, so we'll notice that anyway.
2458 results.append([True, self.cl.SubmitJob(row[1])])
2460 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2461 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2463 self.jobs.append((idx, status, data, name))
2465 def _ChooseJob(self):
2466 """Choose a non-waiting/queued job to poll next.
2469 assert self.jobs, "_ChooseJob called with empty job list"
2471 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2474 for job_data, status in zip(self.jobs, result):
2475 if (isinstance(status, list) and status and
2476 status[0] in (constants.JOB_STATUS_QUEUED,
2477 constants.JOB_STATUS_WAITLOCK,
2478 constants.JOB_STATUS_CANCELING)):
2479 # job is still present and waiting
2481 # good candidate found (either running job or lost job)
2482 self.jobs.remove(job_data)
2486 return self.jobs.pop(0)
2488 def GetResults(self):
2489 """Wait for and return the results of all jobs.
2492 @return: list of tuples (success, job results), in the same order
2493 as the submitted jobs; if a job has failed, instead of the result
2494 there will be the error message
2498 self.SubmitPending()
2501 ok_jobs = [row[2] for row in self.jobs if row[1]]
2503 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2505 # first, remove any non-submitted jobs
2506 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2507 for idx, _, jid, name in failures:
2508 ToStderr("Failed to submit job for %s: %s", name, jid)
2509 results.append((idx, False, jid))
2512 (idx, _, jid, name) = self._ChooseJob()
2513 ToStdout("Waiting for job %s for %s...", jid, name)
2515 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2517 except errors.JobLost, err:
2518 _, job_result = FormatError(err)
2519 ToStderr("Job %s for %s has been archived, cannot check its result",
2522 except (errors.GenericError, luxi.ProtocolError), err:
2523 _, job_result = FormatError(err)
2525 # the error message will always be shown, verbose or not
2526 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2528 results.append((idx, success, job_result))
2530 # sort based on the index, then drop it
2532 results = [i[1:] for i in results]
2536 def WaitOrShow(self, wait):
2537 """Wait for job results or only print the job IDs.
2540 @param wait: whether to wait or not
2544 return self.GetResults()
2547 self.SubmitPending()
2548 for _, status, result, name in self.jobs:
2550 ToStdout("%s: %s", result, name)
2552 ToStderr("Failure for %s: %s", name, result)
2553 return [row[1:3] for row in self.jobs]