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
57 "CLUSTER_DOMAIN_SECRET_OPT",
73 "FILESTORE_DRIVER_OPT",
82 "DEFAULT_IALLOCATOR_OPT",
83 "IDENTIFY_DEFAULTS_OPT",
85 "IGNORE_FAILURES_OPT",
87 "IGNORE_REMOVE_FAILURES_OPT",
88 "IGNORE_SECONDARIES_OPT",
92 "MAINTAIN_NODE_HEALTH_OPT",
97 "NEW_CLUSTER_CERT_OPT",
98 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
99 "NEW_CONFD_HMAC_KEY_OPT",
104 "NODE_PLACEMENT_OPT",
106 "NODRBD_STORAGE_OPT",
112 "NOMODIFY_ETCHOSTS_OPT",
113 "NOMODIFY_SSH_SETUP_OPT",
119 "NOSSH_KEYCHECK_OPT",
128 "PRIMARY_IP_VERSION_OPT",
133 "REMOVE_INSTANCE_OPT",
141 "SHUTDOWN_TIMEOUT_OPT",
156 # Generic functions for CLI programs
158 "GenericInstanceCreate",
162 "JobSubmittedException",
164 "RunWhileClusterStopped",
168 # Formatting functions
169 "ToStderr", "ToStdout",
179 # command line options support infrastructure
180 "ARGS_MANY_INSTANCES",
196 "OPT_COMPL_INST_ADD_NODES",
197 "OPT_COMPL_MANY_NODES",
198 "OPT_COMPL_ONE_IALLOCATOR",
199 "OPT_COMPL_ONE_INSTANCE",
200 "OPT_COMPL_ONE_NODE",
201 "OPT_COMPL_ONE_NODEGROUP",
212 #: Priorities (sorted)
214 ("low", constants.OP_PRIO_LOW),
215 ("normal", constants.OP_PRIO_NORMAL),
216 ("high", constants.OP_PRIO_HIGH),
219 #: Priority dictionary for easier lookup
220 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
221 # we migrate to Python 2.6
222 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
226 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
231 return ("<%s min=%s max=%s>" %
232 (self.__class__.__name__, self.min, self.max))
235 class ArgSuggest(_Argument):
236 """Suggesting argument.
238 Value can be any of the ones passed to the constructor.
241 # pylint: disable-msg=W0622
242 def __init__(self, min=0, max=None, choices=None):
243 _Argument.__init__(self, min=min, max=max)
244 self.choices = choices
247 return ("<%s min=%s max=%s choices=%r>" %
248 (self.__class__.__name__, self.min, self.max, self.choices))
251 class ArgChoice(ArgSuggest):
254 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
255 but value must be one of the choices.
260 class ArgUnknown(_Argument):
261 """Unknown argument to program (e.g. determined at runtime).
266 class ArgInstance(_Argument):
267 """Instances argument.
272 class ArgNode(_Argument):
277 class ArgJobId(_Argument):
283 class ArgFile(_Argument):
284 """File path argument.
289 class ArgCommand(_Argument):
295 class ArgHost(_Argument):
301 class ArgOs(_Argument):
308 ARGS_MANY_INSTANCES = [ArgInstance()]
309 ARGS_MANY_NODES = [ArgNode()]
310 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
311 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
312 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
315 def _ExtractTagsObject(opts, args):
316 """Extract the tag type object.
318 Note that this function will modify its args parameter.
321 if not hasattr(opts, "tag_type"):
322 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
324 if kind == constants.TAG_CLUSTER:
326 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
328 raise errors.OpPrereqError("no arguments passed to the command")
332 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
336 def _ExtendTags(opts, args):
337 """Extend the args if a source file has been given.
339 This function will extend the tags with the contents of the file
340 passed in the 'tags_source' attribute of the opts parameter. A file
341 named '-' will be replaced by stdin.
344 fname = opts.tags_source
350 new_fh = open(fname, "r")
353 # we don't use the nice 'new_data = [line.strip() for line in fh]'
354 # because of python bug 1633941
356 line = new_fh.readline()
359 new_data.append(line.strip())
362 args.extend(new_data)
365 def ListTags(opts, args):
366 """List the tags on a given object.
368 This is a generic implementation that knows how to deal with all
369 three cases of tag objects (cluster, node, instance). The opts
370 argument is expected to contain a tag_type field denoting what
371 object type we work on.
374 kind, name = _ExtractTagsObject(opts, args)
376 result = cl.QueryTags(kind, name)
377 result = list(result)
383 def AddTags(opts, args):
384 """Add tags on a given object.
386 This is a generic implementation that knows how to deal with all
387 three cases of tag objects (cluster, node, instance). The opts
388 argument is expected to contain a tag_type field denoting what
389 object type we work on.
392 kind, name = _ExtractTagsObject(opts, args)
393 _ExtendTags(opts, args)
395 raise errors.OpPrereqError("No tags to be added")
396 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
397 SubmitOpCode(op, opts=opts)
400 def RemoveTags(opts, args):
401 """Remove tags from a given object.
403 This is a generic implementation that knows how to deal with all
404 three cases of tag objects (cluster, node, instance). The opts
405 argument is expected to contain a tag_type field denoting what
406 object type we work on.
409 kind, name = _ExtractTagsObject(opts, args)
410 _ExtendTags(opts, args)
412 raise errors.OpPrereqError("No tags to be removed")
413 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
414 SubmitOpCode(op, opts=opts)
417 def check_unit(option, opt, value): # pylint: disable-msg=W0613
418 """OptParsers custom converter for units.
422 return utils.ParseUnit(value)
423 except errors.UnitParseError, err:
424 raise OptionValueError("option %s: %s" % (opt, err))
427 def _SplitKeyVal(opt, data):
428 """Convert a KeyVal string into a dict.
430 This function will convert a key=val[,...] string into a dict. Empty
431 values will be converted specially: keys which have the prefix 'no_'
432 will have the value=False and the prefix stripped, the others will
436 @param opt: a string holding the option name for which we process the
437 data, used in building error messages
439 @param data: a string of the format key=val,key=val,...
441 @return: {key=val, key=val}
442 @raises errors.ParameterError: if there are duplicate keys
447 for elem in utils.UnescapeAndSplit(data, sep=","):
449 key, val = elem.split("=", 1)
451 if elem.startswith(NO_PREFIX):
452 key, val = elem[len(NO_PREFIX):], False
453 elif elem.startswith(UN_PREFIX):
454 key, val = elem[len(UN_PREFIX):], None
456 key, val = elem, True
458 raise errors.ParameterError("Duplicate key '%s' in option %s" %
464 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
465 """Custom parser for ident:key=val,key=val options.
467 This will store the parsed values as a tuple (ident, {key: val}). As such,
468 multiple uses of this option via action=append is possible.
472 ident, rest = value, ''
474 ident, rest = value.split(":", 1)
476 if ident.startswith(NO_PREFIX):
478 msg = "Cannot pass options when removing parameter groups: %s" % value
479 raise errors.ParameterError(msg)
480 retval = (ident[len(NO_PREFIX):], False)
481 elif ident.startswith(UN_PREFIX):
483 msg = "Cannot pass options when removing parameter groups: %s" % value
484 raise errors.ParameterError(msg)
485 retval = (ident[len(UN_PREFIX):], None)
487 kv_dict = _SplitKeyVal(opt, rest)
488 retval = (ident, kv_dict)
492 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
493 """Custom parser class for key=val,key=val options.
495 This will store the parsed values as a dict {key: val}.
498 return _SplitKeyVal(opt, value)
501 def check_bool(option, opt, value): # pylint: disable-msg=W0613
502 """Custom parser for yes/no options.
504 This will store the parsed value as either True or False.
507 value = value.lower()
508 if value == constants.VALUE_FALSE or value == "no":
510 elif value == constants.VALUE_TRUE or value == "yes":
513 raise errors.ParameterError("Invalid boolean value '%s'" % value)
516 # completion_suggestion is normally a list. Using numeric values not evaluating
517 # to False for dynamic completion.
518 (OPT_COMPL_MANY_NODES,
520 OPT_COMPL_ONE_INSTANCE,
522 OPT_COMPL_ONE_IALLOCATOR,
523 OPT_COMPL_INST_ADD_NODES,
524 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
526 OPT_COMPL_ALL = frozenset([
527 OPT_COMPL_MANY_NODES,
529 OPT_COMPL_ONE_INSTANCE,
531 OPT_COMPL_ONE_IALLOCATOR,
532 OPT_COMPL_INST_ADD_NODES,
533 OPT_COMPL_ONE_NODEGROUP,
537 class CliOption(Option):
538 """Custom option class for optparse.
541 ATTRS = Option.ATTRS + [
542 "completion_suggest",
544 TYPES = Option.TYPES + (
550 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
551 TYPE_CHECKER["identkeyval"] = check_ident_key_val
552 TYPE_CHECKER["keyval"] = check_key_val
553 TYPE_CHECKER["unit"] = check_unit
554 TYPE_CHECKER["bool"] = check_bool
557 # optparse.py sets make_option, so we do it for our own option class, too
558 cli_option = CliOption
563 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
564 help="Increase debugging level")
566 NOHDR_OPT = cli_option("--no-headers", default=False,
567 action="store_true", dest="no_headers",
568 help="Don't display column headers")
570 SEP_OPT = cli_option("--separator", default=None,
571 action="store", dest="separator",
572 help=("Separator between output fields"
573 " (defaults to one space)"))
575 USEUNITS_OPT = cli_option("--units", default=None,
576 dest="units", choices=('h', 'm', 'g', 't'),
577 help="Specify units for output (one of hmgt)")
579 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
580 type="string", metavar="FIELDS",
581 help="Comma separated list of output fields")
583 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
584 default=False, help="Force the operation")
586 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
587 default=False, help="Do not require confirmation")
589 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
590 action="store_true", default=False,
591 help=("Ignore offline nodes and do as much"
594 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
595 default=None, help="File with tag names")
597 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
598 default=False, action="store_true",
599 help=("Submit the job and return the job ID, but"
600 " don't wait for the job to finish"))
602 SYNC_OPT = cli_option("--sync", dest="do_locking",
603 default=False, action="store_true",
604 help=("Grab locks while doing the queries"
605 " in order to ensure more consistent results"))
607 DRY_RUN_OPT = cli_option("--dry-run", default=False,
609 help=("Do not execute the operation, just run the"
610 " check steps and verify it it could be"
613 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
615 help="Increase the verbosity of the operation")
617 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
618 action="store_true", dest="simulate_errors",
619 help="Debugging option that makes the operation"
620 " treat most runtime checks as failed")
622 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
623 default=True, action="store_false",
624 help="Don't wait for sync (DANGEROUS!)")
626 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
627 help="Custom disk setup (diskless, file,"
629 default=None, metavar="TEMPL",
630 choices=list(constants.DISK_TEMPLATES))
632 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
633 help="Do not create any network cards for"
636 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
637 help="Relative path under default cluster-wide"
638 " file storage dir to store file-based disks",
639 default=None, metavar="<DIR>")
641 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
642 help="Driver to use for image files",
643 default="loop", metavar="<DRIVER>",
644 choices=list(constants.FILE_DRIVER))
646 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
647 help="Select nodes for the instance automatically"
648 " using the <NAME> iallocator plugin",
649 default=None, type="string",
650 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
652 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
654 help="Set the default instance allocator plugin",
655 default=None, type="string",
656 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
658 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
660 completion_suggest=OPT_COMPL_ONE_OS)
662 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
663 type="keyval", default={},
664 help="OS parameters")
666 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
667 action="store_true", default=False,
668 help="Force an unknown variant")
670 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
671 action="store_true", default=False,
672 help="Do not install the OS (will"
675 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
676 type="keyval", default={},
677 help="Backend parameters")
679 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
680 default={}, dest="hvparams",
681 help="Hypervisor parameters")
683 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
684 help="Hypervisor and hypervisor options, in the"
685 " format hypervisor:option=value,option=value,...",
686 default=None, type="identkeyval")
688 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
689 help="Hypervisor and hypervisor options, in the"
690 " format hypervisor:option=value,option=value,...",
691 default=[], action="append", type="identkeyval")
693 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
694 action="store_false",
695 help="Don't check that the instance's IP"
698 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
699 default=True, action="store_false",
700 help="Don't check that the instance's name"
703 NET_OPT = cli_option("--net",
704 help="NIC parameters", default=[],
705 dest="nics", action="append", type="identkeyval")
707 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
708 dest="disks", action="append", type="identkeyval")
710 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
711 help="Comma-separated list of disks"
712 " indices to act on (e.g. 0,2) (optional,"
713 " defaults to all disks)")
715 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
716 help="Enforces a single-disk configuration using the"
717 " given disk size, in MiB unless a suffix is used",
718 default=None, type="unit", metavar="<size>")
720 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
721 dest="ignore_consistency",
722 action="store_true", default=False,
723 help="Ignore the consistency of the disks on"
726 NONLIVE_OPT = cli_option("--non-live", dest="live",
727 default=True, action="store_false",
728 help="Do a non-live migration (this usually means"
729 " freeze the instance, save the state, transfer and"
730 " only then resume running on the secondary node)")
732 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
734 choices=list(constants.HT_MIGRATION_MODES),
735 help="Override default migration mode (choose"
736 " either live or non-live")
738 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
739 help="Target node and optional secondary node",
740 metavar="<pnode>[:<snode>]",
741 completion_suggest=OPT_COMPL_INST_ADD_NODES)
743 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
744 action="append", metavar="<node>",
745 help="Use only this node (can be used multiple"
746 " times, if not given defaults to all nodes)",
747 completion_suggest=OPT_COMPL_ONE_NODE)
749 NODEGROUP_OPT = cli_option("-g", "--nodegroup",
751 help="Node group (name or uuid)",
752 metavar="<nodegroup>",
753 default=None, type="string",
754 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
756 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
758 completion_suggest=OPT_COMPL_ONE_NODE)
760 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
761 action="store_false",
762 help="Don't start the instance after creation")
764 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
765 action="store_true", default=False,
766 help="Show command instead of executing it")
768 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
769 default=False, action="store_true",
770 help="Instead of performing the migration, try to"
771 " recover from a failed cleanup. This is safe"
772 " to run even if the instance is healthy, but it"
773 " will create extra replication traffic and "
774 " disrupt briefly the replication (like during the"
777 STATIC_OPT = cli_option("-s", "--static", dest="static",
778 action="store_true", default=False,
779 help="Only show configuration data, not runtime data")
781 ALL_OPT = cli_option("--all", dest="show_all",
782 default=False, action="store_true",
783 help="Show info on all instances on the cluster."
784 " This can take a long time to run, use wisely")
786 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
787 action="store_true", default=False,
788 help="Interactive OS reinstall, lists available"
789 " OS templates for selection")
791 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
792 action="store_true", default=False,
793 help="Remove the instance from the cluster"
794 " configuration even if there are failures"
795 " during the removal process")
797 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
798 dest="ignore_remove_failures",
799 action="store_true", default=False,
800 help="Remove the instance from the"
801 " cluster configuration even if there"
802 " are failures during the removal"
805 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
806 action="store_true", default=False,
807 help="Remove the instance from the cluster")
809 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
810 help="Specifies the new secondary node",
811 metavar="NODE", default=None,
812 completion_suggest=OPT_COMPL_ONE_NODE)
814 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
815 default=False, action="store_true",
816 help="Replace the disk(s) on the primary"
817 " node (only for the drbd template)")
819 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
820 default=False, action="store_true",
821 help="Replace the disk(s) on the secondary"
822 " node (only for the drbd template)")
824 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
825 default=False, action="store_true",
826 help="Lock all nodes and auto-promote as needed"
829 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
830 default=False, action="store_true",
831 help="Automatically replace faulty disks"
832 " (only for the drbd template)")
834 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
835 default=False, action="store_true",
836 help="Ignore current recorded size"
837 " (useful for forcing activation when"
838 " the recorded size is wrong)")
840 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
842 completion_suggest=OPT_COMPL_ONE_NODE)
844 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
847 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
848 help="Specify the secondary ip for the node",
849 metavar="ADDRESS", default=None)
851 READD_OPT = cli_option("--readd", dest="readd",
852 default=False, action="store_true",
853 help="Readd old node after replacing it")
855 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
856 default=True, action="store_false",
857 help="Disable SSH key fingerprint checking")
860 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
861 type="bool", default=None, metavar=_YORNO,
862 help="Set the master_candidate flag on the node")
864 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
865 type="bool", default=None,
866 help="Set the offline flag on the node")
868 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
869 type="bool", default=None,
870 help="Set the drained flag on the node")
872 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
873 type="bool", default=None, metavar=_YORNO,
874 help="Set the allocatable flag on a volume")
876 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
877 help="Disable support for lvm based instances"
879 action="store_false", default=True)
881 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
882 dest="enabled_hypervisors",
883 help="Comma-separated list of hypervisors",
884 type="string", default=None)
886 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
887 type="keyval", default={},
888 help="NIC parameters")
890 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
891 dest="candidate_pool_size", type="int",
892 help="Set the candidate pool size")
894 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
895 help="Enables LVM and specifies the volume group"
896 " name (cluster-wide) for disk allocation [xenvg]",
897 metavar="VG", default=None)
899 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
900 help="Destroy cluster", action="store_true")
902 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
903 help="Skip node agreement check (dangerous)",
904 action="store_true", default=False)
906 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
907 help="Specify the mac prefix for the instance IP"
908 " addresses, in the format XX:XX:XX",
912 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
913 help="Specify the node interface (cluster-wide)"
914 " on which the master IP address will be added "
915 " [%s]" % constants.DEFAULT_BRIDGE,
917 default=constants.DEFAULT_BRIDGE)
919 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
920 help="Specify the default directory (cluster-"
921 "wide) for storing the file-based disks [%s]" %
922 constants.DEFAULT_FILE_STORAGE_DIR,
924 default=constants.DEFAULT_FILE_STORAGE_DIR)
926 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
927 help="Don't modify /etc/hosts",
928 action="store_false", default=True)
930 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
931 help="Don't initialize SSH keys",
932 action="store_false", default=True)
934 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
935 help="Enable parseable error messages",
936 action="store_true", default=False)
938 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
939 help="Skip N+1 memory redundancy tests",
940 action="store_true", default=False)
942 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
943 help="Type of reboot: soft/hard/full",
944 default=constants.INSTANCE_REBOOT_HARD,
946 choices=list(constants.REBOOT_TYPES))
948 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
949 dest="ignore_secondaries",
950 default=False, action="store_true",
951 help="Ignore errors from secondaries")
953 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
954 action="store_false", default=True,
955 help="Don't shutdown the instance (unsafe)")
957 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
958 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
959 help="Maximum time to wait")
961 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
962 dest="shutdown_timeout", type="int",
963 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
964 help="Maximum time to wait for instance shutdown")
966 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
968 help=("Number of seconds between repetions of the"
971 EARLY_RELEASE_OPT = cli_option("--early-release",
972 dest="early_release", default=False,
974 help="Release the locks on the secondary"
977 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
978 dest="new_cluster_cert",
979 default=False, action="store_true",
980 help="Generate a new cluster certificate")
982 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
984 help="File containing new RAPI certificate")
986 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
987 default=None, action="store_true",
988 help=("Generate a new self-signed RAPI"
991 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
992 dest="new_confd_hmac_key",
993 default=False, action="store_true",
994 help=("Create a new HMAC key for %s" %
997 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
998 dest="cluster_domain_secret",
1000 help=("Load new new cluster domain"
1001 " secret from file"))
1003 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1004 dest="new_cluster_domain_secret",
1005 default=False, action="store_true",
1006 help=("Create a new cluster domain"
1009 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1010 dest="use_replication_network",
1011 help="Whether to use the replication network"
1012 " for talking to the nodes",
1013 action="store_true", default=False)
1015 MAINTAIN_NODE_HEALTH_OPT = \
1016 cli_option("--maintain-node-health", dest="maintain_node_health",
1017 metavar=_YORNO, default=None, type="bool",
1018 help="Configure the cluster to automatically maintain node"
1019 " health, by shutting down unknown instances, shutting down"
1020 " unknown DRBD devices, etc.")
1022 IDENTIFY_DEFAULTS_OPT = \
1023 cli_option("--identify-defaults", dest="identify_defaults",
1024 default=False, action="store_true",
1025 help="Identify which saved instance parameters are equal to"
1026 " the current cluster defaults and set them as such, instead"
1027 " of marking them as overridden")
1029 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1030 action="store", dest="uid_pool",
1031 help=("A list of user-ids or user-id"
1032 " ranges separated by commas"))
1034 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1035 action="store", dest="add_uids",
1036 help=("A list of user-ids or user-id"
1037 " ranges separated by commas, to be"
1038 " added to the user-id pool"))
1040 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1041 action="store", dest="remove_uids",
1042 help=("A list of user-ids or user-id"
1043 " ranges separated by commas, to be"
1044 " removed from the user-id pool"))
1046 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1047 action="store", dest="reserved_lvs",
1048 help=("A comma-separated list of reserved"
1049 " logical volumes names, that will be"
1050 " ignored by cluster verify"))
1052 ROMAN_OPT = cli_option("--roman",
1053 dest="roman_integers", default=False,
1054 action="store_true",
1055 help="Use roman numbers for positive integers")
1057 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1058 action="store", default=None,
1059 help="Specifies usermode helper for DRBD")
1061 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1062 action="store_false", default=True,
1063 help="Disable support for DRBD")
1065 PRIMARY_IP_VERSION_OPT = \
1066 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1067 action="store", dest="primary_ip_version",
1068 metavar="%d|%d" % (constants.IP4_VERSION,
1069 constants.IP6_VERSION),
1070 help="Cluster-wide IP version for primary IP")
1072 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1073 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1074 choices=_PRIONAME_TO_VALUE.keys(),
1075 help="Priority for opcode processing")
1077 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1078 type="bool", default=None, metavar=_YORNO,
1079 help="Sets the hidden flag on the OS")
1081 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1082 type="bool", default=None, metavar=_YORNO,
1083 help="Sets the blacklisted flag on the OS")
1086 #: Options provided by all commands
1087 COMMON_OPTS = [DEBUG_OPT]
1090 def _ParseArgs(argv, commands, aliases):
1091 """Parser for the command line arguments.
1093 This function parses the arguments and returns the function which
1094 must be executed together with its (modified) arguments.
1096 @param argv: the command line
1097 @param commands: dictionary with special contents, see the design
1098 doc for cmdline handling
1099 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1103 binary = "<command>"
1105 binary = argv[0].split("/")[-1]
1107 if len(argv) > 1 and argv[1] == "--version":
1108 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1109 constants.RELEASE_VERSION)
1110 # Quit right away. That way we don't have to care about this special
1111 # argument. optparse.py does it the same.
1114 if len(argv) < 2 or not (argv[1] in commands or
1115 argv[1] in aliases):
1116 # let's do a nice thing
1117 sortedcmds = commands.keys()
1120 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1121 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1124 # compute the max line length for cmd + usage
1125 mlen = max([len(" %s" % cmd) for cmd in commands])
1126 mlen = min(60, mlen) # should not get here...
1128 # and format a nice command list
1129 ToStdout("Commands:")
1130 for cmd in sortedcmds:
1131 cmdstr = " %s" % (cmd,)
1132 help_text = commands[cmd][4]
1133 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1134 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1135 for line in help_lines:
1136 ToStdout("%-*s %s", mlen, "", line)
1140 return None, None, None
1142 # get command, unalias it, and look it up in commands
1146 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1149 if aliases[cmd] not in commands:
1150 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1151 " command '%s'" % (cmd, aliases[cmd]))
1155 func, args_def, parser_opts, usage, description = commands[cmd]
1156 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1157 description=description,
1158 formatter=TitledHelpFormatter(),
1159 usage="%%prog %s %s" % (cmd, usage))
1160 parser.disable_interspersed_args()
1161 options, args = parser.parse_args()
1163 if not _CheckArguments(cmd, args_def, args):
1164 return None, None, None
1166 return func, options, args
1169 def _CheckArguments(cmd, args_def, args):
1170 """Verifies the arguments using the argument definition.
1174 1. Abort with error if values specified by user but none expected.
1176 1. For each argument in definition
1178 1. Keep running count of minimum number of values (min_count)
1179 1. Keep running count of maximum number of values (max_count)
1180 1. If it has an unlimited number of values
1182 1. Abort with error if it's not the last argument in the definition
1184 1. If last argument has limited number of values
1186 1. Abort with error if number of values doesn't match or is too large
1188 1. Abort with error if user didn't pass enough values (min_count)
1191 if args and not args_def:
1192 ToStderr("Error: Command %s expects no arguments", cmd)
1199 last_idx = len(args_def) - 1
1201 for idx, arg in enumerate(args_def):
1202 if min_count is None:
1204 elif arg.min is not None:
1205 min_count += arg.min
1207 if max_count is None:
1209 elif arg.max is not None:
1210 max_count += arg.max
1213 check_max = (arg.max is not None)
1215 elif arg.max is None:
1216 raise errors.ProgrammerError("Only the last argument can have max=None")
1219 # Command with exact number of arguments
1220 if (min_count is not None and max_count is not None and
1221 min_count == max_count and len(args) != min_count):
1222 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1225 # Command with limited number of arguments
1226 if max_count is not None and len(args) > max_count:
1227 ToStderr("Error: Command %s expects only %d argument(s)",
1231 # Command with some required arguments
1232 if min_count is not None and len(args) < min_count:
1233 ToStderr("Error: Command %s expects at least %d argument(s)",
1240 def SplitNodeOption(value):
1241 """Splits the value of a --node option.
1244 if value and ':' in value:
1245 return value.split(':', 1)
1247 return (value, None)
1250 def CalculateOSNames(os_name, os_variants):
1251 """Calculates all the names an OS can be called, according to its variants.
1253 @type os_name: string
1254 @param os_name: base name of the os
1255 @type os_variants: list or None
1256 @param os_variants: list of supported variants
1258 @return: list of valid names
1262 return ['%s+%s' % (os_name, v) for v in os_variants]
1267 def ParseFields(selected, default):
1268 """Parses the values of "--field"-like options.
1270 @type selected: string or None
1271 @param selected: User-selected options
1273 @param default: Default fields
1276 if selected is None:
1279 if selected.startswith("+"):
1280 return default + selected[1:].split(",")
1282 return selected.split(",")
1285 UsesRPC = rpc.RunWithRPC
1288 def AskUser(text, choices=None):
1289 """Ask the user a question.
1291 @param text: the question to ask
1293 @param choices: list with elements tuples (input_char, return_value,
1294 description); if not given, it will default to: [('y', True,
1295 'Perform the operation'), ('n', False, 'Do no do the operation')];
1296 note that the '?' char is reserved for help
1298 @return: one of the return values from the choices list; if input is
1299 not possible (i.e. not running with a tty, we return the last
1304 choices = [('y', True, 'Perform the operation'),
1305 ('n', False, 'Do not perform the operation')]
1306 if not choices or not isinstance(choices, list):
1307 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1308 for entry in choices:
1309 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1310 raise errors.ProgrammerError("Invalid choices element to AskUser")
1312 answer = choices[-1][1]
1314 for line in text.splitlines():
1315 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1316 text = "\n".join(new_text)
1318 f = file("/dev/tty", "a+")
1322 chars = [entry[0] for entry in choices]
1323 chars[-1] = "[%s]" % chars[-1]
1325 maps = dict([(entry[0], entry[1]) for entry in choices])
1329 f.write("/".join(chars))
1331 line = f.readline(2).strip().lower()
1336 for entry in choices:
1337 f.write(" %s - %s\n" % (entry[0], entry[2]))
1345 class JobSubmittedException(Exception):
1346 """Job was submitted, client should exit.
1348 This exception has one argument, the ID of the job that was
1349 submitted. The handler should print this ID.
1351 This is not an error, just a structured way to exit from clients.
1356 def SendJob(ops, cl=None):
1357 """Function to submit an opcode without waiting for the results.
1360 @param ops: list of opcodes
1361 @type cl: luxi.Client
1362 @param cl: the luxi client to use for communicating with the master;
1363 if None, a new client will be created
1369 job_id = cl.SubmitJob(ops)
1374 def GenericPollJob(job_id, cbs, report_cbs):
1375 """Generic job-polling function.
1377 @type job_id: number
1378 @param job_id: Job ID
1379 @type cbs: Instance of L{JobPollCbBase}
1380 @param cbs: Data callbacks
1381 @type report_cbs: Instance of L{JobPollReportCbBase}
1382 @param report_cbs: Reporting callbacks
1385 prev_job_info = None
1386 prev_logmsg_serial = None
1391 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1394 # job not found, go away!
1395 raise errors.JobLost("Job with id %s lost" % job_id)
1397 if result == constants.JOB_NOTCHANGED:
1398 report_cbs.ReportNotChanged(job_id, status)
1403 # Split result, a tuple of (field values, log entries)
1404 (job_info, log_entries) = result
1405 (status, ) = job_info
1408 for log_entry in log_entries:
1409 (serial, timestamp, log_type, message) = log_entry
1410 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1412 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1414 # TODO: Handle canceled and archived jobs
1415 elif status in (constants.JOB_STATUS_SUCCESS,
1416 constants.JOB_STATUS_ERROR,
1417 constants.JOB_STATUS_CANCELING,
1418 constants.JOB_STATUS_CANCELED):
1421 prev_job_info = job_info
1423 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1425 raise errors.JobLost("Job with id %s lost" % job_id)
1427 status, opstatus, result = jobs[0]
1429 if status == constants.JOB_STATUS_SUCCESS:
1432 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1433 raise errors.OpExecError("Job was canceled")
1436 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1437 if status == constants.OP_STATUS_SUCCESS:
1439 elif status == constants.OP_STATUS_ERROR:
1440 errors.MaybeRaise(msg)
1443 raise errors.OpExecError("partial failure (opcode %d): %s" %
1446 raise errors.OpExecError(str(msg))
1448 # default failure mode
1449 raise errors.OpExecError(result)
1452 class JobPollCbBase:
1453 """Base class for L{GenericPollJob} callbacks.
1457 """Initializes this class.
1461 def WaitForJobChangeOnce(self, job_id, fields,
1462 prev_job_info, prev_log_serial):
1463 """Waits for changes on a job.
1466 raise NotImplementedError()
1468 def QueryJobs(self, job_ids, fields):
1469 """Returns the selected fields for the selected job IDs.
1471 @type job_ids: list of numbers
1472 @param job_ids: Job IDs
1473 @type fields: list of strings
1474 @param fields: Fields
1477 raise NotImplementedError()
1480 class JobPollReportCbBase:
1481 """Base class for L{GenericPollJob} reporting callbacks.
1485 """Initializes this class.
1489 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1490 """Handles a log message.
1493 raise NotImplementedError()
1495 def ReportNotChanged(self, job_id, status):
1496 """Called for if a job hasn't changed in a while.
1498 @type job_id: number
1499 @param job_id: Job ID
1500 @type status: string or None
1501 @param status: Job status if available
1504 raise NotImplementedError()
1507 class _LuxiJobPollCb(JobPollCbBase):
1508 def __init__(self, cl):
1509 """Initializes this class.
1512 JobPollCbBase.__init__(self)
1515 def WaitForJobChangeOnce(self, job_id, fields,
1516 prev_job_info, prev_log_serial):
1517 """Waits for changes on a job.
1520 return self.cl.WaitForJobChangeOnce(job_id, fields,
1521 prev_job_info, prev_log_serial)
1523 def QueryJobs(self, job_ids, fields):
1524 """Returns the selected fields for the selected job IDs.
1527 return self.cl.QueryJobs(job_ids, fields)
1530 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1531 def __init__(self, feedback_fn):
1532 """Initializes this class.
1535 JobPollReportCbBase.__init__(self)
1537 self.feedback_fn = feedback_fn
1539 assert callable(feedback_fn)
1541 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1542 """Handles a log message.
1545 self.feedback_fn((timestamp, log_type, log_msg))
1547 def ReportNotChanged(self, job_id, status):
1548 """Called if a job hasn't changed in a while.
1554 class StdioJobPollReportCb(JobPollReportCbBase):
1556 """Initializes this class.
1559 JobPollReportCbBase.__init__(self)
1561 self.notified_queued = False
1562 self.notified_waitlock = False
1564 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1565 """Handles a log message.
1568 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1569 FormatLogMessage(log_type, log_msg))
1571 def ReportNotChanged(self, job_id, status):
1572 """Called if a job hasn't changed in a while.
1578 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1579 ToStderr("Job %s is waiting in queue", job_id)
1580 self.notified_queued = True
1582 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1583 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1584 self.notified_waitlock = True
1587 def FormatLogMessage(log_type, log_msg):
1588 """Formats a job message according to its type.
1591 if log_type != constants.ELOG_MESSAGE:
1592 log_msg = str(log_msg)
1594 return utils.SafeEncode(log_msg)
1597 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1598 """Function to poll for the result of a job.
1600 @type job_id: job identified
1601 @param job_id: the job to poll for results
1602 @type cl: luxi.Client
1603 @param cl: the luxi client to use for communicating with the master;
1604 if None, a new client will be created
1610 if reporter is None:
1612 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1614 reporter = StdioJobPollReportCb()
1616 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1618 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1621 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1622 """Legacy function to submit an opcode.
1624 This is just a simple wrapper over the construction of the processor
1625 instance. It should be extended to better handle feedback and
1626 interaction functions.
1632 SetGenericOpcodeOpts([op], opts)
1634 job_id = SendJob([op], cl=cl)
1636 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1639 return op_results[0]
1642 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1643 """Wrapper around SubmitOpCode or SendJob.
1645 This function will decide, based on the 'opts' parameter, whether to
1646 submit and wait for the result of the opcode (and return it), or
1647 whether to just send the job and print its identifier. It is used in
1648 order to simplify the implementation of the '--submit' option.
1650 It will also process the opcodes if we're sending the via SendJob
1651 (otherwise SubmitOpCode does it).
1654 if opts and opts.submit_only:
1656 SetGenericOpcodeOpts(job, opts)
1657 job_id = SendJob(job, cl=cl)
1658 raise JobSubmittedException(job_id)
1660 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1663 def SetGenericOpcodeOpts(opcode_list, options):
1664 """Processor for generic options.
1666 This function updates the given opcodes based on generic command
1667 line options (like debug, dry-run, etc.).
1669 @param opcode_list: list of opcodes
1670 @param options: command line options or None
1671 @return: None (in-place modification)
1676 for op in opcode_list:
1677 op.debug_level = options.debug
1678 if hasattr(options, "dry_run"):
1679 op.dry_run = options.dry_run
1680 if getattr(options, "priority", None) is not None:
1681 op.priority = _PRIONAME_TO_VALUE[options.priority]
1685 # TODO: Cache object?
1687 client = luxi.Client()
1688 except luxi.NoMasterError:
1689 ss = ssconf.SimpleStore()
1691 # Try to read ssconf file
1694 except errors.ConfigurationError:
1695 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1696 " not part of a cluster")
1698 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1699 if master != myself:
1700 raise errors.OpPrereqError("This is not the master node, please connect"
1701 " to node '%s' and rerun the command" %
1707 def FormatError(err):
1708 """Return a formatted error message for a given error.
1710 This function takes an exception instance and returns a tuple
1711 consisting of two values: first, the recommended exit code, and
1712 second, a string describing the error message (not
1713 newline-terminated).
1719 if isinstance(err, errors.ConfigurationError):
1720 txt = "Corrupt configuration file: %s" % msg
1722 obuf.write(txt + "\n")
1723 obuf.write("Aborting.")
1725 elif isinstance(err, errors.HooksAbort):
1726 obuf.write("Failure: hooks execution failed:\n")
1727 for node, script, out in err.args[0]:
1729 obuf.write(" node: %s, script: %s, output: %s\n" %
1730 (node, script, out))
1732 obuf.write(" node: %s, script: %s (no output)\n" %
1734 elif isinstance(err, errors.HooksFailure):
1735 obuf.write("Failure: hooks general failure: %s" % msg)
1736 elif isinstance(err, errors.ResolverError):
1737 this_host = netutils.Hostname.GetSysName()
1738 if err.args[0] == this_host:
1739 msg = "Failure: can't resolve my own hostname ('%s')"
1741 msg = "Failure: can't resolve hostname '%s'"
1742 obuf.write(msg % err.args[0])
1743 elif isinstance(err, errors.OpPrereqError):
1744 if len(err.args) == 2:
1745 obuf.write("Failure: prerequisites not met for this"
1746 " operation:\nerror type: %s, error details:\n%s" %
1747 (err.args[1], err.args[0]))
1749 obuf.write("Failure: prerequisites not met for this"
1750 " operation:\n%s" % msg)
1751 elif isinstance(err, errors.OpExecError):
1752 obuf.write("Failure: command execution error:\n%s" % msg)
1753 elif isinstance(err, errors.TagError):
1754 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1755 elif isinstance(err, errors.JobQueueDrainError):
1756 obuf.write("Failure: the job queue is marked for drain and doesn't"
1757 " accept new requests\n")
1758 elif isinstance(err, errors.JobQueueFull):
1759 obuf.write("Failure: the job queue is full and doesn't accept new"
1760 " job submissions until old jobs are archived\n")
1761 elif isinstance(err, errors.TypeEnforcementError):
1762 obuf.write("Parameter Error: %s" % msg)
1763 elif isinstance(err, errors.ParameterError):
1764 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1765 elif isinstance(err, luxi.NoMasterError):
1766 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1767 " and listening for connections?")
1768 elif isinstance(err, luxi.TimeoutError):
1769 obuf.write("Timeout while talking to the master daemon. Error:\n"
1771 elif isinstance(err, luxi.PermissionError):
1772 obuf.write("It seems you don't have permissions to connect to the"
1773 " master daemon.\nPlease retry as a different user.")
1774 elif isinstance(err, luxi.ProtocolError):
1775 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1777 elif isinstance(err, errors.JobLost):
1778 obuf.write("Error checking job status: %s" % msg)
1779 elif isinstance(err, errors.GenericError):
1780 obuf.write("Unhandled Ganeti error: %s" % msg)
1781 elif isinstance(err, JobSubmittedException):
1782 obuf.write("JobID: %s\n" % err.args[0])
1785 obuf.write("Unhandled exception: %s" % msg)
1786 return retcode, obuf.getvalue().rstrip('\n')
1789 def GenericMain(commands, override=None, aliases=None):
1790 """Generic main function for all the gnt-* commands.
1793 - commands: a dictionary with a special structure, see the design doc
1794 for command line handling.
1795 - override: if not None, we expect a dictionary with keys that will
1796 override command line options; this can be used to pass
1797 options from the scripts to generic functions
1798 - aliases: dictionary with command aliases {'alias': 'target, ...}
1801 # save the program name and the entire command line for later logging
1803 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1804 if len(sys.argv) >= 2:
1805 binary += " " + sys.argv[1]
1806 old_cmdline = " ".join(sys.argv[2:])
1810 binary = "<unknown program>"
1817 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1818 except errors.ParameterError, err:
1819 result, err_msg = FormatError(err)
1823 if func is None: # parse error
1826 if override is not None:
1827 for key, val in override.iteritems():
1828 setattr(options, key, val)
1830 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1831 stderr_logging=True, program=binary)
1834 logging.info("run with arguments '%s'", old_cmdline)
1836 logging.info("run with no arguments")
1839 result = func(options, args)
1840 except (errors.GenericError, luxi.ProtocolError,
1841 JobSubmittedException), err:
1842 result, err_msg = FormatError(err)
1843 logging.exception("Error during command processing")
1849 def ParseNicOption(optvalue):
1850 """Parses the value of the --net option(s).
1854 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1855 except (TypeError, ValueError), err:
1856 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1858 nics = [{}] * nic_max
1859 for nidx, ndict in optvalue:
1862 if not isinstance(ndict, dict):
1863 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1864 " got %s" % (nidx, ndict))
1866 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1873 def GenericInstanceCreate(mode, opts, args):
1874 """Add an instance to the cluster via either creation or import.
1876 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1877 @param opts: the command line options selected by the user
1879 @param args: should contain only one element, the new instance name
1881 @return: the desired exit code
1886 (pnode, snode) = SplitNodeOption(opts.node)
1891 hypervisor, hvparams = opts.hypervisor
1894 nics = ParseNicOption(opts.nics)
1898 elif mode == constants.INSTANCE_CREATE:
1899 # default of one nic, all auto
1905 if opts.disk_template == constants.DT_DISKLESS:
1906 if opts.disks or opts.sd_size is not None:
1907 raise errors.OpPrereqError("Diskless instance but disk"
1908 " information passed")
1911 if (not opts.disks and not opts.sd_size
1912 and mode == constants.INSTANCE_CREATE):
1913 raise errors.OpPrereqError("No disk information specified")
1914 if opts.disks and opts.sd_size is not None:
1915 raise errors.OpPrereqError("Please use either the '--disk' or"
1917 if opts.sd_size is not None:
1918 opts.disks = [(0, {"size": opts.sd_size})]
1922 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1923 except ValueError, err:
1924 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1925 disks = [{}] * disk_max
1928 for didx, ddict in opts.disks:
1930 if not isinstance(ddict, dict):
1931 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1932 raise errors.OpPrereqError(msg)
1933 elif "size" in ddict:
1934 if "adopt" in ddict:
1935 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1936 " (disk %d)" % didx)
1938 ddict["size"] = utils.ParseUnit(ddict["size"])
1939 except ValueError, err:
1940 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1942 elif "adopt" in ddict:
1943 if mode == constants.INSTANCE_IMPORT:
1944 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1948 raise errors.OpPrereqError("Missing size or adoption source for"
1952 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1953 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1955 if mode == constants.INSTANCE_CREATE:
1958 force_variant = opts.force_variant
1961 no_install = opts.no_install
1962 identify_defaults = False
1963 elif mode == constants.INSTANCE_IMPORT:
1966 force_variant = False
1967 src_node = opts.src_node
1968 src_path = opts.src_dir
1970 identify_defaults = opts.identify_defaults
1972 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1974 op = opcodes.OpCreateInstance(instance_name=instance,
1976 disk_template=opts.disk_template,
1978 pnode=pnode, snode=snode,
1979 ip_check=opts.ip_check,
1980 name_check=opts.name_check,
1981 wait_for_sync=opts.wait_for_sync,
1982 file_storage_dir=opts.file_storage_dir,
1983 file_driver=opts.file_driver,
1984 iallocator=opts.iallocator,
1985 hypervisor=hypervisor,
1987 beparams=opts.beparams,
1988 osparams=opts.osparams,
1992 force_variant=force_variant,
1995 no_install=no_install,
1996 identify_defaults=identify_defaults)
1998 SubmitOrSend(op, opts)
2002 class _RunWhileClusterStoppedHelper:
2003 """Helper class for L{RunWhileClusterStopped} to simplify state management
2006 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2007 """Initializes this class.
2009 @type feedback_fn: callable
2010 @param feedback_fn: Feedback function
2011 @type cluster_name: string
2012 @param cluster_name: Cluster name
2013 @type master_node: string
2014 @param master_node Master node name
2015 @type online_nodes: list
2016 @param online_nodes: List of names of online nodes
2019 self.feedback_fn = feedback_fn
2020 self.cluster_name = cluster_name
2021 self.master_node = master_node
2022 self.online_nodes = online_nodes
2024 self.ssh = ssh.SshRunner(self.cluster_name)
2026 self.nonmaster_nodes = [name for name in online_nodes
2027 if name != master_node]
2029 assert self.master_node not in self.nonmaster_nodes
2031 def _RunCmd(self, node_name, cmd):
2032 """Runs a command on the local or a remote machine.
2034 @type node_name: string
2035 @param node_name: Machine name
2040 if node_name is None or node_name == self.master_node:
2041 # No need to use SSH
2042 result = utils.RunCmd(cmd)
2044 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2047 errmsg = ["Failed to run command %s" % result.cmd]
2049 errmsg.append("on node %s" % node_name)
2050 errmsg.append(": exitcode %s and error %s" %
2051 (result.exit_code, result.output))
2052 raise errors.OpExecError(" ".join(errmsg))
2054 def Call(self, fn, *args):
2055 """Call function while all daemons are stopped.
2058 @param fn: Function to be called
2061 # Pause watcher by acquiring an exclusive lock on watcher state file
2062 self.feedback_fn("Blocking watcher")
2063 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2065 # TODO: Currently, this just blocks. There's no timeout.
2066 # TODO: Should it be a shared lock?
2067 watcher_block.Exclusive(blocking=True)
2069 # Stop master daemons, so that no new jobs can come in and all running
2071 self.feedback_fn("Stopping master daemons")
2072 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2074 # Stop daemons on all nodes
2075 for node_name in self.online_nodes:
2076 self.feedback_fn("Stopping daemons on %s" % node_name)
2077 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2079 # All daemons are shut down now
2081 return fn(self, *args)
2082 except Exception, err:
2083 _, errmsg = FormatError(err)
2084 logging.exception("Caught exception")
2085 self.feedback_fn(errmsg)
2088 # Start cluster again, master node last
2089 for node_name in self.nonmaster_nodes + [self.master_node]:
2090 self.feedback_fn("Starting daemons on %s" % node_name)
2091 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2094 watcher_block.Close()
2097 def RunWhileClusterStopped(feedback_fn, fn, *args):
2098 """Calls a function while all cluster daemons are stopped.
2100 @type feedback_fn: callable
2101 @param feedback_fn: Feedback function
2103 @param fn: Function to be called when daemons are stopped
2106 feedback_fn("Gathering cluster information")
2108 # This ensures we're running on the master daemon
2111 (cluster_name, master_node) = \
2112 cl.QueryConfigValues(["cluster_name", "master_node"])
2114 online_nodes = GetOnlineNodes([], cl=cl)
2116 # Don't keep a reference to the client. The master daemon will go away.
2119 assert master_node in online_nodes
2121 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2122 online_nodes).Call(fn, *args)
2125 def GenerateTable(headers, fields, separator, data,
2126 numfields=None, unitfields=None,
2128 """Prints a table with headers and different fields.
2131 @param headers: dictionary mapping field names to headers for
2134 @param fields: the field names corresponding to each row in
2136 @param separator: the separator to be used; if this is None,
2137 the default 'smart' algorithm is used which computes optimal
2138 field width, otherwise just the separator is used between
2141 @param data: a list of lists, each sublist being one row to be output
2142 @type numfields: list
2143 @param numfields: a list with the fields that hold numeric
2144 values and thus should be right-aligned
2145 @type unitfields: list
2146 @param unitfields: a list with the fields that hold numeric
2147 values that should be formatted with the units field
2148 @type units: string or None
2149 @param units: the units we should use for formatting, or None for
2150 automatic choice (human-readable for non-separator usage, otherwise
2151 megabytes); this is a one-letter string
2160 if numfields is None:
2162 if unitfields is None:
2165 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2166 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2169 for field in fields:
2170 if headers and field not in headers:
2171 # TODO: handle better unknown fields (either revert to old
2172 # style of raising exception, or deal more intelligently with
2174 headers[field] = field
2175 if separator is not None:
2176 format_fields.append("%s")
2177 elif numfields.Matches(field):
2178 format_fields.append("%*s")
2180 format_fields.append("%-*s")
2182 if separator is None:
2183 mlens = [0 for name in fields]
2184 format_str = ' '.join(format_fields)
2186 format_str = separator.replace("%", "%%").join(format_fields)
2191 for idx, val in enumerate(row):
2192 if unitfields.Matches(fields[idx]):
2195 except (TypeError, ValueError):
2198 val = row[idx] = utils.FormatUnit(val, units)
2199 val = row[idx] = str(val)
2200 if separator is None:
2201 mlens[idx] = max(mlens[idx], len(val))
2206 for idx, name in enumerate(fields):
2208 if separator is None:
2209 mlens[idx] = max(mlens[idx], len(hdr))
2210 args.append(mlens[idx])
2212 result.append(format_str % tuple(args))
2214 if separator is None:
2215 assert len(mlens) == len(fields)
2217 if fields and not numfields.Matches(fields[-1]):
2223 line = ['-' for _ in fields]
2224 for idx in range(len(fields)):
2225 if separator is None:
2226 args.append(mlens[idx])
2227 args.append(line[idx])
2228 result.append(format_str % tuple(args))
2233 def FormatTimestamp(ts):
2234 """Formats a given timestamp.
2237 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2240 @return: a string with the formatted timestamp
2243 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2246 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2249 def ParseTimespec(value):
2250 """Parse a time specification.
2252 The following suffixed will be recognized:
2260 Without any suffix, the value will be taken to be in seconds.
2265 raise errors.OpPrereqError("Empty time specification passed")
2273 if value[-1] not in suffix_map:
2276 except (TypeError, ValueError):
2277 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2279 multiplier = suffix_map[value[-1]]
2281 if not value: # no data left after stripping the suffix
2282 raise errors.OpPrereqError("Invalid time specification (only"
2285 value = int(value) * multiplier
2286 except (TypeError, ValueError):
2287 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2291 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2292 filter_master=False):
2293 """Returns the names of online nodes.
2295 This function will also log a warning on stderr with the names of
2298 @param nodes: if not empty, use only this subset of nodes (minus the
2300 @param cl: if not None, luxi client to use
2301 @type nowarn: boolean
2302 @param nowarn: by default, this function will output a note with the
2303 offline nodes that are skipped; if this parameter is True the
2304 note is not displayed
2305 @type secondary_ips: boolean
2306 @param secondary_ips: if True, return the secondary IPs instead of the
2307 names, useful for doing network traffic over the replication interface
2309 @type filter_master: boolean
2310 @param filter_master: if True, do not return the master node in the list
2311 (useful in coordination with secondary_ips where we cannot check our
2312 node name against the list)
2324 master_node = cl.QueryConfigValues(["master_node"])[0]
2325 filter_fn = lambda x: x != master_node
2327 filter_fn = lambda _: True
2329 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2331 offline = [row[0] for row in result if row[1]]
2332 if offline and not nowarn:
2333 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2334 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2337 def _ToStream(stream, txt, *args):
2338 """Write a message to a stream, bypassing the logging system
2340 @type stream: file object
2341 @param stream: the file to which we should write
2343 @param txt: the message
2348 stream.write(txt % args)
2355 def ToStdout(txt, *args):
2356 """Write a message to stdout only, bypassing the logging system
2358 This is just a wrapper over _ToStream.
2361 @param txt: the message
2364 _ToStream(sys.stdout, txt, *args)
2367 def ToStderr(txt, *args):
2368 """Write a message to stderr only, bypassing the logging system
2370 This is just a wrapper over _ToStream.
2373 @param txt: the message
2376 _ToStream(sys.stderr, txt, *args)
2379 class JobExecutor(object):
2380 """Class which manages the submission and execution of multiple jobs.
2382 Note that instances of this class should not be reused between
2386 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2391 self.verbose = verbose
2394 self.feedback_fn = feedback_fn
2396 def QueueJob(self, name, *ops):
2397 """Record a job for later submit.
2400 @param name: a description of the job, will be used in WaitJobSet
2402 SetGenericOpcodeOpts(ops, self.opts)
2403 self.queue.append((name, ops))
2405 def SubmitPending(self, each=False):
2406 """Submit all pending jobs.
2411 for row in self.queue:
2412 # SubmitJob will remove the success status, but raise an exception if
2413 # the submission fails, so we'll notice that anyway.
2414 results.append([True, self.cl.SubmitJob(row[1])])
2416 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2417 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2419 self.jobs.append((idx, status, data, name))
2421 def _ChooseJob(self):
2422 """Choose a non-waiting/queued job to poll next.
2425 assert self.jobs, "_ChooseJob called with empty job list"
2427 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2430 for job_data, status in zip(self.jobs, result):
2431 if (isinstance(status, list) and status and
2432 status[0] in (constants.JOB_STATUS_QUEUED,
2433 constants.JOB_STATUS_WAITLOCK,
2434 constants.JOB_STATUS_CANCELING)):
2435 # job is still present and waiting
2437 # good candidate found (either running job or lost job)
2438 self.jobs.remove(job_data)
2442 return self.jobs.pop(0)
2444 def GetResults(self):
2445 """Wait for and return the results of all jobs.
2448 @return: list of tuples (success, job results), in the same order
2449 as the submitted jobs; if a job has failed, instead of the result
2450 there will be the error message
2454 self.SubmitPending()
2457 ok_jobs = [row[2] for row in self.jobs if row[1]]
2459 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2461 # first, remove any non-submitted jobs
2462 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2463 for idx, _, jid, name in failures:
2464 ToStderr("Failed to submit job for %s: %s", name, jid)
2465 results.append((idx, False, jid))
2468 (idx, _, jid, name) = self._ChooseJob()
2469 ToStdout("Waiting for job %s for %s...", jid, name)
2471 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2473 except errors.JobLost, err:
2474 _, job_result = FormatError(err)
2475 ToStderr("Job %s for %s has been archived, cannot check its result",
2478 except (errors.GenericError, luxi.ProtocolError), err:
2479 _, job_result = FormatError(err)
2481 # the error message will always be shown, verbose or not
2482 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2484 results.append((idx, success, job_result))
2486 # sort based on the index, then drop it
2488 results = [i[1:] for i in results]
2492 def WaitOrShow(self, wait):
2493 """Wait for job results or only print the job IDs.
2496 @param wait: whether to wait or not
2500 return self.GetResults()
2503 self.SubmitPending()
2504 for _, status, result, name in self.jobs:
2506 ToStdout("%s: %s", result, name)
2508 ToStderr("Failure for %s: %s", name, result)
2509 return [row[1:3] for row in self.jobs]