4 # Copyright (C) 2006, 2007 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
42 from optparse import (OptionParser, TitledHelpFormatter,
43 Option, OptionValueError)
47 # Command line options
55 "CLUSTER_DOMAIN_SECRET_OPT",
69 "FILESTORE_DRIVER_OPT",
77 "IDENTIFY_DEFAULTS_OPT",
79 "IGNORE_FAILURES_OPT",
80 "IGNORE_REMOVE_FAILURES_OPT",
81 "IGNORE_SECONDARIES_OPT",
84 "MAINTAIN_NODE_HEALTH_OPT",
88 "NEW_CLUSTER_CERT_OPT",
89 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
90 "NEW_CONFD_HMAC_KEY_OPT",
101 "NOMODIFY_ETCHOSTS_OPT",
102 "NOMODIFY_SSH_SETUP_OPT",
108 "NOSSH_KEYCHECK_OPT",
119 "REMOVE_INSTANCE_OPT",
126 "SHUTDOWN_TIMEOUT_OPT",
141 # Generic functions for CLI programs
143 "GenericInstanceCreate",
147 "JobSubmittedException",
149 "RunWhileClusterStopped",
153 # Formatting functions
154 "ToStderr", "ToStdout",
163 # command line options support infrastructure
164 "ARGS_MANY_INSTANCES",
180 "OPT_COMPL_INST_ADD_NODES",
181 "OPT_COMPL_MANY_NODES",
182 "OPT_COMPL_ONE_IALLOCATOR",
183 "OPT_COMPL_ONE_INSTANCE",
184 "OPT_COMPL_ONE_NODE",
196 def __init__(self, min=0, max=None): # pylint: disable-msg=W0622
201 return ("<%s min=%s max=%s>" %
202 (self.__class__.__name__, self.min, self.max))
205 class ArgSuggest(_Argument):
206 """Suggesting argument.
208 Value can be any of the ones passed to the constructor.
211 # pylint: disable-msg=W0622
212 def __init__(self, min=0, max=None, choices=None):
213 _Argument.__init__(self, min=min, max=max)
214 self.choices = choices
217 return ("<%s min=%s max=%s choices=%r>" %
218 (self.__class__.__name__, self.min, self.max, self.choices))
221 class ArgChoice(ArgSuggest):
224 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
225 but value must be one of the choices.
230 class ArgUnknown(_Argument):
231 """Unknown argument to program (e.g. determined at runtime).
236 class ArgInstance(_Argument):
237 """Instances argument.
242 class ArgNode(_Argument):
247 class ArgJobId(_Argument):
253 class ArgFile(_Argument):
254 """File path argument.
259 class ArgCommand(_Argument):
265 class ArgHost(_Argument):
271 class ArgOs(_Argument):
278 ARGS_MANY_INSTANCES = [ArgInstance()]
279 ARGS_MANY_NODES = [ArgNode()]
280 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
281 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
282 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
285 def _ExtractTagsObject(opts, args):
286 """Extract the tag type object.
288 Note that this function will modify its args parameter.
291 if not hasattr(opts, "tag_type"):
292 raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
294 if kind == constants.TAG_CLUSTER:
296 elif kind == constants.TAG_NODE or kind == constants.TAG_INSTANCE:
298 raise errors.OpPrereqError("no arguments passed to the command")
302 raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
306 def _ExtendTags(opts, args):
307 """Extend the args if a source file has been given.
309 This function will extend the tags with the contents of the file
310 passed in the 'tags_source' attribute of the opts parameter. A file
311 named '-' will be replaced by stdin.
314 fname = opts.tags_source
320 new_fh = open(fname, "r")
323 # we don't use the nice 'new_data = [line.strip() for line in fh]'
324 # because of python bug 1633941
326 line = new_fh.readline()
329 new_data.append(line.strip())
332 args.extend(new_data)
335 def ListTags(opts, args):
336 """List the tags on a given object.
338 This is a generic implementation that knows how to deal with all
339 three cases of tag objects (cluster, node, instance). The opts
340 argument is expected to contain a tag_type field denoting what
341 object type we work on.
344 kind, name = _ExtractTagsObject(opts, args)
346 result = cl.QueryTags(kind, name)
347 result = list(result)
353 def AddTags(opts, args):
354 """Add tags on a given object.
356 This is a generic implementation that knows how to deal with all
357 three cases of tag objects (cluster, node, instance). The opts
358 argument is expected to contain a tag_type field denoting what
359 object type we work on.
362 kind, name = _ExtractTagsObject(opts, args)
363 _ExtendTags(opts, args)
365 raise errors.OpPrereqError("No tags to be added")
366 op = opcodes.OpAddTags(kind=kind, name=name, tags=args)
370 def RemoveTags(opts, args):
371 """Remove tags from a given object.
373 This is a generic implementation that knows how to deal with all
374 three cases of tag objects (cluster, node, instance). The opts
375 argument is expected to contain a tag_type field denoting what
376 object type we work on.
379 kind, name = _ExtractTagsObject(opts, args)
380 _ExtendTags(opts, args)
382 raise errors.OpPrereqError("No tags to be removed")
383 op = opcodes.OpDelTags(kind=kind, name=name, tags=args)
387 def check_unit(option, opt, value): # pylint: disable-msg=W0613
388 """OptParsers custom converter for units.
392 return utils.ParseUnit(value)
393 except errors.UnitParseError, err:
394 raise OptionValueError("option %s: %s" % (opt, err))
397 def _SplitKeyVal(opt, data):
398 """Convert a KeyVal string into a dict.
400 This function will convert a key=val[,...] string into a dict. Empty
401 values will be converted specially: keys which have the prefix 'no_'
402 will have the value=False and the prefix stripped, the others will
406 @param opt: a string holding the option name for which we process the
407 data, used in building error messages
409 @param data: a string of the format key=val,key=val,...
411 @return: {key=val, key=val}
412 @raises errors.ParameterError: if there are duplicate keys
417 for elem in utils.UnescapeAndSplit(data, sep=","):
419 key, val = elem.split("=", 1)
421 if elem.startswith(NO_PREFIX):
422 key, val = elem[len(NO_PREFIX):], False
423 elif elem.startswith(UN_PREFIX):
424 key, val = elem[len(UN_PREFIX):], None
426 key, val = elem, True
428 raise errors.ParameterError("Duplicate key '%s' in option %s" %
434 def check_ident_key_val(option, opt, value): # pylint: disable-msg=W0613
435 """Custom parser for ident:key=val,key=val options.
437 This will store the parsed values as a tuple (ident, {key: val}). As such,
438 multiple uses of this option via action=append is possible.
442 ident, rest = value, ''
444 ident, rest = value.split(":", 1)
446 if ident.startswith(NO_PREFIX):
448 msg = "Cannot pass options when removing parameter groups: %s" % value
449 raise errors.ParameterError(msg)
450 retval = (ident[len(NO_PREFIX):], False)
451 elif ident.startswith(UN_PREFIX):
453 msg = "Cannot pass options when removing parameter groups: %s" % value
454 raise errors.ParameterError(msg)
455 retval = (ident[len(UN_PREFIX):], None)
457 kv_dict = _SplitKeyVal(opt, rest)
458 retval = (ident, kv_dict)
462 def check_key_val(option, opt, value): # pylint: disable-msg=W0613
463 """Custom parser class for key=val,key=val options.
465 This will store the parsed values as a dict {key: val}.
468 return _SplitKeyVal(opt, value)
471 def check_bool(option, opt, value): # pylint: disable-msg=W0613
472 """Custom parser for yes/no options.
474 This will store the parsed value as either True or False.
477 value = value.lower()
478 if value == constants.VALUE_FALSE or value == "no":
480 elif value == constants.VALUE_TRUE or value == "yes":
483 raise errors.ParameterError("Invalid boolean value '%s'" % value)
486 # completion_suggestion is normally a list. Using numeric values not evaluating
487 # to False for dynamic completion.
488 (OPT_COMPL_MANY_NODES,
490 OPT_COMPL_ONE_INSTANCE,
492 OPT_COMPL_ONE_IALLOCATOR,
493 OPT_COMPL_INST_ADD_NODES) = range(100, 106)
495 OPT_COMPL_ALL = frozenset([
496 OPT_COMPL_MANY_NODES,
498 OPT_COMPL_ONE_INSTANCE,
500 OPT_COMPL_ONE_IALLOCATOR,
501 OPT_COMPL_INST_ADD_NODES,
505 class CliOption(Option):
506 """Custom option class for optparse.
509 ATTRS = Option.ATTRS + [
510 "completion_suggest",
512 TYPES = Option.TYPES + (
518 TYPE_CHECKER = Option.TYPE_CHECKER.copy()
519 TYPE_CHECKER["identkeyval"] = check_ident_key_val
520 TYPE_CHECKER["keyval"] = check_key_val
521 TYPE_CHECKER["unit"] = check_unit
522 TYPE_CHECKER["bool"] = check_bool
525 # optparse.py sets make_option, so we do it for our own option class, too
526 cli_option = CliOption
531 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
532 help="Increase debugging level")
534 NOHDR_OPT = cli_option("--no-headers", default=False,
535 action="store_true", dest="no_headers",
536 help="Don't display column headers")
538 SEP_OPT = cli_option("--separator", default=None,
539 action="store", dest="separator",
540 help=("Separator between output fields"
541 " (defaults to one space)"))
543 USEUNITS_OPT = cli_option("--units", default=None,
544 dest="units", choices=('h', 'm', 'g', 't'),
545 help="Specify units for output (one of hmgt)")
547 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
548 type="string", metavar="FIELDS",
549 help="Comma separated list of output fields")
551 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
552 default=False, help="Force the operation")
554 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
555 default=False, help="Do not require confirmation")
557 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
558 default=None, help="File with tag names")
560 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
561 default=False, action="store_true",
562 help=("Submit the job and return the job ID, but"
563 " don't wait for the job to finish"))
565 SYNC_OPT = cli_option("--sync", dest="do_locking",
566 default=False, action="store_true",
567 help=("Grab locks while doing the queries"
568 " in order to ensure more consistent results"))
570 _DRY_RUN_OPT = cli_option("--dry-run", default=False,
572 help=("Do not execute the operation, just run the"
573 " check steps and verify it it could be"
576 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
578 help="Increase the verbosity of the operation")
580 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
581 action="store_true", dest="simulate_errors",
582 help="Debugging option that makes the operation"
583 " treat most runtime checks as failed")
585 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
586 default=True, action="store_false",
587 help="Don't wait for sync (DANGEROUS!)")
589 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
590 help="Custom disk setup (diskless, file,"
592 default=None, metavar="TEMPL",
593 choices=list(constants.DISK_TEMPLATES))
595 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
596 help="Do not create any network cards for"
599 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
600 help="Relative path under default cluster-wide"
601 " file storage dir to store file-based disks",
602 default=None, metavar="<DIR>")
604 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
605 help="Driver to use for image files",
606 default="loop", metavar="<DRIVER>",
607 choices=list(constants.FILE_DRIVER))
609 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
610 help="Select nodes for the instance automatically"
611 " using the <NAME> iallocator plugin",
612 default=None, type="string",
613 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
615 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
617 completion_suggest=OPT_COMPL_ONE_OS)
619 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
620 action="store_true", default=False,
621 help="Force an unknown variant")
623 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
624 action="store_true", default=False,
625 help="Do not install the OS (will"
628 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
629 type="keyval", default={},
630 help="Backend parameters")
632 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
633 default={}, dest="hvparams",
634 help="Hypervisor parameters")
636 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
637 help="Hypervisor and hypervisor options, in the"
638 " format hypervisor:option=value,option=value,...",
639 default=None, type="identkeyval")
641 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
642 help="Hypervisor and hypervisor options, in the"
643 " format hypervisor:option=value,option=value,...",
644 default=[], action="append", type="identkeyval")
646 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
647 action="store_false",
648 help="Don't check that the instance's IP"
651 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
652 default=True, action="store_false",
653 help="Don't check that the instance's name"
656 NET_OPT = cli_option("--net",
657 help="NIC parameters", default=[],
658 dest="nics", action="append", type="identkeyval")
660 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
661 dest="disks", action="append", type="identkeyval")
663 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
664 help="Comma-separated list of disks"
665 " indices to act on (e.g. 0,2) (optional,"
666 " defaults to all disks)")
668 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
669 help="Enforces a single-disk configuration using the"
670 " given disk size, in MiB unless a suffix is used",
671 default=None, type="unit", metavar="<size>")
673 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
674 dest="ignore_consistency",
675 action="store_true", default=False,
676 help="Ignore the consistency of the disks on"
679 NONLIVE_OPT = cli_option("--non-live", dest="live",
680 default=True, action="store_false",
681 help="Do a non-live migration (this usually means"
682 " freeze the instance, save the state, transfer and"
683 " only then resume running on the secondary node)")
685 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
686 help="Target node and optional secondary node",
687 metavar="<pnode>[:<snode>]",
688 completion_suggest=OPT_COMPL_INST_ADD_NODES)
690 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
691 action="append", metavar="<node>",
692 help="Use only this node (can be used multiple"
693 " times, if not given defaults to all nodes)",
694 completion_suggest=OPT_COMPL_ONE_NODE)
696 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
698 completion_suggest=OPT_COMPL_ONE_NODE)
700 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
701 action="store_false",
702 help="Don't start the instance after creation")
704 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
705 action="store_true", default=False,
706 help="Show command instead of executing it")
708 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
709 default=False, action="store_true",
710 help="Instead of performing the migration, try to"
711 " recover from a failed cleanup. This is safe"
712 " to run even if the instance is healthy, but it"
713 " will create extra replication traffic and "
714 " disrupt briefly the replication (like during the"
717 STATIC_OPT = cli_option("-s", "--static", dest="static",
718 action="store_true", default=False,
719 help="Only show configuration data, not runtime data")
721 ALL_OPT = cli_option("--all", dest="show_all",
722 default=False, action="store_true",
723 help="Show info on all instances on the cluster."
724 " This can take a long time to run, use wisely")
726 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
727 action="store_true", default=False,
728 help="Interactive OS reinstall, lists available"
729 " OS templates for selection")
731 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
732 action="store_true", default=False,
733 help="Remove the instance from the cluster"
734 " configuration even if there are failures"
735 " during the removal process")
737 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
738 dest="ignore_remove_failures",
739 action="store_true", default=False,
740 help="Remove the instance from the"
741 " cluster configuration even if there"
742 " are failures during the removal"
745 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
746 action="store_true", default=False,
747 help="Remove the instance from the cluster")
749 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
750 help="Specifies the new secondary node",
751 metavar="NODE", default=None,
752 completion_suggest=OPT_COMPL_ONE_NODE)
754 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
755 default=False, action="store_true",
756 help="Replace the disk(s) on the primary"
757 " node (only for the drbd template)")
759 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
760 default=False, action="store_true",
761 help="Replace the disk(s) on the secondary"
762 " node (only for the drbd template)")
764 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
765 default=False, action="store_true",
766 help="Lock all nodes and auto-promote as needed"
769 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
770 default=False, action="store_true",
771 help="Automatically replace faulty disks"
772 " (only for the drbd template)")
774 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
775 default=False, action="store_true",
776 help="Ignore current recorded size"
777 " (useful for forcing activation when"
778 " the recorded size is wrong)")
780 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
782 completion_suggest=OPT_COMPL_ONE_NODE)
784 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
787 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
788 help="Specify the secondary ip for the node",
789 metavar="ADDRESS", default=None)
791 READD_OPT = cli_option("--readd", dest="readd",
792 default=False, action="store_true",
793 help="Readd old node after replacing it")
795 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
796 default=True, action="store_false",
797 help="Disable SSH key fingerprint checking")
800 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
801 type="bool", default=None, metavar=_YORNO,
802 help="Set the master_candidate flag on the node")
804 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
805 type="bool", default=None,
806 help="Set the offline flag on the node")
808 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
809 type="bool", default=None,
810 help="Set the drained flag on the node")
812 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
813 type="bool", default=None, metavar=_YORNO,
814 help="Set the allocatable flag on a volume")
816 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
817 help="Disable support for lvm based instances"
819 action="store_false", default=True)
821 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
822 dest="enabled_hypervisors",
823 help="Comma-separated list of hypervisors",
824 type="string", default=None)
826 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
827 type="keyval", default={},
828 help="NIC parameters")
830 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
831 dest="candidate_pool_size", type="int",
832 help="Set the candidate pool size")
834 VG_NAME_OPT = cli_option("-g", "--vg-name", dest="vg_name",
835 help="Enables LVM and specifies the volume group"
836 " name (cluster-wide) for disk allocation [xenvg]",
837 metavar="VG", default=None)
839 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
840 help="Destroy cluster", action="store_true")
842 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
843 help="Skip node agreement check (dangerous)",
844 action="store_true", default=False)
846 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
847 help="Specify the mac prefix for the instance IP"
848 " addresses, in the format XX:XX:XX",
852 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
853 help="Specify the node interface (cluster-wide)"
854 " on which the master IP address will be added "
855 " [%s]" % constants.DEFAULT_BRIDGE,
857 default=constants.DEFAULT_BRIDGE)
859 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
860 help="Specify the default directory (cluster-"
861 "wide) for storing the file-based disks [%s]" %
862 constants.DEFAULT_FILE_STORAGE_DIR,
864 default=constants.DEFAULT_FILE_STORAGE_DIR)
866 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
867 help="Don't modify /etc/hosts",
868 action="store_false", default=True)
870 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
871 help="Don't initialize SSH keys",
872 action="store_false", default=True)
874 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
875 help="Enable parseable error messages",
876 action="store_true", default=False)
878 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
879 help="Skip N+1 memory redundancy tests",
880 action="store_true", default=False)
882 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
883 help="Type of reboot: soft/hard/full",
884 default=constants.INSTANCE_REBOOT_HARD,
886 choices=list(constants.REBOOT_TYPES))
888 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
889 dest="ignore_secondaries",
890 default=False, action="store_true",
891 help="Ignore errors from secondaries")
893 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
894 action="store_false", default=True,
895 help="Don't shutdown the instance (unsafe)")
897 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
898 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
899 help="Maximum time to wait")
901 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
902 dest="shutdown_timeout", type="int",
903 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
904 help="Maximum time to wait for instance shutdown")
906 EARLY_RELEASE_OPT = cli_option("--early-release",
907 dest="early_release", default=False,
909 help="Release the locks on the secondary"
912 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
913 dest="new_cluster_cert",
914 default=False, action="store_true",
915 help="Generate a new cluster certificate")
917 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
919 help="File containing new RAPI certificate")
921 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
922 default=None, action="store_true",
923 help=("Generate a new self-signed RAPI"
926 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
927 dest="new_confd_hmac_key",
928 default=False, action="store_true",
929 help=("Create a new HMAC key for %s" %
932 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
933 dest="cluster_domain_secret",
935 help=("Load new new cluster domain"
936 " secret from file"))
938 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
939 dest="new_cluster_domain_secret",
940 default=False, action="store_true",
941 help=("Create a new cluster domain"
944 USE_REPL_NET_OPT = cli_option("--use-replication-network",
945 dest="use_replication_network",
946 help="Whether to use the replication network"
947 " for talking to the nodes",
948 action="store_true", default=False)
950 MAINTAIN_NODE_HEALTH_OPT = \
951 cli_option("--maintain-node-health", dest="maintain_node_health",
952 metavar=_YORNO, default=None, type="bool",
953 help="Configure the cluster to automatically maintain node"
954 " health, by shutting down unknown instances, shutting down"
955 " unknown DRBD devices, etc.")
957 IDENTIFY_DEFAULTS_OPT = \
958 cli_option("--identify-defaults", dest="identify_defaults",
959 default=False, action="store_true",
960 help="Identify which saved instance parameters are equal to"
961 " the current cluster defaults and set them as such, instead"
962 " of marking them as overridden")
964 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
965 action="store", dest="uid_pool",
966 help=("A list of user-ids or user-id"
967 " ranges separated by commas"))
969 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
970 action="store", dest="add_uids",
971 help=("A list of user-ids or user-id"
972 " ranges separated by commas, to be"
973 " added to the user-id pool"))
975 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
976 action="store", dest="remove_uids",
977 help=("A list of user-ids or user-id"
978 " ranges separated by commas, to be"
979 " removed from the user-id pool"))
981 ROMAN_OPT = cli_option("--roman",
982 dest="roman_integers", default=False,
984 help="Use roman numbers for positive integers")
988 def _ParseArgs(argv, commands, aliases):
989 """Parser for the command line arguments.
991 This function parses the arguments and returns the function which
992 must be executed together with its (modified) arguments.
994 @param argv: the command line
995 @param commands: dictionary with special contents, see the design
996 doc for cmdline handling
997 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1001 binary = "<command>"
1003 binary = argv[0].split("/")[-1]
1005 if len(argv) > 1 and argv[1] == "--version":
1006 ToStdout("%s (ganeti) %s", binary, constants.RELEASE_VERSION)
1007 # Quit right away. That way we don't have to care about this special
1008 # argument. optparse.py does it the same.
1011 if len(argv) < 2 or not (argv[1] in commands or
1012 argv[1] in aliases):
1013 # let's do a nice thing
1014 sortedcmds = commands.keys()
1017 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1018 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1021 # compute the max line length for cmd + usage
1022 mlen = max([len(" %s" % cmd) for cmd in commands])
1023 mlen = min(60, mlen) # should not get here...
1025 # and format a nice command list
1026 ToStdout("Commands:")
1027 for cmd in sortedcmds:
1028 cmdstr = " %s" % (cmd,)
1029 help_text = commands[cmd][4]
1030 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1031 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1032 for line in help_lines:
1033 ToStdout("%-*s %s", mlen, "", line)
1037 return None, None, None
1039 # get command, unalias it, and look it up in commands
1043 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1046 if aliases[cmd] not in commands:
1047 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1048 " command '%s'" % (cmd, aliases[cmd]))
1052 func, args_def, parser_opts, usage, description = commands[cmd]
1053 parser = OptionParser(option_list=parser_opts + [_DRY_RUN_OPT, DEBUG_OPT],
1054 description=description,
1055 formatter=TitledHelpFormatter(),
1056 usage="%%prog %s %s" % (cmd, usage))
1057 parser.disable_interspersed_args()
1058 options, args = parser.parse_args()
1060 if not _CheckArguments(cmd, args_def, args):
1061 return None, None, None
1063 return func, options, args
1066 def _CheckArguments(cmd, args_def, args):
1067 """Verifies the arguments using the argument definition.
1071 1. Abort with error if values specified by user but none expected.
1073 1. For each argument in definition
1075 1. Keep running count of minimum number of values (min_count)
1076 1. Keep running count of maximum number of values (max_count)
1077 1. If it has an unlimited number of values
1079 1. Abort with error if it's not the last argument in the definition
1081 1. If last argument has limited number of values
1083 1. Abort with error if number of values doesn't match or is too large
1085 1. Abort with error if user didn't pass enough values (min_count)
1088 if args and not args_def:
1089 ToStderr("Error: Command %s expects no arguments", cmd)
1096 last_idx = len(args_def) - 1
1098 for idx, arg in enumerate(args_def):
1099 if min_count is None:
1101 elif arg.min is not None:
1102 min_count += arg.min
1104 if max_count is None:
1106 elif arg.max is not None:
1107 max_count += arg.max
1110 check_max = (arg.max is not None)
1112 elif arg.max is None:
1113 raise errors.ProgrammerError("Only the last argument can have max=None")
1116 # Command with exact number of arguments
1117 if (min_count is not None and max_count is not None and
1118 min_count == max_count and len(args) != min_count):
1119 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1122 # Command with limited number of arguments
1123 if max_count is not None and len(args) > max_count:
1124 ToStderr("Error: Command %s expects only %d argument(s)",
1128 # Command with some required arguments
1129 if min_count is not None and len(args) < min_count:
1130 ToStderr("Error: Command %s expects at least %d argument(s)",
1137 def SplitNodeOption(value):
1138 """Splits the value of a --node option.
1141 if value and ':' in value:
1142 return value.split(':', 1)
1144 return (value, None)
1147 def CalculateOSNames(os_name, os_variants):
1148 """Calculates all the names an OS can be called, according to its variants.
1150 @type os_name: string
1151 @param os_name: base name of the os
1152 @type os_variants: list or None
1153 @param os_variants: list of supported variants
1155 @return: list of valid names
1159 return ['%s+%s' % (os_name, v) for v in os_variants]
1165 def wrapper(*args, **kwargs):
1168 return fn(*args, **kwargs)
1174 def AskUser(text, choices=None):
1175 """Ask the user a question.
1177 @param text: the question to ask
1179 @param choices: list with elements tuples (input_char, return_value,
1180 description); if not given, it will default to: [('y', True,
1181 'Perform the operation'), ('n', False, 'Do no do the operation')];
1182 note that the '?' char is reserved for help
1184 @return: one of the return values from the choices list; if input is
1185 not possible (i.e. not running with a tty, we return the last
1190 choices = [('y', True, 'Perform the operation'),
1191 ('n', False, 'Do not perform the operation')]
1192 if not choices or not isinstance(choices, list):
1193 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1194 for entry in choices:
1195 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1196 raise errors.ProgrammerError("Invalid choices element to AskUser")
1198 answer = choices[-1][1]
1200 for line in text.splitlines():
1201 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1202 text = "\n".join(new_text)
1204 f = file("/dev/tty", "a+")
1208 chars = [entry[0] for entry in choices]
1209 chars[-1] = "[%s]" % chars[-1]
1211 maps = dict([(entry[0], entry[1]) for entry in choices])
1215 f.write("/".join(chars))
1217 line = f.readline(2).strip().lower()
1222 for entry in choices:
1223 f.write(" %s - %s\n" % (entry[0], entry[2]))
1231 class JobSubmittedException(Exception):
1232 """Job was submitted, client should exit.
1234 This exception has one argument, the ID of the job that was
1235 submitted. The handler should print this ID.
1237 This is not an error, just a structured way to exit from clients.
1242 def SendJob(ops, cl=None):
1243 """Function to submit an opcode without waiting for the results.
1246 @param ops: list of opcodes
1247 @type cl: luxi.Client
1248 @param cl: the luxi client to use for communicating with the master;
1249 if None, a new client will be created
1255 job_id = cl.SubmitJob(ops)
1260 def GenericPollJob(job_id, cbs, report_cbs):
1261 """Generic job-polling function.
1263 @type job_id: number
1264 @param job_id: Job ID
1265 @type cbs: Instance of L{JobPollCbBase}
1266 @param cbs: Data callbacks
1267 @type report_cbs: Instance of L{JobPollReportCbBase}
1268 @param report_cbs: Reporting callbacks
1271 prev_job_info = None
1272 prev_logmsg_serial = None
1277 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1280 # job not found, go away!
1281 raise errors.JobLost("Job with id %s lost" % job_id)
1283 if result == constants.JOB_NOTCHANGED:
1284 report_cbs.ReportNotChanged(job_id, status)
1289 # Split result, a tuple of (field values, log entries)
1290 (job_info, log_entries) = result
1291 (status, ) = job_info
1294 for log_entry in log_entries:
1295 (serial, timestamp, log_type, message) = log_entry
1296 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1298 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1300 # TODO: Handle canceled and archived jobs
1301 elif status in (constants.JOB_STATUS_SUCCESS,
1302 constants.JOB_STATUS_ERROR,
1303 constants.JOB_STATUS_CANCELING,
1304 constants.JOB_STATUS_CANCELED):
1307 prev_job_info = job_info
1309 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1311 raise errors.JobLost("Job with id %s lost" % job_id)
1313 status, opstatus, result = jobs[0]
1315 if status == constants.JOB_STATUS_SUCCESS:
1318 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1319 raise errors.OpExecError("Job was canceled")
1322 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1323 if status == constants.OP_STATUS_SUCCESS:
1325 elif status == constants.OP_STATUS_ERROR:
1326 errors.MaybeRaise(msg)
1329 raise errors.OpExecError("partial failure (opcode %d): %s" %
1332 raise errors.OpExecError(str(msg))
1334 # default failure mode
1335 raise errors.OpExecError(result)
1338 class JobPollCbBase:
1339 """Base class for L{GenericPollJob} callbacks.
1343 """Initializes this class.
1347 def WaitForJobChangeOnce(self, job_id, fields,
1348 prev_job_info, prev_log_serial):
1349 """Waits for changes on a job.
1352 raise NotImplementedError()
1354 def QueryJobs(self, job_ids, fields):
1355 """Returns the selected fields for the selected job IDs.
1357 @type job_ids: list of numbers
1358 @param job_ids: Job IDs
1359 @type fields: list of strings
1360 @param fields: Fields
1363 raise NotImplementedError()
1366 class JobPollReportCbBase:
1367 """Base class for L{GenericPollJob} reporting callbacks.
1371 """Initializes this class.
1375 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1376 """Handles a log message.
1379 raise NotImplementedError()
1381 def ReportNotChanged(self, job_id, status):
1382 """Called for if a job hasn't changed in a while.
1384 @type job_id: number
1385 @param job_id: Job ID
1386 @type status: string or None
1387 @param status: Job status if available
1390 raise NotImplementedError()
1393 class _LuxiJobPollCb(JobPollCbBase):
1394 def __init__(self, cl):
1395 """Initializes this class.
1398 JobPollCbBase.__init__(self)
1401 def WaitForJobChangeOnce(self, job_id, fields,
1402 prev_job_info, prev_log_serial):
1403 """Waits for changes on a job.
1406 return self.cl.WaitForJobChangeOnce(job_id, fields,
1407 prev_job_info, prev_log_serial)
1409 def QueryJobs(self, job_ids, fields):
1410 """Returns the selected fields for the selected job IDs.
1413 return self.cl.QueryJobs(job_ids, fields)
1416 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
1417 def __init__(self, feedback_fn):
1418 """Initializes this class.
1421 JobPollReportCbBase.__init__(self)
1423 self.feedback_fn = feedback_fn
1425 assert callable(feedback_fn)
1427 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1428 """Handles a log message.
1431 self.feedback_fn((timestamp, log_type, log_msg))
1433 def ReportNotChanged(self, job_id, status):
1434 """Called if a job hasn't changed in a while.
1440 class StdioJobPollReportCb(JobPollReportCbBase):
1442 """Initializes this class.
1445 JobPollReportCbBase.__init__(self)
1447 self.notified_queued = False
1448 self.notified_waitlock = False
1450 def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
1451 """Handles a log message.
1454 ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
1455 utils.SafeEncode(log_msg))
1457 def ReportNotChanged(self, job_id, status):
1458 """Called if a job hasn't changed in a while.
1464 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1465 ToStderr("Job %s is waiting in queue", job_id)
1466 self.notified_queued = True
1468 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1469 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1470 self.notified_waitlock = True
1473 def PollJob(job_id, cl=None, feedback_fn=None):
1474 """Function to poll for the result of a job.
1476 @type job_id: job identified
1477 @param job_id: the job to poll for results
1478 @type cl: luxi.Client
1479 @param cl: the luxi client to use for communicating with the master;
1480 if None, a new client will be created
1487 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1489 reporter = StdioJobPollReportCb()
1491 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1494 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None):
1495 """Legacy function to submit an opcode.
1497 This is just a simple wrapper over the construction of the processor
1498 instance. It should be extended to better handle feedback and
1499 interaction functions.
1505 SetGenericOpcodeOpts([op], opts)
1507 job_id = SendJob([op], cl)
1509 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn)
1511 return op_results[0]
1514 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
1515 """Wrapper around SubmitOpCode or SendJob.
1517 This function will decide, based on the 'opts' parameter, whether to
1518 submit and wait for the result of the opcode (and return it), or
1519 whether to just send the job and print its identifier. It is used in
1520 order to simplify the implementation of the '--submit' option.
1522 It will also process the opcodes if we're sending the via SendJob
1523 (otherwise SubmitOpCode does it).
1526 if opts and opts.submit_only:
1528 SetGenericOpcodeOpts(job, opts)
1529 job_id = SendJob(job, cl=cl)
1530 raise JobSubmittedException(job_id)
1532 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1535 def SetGenericOpcodeOpts(opcode_list, options):
1536 """Processor for generic options.
1538 This function updates the given opcodes based on generic command
1539 line options (like debug, dry-run, etc.).
1541 @param opcode_list: list of opcodes
1542 @param options: command line options or None
1543 @return: None (in-place modification)
1548 for op in opcode_list:
1549 op.dry_run = options.dry_run
1550 op.debug_level = options.debug
1554 # TODO: Cache object?
1556 client = luxi.Client()
1557 except luxi.NoMasterError:
1558 ss = ssconf.SimpleStore()
1560 # Try to read ssconf file
1563 except errors.ConfigurationError:
1564 raise errors.OpPrereqError("Cluster not initialized or this machine is"
1565 " not part of a cluster")
1567 master, myself = ssconf.GetMasterAndMyself(ss=ss)
1568 if master != myself:
1569 raise errors.OpPrereqError("This is not the master node, please connect"
1570 " to node '%s' and rerun the command" %
1576 def FormatError(err):
1577 """Return a formatted error message for a given error.
1579 This function takes an exception instance and returns a tuple
1580 consisting of two values: first, the recommended exit code, and
1581 second, a string describing the error message (not
1582 newline-terminated).
1588 if isinstance(err, errors.ConfigurationError):
1589 txt = "Corrupt configuration file: %s" % msg
1591 obuf.write(txt + "\n")
1592 obuf.write("Aborting.")
1594 elif isinstance(err, errors.HooksAbort):
1595 obuf.write("Failure: hooks execution failed:\n")
1596 for node, script, out in err.args[0]:
1598 obuf.write(" node: %s, script: %s, output: %s\n" %
1599 (node, script, out))
1601 obuf.write(" node: %s, script: %s (no output)\n" %
1603 elif isinstance(err, errors.HooksFailure):
1604 obuf.write("Failure: hooks general failure: %s" % msg)
1605 elif isinstance(err, errors.ResolverError):
1606 this_host = utils.HostInfo.SysName()
1607 if err.args[0] == this_host:
1608 msg = "Failure: can't resolve my own hostname ('%s')"
1610 msg = "Failure: can't resolve hostname '%s'"
1611 obuf.write(msg % err.args[0])
1612 elif isinstance(err, errors.OpPrereqError):
1613 if len(err.args) == 2:
1614 obuf.write("Failure: prerequisites not met for this"
1615 " operation:\nerror type: %s, error details:\n%s" %
1616 (err.args[1], err.args[0]))
1618 obuf.write("Failure: prerequisites not met for this"
1619 " operation:\n%s" % msg)
1620 elif isinstance(err, errors.OpExecError):
1621 obuf.write("Failure: command execution error:\n%s" % msg)
1622 elif isinstance(err, errors.TagError):
1623 obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
1624 elif isinstance(err, errors.JobQueueDrainError):
1625 obuf.write("Failure: the job queue is marked for drain and doesn't"
1626 " accept new requests\n")
1627 elif isinstance(err, errors.JobQueueFull):
1628 obuf.write("Failure: the job queue is full and doesn't accept new"
1629 " job submissions until old jobs are archived\n")
1630 elif isinstance(err, errors.TypeEnforcementError):
1631 obuf.write("Parameter Error: %s" % msg)
1632 elif isinstance(err, errors.ParameterError):
1633 obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
1634 elif isinstance(err, luxi.NoMasterError):
1635 obuf.write("Cannot communicate with the master daemon.\nIs it running"
1636 " and listening for connections?")
1637 elif isinstance(err, luxi.TimeoutError):
1638 obuf.write("Timeout while talking to the master daemon. Error:\n"
1640 elif isinstance(err, luxi.ProtocolError):
1641 obuf.write("Unhandled protocol error while talking to the master daemon:\n"
1643 elif isinstance(err, errors.GenericError):
1644 obuf.write("Unhandled Ganeti error: %s" % msg)
1645 elif isinstance(err, JobSubmittedException):
1646 obuf.write("JobID: %s\n" % err.args[0])
1649 obuf.write("Unhandled exception: %s" % msg)
1650 return retcode, obuf.getvalue().rstrip('\n')
1653 def GenericMain(commands, override=None, aliases=None):
1654 """Generic main function for all the gnt-* commands.
1657 - commands: a dictionary with a special structure, see the design doc
1658 for command line handling.
1659 - override: if not None, we expect a dictionary with keys that will
1660 override command line options; this can be used to pass
1661 options from the scripts to generic functions
1662 - aliases: dictionary with command aliases {'alias': 'target, ...}
1665 # save the program name and the entire command line for later logging
1667 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1668 if len(sys.argv) >= 2:
1669 binary += " " + sys.argv[1]
1670 old_cmdline = " ".join(sys.argv[2:])
1674 binary = "<unknown program>"
1681 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1682 except errors.ParameterError, err:
1683 result, err_msg = FormatError(err)
1687 if func is None: # parse error
1690 if override is not None:
1691 for key, val in override.iteritems():
1692 setattr(options, key, val)
1694 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1695 stderr_logging=True, program=binary)
1698 logging.info("run with arguments '%s'", old_cmdline)
1700 logging.info("run with no arguments")
1703 result = func(options, args)
1704 except (errors.GenericError, luxi.ProtocolError,
1705 JobSubmittedException), err:
1706 result, err_msg = FormatError(err)
1707 logging.exception("Error during command processing")
1713 def GenericInstanceCreate(mode, opts, args):
1714 """Add an instance to the cluster via either creation or import.
1716 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1717 @param opts: the command line options selected by the user
1719 @param args: should contain only one element, the new instance name
1721 @return: the desired exit code
1726 (pnode, snode) = SplitNodeOption(opts.node)
1731 hypervisor, hvparams = opts.hypervisor
1735 nic_max = max(int(nidx[0]) + 1 for nidx in opts.nics)
1736 except ValueError, err:
1737 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1738 nics = [{}] * nic_max
1739 for nidx, ndict in opts.nics:
1741 if not isinstance(ndict, dict):
1742 msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
1743 raise errors.OpPrereqError(msg)
1748 elif mode == constants.INSTANCE_CREATE:
1749 # default of one nic, all auto
1755 if opts.disk_template == constants.DT_DISKLESS:
1756 if opts.disks or opts.sd_size is not None:
1757 raise errors.OpPrereqError("Diskless instance but disk"
1758 " information passed")
1761 if (not opts.disks and not opts.sd_size
1762 and mode == constants.INSTANCE_CREATE):
1763 raise errors.OpPrereqError("No disk information specified")
1764 if opts.disks and opts.sd_size is not None:
1765 raise errors.OpPrereqError("Please use either the '--disk' or"
1767 if opts.sd_size is not None:
1768 opts.disks = [(0, {"size": opts.sd_size})]
1772 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1773 except ValueError, err:
1774 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1775 disks = [{}] * disk_max
1778 for didx, ddict in opts.disks:
1780 if not isinstance(ddict, dict):
1781 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1782 raise errors.OpPrereqError(msg)
1783 elif "size" in ddict:
1784 if "adopt" in ddict:
1785 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1786 " (disk %d)" % didx)
1788 ddict["size"] = utils.ParseUnit(ddict["size"])
1789 except ValueError, err:
1790 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1792 elif "adopt" in ddict:
1793 if mode == constants.INSTANCE_IMPORT:
1794 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1798 raise errors.OpPrereqError("Missing size or adoption source for"
1802 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1803 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1805 if mode == constants.INSTANCE_CREATE:
1810 no_install = opts.no_install
1811 identify_defaults = False
1812 elif mode == constants.INSTANCE_IMPORT:
1815 src_node = opts.src_node
1816 src_path = opts.src_dir
1818 identify_defaults = opts.identify_defaults
1820 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
1822 op = opcodes.OpCreateInstance(instance_name=instance,
1824 disk_template=opts.disk_template,
1826 pnode=pnode, snode=snode,
1827 ip_check=opts.ip_check,
1828 name_check=opts.name_check,
1829 wait_for_sync=opts.wait_for_sync,
1830 file_storage_dir=opts.file_storage_dir,
1831 file_driver=opts.file_driver,
1832 iallocator=opts.iallocator,
1833 hypervisor=hypervisor,
1835 beparams=opts.beparams,
1841 no_install=no_install,
1842 identify_defaults=identify_defaults)
1844 SubmitOrSend(op, opts)
1848 class _RunWhileClusterStoppedHelper:
1849 """Helper class for L{RunWhileClusterStopped} to simplify state management
1852 def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
1853 """Initializes this class.
1855 @type feedback_fn: callable
1856 @param feedback_fn: Feedback function
1857 @type cluster_name: string
1858 @param cluster_name: Cluster name
1859 @type master_node: string
1860 @param master_node Master node name
1861 @type online_nodes: list
1862 @param online_nodes: List of names of online nodes
1865 self.feedback_fn = feedback_fn
1866 self.cluster_name = cluster_name
1867 self.master_node = master_node
1868 self.online_nodes = online_nodes
1870 self.ssh = ssh.SshRunner(self.cluster_name)
1872 self.nonmaster_nodes = [name for name in online_nodes
1873 if name != master_node]
1875 assert self.master_node not in self.nonmaster_nodes
1877 def _RunCmd(self, node_name, cmd):
1878 """Runs a command on the local or a remote machine.
1880 @type node_name: string
1881 @param node_name: Machine name
1886 if node_name is None or node_name == self.master_node:
1887 # No need to use SSH
1888 result = utils.RunCmd(cmd)
1890 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
1893 errmsg = ["Failed to run command %s" % result.cmd]
1895 errmsg.append("on node %s" % node_name)
1896 errmsg.append(": exitcode %s and error %s" %
1897 (result.exit_code, result.output))
1898 raise errors.OpExecError(" ".join(errmsg))
1900 def Call(self, fn, *args):
1901 """Call function while all daemons are stopped.
1904 @param fn: Function to be called
1907 # Pause watcher by acquiring an exclusive lock on watcher state file
1908 self.feedback_fn("Blocking watcher")
1909 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
1911 # TODO: Currently, this just blocks. There's no timeout.
1912 # TODO: Should it be a shared lock?
1913 watcher_block.Exclusive(blocking=True)
1915 # Stop master daemons, so that no new jobs can come in and all running
1917 self.feedback_fn("Stopping master daemons")
1918 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
1920 # Stop daemons on all nodes
1921 for node_name in self.online_nodes:
1922 self.feedback_fn("Stopping daemons on %s" % node_name)
1923 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
1925 # All daemons are shut down now
1927 return fn(self, *args)
1928 except Exception, err:
1929 _, errmsg = FormatError(err)
1930 logging.exception("Caught exception")
1931 self.feedback_fn(errmsg)
1934 # Start cluster again, master node last
1935 for node_name in self.nonmaster_nodes + [self.master_node]:
1936 self.feedback_fn("Starting daemons on %s" % node_name)
1937 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
1940 watcher_block.Close()
1943 def RunWhileClusterStopped(feedback_fn, fn, *args):
1944 """Calls a function while all cluster daemons are stopped.
1946 @type feedback_fn: callable
1947 @param feedback_fn: Feedback function
1949 @param fn: Function to be called when daemons are stopped
1952 feedback_fn("Gathering cluster information")
1954 # This ensures we're running on the master daemon
1957 (cluster_name, master_node) = \
1958 cl.QueryConfigValues(["cluster_name", "master_node"])
1960 online_nodes = GetOnlineNodes([], cl=cl)
1962 # Don't keep a reference to the client. The master daemon will go away.
1965 assert master_node in online_nodes
1967 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
1968 online_nodes).Call(fn, *args)
1971 def GenerateTable(headers, fields, separator, data,
1972 numfields=None, unitfields=None,
1974 """Prints a table with headers and different fields.
1977 @param headers: dictionary mapping field names to headers for
1980 @param fields: the field names corresponding to each row in
1982 @param separator: the separator to be used; if this is None,
1983 the default 'smart' algorithm is used which computes optimal
1984 field width, otherwise just the separator is used between
1987 @param data: a list of lists, each sublist being one row to be output
1988 @type numfields: list
1989 @param numfields: a list with the fields that hold numeric
1990 values and thus should be right-aligned
1991 @type unitfields: list
1992 @param unitfields: a list with the fields that hold numeric
1993 values that should be formatted with the units field
1994 @type units: string or None
1995 @param units: the units we should use for formatting, or None for
1996 automatic choice (human-readable for non-separator usage, otherwise
1997 megabytes); this is a one-letter string
2006 if numfields is None:
2008 if unitfields is None:
2011 numfields = utils.FieldSet(*numfields) # pylint: disable-msg=W0142
2012 unitfields = utils.FieldSet(*unitfields) # pylint: disable-msg=W0142
2015 for field in fields:
2016 if headers and field not in headers:
2017 # TODO: handle better unknown fields (either revert to old
2018 # style of raising exception, or deal more intelligently with
2020 headers[field] = field
2021 if separator is not None:
2022 format_fields.append("%s")
2023 elif numfields.Matches(field):
2024 format_fields.append("%*s")
2026 format_fields.append("%-*s")
2028 if separator is None:
2029 mlens = [0 for name in fields]
2030 format = ' '.join(format_fields)
2032 format = separator.replace("%", "%%").join(format_fields)
2037 for idx, val in enumerate(row):
2038 if unitfields.Matches(fields[idx]):
2041 except (TypeError, ValueError):
2044 val = row[idx] = utils.FormatUnit(val, units)
2045 val = row[idx] = str(val)
2046 if separator is None:
2047 mlens[idx] = max(mlens[idx], len(val))
2052 for idx, name in enumerate(fields):
2054 if separator is None:
2055 mlens[idx] = max(mlens[idx], len(hdr))
2056 args.append(mlens[idx])
2058 result.append(format % tuple(args))
2060 if separator is None:
2061 assert len(mlens) == len(fields)
2063 if fields and not numfields.Matches(fields[-1]):
2069 line = ['-' for _ in fields]
2070 for idx in range(len(fields)):
2071 if separator is None:
2072 args.append(mlens[idx])
2073 args.append(line[idx])
2074 result.append(format % tuple(args))
2079 def FormatTimestamp(ts):
2080 """Formats a given timestamp.
2083 @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
2086 @return: a string with the formatted timestamp
2089 if not isinstance (ts, (tuple, list)) or len(ts) != 2:
2092 return time.strftime("%F %T", time.localtime(sec)) + ".%06d" % usec
2095 def ParseTimespec(value):
2096 """Parse a time specification.
2098 The following suffixed will be recognized:
2106 Without any suffix, the value will be taken to be in seconds.
2111 raise errors.OpPrereqError("Empty time specification passed")
2119 if value[-1] not in suffix_map:
2122 except (TypeError, ValueError):
2123 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2125 multiplier = suffix_map[value[-1]]
2127 if not value: # no data left after stripping the suffix
2128 raise errors.OpPrereqError("Invalid time specification (only"
2131 value = int(value) * multiplier
2132 except (TypeError, ValueError):
2133 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2137 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2138 filter_master=False):
2139 """Returns the names of online nodes.
2141 This function will also log a warning on stderr with the names of
2144 @param nodes: if not empty, use only this subset of nodes (minus the
2146 @param cl: if not None, luxi client to use
2147 @type nowarn: boolean
2148 @param nowarn: by default, this function will output a note with the
2149 offline nodes that are skipped; if this parameter is True the
2150 note is not displayed
2151 @type secondary_ips: boolean
2152 @param secondary_ips: if True, return the secondary IPs instead of the
2153 names, useful for doing network traffic over the replication interface
2155 @type filter_master: boolean
2156 @param filter_master: if True, do not return the master node in the list
2157 (useful in coordination with secondary_ips where we cannot check our
2158 node name against the list)
2170 master_node = cl.QueryConfigValues(["master_node"])[0]
2171 filter_fn = lambda x: x != master_node
2173 filter_fn = lambda _: True
2175 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2177 offline = [row[0] for row in result if row[1]]
2178 if offline and not nowarn:
2179 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2180 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2183 def _ToStream(stream, txt, *args):
2184 """Write a message to a stream, bypassing the logging system
2186 @type stream: file object
2187 @param stream: the file to which we should write
2189 @param txt: the message
2194 stream.write(txt % args)
2201 def ToStdout(txt, *args):
2202 """Write a message to stdout only, bypassing the logging system
2204 This is just a wrapper over _ToStream.
2207 @param txt: the message
2210 _ToStream(sys.stdout, txt, *args)
2213 def ToStderr(txt, *args):
2214 """Write a message to stderr only, bypassing the logging system
2216 This is just a wrapper over _ToStream.
2219 @param txt: the message
2222 _ToStream(sys.stderr, txt, *args)
2225 class JobExecutor(object):
2226 """Class which manages the submission and execution of multiple jobs.
2228 Note that instances of this class should not be reused between
2232 def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2237 self.verbose = verbose
2240 self.feedback_fn = feedback_fn
2242 def QueueJob(self, name, *ops):
2243 """Record a job for later submit.
2246 @param name: a description of the job, will be used in WaitJobSet
2248 SetGenericOpcodeOpts(ops, self.opts)
2249 self.queue.append((name, ops))
2251 def SubmitPending(self, each=False):
2252 """Submit all pending jobs.
2257 for row in self.queue:
2258 # SubmitJob will remove the success status, but raise an exception if
2259 # the submission fails, so we'll notice that anyway.
2260 results.append([True, self.cl.SubmitJob(row[1])])
2262 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2263 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2265 self.jobs.append((idx, status, data, name))
2267 def _ChooseJob(self):
2268 """Choose a non-waiting/queued job to poll next.
2271 assert self.jobs, "_ChooseJob called with empty job list"
2273 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2276 for job_data, status in zip(self.jobs, result):
2277 if status[0] in (constants.JOB_STATUS_QUEUED,
2278 constants.JOB_STATUS_WAITLOCK,
2279 constants.JOB_STATUS_CANCELING):
2280 # job is still waiting
2282 # good candidate found
2283 self.jobs.remove(job_data)
2287 return self.jobs.pop(0)
2289 def GetResults(self):
2290 """Wait for and return the results of all jobs.
2293 @return: list of tuples (success, job results), in the same order
2294 as the submitted jobs; if a job has failed, instead of the result
2295 there will be the error message
2299 self.SubmitPending()
2302 ok_jobs = [row[2] for row in self.jobs if row[1]]
2304 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2306 # first, remove any non-submitted jobs
2307 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2308 for idx, _, jid, name in failures:
2309 ToStderr("Failed to submit job for %s: %s", name, jid)
2310 results.append((idx, False, jid))
2313 (idx, _, jid, name) = self._ChooseJob()
2314 ToStdout("Waiting for job %s for %s...", jid, name)
2316 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2318 except (errors.GenericError, luxi.ProtocolError), err:
2319 _, job_result = FormatError(err)
2321 # the error message will always be shown, verbose or not
2322 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2324 results.append((idx, success, job_result))
2326 # sort based on the index, then drop it
2328 results = [i[1:] for i in results]
2332 def WaitOrShow(self, wait):
2333 """Wait for job results or only print the job IDs.
2336 @param wait: whether to wait or not
2340 return self.GetResults()
2343 self.SubmitPending()
2344 for _, status, result, name in self.jobs:
2346 ToStdout("%s: %s", result, name)
2348 ToStderr("Failure for %s: %s", name, result)