1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """Module dealing with command line parsing"""
23
24
25 import sys
26 import textwrap
27 import os.path
28 import time
29 import logging
30 from cStringIO import StringIO
31
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
42
43 from optparse import (OptionParser, TitledHelpFormatter,
44 Option, OptionValueError)
45
46
47 __all__ = [
48
49 "ADD_UIDS_OPT",
50 "ALLOCATABLE_OPT",
51 "ALL_OPT",
52 "AUTO_PROMOTE_OPT",
53 "AUTO_REPLACE_OPT",
54 "BACKEND_OPT",
55 "BLK_OS_OPT",
56 "CAPAB_MASTER_OPT",
57 "CAPAB_VM_OPT",
58 "CLEANUP_OPT",
59 "CLUSTER_DOMAIN_SECRET_OPT",
60 "CONFIRM_OPT",
61 "CP_SIZE_OPT",
62 "DEBUG_OPT",
63 "DEBUG_SIMERR_OPT",
64 "DISKIDX_OPT",
65 "DISK_OPT",
66 "DISK_TEMPLATE_OPT",
67 "DRAINED_OPT",
68 "DRY_RUN_OPT",
69 "DRBD_HELPER_OPT",
70 "EARLY_RELEASE_OPT",
71 "ENABLED_HV_OPT",
72 "ERROR_CODES_OPT",
73 "FIELDS_OPT",
74 "FILESTORE_DIR_OPT",
75 "FILESTORE_DRIVER_OPT",
76 "FORCE_OPT",
77 "FORCE_VARIANT_OPT",
78 "GLOBAL_FILEDIR_OPT",
79 "HID_OS_OPT",
80 "HVLIST_OPT",
81 "HVOPTS_OPT",
82 "HYPERVISOR_OPT",
83 "IALLOCATOR_OPT",
84 "DEFAULT_IALLOCATOR_OPT",
85 "IDENTIFY_DEFAULTS_OPT",
86 "IGNORE_CONSIST_OPT",
87 "IGNORE_FAILURES_OPT",
88 "IGNORE_OFFLINE_OPT",
89 "IGNORE_REMOVE_FAILURES_OPT",
90 "IGNORE_SECONDARIES_OPT",
91 "IGNORE_SIZE_OPT",
92 "INTERVAL_OPT",
93 "MAC_PREFIX_OPT",
94 "MAINTAIN_NODE_HEALTH_OPT",
95 "MASTER_NETDEV_OPT",
96 "MC_OPT",
97 "MIGRATION_MODE_OPT",
98 "NET_OPT",
99 "NEW_CLUSTER_CERT_OPT",
100 "NEW_CLUSTER_DOMAIN_SECRET_OPT",
101 "NEW_CONFD_HMAC_KEY_OPT",
102 "NEW_RAPI_CERT_OPT",
103 "NEW_SECONDARY_OPT",
104 "NIC_PARAMS_OPT",
105 "NODE_LIST_OPT",
106 "NODE_PLACEMENT_OPT",
107 "NODEGROUP_OPT",
108 "NODRBD_STORAGE_OPT",
109 "NOHDR_OPT",
110 "NOIPCHECK_OPT",
111 "NO_INSTALL_OPT",
112 "NONAMECHECK_OPT",
113 "NOLVM_STORAGE_OPT",
114 "NOMODIFY_ETCHOSTS_OPT",
115 "NOMODIFY_SSH_SETUP_OPT",
116 "NONICS_OPT",
117 "NONLIVE_OPT",
118 "NONPLUS1_OPT",
119 "NOSHUTDOWN_OPT",
120 "NOSTART_OPT",
121 "NOSSH_KEYCHECK_OPT",
122 "NOVOTING_OPT",
123 "NWSYNC_OPT",
124 "ON_PRIMARY_OPT",
125 "ON_SECONDARY_OPT",
126 "OFFLINE_OPT",
127 "OSPARAMS_OPT",
128 "OS_OPT",
129 "OS_SIZE_OPT",
130 "PREALLOC_WIPE_DISKS_OPT",
131 "PRIMARY_IP_VERSION_OPT",
132 "PRIORITY_OPT",
133 "RAPI_CERT_OPT",
134 "READD_OPT",
135 "REBOOT_TYPE_OPT",
136 "REMOVE_INSTANCE_OPT",
137 "REMOVE_UIDS_OPT",
138 "RESERVED_LVS_OPT",
139 "ROMAN_OPT",
140 "SECONDARY_IP_OPT",
141 "SELECT_OS_OPT",
142 "SEP_OPT",
143 "SHOWCMD_OPT",
144 "SHUTDOWN_TIMEOUT_OPT",
145 "SINGLE_NODE_OPT",
146 "SRC_DIR_OPT",
147 "SRC_NODE_OPT",
148 "SUBMIT_OPT",
149 "STATIC_OPT",
150 "SYNC_OPT",
151 "TAG_SRC_OPT",
152 "TIMEOUT_OPT",
153 "UIDPOOL_OPT",
154 "USEUNITS_OPT",
155 "USE_REPL_NET_OPT",
156 "VERBOSE_OPT",
157 "VG_NAME_OPT",
158 "YES_DOIT_OPT",
159
160 "GenericMain",
161 "GenericInstanceCreate",
162 "GetClient",
163 "GetOnlineNodes",
164 "JobExecutor",
165 "JobSubmittedException",
166 "ParseTimespec",
167 "RunWhileClusterStopped",
168 "SubmitOpCode",
169 "SubmitOrSend",
170 "UsesRPC",
171
172 "ToStderr", "ToStdout",
173 "FormatError",
174 "GenerateTable",
175 "AskUser",
176 "FormatTimestamp",
177 "FormatLogMessage",
178
179 "ListTags",
180 "AddTags",
181 "RemoveTags",
182
183 "ARGS_MANY_INSTANCES",
184 "ARGS_MANY_NODES",
185 "ARGS_NONE",
186 "ARGS_ONE_INSTANCE",
187 "ARGS_ONE_NODE",
188 "ARGS_ONE_OS",
189 "ArgChoice",
190 "ArgCommand",
191 "ArgFile",
192 "ArgHost",
193 "ArgInstance",
194 "ArgJobId",
195 "ArgNode",
196 "ArgOs",
197 "ArgSuggest",
198 "ArgUnknown",
199 "OPT_COMPL_INST_ADD_NODES",
200 "OPT_COMPL_MANY_NODES",
201 "OPT_COMPL_ONE_IALLOCATOR",
202 "OPT_COMPL_ONE_INSTANCE",
203 "OPT_COMPL_ONE_NODE",
204 "OPT_COMPL_ONE_NODEGROUP",
205 "OPT_COMPL_ONE_OS",
206 "cli_option",
207 "SplitNodeOption",
208 "CalculateOSNames",
209 "ParseFields",
210 "COMMON_CREATE_OPTS",
211 ]
212
213 NO_PREFIX = "no_"
214 UN_PREFIX = "-"
215
216
217 _PRIORITY_NAMES = [
218 ("low", constants.OP_PRIO_LOW),
219 ("normal", constants.OP_PRIO_NORMAL),
220 ("high", constants.OP_PRIO_HIGH),
221 ]
222
223
224
225
226 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
227
228
231 self.min = min
232 self.max = max
233
235 return ("<%s min=%s max=%s>" %
236 (self.__class__.__name__, self.min, self.max))
237
238
240 """Suggesting argument.
241
242 Value can be any of the ones passed to the constructor.
243
244 """
245
246 - def __init__(self, min=0, max=None, choices=None):
249
251 return ("<%s min=%s max=%s choices=%r>" %
252 (self.__class__.__name__, self.min, self.max, self.choices))
253
254
256 """Choice argument.
257
258 Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
259 but value must be one of the choices.
260
261 """
262
263
265 """Unknown argument to program (e.g. determined at runtime).
266
267 """
268
269
271 """Instances argument.
272
273 """
274
275
277 """Node argument.
278
279 """
280
282 """Job ID argument.
283
284 """
285
286
288 """File path argument.
289
290 """
291
292
294 """Command argument.
295
296 """
297
298
300 """Host argument.
301
302 """
303
304
306 """OS argument.
307
308 """
309
310
311 ARGS_NONE = []
312 ARGS_MANY_INSTANCES = [ArgInstance()]
313 ARGS_MANY_NODES = [ArgNode()]
314 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
315 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
316 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
317
318
338
339
367
368
385
386
402
403
419
420
422 """OptParsers custom converter for units.
423
424 """
425 try:
426 return utils.ParseUnit(value)
427 except errors.UnitParseError, err:
428 raise OptionValueError("option %s: %s" % (opt, err))
429
430
432 """Convert a KeyVal string into a dict.
433
434 This function will convert a key=val[,...] string into a dict. Empty
435 values will be converted specially: keys which have the prefix 'no_'
436 will have the value=False and the prefix stripped, the others will
437 have value=True.
438
439 @type opt: string
440 @param opt: a string holding the option name for which we process the
441 data, used in building error messages
442 @type data: string
443 @param data: a string of the format key=val,key=val,...
444 @rtype: dict
445 @return: {key=val, key=val}
446 @raises errors.ParameterError: if there are duplicate keys
447
448 """
449 kv_dict = {}
450 if data:
451 for elem in utils.UnescapeAndSplit(data, sep=","):
452 if "=" in elem:
453 key, val = elem.split("=", 1)
454 else:
455 if elem.startswith(NO_PREFIX):
456 key, val = elem[len(NO_PREFIX):], False
457 elif elem.startswith(UN_PREFIX):
458 key, val = elem[len(UN_PREFIX):], None
459 else:
460 key, val = elem, True
461 if key in kv_dict:
462 raise errors.ParameterError("Duplicate key '%s' in option %s" %
463 (key, opt))
464 kv_dict[key] = val
465 return kv_dict
466
467
469 """Custom parser for ident:key=val,key=val options.
470
471 This will store the parsed values as a tuple (ident, {key: val}). As such,
472 multiple uses of this option via action=append is possible.
473
474 """
475 if ":" not in value:
476 ident, rest = value, ''
477 else:
478 ident, rest = value.split(":", 1)
479
480 if ident.startswith(NO_PREFIX):
481 if rest:
482 msg = "Cannot pass options when removing parameter groups: %s" % value
483 raise errors.ParameterError(msg)
484 retval = (ident[len(NO_PREFIX):], False)
485 elif ident.startswith(UN_PREFIX):
486 if rest:
487 msg = "Cannot pass options when removing parameter groups: %s" % value
488 raise errors.ParameterError(msg)
489 retval = (ident[len(UN_PREFIX):], None)
490 else:
491 kv_dict = _SplitKeyVal(opt, rest)
492 retval = (ident, kv_dict)
493 return retval
494
495
497 """Custom parser class for key=val,key=val options.
498
499 This will store the parsed values as a dict {key: val}.
500
501 """
502 return _SplitKeyVal(opt, value)
503
504
506 """Custom parser for yes/no options.
507
508 This will store the parsed value as either True or False.
509
510 """
511 value = value.lower()
512 if value == constants.VALUE_FALSE or value == "no":
513 return False
514 elif value == constants.VALUE_TRUE or value == "yes":
515 return True
516 else:
517 raise errors.ParameterError("Invalid boolean value '%s'" % value)
518
519
520
521
522 (OPT_COMPL_MANY_NODES,
523 OPT_COMPL_ONE_NODE,
524 OPT_COMPL_ONE_INSTANCE,
525 OPT_COMPL_ONE_OS,
526 OPT_COMPL_ONE_IALLOCATOR,
527 OPT_COMPL_INST_ADD_NODES,
528 OPT_COMPL_ONE_NODEGROUP) = range(100, 107)
529
530 OPT_COMPL_ALL = frozenset([
531 OPT_COMPL_MANY_NODES,
532 OPT_COMPL_ONE_NODE,
533 OPT_COMPL_ONE_INSTANCE,
534 OPT_COMPL_ONE_OS,
535 OPT_COMPL_ONE_IALLOCATOR,
536 OPT_COMPL_INST_ADD_NODES,
537 OPT_COMPL_ONE_NODEGROUP,
538 ])
539
540
559
560
561
562 cli_option = CliOption
563
564
565 _YORNO = "yes|no"
566
567 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
568 help="Increase debugging level")
569
570 NOHDR_OPT = cli_option("--no-headers", default=False,
571 action="store_true", dest="no_headers",
572 help="Don't display column headers")
573
574 SEP_OPT = cli_option("--separator", default=None,
575 action="store", dest="separator",
576 help=("Separator between output fields"
577 " (defaults to one space)"))
578
579 USEUNITS_OPT = cli_option("--units", default=None,
580 dest="units", choices=('h', 'm', 'g', 't'),
581 help="Specify units for output (one of hmgt)")
582
583 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
584 type="string", metavar="FIELDS",
585 help="Comma separated list of output fields")
586
587 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
588 default=False, help="Force the operation")
589
590 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
591 default=False, help="Do not require confirmation")
592
593 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
594 action="store_true", default=False,
595 help=("Ignore offline nodes and do as much"
596 " as possible"))
597
598 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
599 default=None, help="File with tag names")
600
601 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
602 default=False, action="store_true",
603 help=("Submit the job and return the job ID, but"
604 " don't wait for the job to finish"))
605
606 SYNC_OPT = cli_option("--sync", dest="do_locking",
607 default=False, action="store_true",
608 help=("Grab locks while doing the queries"
609 " in order to ensure more consistent results"))
610
611 DRY_RUN_OPT = cli_option("--dry-run", default=False,
612 action="store_true",
613 help=("Do not execute the operation, just run the"
614 " check steps and verify it it could be"
615 " executed"))
616
617 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
618 action="store_true",
619 help="Increase the verbosity of the operation")
620
621 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
622 action="store_true", dest="simulate_errors",
623 help="Debugging option that makes the operation"
624 " treat most runtime checks as failed")
625
626 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
627 default=True, action="store_false",
628 help="Don't wait for sync (DANGEROUS!)")
629
630 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
631 help="Custom disk setup (diskless, file,"
632 " plain or drbd)",
633 default=None, metavar="TEMPL",
634 choices=list(constants.DISK_TEMPLATES))
635
636 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
637 help="Do not create any network cards for"
638 " the instance")
639
640 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
641 help="Relative path under default cluster-wide"
642 " file storage dir to store file-based disks",
643 default=None, metavar="<DIR>")
644
645 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
646 help="Driver to use for image files",
647 default="loop", metavar="<DRIVER>",
648 choices=list(constants.FILE_DRIVER))
649
650 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
651 help="Select nodes for the instance automatically"
652 " using the <NAME> iallocator plugin",
653 default=None, type="string",
654 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
655
656 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
657 metavar="<NAME>",
658 help="Set the default instance allocator plugin",
659 default=None, type="string",
660 completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
661
662 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
663 metavar="<os>",
664 completion_suggest=OPT_COMPL_ONE_OS)
665
666 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
667 type="keyval", default={},
668 help="OS parameters")
669
670 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
671 action="store_true", default=False,
672 help="Force an unknown variant")
673
674 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
675 action="store_true", default=False,
676 help="Do not install the OS (will"
677 " enable no-start)")
678
679 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
680 type="keyval", default={},
681 help="Backend parameters")
682
683 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
684 default={}, dest="hvparams",
685 help="Hypervisor parameters")
686
687 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
688 help="Hypervisor and hypervisor options, in the"
689 " format hypervisor:option=value,option=value,...",
690 default=None, type="identkeyval")
691
692 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
693 help="Hypervisor and hypervisor options, in the"
694 " format hypervisor:option=value,option=value,...",
695 default=[], action="append", type="identkeyval")
696
697 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
698 action="store_false",
699 help="Don't check that the instance's IP"
700 " is alive")
701
702 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
703 default=True, action="store_false",
704 help="Don't check that the instance's name"
705 " is resolvable")
706
707 NET_OPT = cli_option("--net",
708 help="NIC parameters", default=[],
709 dest="nics", action="append", type="identkeyval")
710
711 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
712 dest="disks", action="append", type="identkeyval")
713
714 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
715 help="Comma-separated list of disks"
716 " indices to act on (e.g. 0,2) (optional,"
717 " defaults to all disks)")
718
719 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
720 help="Enforces a single-disk configuration using the"
721 " given disk size, in MiB unless a suffix is used",
722 default=None, type="unit", metavar="<size>")
723
724 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
725 dest="ignore_consistency",
726 action="store_true", default=False,
727 help="Ignore the consistency of the disks on"
728 " the secondary")
729
730 NONLIVE_OPT = cli_option("--non-live", dest="live",
731 default=True, action="store_false",
732 help="Do a non-live migration (this usually means"
733 " freeze the instance, save the state, transfer and"
734 " only then resume running on the secondary node)")
735
736 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
737 default=None,
738 choices=list(constants.HT_MIGRATION_MODES),
739 help="Override default migration mode (choose"
740 " either live or non-live")
741
742 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
743 help="Target node and optional secondary node",
744 metavar="<pnode>[:<snode>]",
745 completion_suggest=OPT_COMPL_INST_ADD_NODES)
746
747 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
748 action="append", metavar="<node>",
749 help="Use only this node (can be used multiple"
750 " times, if not given defaults to all nodes)",
751 completion_suggest=OPT_COMPL_ONE_NODE)
752
753 NODEGROUP_OPT = cli_option("-g", "--node-group",
754 dest="nodegroup",
755 help="Node group (name or uuid)",
756 metavar="<nodegroup>",
757 default=None, type="string",
758 completion_suggest=OPT_COMPL_ONE_NODEGROUP)
759
760 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
761 metavar="<node>",
762 completion_suggest=OPT_COMPL_ONE_NODE)
763
764 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
765 action="store_false",
766 help="Don't start the instance after creation")
767
768 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
769 action="store_true", default=False,
770 help="Show command instead of executing it")
771
772 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
773 default=False, action="store_true",
774 help="Instead of performing the migration, try to"
775 " recover from a failed cleanup. This is safe"
776 " to run even if the instance is healthy, but it"
777 " will create extra replication traffic and "
778 " disrupt briefly the replication (like during the"
779 " migration")
780
781 STATIC_OPT = cli_option("-s", "--static", dest="static",
782 action="store_true", default=False,
783 help="Only show configuration data, not runtime data")
784
785 ALL_OPT = cli_option("--all", dest="show_all",
786 default=False, action="store_true",
787 help="Show info on all instances on the cluster."
788 " This can take a long time to run, use wisely")
789
790 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
791 action="store_true", default=False,
792 help="Interactive OS reinstall, lists available"
793 " OS templates for selection")
794
795 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
796 action="store_true", default=False,
797 help="Remove the instance from the cluster"
798 " configuration even if there are failures"
799 " during the removal process")
800
801 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
802 dest="ignore_remove_failures",
803 action="store_true", default=False,
804 help="Remove the instance from the"
805 " cluster configuration even if there"
806 " are failures during the removal"
807 " process")
808
809 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
810 action="store_true", default=False,
811 help="Remove the instance from the cluster")
812
813 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
814 help="Specifies the new secondary node",
815 metavar="NODE", default=None,
816 completion_suggest=OPT_COMPL_ONE_NODE)
817
818 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
819 default=False, action="store_true",
820 help="Replace the disk(s) on the primary"
821 " node (only for the drbd template)")
822
823 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
824 default=False, action="store_true",
825 help="Replace the disk(s) on the secondary"
826 " node (only for the drbd template)")
827
828 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
829 default=False, action="store_true",
830 help="Lock all nodes and auto-promote as needed"
831 " to MC status")
832
833 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
834 default=False, action="store_true",
835 help="Automatically replace faulty disks"
836 " (only for the drbd template)")
837
838 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
839 default=False, action="store_true",
840 help="Ignore current recorded size"
841 " (useful for forcing activation when"
842 " the recorded size is wrong)")
843
844 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
845 metavar="<node>",
846 completion_suggest=OPT_COMPL_ONE_NODE)
847
848 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
849 metavar="<dir>")
850
851 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
852 help="Specify the secondary ip for the node",
853 metavar="ADDRESS", default=None)
854
855 READD_OPT = cli_option("--readd", dest="readd",
856 default=False, action="store_true",
857 help="Readd old node after replacing it")
858
859 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
860 default=True, action="store_false",
861 help="Disable SSH key fingerprint checking")
862
863
864 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
865 type="bool", default=None, metavar=_YORNO,
866 help="Set the master_candidate flag on the node")
867
868 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
869 type="bool", default=None,
870 help="Set the offline flag on the node")
871
872 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
873 type="bool", default=None,
874 help="Set the drained flag on the node")
875
876 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
877 type="bool", default=None, metavar=_YORNO,
878 help="Set the master_capable flag on the node")
879
880 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
881 type="bool", default=None, metavar=_YORNO,
882 help="Set the vm_capable flag on the node")
883
884 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
885 type="bool", default=None, metavar=_YORNO,
886 help="Set the allocatable flag on a volume")
887
888 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
889 help="Disable support for lvm based instances"
890 " (cluster-wide)",
891 action="store_false", default=True)
892
893 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
894 dest="enabled_hypervisors",
895 help="Comma-separated list of hypervisors",
896 type="string", default=None)
897
898 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
899 type="keyval", default={},
900 help="NIC parameters")
901
902 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
903 dest="candidate_pool_size", type="int",
904 help="Set the candidate pool size")
905
906 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
907 help="Enables LVM and specifies the volume group"
908 " name (cluster-wide) for disk allocation [xenvg]",
909 metavar="VG", default=None)
910
911 YES_DOIT_OPT = cli_option("--yes-do-it", dest="yes_do_it",
912 help="Destroy cluster", action="store_true")
913
914 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
915 help="Skip node agreement check (dangerous)",
916 action="store_true", default=False)
917
918 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
919 help="Specify the mac prefix for the instance IP"
920 " addresses, in the format XX:XX:XX",
921 metavar="PREFIX",
922 default=None)
923
924 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
925 help="Specify the node interface (cluster-wide)"
926 " on which the master IP address will be added "
927 " [%s]" % constants.DEFAULT_BRIDGE,
928 metavar="NETDEV",
929 default=constants.DEFAULT_BRIDGE)
930
931 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
932 help="Specify the default directory (cluster-"
933 "wide) for storing the file-based disks [%s]" %
934 constants.DEFAULT_FILE_STORAGE_DIR,
935 metavar="DIR",
936 default=constants.DEFAULT_FILE_STORAGE_DIR)
937
938 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
939 help="Don't modify /etc/hosts",
940 action="store_false", default=True)
941
942 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
943 help="Don't initialize SSH keys",
944 action="store_false", default=True)
945
946 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
947 help="Enable parseable error messages",
948 action="store_true", default=False)
949
950 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
951 help="Skip N+1 memory redundancy tests",
952 action="store_true", default=False)
953
954 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
955 help="Type of reboot: soft/hard/full",
956 default=constants.INSTANCE_REBOOT_HARD,
957 metavar="<REBOOT>",
958 choices=list(constants.REBOOT_TYPES))
959
960 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
961 dest="ignore_secondaries",
962 default=False, action="store_true",
963 help="Ignore errors from secondaries")
964
965 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
966 action="store_false", default=True,
967 help="Don't shutdown the instance (unsafe)")
968
969 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
970 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
971 help="Maximum time to wait")
972
973 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
974 dest="shutdown_timeout", type="int",
975 default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
976 help="Maximum time to wait for instance shutdown")
977
978 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
979 default=None,
980 help=("Number of seconds between repetions of the"
981 " command"))
982
983 EARLY_RELEASE_OPT = cli_option("--early-release",
984 dest="early_release", default=False,
985 action="store_true",
986 help="Release the locks on the secondary"
987 " node(s) early")
988
989 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
990 dest="new_cluster_cert",
991 default=False, action="store_true",
992 help="Generate a new cluster certificate")
993
994 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
995 default=None,
996 help="File containing new RAPI certificate")
997
998 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
999 default=None, action="store_true",
1000 help=("Generate a new self-signed RAPI"
1001 " certificate"))
1002
1003 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1004 dest="new_confd_hmac_key",
1005 default=False, action="store_true",
1006 help=("Create a new HMAC key for %s" %
1007 constants.CONFD))
1008
1009 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1010 dest="cluster_domain_secret",
1011 default=None,
1012 help=("Load new new cluster domain"
1013 " secret from file"))
1014
1015 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1016 dest="new_cluster_domain_secret",
1017 default=False, action="store_true",
1018 help=("Create a new cluster domain"
1019 " secret"))
1020
1021 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1022 dest="use_replication_network",
1023 help="Whether to use the replication network"
1024 " for talking to the nodes",
1025 action="store_true", default=False)
1026
1027 MAINTAIN_NODE_HEALTH_OPT = \
1028 cli_option("--maintain-node-health", dest="maintain_node_health",
1029 metavar=_YORNO, default=None, type="bool",
1030 help="Configure the cluster to automatically maintain node"
1031 " health, by shutting down unknown instances, shutting down"
1032 " unknown DRBD devices, etc.")
1033
1034 IDENTIFY_DEFAULTS_OPT = \
1035 cli_option("--identify-defaults", dest="identify_defaults",
1036 default=False, action="store_true",
1037 help="Identify which saved instance parameters are equal to"
1038 " the current cluster defaults and set them as such, instead"
1039 " of marking them as overridden")
1040
1041 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1042 action="store", dest="uid_pool",
1043 help=("A list of user-ids or user-id"
1044 " ranges separated by commas"))
1045
1046 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1047 action="store", dest="add_uids",
1048 help=("A list of user-ids or user-id"
1049 " ranges separated by commas, to be"
1050 " added to the user-id pool"))
1051
1052 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1053 action="store", dest="remove_uids",
1054 help=("A list of user-ids or user-id"
1055 " ranges separated by commas, to be"
1056 " removed from the user-id pool"))
1057
1058 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1059 action="store", dest="reserved_lvs",
1060 help=("A comma-separated list of reserved"
1061 " logical volumes names, that will be"
1062 " ignored by cluster verify"))
1063
1064 ROMAN_OPT = cli_option("--roman",
1065 dest="roman_integers", default=False,
1066 action="store_true",
1067 help="Use roman numbers for positive integers")
1068
1069 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1070 action="store", default=None,
1071 help="Specifies usermode helper for DRBD")
1072
1073 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1074 action="store_false", default=True,
1075 help="Disable support for DRBD")
1076
1077 PRIMARY_IP_VERSION_OPT = \
1078 cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1079 action="store", dest="primary_ip_version",
1080 metavar="%d|%d" % (constants.IP4_VERSION,
1081 constants.IP6_VERSION),
1082 help="Cluster-wide IP version for primary IP")
1083
1084 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1085 metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1086 choices=_PRIONAME_TO_VALUE.keys(),
1087 help="Priority for opcode processing")
1088
1089 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1090 type="bool", default=None, metavar=_YORNO,
1091 help="Sets the hidden flag on the OS")
1092
1093 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1094 type="bool", default=None, metavar=_YORNO,
1095 help="Sets the blacklisted flag on the OS")
1096
1097 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1098 type="bool", metavar=_YORNO,
1099 dest="prealloc_wipe_disks",
1100 help=("Wipe disks prior to instance"
1101 " creation"))
1102
1103
1104
1105 COMMON_OPTS = [DEBUG_OPT]
1106
1107
1108
1109 COMMON_CREATE_OPTS = [
1110 BACKEND_OPT,
1111 DISK_OPT,
1112 DISK_TEMPLATE_OPT,
1113 FILESTORE_DIR_OPT,
1114 FILESTORE_DRIVER_OPT,
1115 HYPERVISOR_OPT,
1116 IALLOCATOR_OPT,
1117 NET_OPT,
1118 NODE_PLACEMENT_OPT,
1119 NOIPCHECK_OPT,
1120 NONAMECHECK_OPT,
1121 NONICS_OPT,
1122 NWSYNC_OPT,
1123 OSPARAMS_OPT,
1124 OS_SIZE_OPT,
1125 SUBMIT_OPT,
1126 DRY_RUN_OPT,
1127 PRIORITY_OPT,
1128 ]
1129
1130
1132 """Parser for the command line arguments.
1133
1134 This function parses the arguments and returns the function which
1135 must be executed together with its (modified) arguments.
1136
1137 @param argv: the command line
1138 @param commands: dictionary with special contents, see the design
1139 doc for cmdline handling
1140 @param aliases: dictionary with command aliases {'alias': 'target, ...}
1141
1142 """
1143 if len(argv) == 0:
1144 binary = "<command>"
1145 else:
1146 binary = argv[0].split("/")[-1]
1147
1148 if len(argv) > 1 and argv[1] == "--version":
1149 ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
1150 constants.RELEASE_VERSION)
1151
1152
1153 sys.exit(0)
1154
1155 if len(argv) < 2 or not (argv[1] in commands or
1156 argv[1] in aliases):
1157
1158 sortedcmds = commands.keys()
1159 sortedcmds.sort()
1160
1161 ToStdout("Usage: %s {command} [options...] [argument...]", binary)
1162 ToStdout("%s <command> --help to see details, or man %s", binary, binary)
1163 ToStdout("")
1164
1165
1166 mlen = max([len(" %s" % cmd) for cmd in commands])
1167 mlen = min(60, mlen)
1168
1169
1170 ToStdout("Commands:")
1171 for cmd in sortedcmds:
1172 cmdstr = " %s" % (cmd,)
1173 help_text = commands[cmd][4]
1174 help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1175 ToStdout("%-*s - %s", mlen, cmdstr, help_lines.pop(0))
1176 for line in help_lines:
1177 ToStdout("%-*s %s", mlen, "", line)
1178
1179 ToStdout("")
1180
1181 return None, None, None
1182
1183
1184 cmd = argv.pop(1)
1185 if cmd in aliases:
1186 if cmd in commands:
1187 raise errors.ProgrammerError("Alias '%s' overrides an existing"
1188 " command" % cmd)
1189
1190 if aliases[cmd] not in commands:
1191 raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1192 " command '%s'" % (cmd, aliases[cmd]))
1193
1194 cmd = aliases[cmd]
1195
1196 func, args_def, parser_opts, usage, description = commands[cmd]
1197 parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1198 description=description,
1199 formatter=TitledHelpFormatter(),
1200 usage="%%prog %s %s" % (cmd, usage))
1201 parser.disable_interspersed_args()
1202 options, args = parser.parse_args()
1203
1204 if not _CheckArguments(cmd, args_def, args):
1205 return None, None, None
1206
1207 return func, options, args
1208
1209
1211 """Verifies the arguments using the argument definition.
1212
1213 Algorithm:
1214
1215 1. Abort with error if values specified by user but none expected.
1216
1217 1. For each argument in definition
1218
1219 1. Keep running count of minimum number of values (min_count)
1220 1. Keep running count of maximum number of values (max_count)
1221 1. If it has an unlimited number of values
1222
1223 1. Abort with error if it's not the last argument in the definition
1224
1225 1. If last argument has limited number of values
1226
1227 1. Abort with error if number of values doesn't match or is too large
1228
1229 1. Abort with error if user didn't pass enough values (min_count)
1230
1231 """
1232 if args and not args_def:
1233 ToStderr("Error: Command %s expects no arguments", cmd)
1234 return False
1235
1236 min_count = None
1237 max_count = None
1238 check_max = None
1239
1240 last_idx = len(args_def) - 1
1241
1242 for idx, arg in enumerate(args_def):
1243 if min_count is None:
1244 min_count = arg.min
1245 elif arg.min is not None:
1246 min_count += arg.min
1247
1248 if max_count is None:
1249 max_count = arg.max
1250 elif arg.max is not None:
1251 max_count += arg.max
1252
1253 if idx == last_idx:
1254 check_max = (arg.max is not None)
1255
1256 elif arg.max is None:
1257 raise errors.ProgrammerError("Only the last argument can have max=None")
1258
1259 if check_max:
1260
1261 if (min_count is not None and max_count is not None and
1262 min_count == max_count and len(args) != min_count):
1263 ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1264 return False
1265
1266
1267 if max_count is not None and len(args) > max_count:
1268 ToStderr("Error: Command %s expects only %d argument(s)",
1269 cmd, max_count)
1270 return False
1271
1272
1273 if min_count is not None and len(args) < min_count:
1274 ToStderr("Error: Command %s expects at least %d argument(s)",
1275 cmd, min_count)
1276 return False
1277
1278 return True
1279
1280
1282 """Splits the value of a --node option.
1283
1284 """
1285 if value and ':' in value:
1286 return value.split(':', 1)
1287 else:
1288 return (value, None)
1289
1290
1292 """Calculates all the names an OS can be called, according to its variants.
1293
1294 @type os_name: string
1295 @param os_name: base name of the os
1296 @type os_variants: list or None
1297 @param os_variants: list of supported variants
1298 @rtype: list
1299 @return: list of valid names
1300
1301 """
1302 if os_variants:
1303 return ['%s+%s' % (os_name, v) for v in os_variants]
1304 else:
1305 return [os_name]
1306
1307
1309 """Parses the values of "--field"-like options.
1310
1311 @type selected: string or None
1312 @param selected: User-selected options
1313 @type default: list
1314 @param default: Default fields
1315
1316 """
1317 if selected is None:
1318 return default
1319
1320 if selected.startswith("+"):
1321 return default + selected[1:].split(",")
1322
1323 return selected.split(",")
1324
1325
1326 UsesRPC = rpc.RunWithRPC
1327
1328
1330 """Ask the user a question.
1331
1332 @param text: the question to ask
1333
1334 @param choices: list with elements tuples (input_char, return_value,
1335 description); if not given, it will default to: [('y', True,
1336 'Perform the operation'), ('n', False, 'Do no do the operation')];
1337 note that the '?' char is reserved for help
1338
1339 @return: one of the return values from the choices list; if input is
1340 not possible (i.e. not running with a tty, we return the last
1341 entry from the list
1342
1343 """
1344 if choices is None:
1345 choices = [('y', True, 'Perform the operation'),
1346 ('n', False, 'Do not perform the operation')]
1347 if not choices or not isinstance(choices, list):
1348 raise errors.ProgrammerError("Invalid choices argument to AskUser")
1349 for entry in choices:
1350 if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == '?':
1351 raise errors.ProgrammerError("Invalid choices element to AskUser")
1352
1353 answer = choices[-1][1]
1354 new_text = []
1355 for line in text.splitlines():
1356 new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1357 text = "\n".join(new_text)
1358 try:
1359 f = file("/dev/tty", "a+")
1360 except IOError:
1361 return answer
1362 try:
1363 chars = [entry[0] for entry in choices]
1364 chars[-1] = "[%s]" % chars[-1]
1365 chars.append('?')
1366 maps = dict([(entry[0], entry[1]) for entry in choices])
1367 while True:
1368 f.write(text)
1369 f.write('\n')
1370 f.write("/".join(chars))
1371 f.write(": ")
1372 line = f.readline(2).strip().lower()
1373 if line in maps:
1374 answer = maps[line]
1375 break
1376 elif line == '?':
1377 for entry in choices:
1378 f.write(" %s - %s\n" % (entry[0], entry[2]))
1379 f.write("\n")
1380 continue
1381 finally:
1382 f.close()
1383 return answer
1384
1385
1387 """Job was submitted, client should exit.
1388
1389 This exception has one argument, the ID of the job that was
1390 submitted. The handler should print this ID.
1391
1392 This is not an error, just a structured way to exit from clients.
1393
1394 """
1395
1396
1398 """Function to submit an opcode without waiting for the results.
1399
1400 @type ops: list
1401 @param ops: list of opcodes
1402 @type cl: luxi.Client
1403 @param cl: the luxi client to use for communicating with the master;
1404 if None, a new client will be created
1405
1406 """
1407 if cl is None:
1408 cl = GetClient()
1409
1410 job_id = cl.SubmitJob(ops)
1411
1412 return job_id
1413
1414
1416 """Generic job-polling function.
1417
1418 @type job_id: number
1419 @param job_id: Job ID
1420 @type cbs: Instance of L{JobPollCbBase}
1421 @param cbs: Data callbacks
1422 @type report_cbs: Instance of L{JobPollReportCbBase}
1423 @param report_cbs: Reporting callbacks
1424
1425 """
1426 prev_job_info = None
1427 prev_logmsg_serial = None
1428
1429 status = None
1430
1431 while True:
1432 result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
1433 prev_logmsg_serial)
1434 if not result:
1435
1436 raise errors.JobLost("Job with id %s lost" % job_id)
1437
1438 if result == constants.JOB_NOTCHANGED:
1439 report_cbs.ReportNotChanged(job_id, status)
1440
1441
1442 continue
1443
1444
1445 (job_info, log_entries) = result
1446 (status, ) = job_info
1447
1448 if log_entries:
1449 for log_entry in log_entries:
1450 (serial, timestamp, log_type, message) = log_entry
1451 report_cbs.ReportLogMessage(job_id, serial, timestamp,
1452 log_type, message)
1453 prev_logmsg_serial = max(prev_logmsg_serial, serial)
1454
1455
1456 elif status in (constants.JOB_STATUS_SUCCESS,
1457 constants.JOB_STATUS_ERROR,
1458 constants.JOB_STATUS_CANCELING,
1459 constants.JOB_STATUS_CANCELED):
1460 break
1461
1462 prev_job_info = job_info
1463
1464 jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
1465 if not jobs:
1466 raise errors.JobLost("Job with id %s lost" % job_id)
1467
1468 status, opstatus, result = jobs[0]
1469
1470 if status == constants.JOB_STATUS_SUCCESS:
1471 return result
1472
1473 if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
1474 raise errors.OpExecError("Job was canceled")
1475
1476 has_ok = False
1477 for idx, (status, msg) in enumerate(zip(opstatus, result)):
1478 if status == constants.OP_STATUS_SUCCESS:
1479 has_ok = True
1480 elif status == constants.OP_STATUS_ERROR:
1481 errors.MaybeRaise(msg)
1482
1483 if has_ok:
1484 raise errors.OpExecError("partial failure (opcode %d): %s" %
1485 (idx, msg))
1486
1487 raise errors.OpExecError(str(msg))
1488
1489
1490 raise errors.OpExecError(result)
1491
1492
1494 """Base class for L{GenericPollJob} callbacks.
1495
1496 """
1498 """Initializes this class.
1499
1500 """
1501
1504 """Waits for changes on a job.
1505
1506 """
1507 raise NotImplementedError()
1508
1510 """Returns the selected fields for the selected job IDs.
1511
1512 @type job_ids: list of numbers
1513 @param job_ids: Job IDs
1514 @type fields: list of strings
1515 @param fields: Fields
1516
1517 """
1518 raise NotImplementedError()
1519
1520
1522 """Base class for L{GenericPollJob} reporting callbacks.
1523
1524 """
1526 """Initializes this class.
1527
1528 """
1529
1531 """Handles a log message.
1532
1533 """
1534 raise NotImplementedError()
1535
1537 """Called for if a job hasn't changed in a while.
1538
1539 @type job_id: number
1540 @param job_id: Job ID
1541 @type status: string or None
1542 @param status: Job status if available
1543
1544 """
1545 raise NotImplementedError()
1546
1547
1555
1558 """Waits for changes on a job.
1559
1560 """
1561 return self.cl.WaitForJobChangeOnce(job_id, fields,
1562 prev_job_info, prev_log_serial)
1563
1565 """Returns the selected fields for the selected job IDs.
1566
1567 """
1568 return self.cl.QueryJobs(job_ids, fields)
1569
1570
1573 """Initializes this class.
1574
1575 """
1576 JobPollReportCbBase.__init__(self)
1577
1578 self.feedback_fn = feedback_fn
1579
1580 assert callable(feedback_fn)
1581
1583 """Handles a log message.
1584
1585 """
1586 self.feedback_fn((timestamp, log_type, log_msg))
1587
1589 """Called if a job hasn't changed in a while.
1590
1591 """
1592
1593
1594
1597 """Initializes this class.
1598
1599 """
1600 JobPollReportCbBase.__init__(self)
1601
1602 self.notified_queued = False
1603 self.notified_waitlock = False
1604
1611
1613 """Called if a job hasn't changed in a while.
1614
1615 """
1616 if status is None:
1617 return
1618
1619 if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
1620 ToStderr("Job %s is waiting in queue", job_id)
1621 self.notified_queued = True
1622
1623 elif status == constants.JOB_STATUS_WAITLOCK and not self.notified_waitlock:
1624 ToStderr("Job %s is trying to acquire all necessary locks", job_id)
1625 self.notified_waitlock = True
1626
1627
1636
1637
1638 -def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
1639 """Function to poll for the result of a job.
1640
1641 @type job_id: job identified
1642 @param job_id: the job to poll for results
1643 @type cl: luxi.Client
1644 @param cl: the luxi client to use for communicating with the master;
1645 if None, a new client will be created
1646
1647 """
1648 if cl is None:
1649 cl = GetClient()
1650
1651 if reporter is None:
1652 if feedback_fn:
1653 reporter = FeedbackFnJobPollReportCb(feedback_fn)
1654 else:
1655 reporter = StdioJobPollReportCb()
1656 elif feedback_fn:
1657 raise errors.ProgrammerError("Can't specify reporter and feedback function")
1658
1659 return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
1660
1661
1662 -def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
1663 """Legacy function to submit an opcode.
1664
1665 This is just a simple wrapper over the construction of the processor
1666 instance. It should be extended to better handle feedback and
1667 interaction functions.
1668
1669 """
1670 if cl is None:
1671 cl = GetClient()
1672
1673 SetGenericOpcodeOpts([op], opts)
1674
1675 job_id = SendJob([op], cl=cl)
1676
1677 op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
1678 reporter=reporter)
1679
1680 return op_results[0]
1681
1682
1684 """Wrapper around SubmitOpCode or SendJob.
1685
1686 This function will decide, based on the 'opts' parameter, whether to
1687 submit and wait for the result of the opcode (and return it), or
1688 whether to just send the job and print its identifier. It is used in
1689 order to simplify the implementation of the '--submit' option.
1690
1691 It will also process the opcodes if we're sending the via SendJob
1692 (otherwise SubmitOpCode does it).
1693
1694 """
1695 if opts and opts.submit_only:
1696 job = [op]
1697 SetGenericOpcodeOpts(job, opts)
1698 job_id = SendJob(job, cl=cl)
1699 raise JobSubmittedException(job_id)
1700 else:
1701 return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
1702
1703
1705 """Processor for generic options.
1706
1707 This function updates the given opcodes based on generic command
1708 line options (like debug, dry-run, etc.).
1709
1710 @param opcode_list: list of opcodes
1711 @param options: command line options or None
1712 @return: None (in-place modification)
1713
1714 """
1715 if not options:
1716 return
1717 for op in opcode_list:
1718 op.debug_level = options.debug
1719 if hasattr(options, "dry_run"):
1720 op.dry_run = options.dry_run
1721 if getattr(options, "priority", None) is not None:
1722 op.priority = _PRIONAME_TO_VALUE[options.priority]
1723
1724
1746
1747
1828
1829
1830 -def GenericMain(commands, override=None, aliases=None):
1831 """Generic main function for all the gnt-* commands.
1832
1833 Arguments:
1834 - commands: a dictionary with a special structure, see the design doc
1835 for command line handling.
1836 - override: if not None, we expect a dictionary with keys that will
1837 override command line options; this can be used to pass
1838 options from the scripts to generic functions
1839 - aliases: dictionary with command aliases {'alias': 'target, ...}
1840
1841 """
1842
1843 if sys.argv:
1844 binary = os.path.basename(sys.argv[0]) or sys.argv[0]
1845 if len(sys.argv) >= 2:
1846 binary += " " + sys.argv[1]
1847 old_cmdline = " ".join(sys.argv[2:])
1848 else:
1849 old_cmdline = ""
1850 else:
1851 binary = "<unknown program>"
1852 old_cmdline = ""
1853
1854 if aliases is None:
1855 aliases = {}
1856
1857 try:
1858 func, options, args = _ParseArgs(sys.argv, commands, aliases)
1859 except errors.ParameterError, err:
1860 result, err_msg = FormatError(err)
1861 ToStderr(err_msg)
1862 return 1
1863
1864 if func is None:
1865 return 1
1866
1867 if override is not None:
1868 for key, val in override.iteritems():
1869 setattr(options, key, val)
1870
1871 utils.SetupLogging(constants.LOG_COMMANDS, debug=options.debug,
1872 stderr_logging=True, program=binary)
1873
1874 if old_cmdline:
1875 logging.info("run with arguments '%s'", old_cmdline)
1876 else:
1877 logging.info("run with no arguments")
1878
1879 try:
1880 result = func(options, args)
1881 except (errors.GenericError, luxi.ProtocolError,
1882 JobSubmittedException), err:
1883 result, err_msg = FormatError(err)
1884 logging.exception("Error during command processing")
1885 ToStderr(err_msg)
1886
1887 return result
1888
1889
1891 """Parses the value of the --net option(s).
1892
1893 """
1894 try:
1895 nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
1896 except (TypeError, ValueError), err:
1897 raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
1898
1899 nics = [{}] * nic_max
1900 for nidx, ndict in optvalue:
1901 nidx = int(nidx)
1902
1903 if not isinstance(ndict, dict):
1904 raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
1905 " got %s" % (nidx, ndict))
1906
1907 utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
1908
1909 nics[nidx] = ndict
1910
1911 return nics
1912
1913
1915 """Add an instance to the cluster via either creation or import.
1916
1917 @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
1918 @param opts: the command line options selected by the user
1919 @type args: list
1920 @param args: should contain only one element, the new instance name
1921 @rtype: int
1922 @return: the desired exit code
1923
1924 """
1925 instance = args[0]
1926
1927 (pnode, snode) = SplitNodeOption(opts.node)
1928
1929 hypervisor = None
1930 hvparams = {}
1931 if opts.hypervisor:
1932 hypervisor, hvparams = opts.hypervisor
1933
1934 if opts.nics:
1935 nics = ParseNicOption(opts.nics)
1936 elif opts.no_nics:
1937
1938 nics = []
1939 elif mode == constants.INSTANCE_CREATE:
1940
1941 nics = [{}]
1942 else:
1943
1944 nics = []
1945
1946 if opts.disk_template == constants.DT_DISKLESS:
1947 if opts.disks or opts.sd_size is not None:
1948 raise errors.OpPrereqError("Diskless instance but disk"
1949 " information passed")
1950 disks = []
1951 else:
1952 if (not opts.disks and not opts.sd_size
1953 and mode == constants.INSTANCE_CREATE):
1954 raise errors.OpPrereqError("No disk information specified")
1955 if opts.disks and opts.sd_size is not None:
1956 raise errors.OpPrereqError("Please use either the '--disk' or"
1957 " '-s' option")
1958 if opts.sd_size is not None:
1959 opts.disks = [(0, {"size": opts.sd_size})]
1960
1961 if opts.disks:
1962 try:
1963 disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
1964 except ValueError, err:
1965 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
1966 disks = [{}] * disk_max
1967 else:
1968 disks = []
1969 for didx, ddict in opts.disks:
1970 didx = int(didx)
1971 if not isinstance(ddict, dict):
1972 msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
1973 raise errors.OpPrereqError(msg)
1974 elif "size" in ddict:
1975 if "adopt" in ddict:
1976 raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
1977 " (disk %d)" % didx)
1978 try:
1979 ddict["size"] = utils.ParseUnit(ddict["size"])
1980 except ValueError, err:
1981 raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
1982 (didx, err))
1983 elif "adopt" in ddict:
1984 if mode == constants.INSTANCE_IMPORT:
1985 raise errors.OpPrereqError("Disk adoption not allowed for instance"
1986 " import")
1987 ddict["size"] = 0
1988 else:
1989 raise errors.OpPrereqError("Missing size or adoption source for"
1990 " disk %d" % didx)
1991 disks[didx] = ddict
1992
1993 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
1994 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
1995
1996 if mode == constants.INSTANCE_CREATE:
1997 start = opts.start
1998 os_type = opts.os
1999 force_variant = opts.force_variant
2000 src_node = None
2001 src_path = None
2002 no_install = opts.no_install
2003 identify_defaults = False
2004 elif mode == constants.INSTANCE_IMPORT:
2005 start = False
2006 os_type = None
2007 force_variant = False
2008 src_node = opts.src_node
2009 src_path = opts.src_dir
2010 no_install = None
2011 identify_defaults = opts.identify_defaults
2012 else:
2013 raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2014
2015 op = opcodes.OpCreateInstance(instance_name=instance,
2016 disks=disks,
2017 disk_template=opts.disk_template,
2018 nics=nics,
2019 pnode=pnode, snode=snode,
2020 ip_check=opts.ip_check,
2021 name_check=opts.name_check,
2022 wait_for_sync=opts.wait_for_sync,
2023 file_storage_dir=opts.file_storage_dir,
2024 file_driver=opts.file_driver,
2025 iallocator=opts.iallocator,
2026 hypervisor=hypervisor,
2027 hvparams=hvparams,
2028 beparams=opts.beparams,
2029 osparams=opts.osparams,
2030 mode=mode,
2031 start=start,
2032 os_type=os_type,
2033 force_variant=force_variant,
2034 src_node=src_node,
2035 src_path=src_path,
2036 no_install=no_install,
2037 identify_defaults=identify_defaults)
2038
2039 SubmitOrSend(op, opts)
2040 return 0
2041
2042
2044 """Helper class for L{RunWhileClusterStopped} to simplify state management
2045
2046 """
2047 - def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2048 """Initializes this class.
2049
2050 @type feedback_fn: callable
2051 @param feedback_fn: Feedback function
2052 @type cluster_name: string
2053 @param cluster_name: Cluster name
2054 @type master_node: string
2055 @param master_node Master node name
2056 @type online_nodes: list
2057 @param online_nodes: List of names of online nodes
2058
2059 """
2060 self.feedback_fn = feedback_fn
2061 self.cluster_name = cluster_name
2062 self.master_node = master_node
2063 self.online_nodes = online_nodes
2064
2065 self.ssh = ssh.SshRunner(self.cluster_name)
2066
2067 self.nonmaster_nodes = [name for name in online_nodes
2068 if name != master_node]
2069
2070 assert self.master_node not in self.nonmaster_nodes
2071
2072 - def _RunCmd(self, node_name, cmd):
2073 """Runs a command on the local or a remote machine.
2074
2075 @type node_name: string
2076 @param node_name: Machine name
2077 @type cmd: list
2078 @param cmd: Command
2079
2080 """
2081 if node_name is None or node_name == self.master_node:
2082
2083 result = utils.RunCmd(cmd)
2084 else:
2085 result = self.ssh.Run(node_name, "root", utils.ShellQuoteArgs(cmd))
2086
2087 if result.failed:
2088 errmsg = ["Failed to run command %s" % result.cmd]
2089 if node_name:
2090 errmsg.append("on node %s" % node_name)
2091 errmsg.append(": exitcode %s and error %s" %
2092 (result.exit_code, result.output))
2093 raise errors.OpExecError(" ".join(errmsg))
2094
2095 - def Call(self, fn, *args):
2096 """Call function while all daemons are stopped.
2097
2098 @type fn: callable
2099 @param fn: Function to be called
2100
2101 """
2102
2103 self.feedback_fn("Blocking watcher")
2104 watcher_block = utils.FileLock.Open(constants.WATCHER_STATEFILE)
2105 try:
2106
2107
2108 watcher_block.Exclusive(blocking=True)
2109
2110
2111
2112 self.feedback_fn("Stopping master daemons")
2113 self._RunCmd(None, [constants.DAEMON_UTIL, "stop-master"])
2114 try:
2115
2116 for node_name in self.online_nodes:
2117 self.feedback_fn("Stopping daemons on %s" % node_name)
2118 self._RunCmd(node_name, [constants.DAEMON_UTIL, "stop-all"])
2119
2120
2121 try:
2122 return fn(self, *args)
2123 except Exception, err:
2124 _, errmsg = FormatError(err)
2125 logging.exception("Caught exception")
2126 self.feedback_fn(errmsg)
2127 raise
2128 finally:
2129
2130 for node_name in self.nonmaster_nodes + [self.master_node]:
2131 self.feedback_fn("Starting daemons on %s" % node_name)
2132 self._RunCmd(node_name, [constants.DAEMON_UTIL, "start-all"])
2133 finally:
2134
2135 watcher_block.Close()
2136
2137
2139 """Calls a function while all cluster daemons are stopped.
2140
2141 @type feedback_fn: callable
2142 @param feedback_fn: Feedback function
2143 @type fn: callable
2144 @param fn: Function to be called when daemons are stopped
2145
2146 """
2147 feedback_fn("Gathering cluster information")
2148
2149
2150 cl = GetClient()
2151
2152 (cluster_name, master_node) = \
2153 cl.QueryConfigValues(["cluster_name", "master_node"])
2154
2155 online_nodes = GetOnlineNodes([], cl=cl)
2156
2157
2158 del cl
2159
2160 assert master_node in online_nodes
2161
2162 return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2163 online_nodes).Call(fn, *args)
2164
2165
2166 -def GenerateTable(headers, fields, separator, data,
2167 numfields=None, unitfields=None,
2168 units=None):
2169 """Prints a table with headers and different fields.
2170
2171 @type headers: dict
2172 @param headers: dictionary mapping field names to headers for
2173 the table
2174 @type fields: list
2175 @param fields: the field names corresponding to each row in
2176 the data field
2177 @param separator: the separator to be used; if this is None,
2178 the default 'smart' algorithm is used which computes optimal
2179 field width, otherwise just the separator is used between
2180 each field
2181 @type data: list
2182 @param data: a list of lists, each sublist being one row to be output
2183 @type numfields: list
2184 @param numfields: a list with the fields that hold numeric
2185 values and thus should be right-aligned
2186 @type unitfields: list
2187 @param unitfields: a list with the fields that hold numeric
2188 values that should be formatted with the units field
2189 @type units: string or None
2190 @param units: the units we should use for formatting, or None for
2191 automatic choice (human-readable for non-separator usage, otherwise
2192 megabytes); this is a one-letter string
2193
2194 """
2195 if units is None:
2196 if separator:
2197 units = "m"
2198 else:
2199 units = "h"
2200
2201 if numfields is None:
2202 numfields = []
2203 if unitfields is None:
2204 unitfields = []
2205
2206 numfields = utils.FieldSet(*numfields)
2207 unitfields = utils.FieldSet(*unitfields)
2208
2209 format_fields = []
2210 for field in fields:
2211 if headers and field not in headers:
2212
2213
2214
2215 headers[field] = field
2216 if separator is not None:
2217 format_fields.append("%s")
2218 elif numfields.Matches(field):
2219 format_fields.append("%*s")
2220 else:
2221 format_fields.append("%-*s")
2222
2223 if separator is None:
2224 mlens = [0 for name in fields]
2225 format_str = ' '.join(format_fields)
2226 else:
2227 format_str = separator.replace("%", "%%").join(format_fields)
2228
2229 for row in data:
2230 if row is None:
2231 continue
2232 for idx, val in enumerate(row):
2233 if unitfields.Matches(fields[idx]):
2234 try:
2235 val = int(val)
2236 except (TypeError, ValueError):
2237 pass
2238 else:
2239 val = row[idx] = utils.FormatUnit(val, units)
2240 val = row[idx] = str(val)
2241 if separator is None:
2242 mlens[idx] = max(mlens[idx], len(val))
2243
2244 result = []
2245 if headers:
2246 args = []
2247 for idx, name in enumerate(fields):
2248 hdr = headers[name]
2249 if separator is None:
2250 mlens[idx] = max(mlens[idx], len(hdr))
2251 args.append(mlens[idx])
2252 args.append(hdr)
2253 result.append(format_str % tuple(args))
2254
2255 if separator is None:
2256 assert len(mlens) == len(fields)
2257
2258 if fields and not numfields.Matches(fields[-1]):
2259 mlens[-1] = 0
2260
2261 for line in data:
2262 args = []
2263 if line is None:
2264 line = ['-' for _ in fields]
2265 for idx in range(len(fields)):
2266 if separator is None:
2267 args.append(mlens[idx])
2268 args.append(line[idx])
2269 result.append(format_str % tuple(args))
2270
2271 return result
2272
2273
2288
2289
2291 """Parse a time specification.
2292
2293 The following suffixed will be recognized:
2294
2295 - s: seconds
2296 - m: minutes
2297 - h: hours
2298 - d: day
2299 - w: weeks
2300
2301 Without any suffix, the value will be taken to be in seconds.
2302
2303 """
2304 value = str(value)
2305 if not value:
2306 raise errors.OpPrereqError("Empty time specification passed")
2307 suffix_map = {
2308 's': 1,
2309 'm': 60,
2310 'h': 3600,
2311 'd': 86400,
2312 'w': 604800,
2313 }
2314 if value[-1] not in suffix_map:
2315 try:
2316 value = int(value)
2317 except (TypeError, ValueError):
2318 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2319 else:
2320 multiplier = suffix_map[value[-1]]
2321 value = value[:-1]
2322 if not value:
2323 raise errors.OpPrereqError("Invalid time specification (only"
2324 " suffix passed)")
2325 try:
2326 value = int(value) * multiplier
2327 except (TypeError, ValueError):
2328 raise errors.OpPrereqError("Invalid time specification '%s'" % value)
2329 return value
2330
2331
2332 -def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
2333 filter_master=False):
2334 """Returns the names of online nodes.
2335
2336 This function will also log a warning on stderr with the names of
2337 the online nodes.
2338
2339 @param nodes: if not empty, use only this subset of nodes (minus the
2340 offline ones)
2341 @param cl: if not None, luxi client to use
2342 @type nowarn: boolean
2343 @param nowarn: by default, this function will output a note with the
2344 offline nodes that are skipped; if this parameter is True the
2345 note is not displayed
2346 @type secondary_ips: boolean
2347 @param secondary_ips: if True, return the secondary IPs instead of the
2348 names, useful for doing network traffic over the replication interface
2349 (if any)
2350 @type filter_master: boolean
2351 @param filter_master: if True, do not return the master node in the list
2352 (useful in coordination with secondary_ips where we cannot check our
2353 node name against the list)
2354
2355 """
2356 if cl is None:
2357 cl = GetClient()
2358
2359 if secondary_ips:
2360 name_idx = 2
2361 else:
2362 name_idx = 0
2363
2364 if filter_master:
2365 master_node = cl.QueryConfigValues(["master_node"])[0]
2366 filter_fn = lambda x: x != master_node
2367 else:
2368 filter_fn = lambda _: True
2369
2370 result = cl.QueryNodes(names=nodes, fields=["name", "offline", "sip"],
2371 use_locking=False)
2372 offline = [row[0] for row in result if row[1]]
2373 if offline and not nowarn:
2374 ToStderr("Note: skipping offline node(s): %s" % utils.CommaJoin(offline))
2375 return [row[name_idx] for row in result if not row[1] and filter_fn(row[0])]
2376
2377
2379 """Write a message to a stream, bypassing the logging system
2380
2381 @type stream: file object
2382 @param stream: the file to which we should write
2383 @type txt: str
2384 @param txt: the message
2385
2386 """
2387 if args:
2388 args = tuple(args)
2389 stream.write(txt % args)
2390 else:
2391 stream.write(txt)
2392 stream.write('\n')
2393 stream.flush()
2394
2395
2397 """Write a message to stdout only, bypassing the logging system
2398
2399 This is just a wrapper over _ToStream.
2400
2401 @type txt: str
2402 @param txt: the message
2403
2404 """
2405 _ToStream(sys.stdout, txt, *args)
2406
2407
2409 """Write a message to stderr only, bypassing the logging system
2410
2411 This is just a wrapper over _ToStream.
2412
2413 @type txt: str
2414 @param txt: the message
2415
2416 """
2417 _ToStream(sys.stderr, txt, *args)
2418
2419
2421 """Class which manages the submission and execution of multiple jobs.
2422
2423 Note that instances of this class should not be reused between
2424 GetResults() calls.
2425
2426 """
2427 - def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
2428 self.queue = []
2429 if cl is None:
2430 cl = GetClient()
2431 self.cl = cl
2432 self.verbose = verbose
2433 self.jobs = []
2434 self.opts = opts
2435 self.feedback_fn = feedback_fn
2436
2438 """Record a job for later submit.
2439
2440 @type name: string
2441 @param name: a description of the job, will be used in WaitJobSet
2442 """
2443 SetGenericOpcodeOpts(ops, self.opts)
2444 self.queue.append((name, ops))
2445
2447 """Submit all pending jobs.
2448
2449 """
2450 if each:
2451 results = []
2452 for row in self.queue:
2453
2454
2455 results.append([True, self.cl.SubmitJob(row[1])])
2456 else:
2457 results = self.cl.SubmitManyJobs([row[1] for row in self.queue])
2458 for (idx, ((status, data), (name, _))) in enumerate(zip(results,
2459 self.queue)):
2460 self.jobs.append((idx, status, data, name))
2461
2463 """Choose a non-waiting/queued job to poll next.
2464
2465 """
2466 assert self.jobs, "_ChooseJob called with empty job list"
2467
2468 result = self.cl.QueryJobs([i[2] for i in self.jobs], ["status"])
2469 assert result
2470
2471 for job_data, status in zip(self.jobs, result):
2472 if (isinstance(status, list) and status and
2473 status[0] in (constants.JOB_STATUS_QUEUED,
2474 constants.JOB_STATUS_WAITLOCK,
2475 constants.JOB_STATUS_CANCELING)):
2476
2477 continue
2478
2479 self.jobs.remove(job_data)
2480 return job_data
2481
2482
2483 return self.jobs.pop(0)
2484
2486 """Wait for and return the results of all jobs.
2487
2488 @rtype: list
2489 @return: list of tuples (success, job results), in the same order
2490 as the submitted jobs; if a job has failed, instead of the result
2491 there will be the error message
2492
2493 """
2494 if not self.jobs:
2495 self.SubmitPending()
2496 results = []
2497 if self.verbose:
2498 ok_jobs = [row[2] for row in self.jobs if row[1]]
2499 if ok_jobs:
2500 ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
2501
2502
2503 self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
2504 for idx, _, jid, name in failures:
2505 ToStderr("Failed to submit job for %s: %s", name, jid)
2506 results.append((idx, False, jid))
2507
2508 while self.jobs:
2509 (idx, _, jid, name) = self._ChooseJob()
2510 ToStdout("Waiting for job %s for %s...", jid, name)
2511 try:
2512 job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
2513 success = True
2514 except errors.JobLost, err:
2515 _, job_result = FormatError(err)
2516 ToStderr("Job %s for %s has been archived, cannot check its result",
2517 jid, name)
2518 success = False
2519 except (errors.GenericError, luxi.ProtocolError), err:
2520 _, job_result = FormatError(err)
2521 success = False
2522
2523 ToStderr("Job %s for %s has failed: %s", jid, name, job_result)
2524
2525 results.append((idx, success, job_result))
2526
2527
2528 results.sort()
2529 results = [i[1:] for i in results]
2530
2531 return results
2532
2534 """Wait for job results or only print the job IDs.
2535
2536 @type wait: boolean
2537 @param wait: whether to wait or not
2538
2539 """
2540 if wait:
2541 return self.GetResults()
2542 else:
2543 if not self.jobs:
2544 self.SubmitPending()
2545 for _, status, result, name in self.jobs:
2546 if status:
2547 ToStdout("%s: %s", result, name)
2548 else:
2549 ToStderr("Failure for %s: %s", name, result)
2550 return [row[1:3] for row in self.jobs]
2551