Merge branch 'stable-2.8' into stable-2.9
[ganeti-local] / lib / cli.py
1 #
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
5 #
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.
10 #
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.
15 #
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
19 # 02110-1301, USA.
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 import errno
31 import itertools
32 import shlex
33 from cStringIO import StringIO
34
35 from ganeti import utils
36 from ganeti import errors
37 from ganeti import constants
38 from ganeti import opcodes
39 from ganeti import luxi
40 from ganeti import ssconf
41 from ganeti import rpc
42 from ganeti import ssh
43 from ganeti import compat
44 from ganeti import netutils
45 from ganeti import qlang
46 from ganeti import objects
47 from ganeti import pathutils
48
49 from optparse import (OptionParser, TitledHelpFormatter,
50                       Option, OptionValueError)
51
52
53 __all__ = [
54   # Command line options
55   "ABSOLUTE_OPT",
56   "ADD_UIDS_OPT",
57   "ADD_RESERVED_IPS_OPT",
58   "ALLOCATABLE_OPT",
59   "ALLOC_POLICY_OPT",
60   "ALL_OPT",
61   "ALLOW_FAILOVER_OPT",
62   "AUTO_PROMOTE_OPT",
63   "AUTO_REPLACE_OPT",
64   "BACKEND_OPT",
65   "BLK_OS_OPT",
66   "CAPAB_MASTER_OPT",
67   "CAPAB_VM_OPT",
68   "CLEANUP_OPT",
69   "CLUSTER_DOMAIN_SECRET_OPT",
70   "CONFIRM_OPT",
71   "CP_SIZE_OPT",
72   "DEBUG_OPT",
73   "DEBUG_SIMERR_OPT",
74   "DISKIDX_OPT",
75   "DISK_OPT",
76   "DISK_PARAMS_OPT",
77   "DISK_TEMPLATE_OPT",
78   "DRAINED_OPT",
79   "DRY_RUN_OPT",
80   "DRBD_HELPER_OPT",
81   "DST_NODE_OPT",
82   "EARLY_RELEASE_OPT",
83   "ENABLED_HV_OPT",
84   "ENABLED_DISK_TEMPLATES_OPT",
85   "ERROR_CODES_OPT",
86   "FAILURE_ONLY_OPT",
87   "FIELDS_OPT",
88   "FILESTORE_DIR_OPT",
89   "FILESTORE_DRIVER_OPT",
90   "FORCE_FILTER_OPT",
91   "FORCE_OPT",
92   "FORCE_VARIANT_OPT",
93   "GATEWAY_OPT",
94   "GATEWAY6_OPT",
95   "GLOBAL_FILEDIR_OPT",
96   "HID_OS_OPT",
97   "GLOBAL_SHARED_FILEDIR_OPT",
98   "HVLIST_OPT",
99   "HVOPTS_OPT",
100   "HYPERVISOR_OPT",
101   "IALLOCATOR_OPT",
102   "DEFAULT_IALLOCATOR_OPT",
103   "IDENTIFY_DEFAULTS_OPT",
104   "IGNORE_CONSIST_OPT",
105   "IGNORE_ERRORS_OPT",
106   "IGNORE_FAILURES_OPT",
107   "IGNORE_OFFLINE_OPT",
108   "IGNORE_REMOVE_FAILURES_OPT",
109   "IGNORE_SECONDARIES_OPT",
110   "IGNORE_SIZE_OPT",
111   "INCLUDEDEFAULTS_OPT",
112   "INTERVAL_OPT",
113   "MAC_PREFIX_OPT",
114   "MAINTAIN_NODE_HEALTH_OPT",
115   "MASTER_NETDEV_OPT",
116   "MASTER_NETMASK_OPT",
117   "MC_OPT",
118   "MIGRATION_MODE_OPT",
119   "MODIFY_ETCHOSTS_OPT",
120   "NET_OPT",
121   "NETWORK_OPT",
122   "NETWORK6_OPT",
123   "NEW_CLUSTER_CERT_OPT",
124   "NEW_CLUSTER_DOMAIN_SECRET_OPT",
125   "NEW_CONFD_HMAC_KEY_OPT",
126   "NEW_RAPI_CERT_OPT",
127   "NEW_PRIMARY_OPT",
128   "NEW_SECONDARY_OPT",
129   "NEW_SPICE_CERT_OPT",
130   "NIC_PARAMS_OPT",
131   "NOCONFLICTSCHECK_OPT",
132   "NODE_FORCE_JOIN_OPT",
133   "NODE_LIST_OPT",
134   "NODE_PLACEMENT_OPT",
135   "NODEGROUP_OPT",
136   "NODE_PARAMS_OPT",
137   "NODE_POWERED_OPT",
138   "NODRBD_STORAGE_OPT",
139   "NOHDR_OPT",
140   "NOIPCHECK_OPT",
141   "NO_INSTALL_OPT",
142   "NONAMECHECK_OPT",
143   "NOLVM_STORAGE_OPT",
144   "NOMODIFY_ETCHOSTS_OPT",
145   "NOMODIFY_SSH_SETUP_OPT",
146   "NONICS_OPT",
147   "NONLIVE_OPT",
148   "NONPLUS1_OPT",
149   "NORUNTIME_CHGS_OPT",
150   "NOSHUTDOWN_OPT",
151   "NOSTART_OPT",
152   "NOSSH_KEYCHECK_OPT",
153   "NOVOTING_OPT",
154   "NO_REMEMBER_OPT",
155   "NWSYNC_OPT",
156   "OFFLINE_INST_OPT",
157   "ONLINE_INST_OPT",
158   "ON_PRIMARY_OPT",
159   "ON_SECONDARY_OPT",
160   "OFFLINE_OPT",
161   "OSPARAMS_OPT",
162   "OS_OPT",
163   "OS_SIZE_OPT",
164   "OOB_TIMEOUT_OPT",
165   "POWER_DELAY_OPT",
166   "PREALLOC_WIPE_DISKS_OPT",
167   "PRIMARY_IP_VERSION_OPT",
168   "PRIMARY_ONLY_OPT",
169   "PRINT_JOBID_OPT",
170   "PRIORITY_OPT",
171   "RAPI_CERT_OPT",
172   "READD_OPT",
173   "REASON_OPT",
174   "REBOOT_TYPE_OPT",
175   "REMOVE_INSTANCE_OPT",
176   "REMOVE_RESERVED_IPS_OPT",
177   "REMOVE_UIDS_OPT",
178   "RESERVED_LVS_OPT",
179   "RUNTIME_MEM_OPT",
180   "ROMAN_OPT",
181   "SECONDARY_IP_OPT",
182   "SECONDARY_ONLY_OPT",
183   "SELECT_OS_OPT",
184   "SEP_OPT",
185   "SHOWCMD_OPT",
186   "SHOW_MACHINE_OPT",
187   "SHUTDOWN_TIMEOUT_OPT",
188   "SINGLE_NODE_OPT",
189   "SPECS_CPU_COUNT_OPT",
190   "SPECS_DISK_COUNT_OPT",
191   "SPECS_DISK_SIZE_OPT",
192   "SPECS_MEM_SIZE_OPT",
193   "SPECS_NIC_COUNT_OPT",
194   "SPLIT_ISPECS_OPTS",
195   "IPOLICY_STD_SPECS_OPT",
196   "IPOLICY_DISK_TEMPLATES",
197   "IPOLICY_VCPU_RATIO",
198   "SPICE_CACERT_OPT",
199   "SPICE_CERT_OPT",
200   "SRC_DIR_OPT",
201   "SRC_NODE_OPT",
202   "SUBMIT_OPT",
203   "SUBMIT_OPTS",
204   "STARTUP_PAUSED_OPT",
205   "STATIC_OPT",
206   "SYNC_OPT",
207   "TAG_ADD_OPT",
208   "TAG_SRC_OPT",
209   "TIMEOUT_OPT",
210   "TO_GROUP_OPT",
211   "UIDPOOL_OPT",
212   "USEUNITS_OPT",
213   "USE_EXTERNAL_MIP_SCRIPT",
214   "USE_REPL_NET_OPT",
215   "VERBOSE_OPT",
216   "VG_NAME_OPT",
217   "WFSYNC_OPT",
218   "YES_DOIT_OPT",
219   "DISK_STATE_OPT",
220   "HV_STATE_OPT",
221   "IGNORE_IPOLICY_OPT",
222   "INSTANCE_POLICY_OPTS",
223   # Generic functions for CLI programs
224   "ConfirmOperation",
225   "CreateIPolicyFromOpts",
226   "GenericMain",
227   "GenericInstanceCreate",
228   "GenericList",
229   "GenericListFields",
230   "GetClient",
231   "GetOnlineNodes",
232   "JobExecutor",
233   "JobSubmittedException",
234   "ParseTimespec",
235   "RunWhileClusterStopped",
236   "SubmitOpCode",
237   "SubmitOrSend",
238   "UsesRPC",
239   # Formatting functions
240   "ToStderr", "ToStdout",
241   "FormatError",
242   "FormatQueryResult",
243   "FormatParamsDictInfo",
244   "FormatPolicyInfo",
245   "PrintIPolicyCommand",
246   "PrintGenericInfo",
247   "GenerateTable",
248   "AskUser",
249   "FormatTimestamp",
250   "FormatLogMessage",
251   # Tags functions
252   "ListTags",
253   "AddTags",
254   "RemoveTags",
255   # command line options support infrastructure
256   "ARGS_MANY_INSTANCES",
257   "ARGS_MANY_NODES",
258   "ARGS_MANY_GROUPS",
259   "ARGS_MANY_NETWORKS",
260   "ARGS_NONE",
261   "ARGS_ONE_INSTANCE",
262   "ARGS_ONE_NODE",
263   "ARGS_ONE_GROUP",
264   "ARGS_ONE_OS",
265   "ARGS_ONE_NETWORK",
266   "ArgChoice",
267   "ArgCommand",
268   "ArgFile",
269   "ArgGroup",
270   "ArgHost",
271   "ArgInstance",
272   "ArgJobId",
273   "ArgNetwork",
274   "ArgNode",
275   "ArgOs",
276   "ArgExtStorage",
277   "ArgSuggest",
278   "ArgUnknown",
279   "OPT_COMPL_INST_ADD_NODES",
280   "OPT_COMPL_MANY_NODES",
281   "OPT_COMPL_ONE_IALLOCATOR",
282   "OPT_COMPL_ONE_INSTANCE",
283   "OPT_COMPL_ONE_NODE",
284   "OPT_COMPL_ONE_NODEGROUP",
285   "OPT_COMPL_ONE_NETWORK",
286   "OPT_COMPL_ONE_OS",
287   "OPT_COMPL_ONE_EXTSTORAGE",
288   "cli_option",
289   "FixHvParams",
290   "SplitNodeOption",
291   "CalculateOSNames",
292   "ParseFields",
293   "COMMON_CREATE_OPTS",
294   ]
295
296 NO_PREFIX = "no_"
297 UN_PREFIX = "-"
298
299 #: Priorities (sorted)
300 _PRIORITY_NAMES = [
301   ("low", constants.OP_PRIO_LOW),
302   ("normal", constants.OP_PRIO_NORMAL),
303   ("high", constants.OP_PRIO_HIGH),
304   ]
305
306 #: Priority dictionary for easier lookup
307 # TODO: Replace this and _PRIORITY_NAMES with a single sorted dictionary once
308 # we migrate to Python 2.6
309 _PRIONAME_TO_VALUE = dict(_PRIORITY_NAMES)
310
311 # Query result status for clients
312 (QR_NORMAL,
313  QR_UNKNOWN,
314  QR_INCOMPLETE) = range(3)
315
316 #: Maximum batch size for ChooseJob
317 _CHOOSE_BATCH = 25
318
319
320 # constants used to create InstancePolicy dictionary
321 TISPECS_GROUP_TYPES = {
322   constants.ISPECS_MIN: constants.VTYPE_INT,
323   constants.ISPECS_MAX: constants.VTYPE_INT,
324   }
325
326 TISPECS_CLUSTER_TYPES = {
327   constants.ISPECS_MIN: constants.VTYPE_INT,
328   constants.ISPECS_MAX: constants.VTYPE_INT,
329   constants.ISPECS_STD: constants.VTYPE_INT,
330   }
331
332 #: User-friendly names for query2 field types
333 _QFT_NAMES = {
334   constants.QFT_UNKNOWN: "Unknown",
335   constants.QFT_TEXT: "Text",
336   constants.QFT_BOOL: "Boolean",
337   constants.QFT_NUMBER: "Number",
338   constants.QFT_UNIT: "Storage size",
339   constants.QFT_TIMESTAMP: "Timestamp",
340   constants.QFT_OTHER: "Custom",
341   }
342
343
344 class _Argument:
345   def __init__(self, min=0, max=None): # pylint: disable=W0622
346     self.min = min
347     self.max = max
348
349   def __repr__(self):
350     return ("<%s min=%s max=%s>" %
351             (self.__class__.__name__, self.min, self.max))
352
353
354 class ArgSuggest(_Argument):
355   """Suggesting argument.
356
357   Value can be any of the ones passed to the constructor.
358
359   """
360   # pylint: disable=W0622
361   def __init__(self, min=0, max=None, choices=None):
362     _Argument.__init__(self, min=min, max=max)
363     self.choices = choices
364
365   def __repr__(self):
366     return ("<%s min=%s max=%s choices=%r>" %
367             (self.__class__.__name__, self.min, self.max, self.choices))
368
369
370 class ArgChoice(ArgSuggest):
371   """Choice argument.
372
373   Value can be any of the ones passed to the constructor. Like L{ArgSuggest},
374   but value must be one of the choices.
375
376   """
377
378
379 class ArgUnknown(_Argument):
380   """Unknown argument to program (e.g. determined at runtime).
381
382   """
383
384
385 class ArgInstance(_Argument):
386   """Instances argument.
387
388   """
389
390
391 class ArgNode(_Argument):
392   """Node argument.
393
394   """
395
396
397 class ArgNetwork(_Argument):
398   """Network argument.
399
400   """
401
402
403 class ArgGroup(_Argument):
404   """Node group argument.
405
406   """
407
408
409 class ArgJobId(_Argument):
410   """Job ID argument.
411
412   """
413
414
415 class ArgFile(_Argument):
416   """File path argument.
417
418   """
419
420
421 class ArgCommand(_Argument):
422   """Command argument.
423
424   """
425
426
427 class ArgHost(_Argument):
428   """Host argument.
429
430   """
431
432
433 class ArgOs(_Argument):
434   """OS argument.
435
436   """
437
438
439 class ArgExtStorage(_Argument):
440   """ExtStorage argument.
441
442   """
443
444
445 ARGS_NONE = []
446 ARGS_MANY_INSTANCES = [ArgInstance()]
447 ARGS_MANY_NETWORKS = [ArgNetwork()]
448 ARGS_MANY_NODES = [ArgNode()]
449 ARGS_MANY_GROUPS = [ArgGroup()]
450 ARGS_ONE_INSTANCE = [ArgInstance(min=1, max=1)]
451 ARGS_ONE_NETWORK = [ArgNetwork(min=1, max=1)]
452 ARGS_ONE_NODE = [ArgNode(min=1, max=1)]
453 # TODO
454 ARGS_ONE_GROUP = [ArgGroup(min=1, max=1)]
455 ARGS_ONE_OS = [ArgOs(min=1, max=1)]
456
457
458 def _ExtractTagsObject(opts, args):
459   """Extract the tag type object.
460
461   Note that this function will modify its args parameter.
462
463   """
464   if not hasattr(opts, "tag_type"):
465     raise errors.ProgrammerError("tag_type not passed to _ExtractTagsObject")
466   kind = opts.tag_type
467   if kind == constants.TAG_CLUSTER:
468     retval = kind, None
469   elif kind in (constants.TAG_NODEGROUP,
470                 constants.TAG_NODE,
471                 constants.TAG_NETWORK,
472                 constants.TAG_INSTANCE):
473     if not args:
474       raise errors.OpPrereqError("no arguments passed to the command",
475                                  errors.ECODE_INVAL)
476     name = args.pop(0)
477     retval = kind, name
478   else:
479     raise errors.ProgrammerError("Unhandled tag type '%s'" % kind)
480   return retval
481
482
483 def _ExtendTags(opts, args):
484   """Extend the args if a source file has been given.
485
486   This function will extend the tags with the contents of the file
487   passed in the 'tags_source' attribute of the opts parameter. A file
488   named '-' will be replaced by stdin.
489
490   """
491   fname = opts.tags_source
492   if fname is None:
493     return
494   if fname == "-":
495     new_fh = sys.stdin
496   else:
497     new_fh = open(fname, "r")
498   new_data = []
499   try:
500     # we don't use the nice 'new_data = [line.strip() for line in fh]'
501     # because of python bug 1633941
502     while True:
503       line = new_fh.readline()
504       if not line:
505         break
506       new_data.append(line.strip())
507   finally:
508     new_fh.close()
509   args.extend(new_data)
510
511
512 def ListTags(opts, args):
513   """List the tags on a given object.
514
515   This is a generic implementation that knows how to deal with all
516   three cases of tag objects (cluster, node, instance). The opts
517   argument is expected to contain a tag_type field denoting what
518   object type we work on.
519
520   """
521   kind, name = _ExtractTagsObject(opts, args)
522   cl = GetClient(query=True)
523   result = cl.QueryTags(kind, name)
524   result = list(result)
525   result.sort()
526   for tag in result:
527     ToStdout(tag)
528
529
530 def AddTags(opts, args):
531   """Add tags on a given object.
532
533   This is a generic implementation that knows how to deal with all
534   three cases of tag objects (cluster, node, instance). The opts
535   argument is expected to contain a tag_type field denoting what
536   object type we work on.
537
538   """
539   kind, name = _ExtractTagsObject(opts, args)
540   _ExtendTags(opts, args)
541   if not args:
542     raise errors.OpPrereqError("No tags to be added", errors.ECODE_INVAL)
543   op = opcodes.OpTagsSet(kind=kind, name=name, tags=args)
544   SubmitOrSend(op, opts)
545
546
547 def RemoveTags(opts, args):
548   """Remove tags from a given object.
549
550   This is a generic implementation that knows how to deal with all
551   three cases of tag objects (cluster, node, instance). The opts
552   argument is expected to contain a tag_type field denoting what
553   object type we work on.
554
555   """
556   kind, name = _ExtractTagsObject(opts, args)
557   _ExtendTags(opts, args)
558   if not args:
559     raise errors.OpPrereqError("No tags to be removed", errors.ECODE_INVAL)
560   op = opcodes.OpTagsDel(kind=kind, name=name, tags=args)
561   SubmitOrSend(op, opts)
562
563
564 def check_unit(option, opt, value): # pylint: disable=W0613
565   """OptParsers custom converter for units.
566
567   """
568   try:
569     return utils.ParseUnit(value)
570   except errors.UnitParseError, err:
571     raise OptionValueError("option %s: %s" % (opt, err))
572
573
574 def _SplitKeyVal(opt, data, parse_prefixes):
575   """Convert a KeyVal string into a dict.
576
577   This function will convert a key=val[,...] string into a dict. Empty
578   values will be converted specially: keys which have the prefix 'no_'
579   will have the value=False and the prefix stripped, keys with the prefix
580   "-" will have value=None and the prefix stripped, and the others will
581   have value=True.
582
583   @type opt: string
584   @param opt: a string holding the option name for which we process the
585       data, used in building error messages
586   @type data: string
587   @param data: a string of the format key=val,key=val,...
588   @type parse_prefixes: bool
589   @param parse_prefixes: whether to handle prefixes specially
590   @rtype: dict
591   @return: {key=val, key=val}
592   @raises errors.ParameterError: if there are duplicate keys
593
594   """
595   kv_dict = {}
596   if data:
597     for elem in utils.UnescapeAndSplit(data, sep=","):
598       if "=" in elem:
599         key, val = elem.split("=", 1)
600       elif parse_prefixes:
601         if elem.startswith(NO_PREFIX):
602           key, val = elem[len(NO_PREFIX):], False
603         elif elem.startswith(UN_PREFIX):
604           key, val = elem[len(UN_PREFIX):], None
605         else:
606           key, val = elem, True
607       else:
608         raise errors.ParameterError("Missing value for key '%s' in option %s" %
609                                     (elem, opt))
610       if key in kv_dict:
611         raise errors.ParameterError("Duplicate key '%s' in option %s" %
612                                     (key, opt))
613       kv_dict[key] = val
614   return kv_dict
615
616
617 def _SplitIdentKeyVal(opt, value, parse_prefixes):
618   """Helper function to parse "ident:key=val,key=val" options.
619
620   @type opt: string
621   @param opt: option name, used in error messages
622   @type value: string
623   @param value: expected to be in the format "ident:key=val,key=val,..."
624   @type parse_prefixes: bool
625   @param parse_prefixes: whether to handle prefixes specially (see
626       L{_SplitKeyVal})
627   @rtype: tuple
628   @return: (ident, {key=val, key=val})
629   @raises errors.ParameterError: in case of duplicates or other parsing errors
630
631   """
632   if ":" not in value:
633     ident, rest = value, ""
634   else:
635     ident, rest = value.split(":", 1)
636
637   if parse_prefixes and ident.startswith(NO_PREFIX):
638     if rest:
639       msg = "Cannot pass options when removing parameter groups: %s" % value
640       raise errors.ParameterError(msg)
641     retval = (ident[len(NO_PREFIX):], False)
642   elif (parse_prefixes and ident.startswith(UN_PREFIX) and
643         (len(ident) <= len(UN_PREFIX) or not ident[len(UN_PREFIX)].isdigit())):
644     if rest:
645       msg = "Cannot pass options when removing parameter groups: %s" % value
646       raise errors.ParameterError(msg)
647     retval = (ident[len(UN_PREFIX):], None)
648   else:
649     kv_dict = _SplitKeyVal(opt, rest, parse_prefixes)
650     retval = (ident, kv_dict)
651   return retval
652
653
654 def check_ident_key_val(option, opt, value):  # pylint: disable=W0613
655   """Custom parser for ident:key=val,key=val options.
656
657   This will store the parsed values as a tuple (ident, {key: val}). As such,
658   multiple uses of this option via action=append is possible.
659
660   """
661   return _SplitIdentKeyVal(opt, value, True)
662
663
664 def check_key_val(option, opt, value):  # pylint: disable=W0613
665   """Custom parser class for key=val,key=val options.
666
667   This will store the parsed values as a dict {key: val}.
668
669   """
670   return _SplitKeyVal(opt, value, True)
671
672
673 def _SplitListKeyVal(opt, value):
674   retval = {}
675   for elem in value.split("/"):
676     if not elem:
677       raise errors.ParameterError("Empty section in option '%s'" % opt)
678     (ident, valdict) = _SplitIdentKeyVal(opt, elem, False)
679     if ident in retval:
680       msg = ("Duplicated parameter '%s' in parsing %s: %s" %
681              (ident, opt, elem))
682       raise errors.ParameterError(msg)
683     retval[ident] = valdict
684   return retval
685
686
687 def check_multilist_ident_key_val(_, opt, value):
688   """Custom parser for "ident:key=val,key=val/ident:key=val//ident:.." options.
689
690   @rtype: list of dictionary
691   @return: [{ident: {key: val, key: val}, ident: {key: val}}, {ident:..}]
692
693   """
694   retval = []
695   for line in value.split("//"):
696     retval.append(_SplitListKeyVal(opt, line))
697   return retval
698
699
700 def check_bool(option, opt, value): # pylint: disable=W0613
701   """Custom parser for yes/no options.
702
703   This will store the parsed value as either True or False.
704
705   """
706   value = value.lower()
707   if value == constants.VALUE_FALSE or value == "no":
708     return False
709   elif value == constants.VALUE_TRUE or value == "yes":
710     return True
711   else:
712     raise errors.ParameterError("Invalid boolean value '%s'" % value)
713
714
715 def check_list(option, opt, value): # pylint: disable=W0613
716   """Custom parser for comma-separated lists.
717
718   """
719   # we have to make this explicit check since "".split(",") is [""],
720   # not an empty list :(
721   if not value:
722     return []
723   else:
724     return utils.UnescapeAndSplit(value)
725
726
727 def check_maybefloat(option, opt, value): # pylint: disable=W0613
728   """Custom parser for float numbers which might be also defaults.
729
730   """
731   value = value.lower()
732
733   if value == constants.VALUE_DEFAULT:
734     return value
735   else:
736     return float(value)
737
738
739 # completion_suggestion is normally a list. Using numeric values not evaluating
740 # to False for dynamic completion.
741 (OPT_COMPL_MANY_NODES,
742  OPT_COMPL_ONE_NODE,
743  OPT_COMPL_ONE_INSTANCE,
744  OPT_COMPL_ONE_OS,
745  OPT_COMPL_ONE_EXTSTORAGE,
746  OPT_COMPL_ONE_IALLOCATOR,
747  OPT_COMPL_ONE_NETWORK,
748  OPT_COMPL_INST_ADD_NODES,
749  OPT_COMPL_ONE_NODEGROUP) = range(100, 109)
750
751 OPT_COMPL_ALL = compat.UniqueFrozenset([
752   OPT_COMPL_MANY_NODES,
753   OPT_COMPL_ONE_NODE,
754   OPT_COMPL_ONE_INSTANCE,
755   OPT_COMPL_ONE_OS,
756   OPT_COMPL_ONE_EXTSTORAGE,
757   OPT_COMPL_ONE_IALLOCATOR,
758   OPT_COMPL_ONE_NETWORK,
759   OPT_COMPL_INST_ADD_NODES,
760   OPT_COMPL_ONE_NODEGROUP,
761   ])
762
763
764 class CliOption(Option):
765   """Custom option class for optparse.
766
767   """
768   ATTRS = Option.ATTRS + [
769     "completion_suggest",
770     ]
771   TYPES = Option.TYPES + (
772     "multilistidentkeyval",
773     "identkeyval",
774     "keyval",
775     "unit",
776     "bool",
777     "list",
778     "maybefloat",
779     )
780   TYPE_CHECKER = Option.TYPE_CHECKER.copy()
781   TYPE_CHECKER["multilistidentkeyval"] = check_multilist_ident_key_val
782   TYPE_CHECKER["identkeyval"] = check_ident_key_val
783   TYPE_CHECKER["keyval"] = check_key_val
784   TYPE_CHECKER["unit"] = check_unit
785   TYPE_CHECKER["bool"] = check_bool
786   TYPE_CHECKER["list"] = check_list
787   TYPE_CHECKER["maybefloat"] = check_maybefloat
788
789
790 # optparse.py sets make_option, so we do it for our own option class, too
791 cli_option = CliOption
792
793
794 _YORNO = "yes|no"
795
796 DEBUG_OPT = cli_option("-d", "--debug", default=0, action="count",
797                        help="Increase debugging level")
798
799 NOHDR_OPT = cli_option("--no-headers", default=False,
800                        action="store_true", dest="no_headers",
801                        help="Don't display column headers")
802
803 SEP_OPT = cli_option("--separator", default=None,
804                      action="store", dest="separator",
805                      help=("Separator between output fields"
806                            " (defaults to one space)"))
807
808 USEUNITS_OPT = cli_option("--units", default=None,
809                           dest="units", choices=("h", "m", "g", "t"),
810                           help="Specify units for output (one of h/m/g/t)")
811
812 FIELDS_OPT = cli_option("-o", "--output", dest="output", action="store",
813                         type="string", metavar="FIELDS",
814                         help="Comma separated list of output fields")
815
816 FORCE_OPT = cli_option("-f", "--force", dest="force", action="store_true",
817                        default=False, help="Force the operation")
818
819 CONFIRM_OPT = cli_option("--yes", dest="confirm", action="store_true",
820                          default=False, help="Do not require confirmation")
821
822 IGNORE_OFFLINE_OPT = cli_option("--ignore-offline", dest="ignore_offline",
823                                   action="store_true", default=False,
824                                   help=("Ignore offline nodes and do as much"
825                                         " as possible"))
826
827 TAG_ADD_OPT = cli_option("--tags", dest="tags",
828                          default=None, help="Comma-separated list of instance"
829                                             " tags")
830
831 TAG_SRC_OPT = cli_option("--from", dest="tags_source",
832                          default=None, help="File with tag names")
833
834 SUBMIT_OPT = cli_option("--submit", dest="submit_only",
835                         default=False, action="store_true",
836                         help=("Submit the job and return the job ID, but"
837                               " don't wait for the job to finish"))
838
839 PRINT_JOBID_OPT = cli_option("--print-jobid", dest="print_jobid",
840                              default=False, action="store_true",
841                              help=("Additionally print the job as first line"
842                                    " on stdout (for scripting)."))
843
844 SYNC_OPT = cli_option("--sync", dest="do_locking",
845                       default=False, action="store_true",
846                       help=("Grab locks while doing the queries"
847                             " in order to ensure more consistent results"))
848
849 DRY_RUN_OPT = cli_option("--dry-run", default=False,
850                          action="store_true",
851                          help=("Do not execute the operation, just run the"
852                                " check steps and verify if it could be"
853                                " executed"))
854
855 VERBOSE_OPT = cli_option("-v", "--verbose", default=False,
856                          action="store_true",
857                          help="Increase the verbosity of the operation")
858
859 DEBUG_SIMERR_OPT = cli_option("--debug-simulate-errors", default=False,
860                               action="store_true", dest="simulate_errors",
861                               help="Debugging option that makes the operation"
862                               " treat most runtime checks as failed")
863
864 NWSYNC_OPT = cli_option("--no-wait-for-sync", dest="wait_for_sync",
865                         default=True, action="store_false",
866                         help="Don't wait for sync (DANGEROUS!)")
867
868 WFSYNC_OPT = cli_option("--wait-for-sync", dest="wait_for_sync",
869                         default=False, action="store_true",
870                         help="Wait for disks to sync")
871
872 ONLINE_INST_OPT = cli_option("--online", dest="online_inst",
873                              action="store_true", default=False,
874                              help="Enable offline instance")
875
876 OFFLINE_INST_OPT = cli_option("--offline", dest="offline_inst",
877                               action="store_true", default=False,
878                               help="Disable down instance")
879
880 DISK_TEMPLATE_OPT = cli_option("-t", "--disk-template", dest="disk_template",
881                                help=("Custom disk setup (%s)" %
882                                      utils.CommaJoin(constants.DISK_TEMPLATES)),
883                                default=None, metavar="TEMPL",
884                                choices=list(constants.DISK_TEMPLATES))
885
886 NONICS_OPT = cli_option("--no-nics", default=False, action="store_true",
887                         help="Do not create any network cards for"
888                         " the instance")
889
890 FILESTORE_DIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
891                                help="Relative path under default cluster-wide"
892                                " file storage dir to store file-based disks",
893                                default=None, metavar="<DIR>")
894
895 FILESTORE_DRIVER_OPT = cli_option("--file-driver", dest="file_driver",
896                                   help="Driver to use for image files",
897                                   default=None, metavar="<DRIVER>",
898                                   choices=list(constants.FILE_DRIVER))
899
900 IALLOCATOR_OPT = cli_option("-I", "--iallocator", metavar="<NAME>",
901                             help="Select nodes for the instance automatically"
902                             " using the <NAME> iallocator plugin",
903                             default=None, type="string",
904                             completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
905
906 DEFAULT_IALLOCATOR_OPT = cli_option("-I", "--default-iallocator",
907                                     metavar="<NAME>",
908                                     help="Set the default instance"
909                                     " allocator plugin",
910                                     default=None, type="string",
911                                     completion_suggest=OPT_COMPL_ONE_IALLOCATOR)
912
913 OS_OPT = cli_option("-o", "--os-type", dest="os", help="What OS to run",
914                     metavar="<os>",
915                     completion_suggest=OPT_COMPL_ONE_OS)
916
917 OSPARAMS_OPT = cli_option("-O", "--os-parameters", dest="osparams",
918                           type="keyval", default={},
919                           help="OS parameters")
920
921 FORCE_VARIANT_OPT = cli_option("--force-variant", dest="force_variant",
922                                action="store_true", default=False,
923                                help="Force an unknown variant")
924
925 NO_INSTALL_OPT = cli_option("--no-install", dest="no_install",
926                             action="store_true", default=False,
927                             help="Do not install the OS (will"
928                             " enable no-start)")
929
930 NORUNTIME_CHGS_OPT = cli_option("--no-runtime-changes",
931                                 dest="allow_runtime_chgs",
932                                 default=True, action="store_false",
933                                 help="Don't allow runtime changes")
934
935 BACKEND_OPT = cli_option("-B", "--backend-parameters", dest="beparams",
936                          type="keyval", default={},
937                          help="Backend parameters")
938
939 HVOPTS_OPT = cli_option("-H", "--hypervisor-parameters", type="keyval",
940                         default={}, dest="hvparams",
941                         help="Hypervisor parameters")
942
943 DISK_PARAMS_OPT = cli_option("-D", "--disk-parameters", dest="diskparams",
944                              help="Disk template parameters, in the format"
945                              " template:option=value,option=value,...",
946                              type="identkeyval", action="append", default=[])
947
948 SPECS_MEM_SIZE_OPT = cli_option("--specs-mem-size", dest="ispecs_mem_size",
949                                  type="keyval", default={},
950                                  help="Memory size specs: list of key=value,"
951                                 " where key is one of min, max, std"
952                                  " (in MB or using a unit)")
953
954 SPECS_CPU_COUNT_OPT = cli_option("--specs-cpu-count", dest="ispecs_cpu_count",
955                                  type="keyval", default={},
956                                  help="CPU count specs: list of key=value,"
957                                  " where key is one of min, max, std")
958
959 SPECS_DISK_COUNT_OPT = cli_option("--specs-disk-count",
960                                   dest="ispecs_disk_count",
961                                   type="keyval", default={},
962                                   help="Disk count specs: list of key=value,"
963                                   " where key is one of min, max, std")
964
965 SPECS_DISK_SIZE_OPT = cli_option("--specs-disk-size", dest="ispecs_disk_size",
966                                  type="keyval", default={},
967                                  help="Disk size specs: list of key=value,"
968                                  " where key is one of min, max, std"
969                                  " (in MB or using a unit)")
970
971 SPECS_NIC_COUNT_OPT = cli_option("--specs-nic-count", dest="ispecs_nic_count",
972                                  type="keyval", default={},
973                                  help="NIC count specs: list of key=value,"
974                                  " where key is one of min, max, std")
975
976 IPOLICY_BOUNDS_SPECS_STR = "--ipolicy-bounds-specs"
977 IPOLICY_BOUNDS_SPECS_OPT = cli_option(IPOLICY_BOUNDS_SPECS_STR,
978                                       dest="ipolicy_bounds_specs",
979                                       type="multilistidentkeyval", default=None,
980                                       help="Complete instance specs limits")
981
982 IPOLICY_STD_SPECS_STR = "--ipolicy-std-specs"
983 IPOLICY_STD_SPECS_OPT = cli_option(IPOLICY_STD_SPECS_STR,
984                                    dest="ipolicy_std_specs",
985                                    type="keyval", default=None,
986                                    help="Complte standard instance specs")
987
988 IPOLICY_DISK_TEMPLATES = cli_option("--ipolicy-disk-templates",
989                                     dest="ipolicy_disk_templates",
990                                     type="list", default=None,
991                                     help="Comma-separated list of"
992                                     " enabled disk templates")
993
994 IPOLICY_VCPU_RATIO = cli_option("--ipolicy-vcpu-ratio",
995                                  dest="ipolicy_vcpu_ratio",
996                                  type="maybefloat", default=None,
997                                  help="The maximum allowed vcpu-to-cpu ratio")
998
999 IPOLICY_SPINDLE_RATIO = cli_option("--ipolicy-spindle-ratio",
1000                                    dest="ipolicy_spindle_ratio",
1001                                    type="maybefloat", default=None,
1002                                    help=("The maximum allowed instances to"
1003                                          " spindle ratio"))
1004
1005 HYPERVISOR_OPT = cli_option("-H", "--hypervisor-parameters", dest="hypervisor",
1006                             help="Hypervisor and hypervisor options, in the"
1007                             " format hypervisor:option=value,option=value,...",
1008                             default=None, type="identkeyval")
1009
1010 HVLIST_OPT = cli_option("-H", "--hypervisor-parameters", dest="hvparams",
1011                         help="Hypervisor and hypervisor options, in the"
1012                         " format hypervisor:option=value,option=value,...",
1013                         default=[], action="append", type="identkeyval")
1014
1015 NOIPCHECK_OPT = cli_option("--no-ip-check", dest="ip_check", default=True,
1016                            action="store_false",
1017                            help="Don't check that the instance's IP"
1018                            " is alive")
1019
1020 NONAMECHECK_OPT = cli_option("--no-name-check", dest="name_check",
1021                              default=True, action="store_false",
1022                              help="Don't check that the instance's name"
1023                              " is resolvable")
1024
1025 NET_OPT = cli_option("--net",
1026                      help="NIC parameters", default=[],
1027                      dest="nics", action="append", type="identkeyval")
1028
1029 DISK_OPT = cli_option("--disk", help="Disk parameters", default=[],
1030                       dest="disks", action="append", type="identkeyval")
1031
1032 DISKIDX_OPT = cli_option("--disks", dest="disks", default=None,
1033                          help="Comma-separated list of disks"
1034                          " indices to act on (e.g. 0,2) (optional,"
1035                          " defaults to all disks)")
1036
1037 OS_SIZE_OPT = cli_option("-s", "--os-size", dest="sd_size",
1038                          help="Enforces a single-disk configuration using the"
1039                          " given disk size, in MiB unless a suffix is used",
1040                          default=None, type="unit", metavar="<size>")
1041
1042 IGNORE_CONSIST_OPT = cli_option("--ignore-consistency",
1043                                 dest="ignore_consistency",
1044                                 action="store_true", default=False,
1045                                 help="Ignore the consistency of the disks on"
1046                                 " the secondary")
1047
1048 ALLOW_FAILOVER_OPT = cli_option("--allow-failover",
1049                                 dest="allow_failover",
1050                                 action="store_true", default=False,
1051                                 help="If migration is not possible fallback to"
1052                                      " failover")
1053
1054 NONLIVE_OPT = cli_option("--non-live", dest="live",
1055                          default=True, action="store_false",
1056                          help="Do a non-live migration (this usually means"
1057                          " freeze the instance, save the state, transfer and"
1058                          " only then resume running on the secondary node)")
1059
1060 MIGRATION_MODE_OPT = cli_option("--migration-mode", dest="migration_mode",
1061                                 default=None,
1062                                 choices=list(constants.HT_MIGRATION_MODES),
1063                                 help="Override default migration mode (choose"
1064                                 " either live or non-live")
1065
1066 NODE_PLACEMENT_OPT = cli_option("-n", "--node", dest="node",
1067                                 help="Target node and optional secondary node",
1068                                 metavar="<pnode>[:<snode>]",
1069                                 completion_suggest=OPT_COMPL_INST_ADD_NODES)
1070
1071 NODE_LIST_OPT = cli_option("-n", "--node", dest="nodes", default=[],
1072                            action="append", metavar="<node>",
1073                            help="Use only this node (can be used multiple"
1074                            " times, if not given defaults to all nodes)",
1075                            completion_suggest=OPT_COMPL_ONE_NODE)
1076
1077 NODEGROUP_OPT_NAME = "--node-group"
1078 NODEGROUP_OPT = cli_option("-g", NODEGROUP_OPT_NAME,
1079                            dest="nodegroup",
1080                            help="Node group (name or uuid)",
1081                            metavar="<nodegroup>",
1082                            default=None, type="string",
1083                            completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1084
1085 SINGLE_NODE_OPT = cli_option("-n", "--node", dest="node", help="Target node",
1086                              metavar="<node>",
1087                              completion_suggest=OPT_COMPL_ONE_NODE)
1088
1089 NOSTART_OPT = cli_option("--no-start", dest="start", default=True,
1090                          action="store_false",
1091                          help="Don't start the instance after creation")
1092
1093 SHOWCMD_OPT = cli_option("--show-cmd", dest="show_command",
1094                          action="store_true", default=False,
1095                          help="Show command instead of executing it")
1096
1097 CLEANUP_OPT = cli_option("--cleanup", dest="cleanup",
1098                          default=False, action="store_true",
1099                          help="Instead of performing the migration/failover,"
1100                          " try to recover from a failed cleanup. This is safe"
1101                          " to run even if the instance is healthy, but it"
1102                          " will create extra replication traffic and "
1103                          " disrupt briefly the replication (like during the"
1104                          " migration/failover")
1105
1106 STATIC_OPT = cli_option("-s", "--static", dest="static",
1107                         action="store_true", default=False,
1108                         help="Only show configuration data, not runtime data")
1109
1110 ALL_OPT = cli_option("--all", dest="show_all",
1111                      default=False, action="store_true",
1112                      help="Show info on all instances on the cluster."
1113                      " This can take a long time to run, use wisely")
1114
1115 SELECT_OS_OPT = cli_option("--select-os", dest="select_os",
1116                            action="store_true", default=False,
1117                            help="Interactive OS reinstall, lists available"
1118                            " OS templates for selection")
1119
1120 IGNORE_FAILURES_OPT = cli_option("--ignore-failures", dest="ignore_failures",
1121                                  action="store_true", default=False,
1122                                  help="Remove the instance from the cluster"
1123                                  " configuration even if there are failures"
1124                                  " during the removal process")
1125
1126 IGNORE_REMOVE_FAILURES_OPT = cli_option("--ignore-remove-failures",
1127                                         dest="ignore_remove_failures",
1128                                         action="store_true", default=False,
1129                                         help="Remove the instance from the"
1130                                         " cluster configuration even if there"
1131                                         " are failures during the removal"
1132                                         " process")
1133
1134 REMOVE_INSTANCE_OPT = cli_option("--remove-instance", dest="remove_instance",
1135                                  action="store_true", default=False,
1136                                  help="Remove the instance from the cluster")
1137
1138 DST_NODE_OPT = cli_option("-n", "--target-node", dest="dst_node",
1139                                help="Specifies the new node for the instance",
1140                                metavar="NODE", default=None,
1141                                completion_suggest=OPT_COMPL_ONE_NODE)
1142
1143 NEW_SECONDARY_OPT = cli_option("-n", "--new-secondary", dest="dst_node",
1144                                help="Specifies the new secondary node",
1145                                metavar="NODE", default=None,
1146                                completion_suggest=OPT_COMPL_ONE_NODE)
1147
1148 NEW_PRIMARY_OPT = cli_option("--new-primary", dest="new_primary_node",
1149                              help="Specifies the new primary node",
1150                              metavar="<node>", default=None,
1151                              completion_suggest=OPT_COMPL_ONE_NODE)
1152
1153 ON_PRIMARY_OPT = cli_option("-p", "--on-primary", dest="on_primary",
1154                             default=False, action="store_true",
1155                             help="Replace the disk(s) on the primary"
1156                                  " node (applies only to internally mirrored"
1157                                  " disk templates, e.g. %s)" %
1158                                  utils.CommaJoin(constants.DTS_INT_MIRROR))
1159
1160 ON_SECONDARY_OPT = cli_option("-s", "--on-secondary", dest="on_secondary",
1161                               default=False, action="store_true",
1162                               help="Replace the disk(s) on the secondary"
1163                                    " node (applies only to internally mirrored"
1164                                    " disk templates, e.g. %s)" %
1165                                    utils.CommaJoin(constants.DTS_INT_MIRROR))
1166
1167 AUTO_PROMOTE_OPT = cli_option("--auto-promote", dest="auto_promote",
1168                               default=False, action="store_true",
1169                               help="Lock all nodes and auto-promote as needed"
1170                               " to MC status")
1171
1172 AUTO_REPLACE_OPT = cli_option("-a", "--auto", dest="auto",
1173                               default=False, action="store_true",
1174                               help="Automatically replace faulty disks"
1175                                    " (applies only to internally mirrored"
1176                                    " disk templates, e.g. %s)" %
1177                                    utils.CommaJoin(constants.DTS_INT_MIRROR))
1178
1179 IGNORE_SIZE_OPT = cli_option("--ignore-size", dest="ignore_size",
1180                              default=False, action="store_true",
1181                              help="Ignore current recorded size"
1182                              " (useful for forcing activation when"
1183                              " the recorded size is wrong)")
1184
1185 SRC_NODE_OPT = cli_option("--src-node", dest="src_node", help="Source node",
1186                           metavar="<node>",
1187                           completion_suggest=OPT_COMPL_ONE_NODE)
1188
1189 SRC_DIR_OPT = cli_option("--src-dir", dest="src_dir", help="Source directory",
1190                          metavar="<dir>")
1191
1192 SECONDARY_IP_OPT = cli_option("-s", "--secondary-ip", dest="secondary_ip",
1193                               help="Specify the secondary ip for the node",
1194                               metavar="ADDRESS", default=None)
1195
1196 READD_OPT = cli_option("--readd", dest="readd",
1197                        default=False, action="store_true",
1198                        help="Readd old node after replacing it")
1199
1200 NOSSH_KEYCHECK_OPT = cli_option("--no-ssh-key-check", dest="ssh_key_check",
1201                                 default=True, action="store_false",
1202                                 help="Disable SSH key fingerprint checking")
1203
1204 NODE_FORCE_JOIN_OPT = cli_option("--force-join", dest="force_join",
1205                                  default=False, action="store_true",
1206                                  help="Force the joining of a node")
1207
1208 MC_OPT = cli_option("-C", "--master-candidate", dest="master_candidate",
1209                     type="bool", default=None, metavar=_YORNO,
1210                     help="Set the master_candidate flag on the node")
1211
1212 OFFLINE_OPT = cli_option("-O", "--offline", dest="offline", metavar=_YORNO,
1213                          type="bool", default=None,
1214                          help=("Set the offline flag on the node"
1215                                " (cluster does not communicate with offline"
1216                                " nodes)"))
1217
1218 DRAINED_OPT = cli_option("-D", "--drained", dest="drained", metavar=_YORNO,
1219                          type="bool", default=None,
1220                          help=("Set the drained flag on the node"
1221                                " (excluded from allocation operations)"))
1222
1223 CAPAB_MASTER_OPT = cli_option("--master-capable", dest="master_capable",
1224                               type="bool", default=None, metavar=_YORNO,
1225                               help="Set the master_capable flag on the node")
1226
1227 CAPAB_VM_OPT = cli_option("--vm-capable", dest="vm_capable",
1228                           type="bool", default=None, metavar=_YORNO,
1229                           help="Set the vm_capable flag on the node")
1230
1231 ALLOCATABLE_OPT = cli_option("--allocatable", dest="allocatable",
1232                              type="bool", default=None, metavar=_YORNO,
1233                              help="Set the allocatable flag on a volume")
1234
1235 NOLVM_STORAGE_OPT = cli_option("--no-lvm-storage", dest="lvm_storage",
1236                                help="Disable support for lvm based instances"
1237                                " (cluster-wide)",
1238                                action="store_false", default=True)
1239
1240 ENABLED_HV_OPT = cli_option("--enabled-hypervisors",
1241                             dest="enabled_hypervisors",
1242                             help="Comma-separated list of hypervisors",
1243                             type="string", default=None)
1244
1245 ENABLED_DISK_TEMPLATES_OPT = cli_option("--enabled-disk-templates",
1246                                         dest="enabled_disk_templates",
1247                                         help="Comma-separated list of "
1248                                              "disk templates",
1249                                         type="string", default=None)
1250
1251 NIC_PARAMS_OPT = cli_option("-N", "--nic-parameters", dest="nicparams",
1252                             type="keyval", default={},
1253                             help="NIC parameters")
1254
1255 CP_SIZE_OPT = cli_option("-C", "--candidate-pool-size", default=None,
1256                          dest="candidate_pool_size", type="int",
1257                          help="Set the candidate pool size")
1258
1259 VG_NAME_OPT = cli_option("--vg-name", dest="vg_name",
1260                          help=("Enables LVM and specifies the volume group"
1261                                " name (cluster-wide) for disk allocation"
1262                                " [%s]" % constants.DEFAULT_VG),
1263                          metavar="VG", default=None)
1264
1265 YES_DOIT_OPT = cli_option("--yes-do-it", "--ya-rly", dest="yes_do_it",
1266                           help="Destroy cluster", action="store_true")
1267
1268 NOVOTING_OPT = cli_option("--no-voting", dest="no_voting",
1269                           help="Skip node agreement check (dangerous)",
1270                           action="store_true", default=False)
1271
1272 MAC_PREFIX_OPT = cli_option("-m", "--mac-prefix", dest="mac_prefix",
1273                             help="Specify the mac prefix for the instance IP"
1274                             " addresses, in the format XX:XX:XX",
1275                             metavar="PREFIX",
1276                             default=None)
1277
1278 MASTER_NETDEV_OPT = cli_option("--master-netdev", dest="master_netdev",
1279                                help="Specify the node interface (cluster-wide)"
1280                                " on which the master IP address will be added"
1281                                " (cluster init default: %s)" %
1282                                constants.DEFAULT_BRIDGE,
1283                                metavar="NETDEV",
1284                                default=None)
1285
1286 MASTER_NETMASK_OPT = cli_option("--master-netmask", dest="master_netmask",
1287                                 help="Specify the netmask of the master IP",
1288                                 metavar="NETMASK",
1289                                 default=None)
1290
1291 USE_EXTERNAL_MIP_SCRIPT = cli_option("--use-external-mip-script",
1292                                      dest="use_external_mip_script",
1293                                      help="Specify whether to run a"
1294                                      " user-provided script for the master"
1295                                      " IP address turnup and"
1296                                      " turndown operations",
1297                                      type="bool", metavar=_YORNO, default=None)
1298
1299 GLOBAL_FILEDIR_OPT = cli_option("--file-storage-dir", dest="file_storage_dir",
1300                                 help="Specify the default directory (cluster-"
1301                                 "wide) for storing the file-based disks [%s]" %
1302                                 pathutils.DEFAULT_FILE_STORAGE_DIR,
1303                                 metavar="DIR",
1304                                 default=None)
1305
1306 GLOBAL_SHARED_FILEDIR_OPT = cli_option(
1307   "--shared-file-storage-dir",
1308   dest="shared_file_storage_dir",
1309   help="Specify the default directory (cluster-wide) for storing the"
1310   " shared file-based disks [%s]" %
1311   pathutils.DEFAULT_SHARED_FILE_STORAGE_DIR,
1312   metavar="SHAREDDIR", default=None)
1313
1314 NOMODIFY_ETCHOSTS_OPT = cli_option("--no-etc-hosts", dest="modify_etc_hosts",
1315                                    help="Don't modify %s" % pathutils.ETC_HOSTS,
1316                                    action="store_false", default=True)
1317
1318 MODIFY_ETCHOSTS_OPT = \
1319  cli_option("--modify-etc-hosts", dest="modify_etc_hosts", metavar=_YORNO,
1320             default=None, type="bool",
1321             help="Defines whether the cluster should autonomously modify"
1322             " and keep in sync the /etc/hosts file of the nodes")
1323
1324 NOMODIFY_SSH_SETUP_OPT = cli_option("--no-ssh-init", dest="modify_ssh_setup",
1325                                     help="Don't initialize SSH keys",
1326                                     action="store_false", default=True)
1327
1328 ERROR_CODES_OPT = cli_option("--error-codes", dest="error_codes",
1329                              help="Enable parseable error messages",
1330                              action="store_true", default=False)
1331
1332 NONPLUS1_OPT = cli_option("--no-nplus1-mem", dest="skip_nplusone_mem",
1333                           help="Skip N+1 memory redundancy tests",
1334                           action="store_true", default=False)
1335
1336 REBOOT_TYPE_OPT = cli_option("-t", "--type", dest="reboot_type",
1337                              help="Type of reboot: soft/hard/full",
1338                              default=constants.INSTANCE_REBOOT_HARD,
1339                              metavar="<REBOOT>",
1340                              choices=list(constants.REBOOT_TYPES))
1341
1342 IGNORE_SECONDARIES_OPT = cli_option("--ignore-secondaries",
1343                                     dest="ignore_secondaries",
1344                                     default=False, action="store_true",
1345                                     help="Ignore errors from secondaries")
1346
1347 NOSHUTDOWN_OPT = cli_option("--noshutdown", dest="shutdown",
1348                             action="store_false", default=True,
1349                             help="Don't shutdown the instance (unsafe)")
1350
1351 TIMEOUT_OPT = cli_option("--timeout", dest="timeout", type="int",
1352                          default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1353                          help="Maximum time to wait")
1354
1355 SHUTDOWN_TIMEOUT_OPT = cli_option("--shutdown-timeout",
1356                                   dest="shutdown_timeout", type="int",
1357                                   default=constants.DEFAULT_SHUTDOWN_TIMEOUT,
1358                                   help="Maximum time to wait for instance"
1359                                   " shutdown")
1360
1361 INTERVAL_OPT = cli_option("--interval", dest="interval", type="int",
1362                           default=None,
1363                           help=("Number of seconds between repetions of the"
1364                                 " command"))
1365
1366 EARLY_RELEASE_OPT = cli_option("--early-release",
1367                                dest="early_release", default=False,
1368                                action="store_true",
1369                                help="Release the locks on the secondary"
1370                                " node(s) early")
1371
1372 NEW_CLUSTER_CERT_OPT = cli_option("--new-cluster-certificate",
1373                                   dest="new_cluster_cert",
1374                                   default=False, action="store_true",
1375                                   help="Generate a new cluster certificate")
1376
1377 RAPI_CERT_OPT = cli_option("--rapi-certificate", dest="rapi_cert",
1378                            default=None,
1379                            help="File containing new RAPI certificate")
1380
1381 NEW_RAPI_CERT_OPT = cli_option("--new-rapi-certificate", dest="new_rapi_cert",
1382                                default=None, action="store_true",
1383                                help=("Generate a new self-signed RAPI"
1384                                      " certificate"))
1385
1386 SPICE_CERT_OPT = cli_option("--spice-certificate", dest="spice_cert",
1387                             default=None,
1388                             help="File containing new SPICE certificate")
1389
1390 SPICE_CACERT_OPT = cli_option("--spice-ca-certificate", dest="spice_cacert",
1391                               default=None,
1392                               help="File containing the certificate of the CA"
1393                               " which signed the SPICE certificate")
1394
1395 NEW_SPICE_CERT_OPT = cli_option("--new-spice-certificate",
1396                                 dest="new_spice_cert", default=None,
1397                                 action="store_true",
1398                                 help=("Generate a new self-signed SPICE"
1399                                       " certificate"))
1400
1401 NEW_CONFD_HMAC_KEY_OPT = cli_option("--new-confd-hmac-key",
1402                                     dest="new_confd_hmac_key",
1403                                     default=False, action="store_true",
1404                                     help=("Create a new HMAC key for %s" %
1405                                           constants.CONFD))
1406
1407 CLUSTER_DOMAIN_SECRET_OPT = cli_option("--cluster-domain-secret",
1408                                        dest="cluster_domain_secret",
1409                                        default=None,
1410                                        help=("Load new new cluster domain"
1411                                              " secret from file"))
1412
1413 NEW_CLUSTER_DOMAIN_SECRET_OPT = cli_option("--new-cluster-domain-secret",
1414                                            dest="new_cluster_domain_secret",
1415                                            default=False, action="store_true",
1416                                            help=("Create a new cluster domain"
1417                                                  " secret"))
1418
1419 USE_REPL_NET_OPT = cli_option("--use-replication-network",
1420                               dest="use_replication_network",
1421                               help="Whether to use the replication network"
1422                               " for talking to the nodes",
1423                               action="store_true", default=False)
1424
1425 MAINTAIN_NODE_HEALTH_OPT = \
1426     cli_option("--maintain-node-health", dest="maintain_node_health",
1427                metavar=_YORNO, default=None, type="bool",
1428                help="Configure the cluster to automatically maintain node"
1429                " health, by shutting down unknown instances, shutting down"
1430                " unknown DRBD devices, etc.")
1431
1432 IDENTIFY_DEFAULTS_OPT = \
1433     cli_option("--identify-defaults", dest="identify_defaults",
1434                default=False, action="store_true",
1435                help="Identify which saved instance parameters are equal to"
1436                " the current cluster defaults and set them as such, instead"
1437                " of marking them as overridden")
1438
1439 UIDPOOL_OPT = cli_option("--uid-pool", default=None,
1440                          action="store", dest="uid_pool",
1441                          help=("A list of user-ids or user-id"
1442                                " ranges separated by commas"))
1443
1444 ADD_UIDS_OPT = cli_option("--add-uids", default=None,
1445                           action="store", dest="add_uids",
1446                           help=("A list of user-ids or user-id"
1447                                 " ranges separated by commas, to be"
1448                                 " added to the user-id pool"))
1449
1450 REMOVE_UIDS_OPT = cli_option("--remove-uids", default=None,
1451                              action="store", dest="remove_uids",
1452                              help=("A list of user-ids or user-id"
1453                                    " ranges separated by commas, to be"
1454                                    " removed from the user-id pool"))
1455
1456 RESERVED_LVS_OPT = cli_option("--reserved-lvs", default=None,
1457                               action="store", dest="reserved_lvs",
1458                               help=("A comma-separated list of reserved"
1459                                     " logical volumes names, that will be"
1460                                     " ignored by cluster verify"))
1461
1462 ROMAN_OPT = cli_option("--roman",
1463                        dest="roman_integers", default=False,
1464                        action="store_true",
1465                        help="Use roman numbers for positive integers")
1466
1467 DRBD_HELPER_OPT = cli_option("--drbd-usermode-helper", dest="drbd_helper",
1468                              action="store", default=None,
1469                              help="Specifies usermode helper for DRBD")
1470
1471 NODRBD_STORAGE_OPT = cli_option("--no-drbd-storage", dest="drbd_storage",
1472                                 action="store_false", default=True,
1473                                 help="Disable support for DRBD")
1474
1475 PRIMARY_IP_VERSION_OPT = \
1476     cli_option("--primary-ip-version", default=constants.IP4_VERSION,
1477                action="store", dest="primary_ip_version",
1478                metavar="%d|%d" % (constants.IP4_VERSION,
1479                                   constants.IP6_VERSION),
1480                help="Cluster-wide IP version for primary IP")
1481
1482 SHOW_MACHINE_OPT = cli_option("-M", "--show-machine-names", default=False,
1483                               action="store_true",
1484                               help="Show machine name for every line in output")
1485
1486 FAILURE_ONLY_OPT = cli_option("--failure-only", default=False,
1487                               action="store_true",
1488                               help=("Hide successful results and show failures"
1489                                     " only (determined by the exit code)"))
1490
1491 REASON_OPT = cli_option("--reason", default=None,
1492                         help="The reason for executing the command")
1493
1494
1495 def _PriorityOptionCb(option, _, value, parser):
1496   """Callback for processing C{--priority} option.
1497
1498   """
1499   value = _PRIONAME_TO_VALUE[value]
1500
1501   setattr(parser.values, option.dest, value)
1502
1503
1504 PRIORITY_OPT = cli_option("--priority", default=None, dest="priority",
1505                           metavar="|".join(name for name, _ in _PRIORITY_NAMES),
1506                           choices=_PRIONAME_TO_VALUE.keys(),
1507                           action="callback", type="choice",
1508                           callback=_PriorityOptionCb,
1509                           help="Priority for opcode processing")
1510
1511 HID_OS_OPT = cli_option("--hidden", dest="hidden",
1512                         type="bool", default=None, metavar=_YORNO,
1513                         help="Sets the hidden flag on the OS")
1514
1515 BLK_OS_OPT = cli_option("--blacklisted", dest="blacklisted",
1516                         type="bool", default=None, metavar=_YORNO,
1517                         help="Sets the blacklisted flag on the OS")
1518
1519 PREALLOC_WIPE_DISKS_OPT = cli_option("--prealloc-wipe-disks", default=None,
1520                                      type="bool", metavar=_YORNO,
1521                                      dest="prealloc_wipe_disks",
1522                                      help=("Wipe disks prior to instance"
1523                                            " creation"))
1524
1525 NODE_PARAMS_OPT = cli_option("--node-parameters", dest="ndparams",
1526                              type="keyval", default=None,
1527                              help="Node parameters")
1528
1529 ALLOC_POLICY_OPT = cli_option("--alloc-policy", dest="alloc_policy",
1530                               action="store", metavar="POLICY", default=None,
1531                               help="Allocation policy for the node group")
1532
1533 NODE_POWERED_OPT = cli_option("--node-powered", default=None,
1534                               type="bool", metavar=_YORNO,
1535                               dest="node_powered",
1536                               help="Specify if the SoR for node is powered")
1537
1538 OOB_TIMEOUT_OPT = cli_option("--oob-timeout", dest="oob_timeout", type="int",
1539                              default=constants.OOB_TIMEOUT,
1540                              help="Maximum time to wait for out-of-band helper")
1541
1542 POWER_DELAY_OPT = cli_option("--power-delay", dest="power_delay", type="float",
1543                              default=constants.OOB_POWER_DELAY,
1544                              help="Time in seconds to wait between power-ons")
1545
1546 FORCE_FILTER_OPT = cli_option("-F", "--filter", dest="force_filter",
1547                               action="store_true", default=False,
1548                               help=("Whether command argument should be treated"
1549                                     " as filter"))
1550
1551 NO_REMEMBER_OPT = cli_option("--no-remember",
1552                              dest="no_remember",
1553                              action="store_true", default=False,
1554                              help="Perform but do not record the change"
1555                              " in the configuration")
1556
1557 PRIMARY_ONLY_OPT = cli_option("-p", "--primary-only",
1558                               default=False, action="store_true",
1559                               help="Evacuate primary instances only")
1560
1561 SECONDARY_ONLY_OPT = cli_option("-s", "--secondary-only",
1562                                 default=False, action="store_true",
1563                                 help="Evacuate secondary instances only"
1564                                      " (applies only to internally mirrored"
1565                                      " disk templates, e.g. %s)" %
1566                                      utils.CommaJoin(constants.DTS_INT_MIRROR))
1567
1568 STARTUP_PAUSED_OPT = cli_option("--paused", dest="startup_paused",
1569                                 action="store_true", default=False,
1570                                 help="Pause instance at startup")
1571
1572 TO_GROUP_OPT = cli_option("--to", dest="to", metavar="<group>",
1573                           help="Destination node group (name or uuid)",
1574                           default=None, action="append",
1575                           completion_suggest=OPT_COMPL_ONE_NODEGROUP)
1576
1577 IGNORE_ERRORS_OPT = cli_option("-I", "--ignore-errors", default=[],
1578                                action="append", dest="ignore_errors",
1579                                choices=list(constants.CV_ALL_ECODES_STRINGS),
1580                                help="Error code to be ignored")
1581
1582 DISK_STATE_OPT = cli_option("--disk-state", default=[], dest="disk_state",
1583                             action="append",
1584                             help=("Specify disk state information in the"
1585                                   " format"
1586                                   " storage_type/identifier:option=value,...;"
1587                                   " note this is unused for now"),
1588                             type="identkeyval")
1589
1590 HV_STATE_OPT = cli_option("--hypervisor-state", default=[], dest="hv_state",
1591                           action="append",
1592                           help=("Specify hypervisor state information in the"
1593                                 " format hypervisor:option=value,...;"
1594                                 " note this is unused for now"),
1595                           type="identkeyval")
1596
1597 IGNORE_IPOLICY_OPT = cli_option("--ignore-ipolicy", dest="ignore_ipolicy",
1598                                 action="store_true", default=False,
1599                                 help="Ignore instance policy violations")
1600
1601 RUNTIME_MEM_OPT = cli_option("-m", "--runtime-memory", dest="runtime_mem",
1602                              help="Sets the instance's runtime memory,"
1603                              " ballooning it up or down to the new value",
1604                              default=None, type="unit", metavar="<size>")
1605
1606 ABSOLUTE_OPT = cli_option("--absolute", dest="absolute",
1607                           action="store_true", default=False,
1608                           help="Marks the grow as absolute instead of the"
1609                           " (default) relative mode")
1610
1611 NETWORK_OPT = cli_option("--network",
1612                          action="store", default=None, dest="network",
1613                          help="IP network in CIDR notation")
1614
1615 GATEWAY_OPT = cli_option("--gateway",
1616                          action="store", default=None, dest="gateway",
1617                          help="IP address of the router (gateway)")
1618
1619 ADD_RESERVED_IPS_OPT = cli_option("--add-reserved-ips",
1620                                   action="store", default=None,
1621                                   dest="add_reserved_ips",
1622                                   help="Comma-separated list of"
1623                                   " reserved IPs to add")
1624
1625 REMOVE_RESERVED_IPS_OPT = cli_option("--remove-reserved-ips",
1626                                      action="store", default=None,
1627                                      dest="remove_reserved_ips",
1628                                      help="Comma-delimited list of"
1629                                      " reserved IPs to remove")
1630
1631 NETWORK6_OPT = cli_option("--network6",
1632                           action="store", default=None, dest="network6",
1633                           help="IP network in CIDR notation")
1634
1635 GATEWAY6_OPT = cli_option("--gateway6",
1636                           action="store", default=None, dest="gateway6",
1637                           help="IP6 address of the router (gateway)")
1638
1639 NOCONFLICTSCHECK_OPT = cli_option("--no-conflicts-check",
1640                                   dest="conflicts_check",
1641                                   default=True,
1642                                   action="store_false",
1643                                   help="Don't check for conflicting IPs")
1644
1645 INCLUDEDEFAULTS_OPT = cli_option("--include-defaults", dest="include_defaults",
1646                                  default=False, action="store_true",
1647                                  help="Include default values")
1648
1649 #: Options provided by all commands
1650 COMMON_OPTS = [DEBUG_OPT, REASON_OPT]
1651
1652 # options related to asynchronous job handling
1653
1654 SUBMIT_OPTS = [
1655   SUBMIT_OPT,
1656   PRINT_JOBID_OPT,
1657   ]
1658
1659 # common options for creating instances. add and import then add their own
1660 # specific ones.
1661 COMMON_CREATE_OPTS = [
1662   BACKEND_OPT,
1663   DISK_OPT,
1664   DISK_TEMPLATE_OPT,
1665   FILESTORE_DIR_OPT,
1666   FILESTORE_DRIVER_OPT,
1667   HYPERVISOR_OPT,
1668   IALLOCATOR_OPT,
1669   NET_OPT,
1670   NODE_PLACEMENT_OPT,
1671   NOIPCHECK_OPT,
1672   NOCONFLICTSCHECK_OPT,
1673   NONAMECHECK_OPT,
1674   NONICS_OPT,
1675   NWSYNC_OPT,
1676   OSPARAMS_OPT,
1677   OS_SIZE_OPT,
1678   SUBMIT_OPT,
1679   PRINT_JOBID_OPT,
1680   TAG_ADD_OPT,
1681   DRY_RUN_OPT,
1682   PRIORITY_OPT,
1683   ]
1684
1685 # common instance policy options
1686 INSTANCE_POLICY_OPTS = [
1687   IPOLICY_BOUNDS_SPECS_OPT,
1688   IPOLICY_DISK_TEMPLATES,
1689   IPOLICY_VCPU_RATIO,
1690   IPOLICY_SPINDLE_RATIO,
1691   ]
1692
1693 # instance policy split specs options
1694 SPLIT_ISPECS_OPTS = [
1695   SPECS_CPU_COUNT_OPT,
1696   SPECS_DISK_COUNT_OPT,
1697   SPECS_DISK_SIZE_OPT,
1698   SPECS_MEM_SIZE_OPT,
1699   SPECS_NIC_COUNT_OPT,
1700   ]
1701
1702
1703 class _ShowUsage(Exception):
1704   """Exception class for L{_ParseArgs}.
1705
1706   """
1707   def __init__(self, exit_error):
1708     """Initializes instances of this class.
1709
1710     @type exit_error: bool
1711     @param exit_error: Whether to report failure on exit
1712
1713     """
1714     Exception.__init__(self)
1715     self.exit_error = exit_error
1716
1717
1718 class _ShowVersion(Exception):
1719   """Exception class for L{_ParseArgs}.
1720
1721   """
1722
1723
1724 def _ParseArgs(binary, argv, commands, aliases, env_override):
1725   """Parser for the command line arguments.
1726
1727   This function parses the arguments and returns the function which
1728   must be executed together with its (modified) arguments.
1729
1730   @param binary: Script name
1731   @param argv: Command line arguments
1732   @param commands: Dictionary containing command definitions
1733   @param aliases: dictionary with command aliases {"alias": "target", ...}
1734   @param env_override: list of env variables allowed for default args
1735   @raise _ShowUsage: If usage description should be shown
1736   @raise _ShowVersion: If version should be shown
1737
1738   """
1739   assert not (env_override - set(commands))
1740   assert not (set(aliases.keys()) & set(commands.keys()))
1741
1742   if len(argv) > 1:
1743     cmd = argv[1]
1744   else:
1745     # No option or command given
1746     raise _ShowUsage(exit_error=True)
1747
1748   if cmd == "--version":
1749     raise _ShowVersion()
1750   elif cmd == "--help":
1751     raise _ShowUsage(exit_error=False)
1752   elif not (cmd in commands or cmd in aliases):
1753     raise _ShowUsage(exit_error=True)
1754
1755   # get command, unalias it, and look it up in commands
1756   if cmd in aliases:
1757     if aliases[cmd] not in commands:
1758       raise errors.ProgrammerError("Alias '%s' maps to non-existing"
1759                                    " command '%s'" % (cmd, aliases[cmd]))
1760
1761     cmd = aliases[cmd]
1762
1763   if cmd in env_override:
1764     args_env_name = ("%s_%s" % (binary.replace("-", "_"), cmd)).upper()
1765     env_args = os.environ.get(args_env_name)
1766     if env_args:
1767       argv = utils.InsertAtPos(argv, 2, shlex.split(env_args))
1768
1769   func, args_def, parser_opts, usage, description = commands[cmd]
1770   parser = OptionParser(option_list=parser_opts + COMMON_OPTS,
1771                         description=description,
1772                         formatter=TitledHelpFormatter(),
1773                         usage="%%prog %s %s" % (cmd, usage))
1774   parser.disable_interspersed_args()
1775   options, args = parser.parse_args(args=argv[2:])
1776
1777   if not _CheckArguments(cmd, args_def, args):
1778     return None, None, None
1779
1780   return func, options, args
1781
1782
1783 def _FormatUsage(binary, commands):
1784   """Generates a nice description of all commands.
1785
1786   @param binary: Script name
1787   @param commands: Dictionary containing command definitions
1788
1789   """
1790   # compute the max line length for cmd + usage
1791   mlen = min(60, max(map(len, commands)))
1792
1793   yield "Usage: %s {command} [options...] [argument...]" % binary
1794   yield "%s <command> --help to see details, or man %s" % (binary, binary)
1795   yield ""
1796   yield "Commands:"
1797
1798   # and format a nice command list
1799   for (cmd, (_, _, _, _, help_text)) in sorted(commands.items()):
1800     help_lines = textwrap.wrap(help_text, 79 - 3 - mlen)
1801     yield " %-*s - %s" % (mlen, cmd, help_lines.pop(0))
1802     for line in help_lines:
1803       yield " %-*s   %s" % (mlen, "", line)
1804
1805   yield ""
1806
1807
1808 def _CheckArguments(cmd, args_def, args):
1809   """Verifies the arguments using the argument definition.
1810
1811   Algorithm:
1812
1813     1. Abort with error if values specified by user but none expected.
1814
1815     1. For each argument in definition
1816
1817       1. Keep running count of minimum number of values (min_count)
1818       1. Keep running count of maximum number of values (max_count)
1819       1. If it has an unlimited number of values
1820
1821         1. Abort with error if it's not the last argument in the definition
1822
1823     1. If last argument has limited number of values
1824
1825       1. Abort with error if number of values doesn't match or is too large
1826
1827     1. Abort with error if user didn't pass enough values (min_count)
1828
1829   """
1830   if args and not args_def:
1831     ToStderr("Error: Command %s expects no arguments", cmd)
1832     return False
1833
1834   min_count = None
1835   max_count = None
1836   check_max = None
1837
1838   last_idx = len(args_def) - 1
1839
1840   for idx, arg in enumerate(args_def):
1841     if min_count is None:
1842       min_count = arg.min
1843     elif arg.min is not None:
1844       min_count += arg.min
1845
1846     if max_count is None:
1847       max_count = arg.max
1848     elif arg.max is not None:
1849       max_count += arg.max
1850
1851     if idx == last_idx:
1852       check_max = (arg.max is not None)
1853
1854     elif arg.max is None:
1855       raise errors.ProgrammerError("Only the last argument can have max=None")
1856
1857   if check_max:
1858     # Command with exact number of arguments
1859     if (min_count is not None and max_count is not None and
1860         min_count == max_count and len(args) != min_count):
1861       ToStderr("Error: Command %s expects %d argument(s)", cmd, min_count)
1862       return False
1863
1864     # Command with limited number of arguments
1865     if max_count is not None and len(args) > max_count:
1866       ToStderr("Error: Command %s expects only %d argument(s)",
1867                cmd, max_count)
1868       return False
1869
1870   # Command with some required arguments
1871   if min_count is not None and len(args) < min_count:
1872     ToStderr("Error: Command %s expects at least %d argument(s)",
1873              cmd, min_count)
1874     return False
1875
1876   return True
1877
1878
1879 def SplitNodeOption(value):
1880   """Splits the value of a --node option.
1881
1882   """
1883   if value and ":" in value:
1884     return value.split(":", 1)
1885   else:
1886     return (value, None)
1887
1888
1889 def CalculateOSNames(os_name, os_variants):
1890   """Calculates all the names an OS can be called, according to its variants.
1891
1892   @type os_name: string
1893   @param os_name: base name of the os
1894   @type os_variants: list or None
1895   @param os_variants: list of supported variants
1896   @rtype: list
1897   @return: list of valid names
1898
1899   """
1900   if os_variants:
1901     return ["%s+%s" % (os_name, v) for v in os_variants]
1902   else:
1903     return [os_name]
1904
1905
1906 def ParseFields(selected, default):
1907   """Parses the values of "--field"-like options.
1908
1909   @type selected: string or None
1910   @param selected: User-selected options
1911   @type default: list
1912   @param default: Default fields
1913
1914   """
1915   if selected is None:
1916     return default
1917
1918   if selected.startswith("+"):
1919     return default + selected[1:].split(",")
1920
1921   return selected.split(",")
1922
1923
1924 UsesRPC = rpc.RunWithRPC
1925
1926
1927 def AskUser(text, choices=None):
1928   """Ask the user a question.
1929
1930   @param text: the question to ask
1931
1932   @param choices: list with elements tuples (input_char, return_value,
1933       description); if not given, it will default to: [('y', True,
1934       'Perform the operation'), ('n', False, 'Do no do the operation')];
1935       note that the '?' char is reserved for help
1936
1937   @return: one of the return values from the choices list; if input is
1938       not possible (i.e. not running with a tty, we return the last
1939       entry from the list
1940
1941   """
1942   if choices is None:
1943     choices = [("y", True, "Perform the operation"),
1944                ("n", False, "Do not perform the operation")]
1945   if not choices or not isinstance(choices, list):
1946     raise errors.ProgrammerError("Invalid choices argument to AskUser")
1947   for entry in choices:
1948     if not isinstance(entry, tuple) or len(entry) < 3 or entry[0] == "?":
1949       raise errors.ProgrammerError("Invalid choices element to AskUser")
1950
1951   answer = choices[-1][1]
1952   new_text = []
1953   for line in text.splitlines():
1954     new_text.append(textwrap.fill(line, 70, replace_whitespace=False))
1955   text = "\n".join(new_text)
1956   try:
1957     f = file("/dev/tty", "a+")
1958   except IOError:
1959     return answer
1960   try:
1961     chars = [entry[0] for entry in choices]
1962     chars[-1] = "[%s]" % chars[-1]
1963     chars.append("?")
1964     maps = dict([(entry[0], entry[1]) for entry in choices])
1965     while True:
1966       f.write(text)
1967       f.write("\n")
1968       f.write("/".join(chars))
1969       f.write(": ")
1970       line = f.readline(2).strip().lower()
1971       if line in maps:
1972         answer = maps[line]
1973         break
1974       elif line == "?":
1975         for entry in choices:
1976           f.write(" %s - %s\n" % (entry[0], entry[2]))
1977         f.write("\n")
1978         continue
1979   finally:
1980     f.close()
1981   return answer
1982
1983
1984 class JobSubmittedException(Exception):
1985   """Job was submitted, client should exit.
1986
1987   This exception has one argument, the ID of the job that was
1988   submitted. The handler should print this ID.
1989
1990   This is not an error, just a structured way to exit from clients.
1991
1992   """
1993
1994
1995 def SendJob(ops, cl=None):
1996   """Function to submit an opcode without waiting for the results.
1997
1998   @type ops: list
1999   @param ops: list of opcodes
2000   @type cl: luxi.Client
2001   @param cl: the luxi client to use for communicating with the master;
2002              if None, a new client will be created
2003
2004   """
2005   if cl is None:
2006     cl = GetClient()
2007
2008   job_id = cl.SubmitJob(ops)
2009
2010   return job_id
2011
2012
2013 def GenericPollJob(job_id, cbs, report_cbs):
2014   """Generic job-polling function.
2015
2016   @type job_id: number
2017   @param job_id: Job ID
2018   @type cbs: Instance of L{JobPollCbBase}
2019   @param cbs: Data callbacks
2020   @type report_cbs: Instance of L{JobPollReportCbBase}
2021   @param report_cbs: Reporting callbacks
2022
2023   """
2024   prev_job_info = None
2025   prev_logmsg_serial = None
2026
2027   status = None
2028
2029   while True:
2030     result = cbs.WaitForJobChangeOnce(job_id, ["status"], prev_job_info,
2031                                       prev_logmsg_serial)
2032     if not result:
2033       # job not found, go away!
2034       raise errors.JobLost("Job with id %s lost" % job_id)
2035
2036     if result == constants.JOB_NOTCHANGED:
2037       report_cbs.ReportNotChanged(job_id, status)
2038
2039       # Wait again
2040       continue
2041
2042     # Split result, a tuple of (field values, log entries)
2043     (job_info, log_entries) = result
2044     (status, ) = job_info
2045
2046     if log_entries:
2047       for log_entry in log_entries:
2048         (serial, timestamp, log_type, message) = log_entry
2049         report_cbs.ReportLogMessage(job_id, serial, timestamp,
2050                                     log_type, message)
2051         prev_logmsg_serial = max(prev_logmsg_serial, serial)
2052
2053     # TODO: Handle canceled and archived jobs
2054     elif status in (constants.JOB_STATUS_SUCCESS,
2055                     constants.JOB_STATUS_ERROR,
2056                     constants.JOB_STATUS_CANCELING,
2057                     constants.JOB_STATUS_CANCELED):
2058       break
2059
2060     prev_job_info = job_info
2061
2062   jobs = cbs.QueryJobs([job_id], ["status", "opstatus", "opresult"])
2063   if not jobs:
2064     raise errors.JobLost("Job with id %s lost" % job_id)
2065
2066   status, opstatus, result = jobs[0]
2067
2068   if status == constants.JOB_STATUS_SUCCESS:
2069     return result
2070
2071   if status in (constants.JOB_STATUS_CANCELING, constants.JOB_STATUS_CANCELED):
2072     raise errors.OpExecError("Job was canceled")
2073
2074   has_ok = False
2075   for idx, (status, msg) in enumerate(zip(opstatus, result)):
2076     if status == constants.OP_STATUS_SUCCESS:
2077       has_ok = True
2078     elif status == constants.OP_STATUS_ERROR:
2079       errors.MaybeRaise(msg)
2080
2081       if has_ok:
2082         raise errors.OpExecError("partial failure (opcode %d): %s" %
2083                                  (idx, msg))
2084
2085       raise errors.OpExecError(str(msg))
2086
2087   # default failure mode
2088   raise errors.OpExecError(result)
2089
2090
2091 class JobPollCbBase:
2092   """Base class for L{GenericPollJob} callbacks.
2093
2094   """
2095   def __init__(self):
2096     """Initializes this class.
2097
2098     """
2099
2100   def WaitForJobChangeOnce(self, job_id, fields,
2101                            prev_job_info, prev_log_serial):
2102     """Waits for changes on a job.
2103
2104     """
2105     raise NotImplementedError()
2106
2107   def QueryJobs(self, job_ids, fields):
2108     """Returns the selected fields for the selected job IDs.
2109
2110     @type job_ids: list of numbers
2111     @param job_ids: Job IDs
2112     @type fields: list of strings
2113     @param fields: Fields
2114
2115     """
2116     raise NotImplementedError()
2117
2118
2119 class JobPollReportCbBase:
2120   """Base class for L{GenericPollJob} reporting callbacks.
2121
2122   """
2123   def __init__(self):
2124     """Initializes this class.
2125
2126     """
2127
2128   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2129     """Handles a log message.
2130
2131     """
2132     raise NotImplementedError()
2133
2134   def ReportNotChanged(self, job_id, status):
2135     """Called for if a job hasn't changed in a while.
2136
2137     @type job_id: number
2138     @param job_id: Job ID
2139     @type status: string or None
2140     @param status: Job status if available
2141
2142     """
2143     raise NotImplementedError()
2144
2145
2146 class _LuxiJobPollCb(JobPollCbBase):
2147   def __init__(self, cl):
2148     """Initializes this class.
2149
2150     """
2151     JobPollCbBase.__init__(self)
2152     self.cl = cl
2153
2154   def WaitForJobChangeOnce(self, job_id, fields,
2155                            prev_job_info, prev_log_serial):
2156     """Waits for changes on a job.
2157
2158     """
2159     return self.cl.WaitForJobChangeOnce(job_id, fields,
2160                                         prev_job_info, prev_log_serial)
2161
2162   def QueryJobs(self, job_ids, fields):
2163     """Returns the selected fields for the selected job IDs.
2164
2165     """
2166     return self.cl.QueryJobs(job_ids, fields)
2167
2168
2169 class FeedbackFnJobPollReportCb(JobPollReportCbBase):
2170   def __init__(self, feedback_fn):
2171     """Initializes this class.
2172
2173     """
2174     JobPollReportCbBase.__init__(self)
2175
2176     self.feedback_fn = feedback_fn
2177
2178     assert callable(feedback_fn)
2179
2180   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2181     """Handles a log message.
2182
2183     """
2184     self.feedback_fn((timestamp, log_type, log_msg))
2185
2186   def ReportNotChanged(self, job_id, status):
2187     """Called if a job hasn't changed in a while.
2188
2189     """
2190     # Ignore
2191
2192
2193 class StdioJobPollReportCb(JobPollReportCbBase):
2194   def __init__(self):
2195     """Initializes this class.
2196
2197     """
2198     JobPollReportCbBase.__init__(self)
2199
2200     self.notified_queued = False
2201     self.notified_waitlock = False
2202
2203   def ReportLogMessage(self, job_id, serial, timestamp, log_type, log_msg):
2204     """Handles a log message.
2205
2206     """
2207     ToStdout("%s %s", time.ctime(utils.MergeTime(timestamp)),
2208              FormatLogMessage(log_type, log_msg))
2209
2210   def ReportNotChanged(self, job_id, status):
2211     """Called if a job hasn't changed in a while.
2212
2213     """
2214     if status is None:
2215       return
2216
2217     if status == constants.JOB_STATUS_QUEUED and not self.notified_queued:
2218       ToStderr("Job %s is waiting in queue", job_id)
2219       self.notified_queued = True
2220
2221     elif status == constants.JOB_STATUS_WAITING and not self.notified_waitlock:
2222       ToStderr("Job %s is trying to acquire all necessary locks", job_id)
2223       self.notified_waitlock = True
2224
2225
2226 def FormatLogMessage(log_type, log_msg):
2227   """Formats a job message according to its type.
2228
2229   """
2230   if log_type != constants.ELOG_MESSAGE:
2231     log_msg = str(log_msg)
2232
2233   return utils.SafeEncode(log_msg)
2234
2235
2236 def PollJob(job_id, cl=None, feedback_fn=None, reporter=None):
2237   """Function to poll for the result of a job.
2238
2239   @type job_id: job identified
2240   @param job_id: the job to poll for results
2241   @type cl: luxi.Client
2242   @param cl: the luxi client to use for communicating with the master;
2243              if None, a new client will be created
2244
2245   """
2246   if cl is None:
2247     cl = GetClient()
2248
2249   if reporter is None:
2250     if feedback_fn:
2251       reporter = FeedbackFnJobPollReportCb(feedback_fn)
2252     else:
2253       reporter = StdioJobPollReportCb()
2254   elif feedback_fn:
2255     raise errors.ProgrammerError("Can't specify reporter and feedback function")
2256
2257   return GenericPollJob(job_id, _LuxiJobPollCb(cl), reporter)
2258
2259
2260 def SubmitOpCode(op, cl=None, feedback_fn=None, opts=None, reporter=None):
2261   """Legacy function to submit an opcode.
2262
2263   This is just a simple wrapper over the construction of the processor
2264   instance. It should be extended to better handle feedback and
2265   interaction functions.
2266
2267   """
2268   if cl is None:
2269     cl = GetClient()
2270
2271   SetGenericOpcodeOpts([op], opts)
2272
2273   job_id = SendJob([op], cl=cl)
2274   if hasattr(opts, "print_jobid") and opts.print_jobid:
2275     ToStdout("%d" % job_id)
2276
2277   op_results = PollJob(job_id, cl=cl, feedback_fn=feedback_fn,
2278                        reporter=reporter)
2279
2280   return op_results[0]
2281
2282
2283 def SubmitOrSend(op, opts, cl=None, feedback_fn=None):
2284   """Wrapper around SubmitOpCode or SendJob.
2285
2286   This function will decide, based on the 'opts' parameter, whether to
2287   submit and wait for the result of the opcode (and return it), or
2288   whether to just send the job and print its identifier. It is used in
2289   order to simplify the implementation of the '--submit' option.
2290
2291   It will also process the opcodes if we're sending the via SendJob
2292   (otherwise SubmitOpCode does it).
2293
2294   """
2295   if opts and opts.submit_only:
2296     job = [op]
2297     SetGenericOpcodeOpts(job, opts)
2298     job_id = SendJob(job, cl=cl)
2299     if opts.print_jobid:
2300       ToStdout("%d" % job_id)
2301     raise JobSubmittedException(job_id)
2302   else:
2303     return SubmitOpCode(op, cl=cl, feedback_fn=feedback_fn, opts=opts)
2304
2305
2306 def _InitReasonTrail(op, opts):
2307   """Builds the first part of the reason trail
2308
2309   Builds the initial part of the reason trail, adding the user provided reason
2310   (if it exists) and the name of the command starting the operation.
2311
2312   @param op: the opcode the reason trail will be added to
2313   @param opts: the command line options selected by the user
2314
2315   """
2316   assert len(sys.argv) >= 2
2317   trail = []
2318
2319   if opts.reason:
2320     trail.append((constants.OPCODE_REASON_SRC_USER,
2321                   opts.reason,
2322                   utils.EpochNano()))
2323
2324   binary = os.path.basename(sys.argv[0])
2325   source = "%s:%s" % (constants.OPCODE_REASON_SRC_CLIENT, binary)
2326   command = sys.argv[1]
2327   trail.append((source, command, utils.EpochNano()))
2328   op.reason = trail
2329
2330
2331 def SetGenericOpcodeOpts(opcode_list, options):
2332   """Processor for generic options.
2333
2334   This function updates the given opcodes based on generic command
2335   line options (like debug, dry-run, etc.).
2336
2337   @param opcode_list: list of opcodes
2338   @param options: command line options or None
2339   @return: None (in-place modification)
2340
2341   """
2342   if not options:
2343     return
2344   for op in opcode_list:
2345     op.debug_level = options.debug
2346     if hasattr(options, "dry_run"):
2347       op.dry_run = options.dry_run
2348     if getattr(options, "priority", None) is not None:
2349       op.priority = options.priority
2350     _InitReasonTrail(op, options)
2351
2352
2353 def GetClient(query=False):
2354   """Connects to the a luxi socket and returns a client.
2355
2356   @type query: boolean
2357   @param query: this signifies that the client will only be
2358       used for queries; if the build-time parameter
2359       enable-split-queries is enabled, then the client will be
2360       connected to the query socket instead of the masterd socket
2361
2362   """
2363   override_socket = os.getenv(constants.LUXI_OVERRIDE, "")
2364   if override_socket:
2365     if override_socket == constants.LUXI_OVERRIDE_MASTER:
2366       address = pathutils.MASTER_SOCKET
2367     elif override_socket == constants.LUXI_OVERRIDE_QUERY:
2368       address = pathutils.QUERY_SOCKET
2369     else:
2370       address = override_socket
2371   elif query and constants.ENABLE_SPLIT_QUERY:
2372     address = pathutils.QUERY_SOCKET
2373   else:
2374     address = None
2375   # TODO: Cache object?
2376   try:
2377     client = luxi.Client(address=address)
2378   except luxi.NoMasterError:
2379     ss = ssconf.SimpleStore()
2380
2381     # Try to read ssconf file
2382     try:
2383       ss.GetMasterNode()
2384     except errors.ConfigurationError:
2385       raise errors.OpPrereqError("Cluster not initialized or this machine is"
2386                                  " not part of a cluster",
2387                                  errors.ECODE_INVAL)
2388
2389     master, myself = ssconf.GetMasterAndMyself(ss=ss)
2390     if master != myself:
2391       raise errors.OpPrereqError("This is not the master node, please connect"
2392                                  " to node '%s' and rerun the command" %
2393                                  master, errors.ECODE_INVAL)
2394     raise
2395   return client
2396
2397
2398 def FormatError(err):
2399   """Return a formatted error message for a given error.
2400
2401   This function takes an exception instance and returns a tuple
2402   consisting of two values: first, the recommended exit code, and
2403   second, a string describing the error message (not
2404   newline-terminated).
2405
2406   """
2407   retcode = 1
2408   obuf = StringIO()
2409   msg = str(err)
2410   if isinstance(err, errors.ConfigurationError):
2411     txt = "Corrupt configuration file: %s" % msg
2412     logging.error(txt)
2413     obuf.write(txt + "\n")
2414     obuf.write("Aborting.")
2415     retcode = 2
2416   elif isinstance(err, errors.HooksAbort):
2417     obuf.write("Failure: hooks execution failed:\n")
2418     for node, script, out in err.args[0]:
2419       if out:
2420         obuf.write("  node: %s, script: %s, output: %s\n" %
2421                    (node, script, out))
2422       else:
2423         obuf.write("  node: %s, script: %s (no output)\n" %
2424                    (node, script))
2425   elif isinstance(err, errors.HooksFailure):
2426     obuf.write("Failure: hooks general failure: %s" % msg)
2427   elif isinstance(err, errors.ResolverError):
2428     this_host = netutils.Hostname.GetSysName()
2429     if err.args[0] == this_host:
2430       msg = "Failure: can't resolve my own hostname ('%s')"
2431     else:
2432       msg = "Failure: can't resolve hostname '%s'"
2433     obuf.write(msg % err.args[0])
2434   elif isinstance(err, errors.OpPrereqError):
2435     if len(err.args) == 2:
2436       obuf.write("Failure: prerequisites not met for this"
2437                  " operation:\nerror type: %s, error details:\n%s" %
2438                  (err.args[1], err.args[0]))
2439     else:
2440       obuf.write("Failure: prerequisites not met for this"
2441                  " operation:\n%s" % msg)
2442   elif isinstance(err, errors.OpExecError):
2443     obuf.write("Failure: command execution error:\n%s" % msg)
2444   elif isinstance(err, errors.TagError):
2445     obuf.write("Failure: invalid tag(s) given:\n%s" % msg)
2446   elif isinstance(err, errors.JobQueueDrainError):
2447     obuf.write("Failure: the job queue is marked for drain and doesn't"
2448                " accept new requests\n")
2449   elif isinstance(err, errors.JobQueueFull):
2450     obuf.write("Failure: the job queue is full and doesn't accept new"
2451                " job submissions until old jobs are archived\n")
2452   elif isinstance(err, errors.TypeEnforcementError):
2453     obuf.write("Parameter Error: %s" % msg)
2454   elif isinstance(err, errors.ParameterError):
2455     obuf.write("Failure: unknown/wrong parameter name '%s'" % msg)
2456   elif isinstance(err, luxi.NoMasterError):
2457     if err.args[0] == pathutils.MASTER_SOCKET:
2458       daemon = "the master daemon"
2459     elif err.args[0] == pathutils.QUERY_SOCKET:
2460       daemon = "the config daemon"
2461     else:
2462       daemon = "socket '%s'" % str(err.args[0])
2463     obuf.write("Cannot communicate with %s.\nIs the process running"
2464                " and listening for connections?" % daemon)
2465   elif isinstance(err, luxi.TimeoutError):
2466     obuf.write("Timeout while talking to the master daemon. Jobs might have"
2467                " been submitted and will continue to run even if the call"
2468                " timed out. Useful commands in this situation are \"gnt-job"
2469                " list\", \"gnt-job cancel\" and \"gnt-job watch\". Error:\n")
2470     obuf.write(msg)
2471   elif isinstance(err, luxi.PermissionError):
2472     obuf.write("It seems you don't have permissions to connect to the"
2473                " master daemon.\nPlease retry as a different user.")
2474   elif isinstance(err, luxi.ProtocolError):
2475     obuf.write("Unhandled protocol error while talking to the master daemon:\n"
2476                "%s" % msg)
2477   elif isinstance(err, errors.JobLost):
2478     obuf.write("Error checking job status: %s" % msg)
2479   elif isinstance(err, errors.QueryFilterParseError):
2480     obuf.write("Error while parsing query filter: %s\n" % err.args[0])
2481     obuf.write("\n".join(err.GetDetails()))
2482   elif isinstance(err, errors.GenericError):
2483     obuf.write("Unhandled Ganeti error: %s" % msg)
2484   elif isinstance(err, JobSubmittedException):
2485     obuf.write("JobID: %s\n" % err.args[0])
2486     retcode = 0
2487   else:
2488     obuf.write("Unhandled exception: %s" % msg)
2489   return retcode, obuf.getvalue().rstrip("\n")
2490
2491
2492 def GenericMain(commands, override=None, aliases=None,
2493                 env_override=frozenset()):
2494   """Generic main function for all the gnt-* commands.
2495
2496   @param commands: a dictionary with a special structure, see the design doc
2497                    for command line handling.
2498   @param override: if not None, we expect a dictionary with keys that will
2499                    override command line options; this can be used to pass
2500                    options from the scripts to generic functions
2501   @param aliases: dictionary with command aliases {'alias': 'target, ...}
2502   @param env_override: list of environment names which are allowed to submit
2503                        default args for commands
2504
2505   """
2506   # save the program name and the entire command line for later logging
2507   if sys.argv:
2508     binary = os.path.basename(sys.argv[0])
2509     if not binary:
2510       binary = sys.argv[0]
2511
2512     if len(sys.argv) >= 2:
2513       logname = utils.ShellQuoteArgs([binary, sys.argv[1]])
2514     else:
2515       logname = binary
2516
2517     cmdline = utils.ShellQuoteArgs([binary] + sys.argv[1:])
2518   else:
2519     binary = "<unknown program>"
2520     cmdline = "<unknown>"
2521
2522   if aliases is None:
2523     aliases = {}
2524
2525   try:
2526     (func, options, args) = _ParseArgs(binary, sys.argv, commands, aliases,
2527                                        env_override)
2528   except _ShowVersion:
2529     ToStdout("%s (ganeti %s) %s", binary, constants.VCS_VERSION,
2530              constants.RELEASE_VERSION)
2531     return constants.EXIT_SUCCESS
2532   except _ShowUsage, err:
2533     for line in _FormatUsage(binary, commands):
2534       ToStdout(line)
2535
2536     if err.exit_error:
2537       return constants.EXIT_FAILURE
2538     else:
2539       return constants.EXIT_SUCCESS
2540   except errors.ParameterError, err:
2541     result, err_msg = FormatError(err)
2542     ToStderr(err_msg)
2543     return 1
2544
2545   if func is None: # parse error
2546     return 1
2547
2548   if override is not None:
2549     for key, val in override.iteritems():
2550       setattr(options, key, val)
2551
2552   utils.SetupLogging(pathutils.LOG_COMMANDS, logname, debug=options.debug,
2553                      stderr_logging=True)
2554
2555   logging.info("Command line: %s", cmdline)
2556
2557   try:
2558     result = func(options, args)
2559   except (errors.GenericError, luxi.ProtocolError,
2560           JobSubmittedException), err:
2561     result, err_msg = FormatError(err)
2562     logging.exception("Error during command processing")
2563     ToStderr(err_msg)
2564   except KeyboardInterrupt:
2565     result = constants.EXIT_FAILURE
2566     ToStderr("Aborted. Note that if the operation created any jobs, they"
2567              " might have been submitted and"
2568              " will continue to run in the background.")
2569   except IOError, err:
2570     if err.errno == errno.EPIPE:
2571       # our terminal went away, we'll exit
2572       sys.exit(constants.EXIT_FAILURE)
2573     else:
2574       raise
2575
2576   return result
2577
2578
2579 def ParseNicOption(optvalue):
2580   """Parses the value of the --net option(s).
2581
2582   """
2583   try:
2584     nic_max = max(int(nidx[0]) + 1 for nidx in optvalue)
2585   except (TypeError, ValueError), err:
2586     raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err),
2587                                errors.ECODE_INVAL)
2588
2589   nics = [{}] * nic_max
2590   for nidx, ndict in optvalue:
2591     nidx = int(nidx)
2592
2593     if not isinstance(ndict, dict):
2594       raise errors.OpPrereqError("Invalid nic/%d value: expected dict,"
2595                                  " got %s" % (nidx, ndict), errors.ECODE_INVAL)
2596
2597     utils.ForceDictType(ndict, constants.INIC_PARAMS_TYPES)
2598
2599     nics[nidx] = ndict
2600
2601   return nics
2602
2603
2604 def FixHvParams(hvparams):
2605   # In Ganeti 2.8.4 the separator for the usb_devices hvparam was changed from
2606   # comma to space because commas cannot be accepted on the command line
2607   # (they already act as the separator between different hvparams). Still,
2608   # RAPI should be able to accept commas for backwards compatibility.
2609   # Therefore, we convert spaces into commas here, and we keep the old
2610   # parsing logic everywhere else.
2611   try:
2612     new_usb_devices = hvparams[constants.HV_USB_DEVICES].replace(" ", ",")
2613     hvparams[constants.HV_USB_DEVICES] = new_usb_devices
2614   except KeyError:
2615     #No usb_devices, no modification required
2616     pass
2617
2618
2619 def GenericInstanceCreate(mode, opts, args):
2620   """Add an instance to the cluster via either creation or import.
2621
2622   @param mode: constants.INSTANCE_CREATE or constants.INSTANCE_IMPORT
2623   @param opts: the command line options selected by the user
2624   @type args: list
2625   @param args: should contain only one element, the new instance name
2626   @rtype: int
2627   @return: the desired exit code
2628
2629   """
2630   instance = args[0]
2631
2632   (pnode, snode) = SplitNodeOption(opts.node)
2633
2634   hypervisor = None
2635   hvparams = {}
2636   if opts.hypervisor:
2637     hypervisor, hvparams = opts.hypervisor
2638
2639   if opts.nics:
2640     nics = ParseNicOption(opts.nics)
2641   elif opts.no_nics:
2642     # no nics
2643     nics = []
2644   elif mode == constants.INSTANCE_CREATE:
2645     # default of one nic, all auto
2646     nics = [{}]
2647   else:
2648     # mode == import
2649     nics = []
2650
2651   if opts.disk_template == constants.DT_DISKLESS:
2652     if opts.disks or opts.sd_size is not None:
2653       raise errors.OpPrereqError("Diskless instance but disk"
2654                                  " information passed", errors.ECODE_INVAL)
2655     disks = []
2656   else:
2657     if (not opts.disks and not opts.sd_size
2658         and mode == constants.INSTANCE_CREATE):
2659       raise errors.OpPrereqError("No disk information specified",
2660                                  errors.ECODE_INVAL)
2661     if opts.disks and opts.sd_size is not None:
2662       raise errors.OpPrereqError("Please use either the '--disk' or"
2663                                  " '-s' option", errors.ECODE_INVAL)
2664     if opts.sd_size is not None:
2665       opts.disks = [(0, {constants.IDISK_SIZE: opts.sd_size})]
2666
2667     if opts.disks:
2668       try:
2669         disk_max = max(int(didx[0]) + 1 for didx in opts.disks)
2670       except ValueError, err:
2671         raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
2672                                    errors.ECODE_INVAL)
2673       disks = [{}] * disk_max
2674     else:
2675       disks = []
2676     for didx, ddict in opts.disks:
2677       didx = int(didx)
2678       if not isinstance(ddict, dict):
2679         msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
2680         raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
2681       elif constants.IDISK_SIZE in ddict:
2682         if constants.IDISK_ADOPT in ddict:
2683           raise errors.OpPrereqError("Only one of 'size' and 'adopt' allowed"
2684                                      " (disk %d)" % didx, errors.ECODE_INVAL)
2685         try:
2686           ddict[constants.IDISK_SIZE] = \
2687             utils.ParseUnit(ddict[constants.IDISK_SIZE])
2688         except ValueError, err:
2689           raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
2690                                      (didx, err), errors.ECODE_INVAL)
2691       elif constants.IDISK_ADOPT in ddict:
2692         if constants.IDISK_SPINDLES in ddict:
2693           raise errors.OpPrereqError("spindles is not a valid option when"
2694                                      " adopting a disk", errors.ECODE_INVAL)
2695         if mode == constants.INSTANCE_IMPORT:
2696           raise errors.OpPrereqError("Disk adoption not allowed for instance"
2697                                      " import", errors.ECODE_INVAL)
2698         ddict[constants.IDISK_SIZE] = 0
2699       else:
2700         raise errors.OpPrereqError("Missing size or adoption source for"
2701                                    " disk %d" % didx, errors.ECODE_INVAL)
2702       disks[didx] = ddict
2703
2704   if opts.tags is not None:
2705     tags = opts.tags.split(",")
2706   else:
2707     tags = []
2708
2709   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_COMPAT)
2710   utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
2711   FixHvParams(hvparams)
2712
2713   if mode == constants.INSTANCE_CREATE:
2714     start = opts.start
2715     os_type = opts.os
2716     force_variant = opts.force_variant
2717     src_node = None
2718     src_path = None
2719     no_install = opts.no_install
2720     identify_defaults = False
2721   elif mode == constants.INSTANCE_IMPORT:
2722     start = False
2723     os_type = None
2724     force_variant = False
2725     src_node = opts.src_node
2726     src_path = opts.src_dir
2727     no_install = None
2728     identify_defaults = opts.identify_defaults
2729   else:
2730     raise errors.ProgrammerError("Invalid creation mode %s" % mode)
2731
2732   op = opcodes.OpInstanceCreate(instance_name=instance,
2733                                 disks=disks,
2734                                 disk_template=opts.disk_template,
2735                                 nics=nics,
2736                                 conflicts_check=opts.conflicts_check,
2737                                 pnode=pnode, snode=snode,
2738                                 ip_check=opts.ip_check,
2739                                 name_check=opts.name_check,
2740                                 wait_for_sync=opts.wait_for_sync,
2741                                 file_storage_dir=opts.file_storage_dir,
2742                                 file_driver=opts.file_driver,
2743                                 iallocator=opts.iallocator,
2744                                 hypervisor=hypervisor,
2745                                 hvparams=hvparams,
2746                                 beparams=opts.beparams,
2747                                 osparams=opts.osparams,
2748                                 mode=mode,
2749                                 start=start,
2750                                 os_type=os_type,
2751                                 force_variant=force_variant,
2752                                 src_node=src_node,
2753                                 src_path=src_path,
2754                                 tags=tags,
2755                                 no_install=no_install,
2756                                 identify_defaults=identify_defaults,
2757                                 ignore_ipolicy=opts.ignore_ipolicy)
2758
2759   SubmitOrSend(op, opts)
2760   return 0
2761
2762
2763 class _RunWhileClusterStoppedHelper:
2764   """Helper class for L{RunWhileClusterStopped} to simplify state management
2765
2766   """
2767   def __init__(self, feedback_fn, cluster_name, master_node, online_nodes):
2768     """Initializes this class.
2769
2770     @type feedback_fn: callable
2771     @param feedback_fn: Feedback function
2772     @type cluster_name: string
2773     @param cluster_name: Cluster name
2774     @type master_node: string
2775     @param master_node Master node name
2776     @type online_nodes: list
2777     @param online_nodes: List of names of online nodes
2778
2779     """
2780     self.feedback_fn = feedback_fn
2781     self.cluster_name = cluster_name
2782     self.master_node = master_node
2783     self.online_nodes = online_nodes
2784
2785     self.ssh = ssh.SshRunner(self.cluster_name)
2786
2787     self.nonmaster_nodes = [name for name in online_nodes
2788                             if name != master_node]
2789
2790     assert self.master_node not in self.nonmaster_nodes
2791
2792   def _RunCmd(self, node_name, cmd):
2793     """Runs a command on the local or a remote machine.
2794
2795     @type node_name: string
2796     @param node_name: Machine name
2797     @type cmd: list
2798     @param cmd: Command
2799
2800     """
2801     if node_name is None or node_name == self.master_node:
2802       # No need to use SSH
2803       result = utils.RunCmd(cmd)
2804     else:
2805       result = self.ssh.Run(node_name, constants.SSH_LOGIN_USER,
2806                             utils.ShellQuoteArgs(cmd))
2807
2808     if result.failed:
2809       errmsg = ["Failed to run command %s" % result.cmd]
2810       if node_name:
2811         errmsg.append("on node %s" % node_name)
2812       errmsg.append(": exitcode %s and error %s" %
2813                     (result.exit_code, result.output))
2814       raise errors.OpExecError(" ".join(errmsg))
2815
2816   def Call(self, fn, *args):
2817     """Call function while all daemons are stopped.
2818
2819     @type fn: callable
2820     @param fn: Function to be called
2821
2822     """
2823     # Pause watcher by acquiring an exclusive lock on watcher state file
2824     self.feedback_fn("Blocking watcher")
2825     watcher_block = utils.FileLock.Open(pathutils.WATCHER_LOCK_FILE)
2826     try:
2827       # TODO: Currently, this just blocks. There's no timeout.
2828       # TODO: Should it be a shared lock?
2829       watcher_block.Exclusive(blocking=True)
2830
2831       # Stop master daemons, so that no new jobs can come in and all running
2832       # ones are finished
2833       self.feedback_fn("Stopping master daemons")
2834       self._RunCmd(None, [pathutils.DAEMON_UTIL, "stop-master"])
2835       try:
2836         # Stop daemons on all nodes
2837         for node_name in self.online_nodes:
2838           self.feedback_fn("Stopping daemons on %s" % node_name)
2839           self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "stop-all"])
2840
2841         # All daemons are shut down now
2842         try:
2843           return fn(self, *args)
2844         except Exception, err:
2845           _, errmsg = FormatError(err)
2846           logging.exception("Caught exception")
2847           self.feedback_fn(errmsg)
2848           raise
2849       finally:
2850         # Start cluster again, master node last
2851         for node_name in self.nonmaster_nodes + [self.master_node]:
2852           self.feedback_fn("Starting daemons on %s" % node_name)
2853           self._RunCmd(node_name, [pathutils.DAEMON_UTIL, "start-all"])
2854     finally:
2855       # Resume watcher
2856       watcher_block.Close()
2857
2858
2859 def RunWhileClusterStopped(feedback_fn, fn, *args):
2860   """Calls a function while all cluster daemons are stopped.
2861
2862   @type feedback_fn: callable
2863   @param feedback_fn: Feedback function
2864   @type fn: callable
2865   @param fn: Function to be called when daemons are stopped
2866
2867   """
2868   feedback_fn("Gathering cluster information")
2869
2870   # This ensures we're running on the master daemon
2871   cl = GetClient()
2872
2873   (cluster_name, master_node) = \
2874     cl.QueryConfigValues(["cluster_name", "master_node"])
2875
2876   online_nodes = GetOnlineNodes([], cl=cl)
2877
2878   # Don't keep a reference to the client. The master daemon will go away.
2879   del cl
2880
2881   assert master_node in online_nodes
2882
2883   return _RunWhileClusterStoppedHelper(feedback_fn, cluster_name, master_node,
2884                                        online_nodes).Call(fn, *args)
2885
2886
2887 def GenerateTable(headers, fields, separator, data,
2888                   numfields=None, unitfields=None,
2889                   units=None):
2890   """Prints a table with headers and different fields.
2891
2892   @type headers: dict
2893   @param headers: dictionary mapping field names to headers for
2894       the table
2895   @type fields: list
2896   @param fields: the field names corresponding to each row in
2897       the data field
2898   @param separator: the separator to be used; if this is None,
2899       the default 'smart' algorithm is used which computes optimal
2900       field width, otherwise just the separator is used between
2901       each field
2902   @type data: list
2903   @param data: a list of lists, each sublist being one row to be output
2904   @type numfields: list
2905   @param numfields: a list with the fields that hold numeric
2906       values and thus should be right-aligned
2907   @type unitfields: list
2908   @param unitfields: a list with the fields that hold numeric
2909       values that should be formatted with the units field
2910   @type units: string or None
2911   @param units: the units we should use for formatting, or None for
2912       automatic choice (human-readable for non-separator usage, otherwise
2913       megabytes); this is a one-letter string
2914
2915   """
2916   if units is None:
2917     if separator:
2918       units = "m"
2919     else:
2920       units = "h"
2921
2922   if numfields is None:
2923     numfields = []
2924   if unitfields is None:
2925     unitfields = []
2926
2927   numfields = utils.FieldSet(*numfields)   # pylint: disable=W0142
2928   unitfields = utils.FieldSet(*unitfields) # pylint: disable=W0142
2929
2930   format_fields = []
2931   for field in fields:
2932     if headers and field not in headers:
2933       # TODO: handle better unknown fields (either revert to old
2934       # style of raising exception, or deal more intelligently with
2935       # variable fields)
2936       headers[field] = field
2937     if separator is not None:
2938       format_fields.append("%s")
2939     elif numfields.Matches(field):
2940       format_fields.append("%*s")
2941     else:
2942       format_fields.append("%-*s")
2943
2944   if separator is None:
2945     mlens = [0 for name in fields]
2946     format_str = " ".join(format_fields)
2947   else:
2948     format_str = separator.replace("%", "%%").join(format_fields)
2949
2950   for row in data:
2951     if row is None:
2952       continue
2953     for idx, val in enumerate(row):
2954       if unitfields.Matches(fields[idx]):
2955         try:
2956           val = int(val)
2957         except (TypeError, ValueError):
2958           pass
2959         else:
2960           val = row[idx] = utils.FormatUnit(val, units)
2961       val = row[idx] = str(val)
2962       if separator is None:
2963         mlens[idx] = max(mlens[idx], len(val))
2964
2965   result = []
2966   if headers:
2967     args = []
2968     for idx, name in enumerate(fields):
2969       hdr = headers[name]
2970       if separator is None:
2971         mlens[idx] = max(mlens[idx], len(hdr))
2972         args.append(mlens[idx])
2973       args.append(hdr)
2974     result.append(format_str % tuple(args))
2975
2976   if separator is None:
2977     assert len(mlens) == len(fields)
2978
2979     if fields and not numfields.Matches(fields[-1]):
2980       mlens[-1] = 0
2981
2982   for line in data:
2983     args = []
2984     if line is None:
2985       line = ["-" for _ in fields]
2986     for idx in range(len(fields)):
2987       if separator is None:
2988         args.append(mlens[idx])
2989       args.append(line[idx])
2990     result.append(format_str % tuple(args))
2991
2992   return result
2993
2994
2995 def _FormatBool(value):
2996   """Formats a boolean value as a string.
2997
2998   """
2999   if value:
3000     return "Y"
3001   return "N"
3002
3003
3004 #: Default formatting for query results; (callback, align right)
3005 _DEFAULT_FORMAT_QUERY = {
3006   constants.QFT_TEXT: (str, False),
3007   constants.QFT_BOOL: (_FormatBool, False),
3008   constants.QFT_NUMBER: (str, True),
3009   constants.QFT_TIMESTAMP: (utils.FormatTime, False),
3010   constants.QFT_OTHER: (str, False),
3011   constants.QFT_UNKNOWN: (str, False),
3012   }
3013
3014
3015 def _GetColumnFormatter(fdef, override, unit):
3016   """Returns formatting function for a field.
3017
3018   @type fdef: L{objects.QueryFieldDefinition}
3019   @type override: dict
3020   @param override: Dictionary for overriding field formatting functions,
3021     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
3022   @type unit: string
3023   @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT}
3024   @rtype: tuple; (callable, bool)
3025   @return: Returns the function to format a value (takes one parameter) and a
3026     boolean for aligning the value on the right-hand side
3027
3028   """
3029   fmt = override.get(fdef.name, None)
3030   if fmt is not None:
3031     return fmt
3032
3033   assert constants.QFT_UNIT not in _DEFAULT_FORMAT_QUERY
3034
3035   if fdef.kind == constants.QFT_UNIT:
3036     # Can't keep this information in the static dictionary
3037     return (lambda value: utils.FormatUnit(value, unit), True)
3038
3039   fmt = _DEFAULT_FORMAT_QUERY.get(fdef.kind, None)
3040   if fmt is not None:
3041     return fmt
3042
3043   raise NotImplementedError("Can't format column type '%s'" % fdef.kind)
3044
3045
3046 class _QueryColumnFormatter:
3047   """Callable class for formatting fields of a query.
3048
3049   """
3050   def __init__(self, fn, status_fn, verbose):
3051     """Initializes this class.
3052
3053     @type fn: callable
3054     @param fn: Formatting function
3055     @type status_fn: callable
3056     @param status_fn: Function to report fields' status
3057     @type verbose: boolean
3058     @param verbose: whether to use verbose field descriptions or not
3059
3060     """
3061     self._fn = fn
3062     self._status_fn = status_fn
3063     self._verbose = verbose
3064
3065   def __call__(self, data):
3066     """Returns a field's string representation.
3067
3068     """
3069     (status, value) = data
3070
3071     # Report status
3072     self._status_fn(status)
3073
3074     if status == constants.RS_NORMAL:
3075       return self._fn(value)
3076
3077     assert value is None, \
3078            "Found value %r for abnormal status %s" % (value, status)
3079
3080     return FormatResultError(status, self._verbose)
3081
3082
3083 def FormatResultError(status, verbose):
3084   """Formats result status other than L{constants.RS_NORMAL}.
3085
3086   @param status: The result status
3087   @type verbose: boolean
3088   @param verbose: Whether to return the verbose text
3089   @return: Text of result status
3090
3091   """
3092   assert status != constants.RS_NORMAL, \
3093          "FormatResultError called with status equal to constants.RS_NORMAL"
3094   try:
3095     (verbose_text, normal_text) = constants.RSS_DESCRIPTION[status]
3096   except KeyError:
3097     raise NotImplementedError("Unknown status %s" % status)
3098   else:
3099     if verbose:
3100       return verbose_text
3101     return normal_text
3102
3103
3104 def FormatQueryResult(result, unit=None, format_override=None, separator=None,
3105                       header=False, verbose=False):
3106   """Formats data in L{objects.QueryResponse}.
3107
3108   @type result: L{objects.QueryResponse}
3109   @param result: result of query operation
3110   @type unit: string
3111   @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT},
3112     see L{utils.text.FormatUnit}
3113   @type format_override: dict
3114   @param format_override: Dictionary for overriding field formatting functions,
3115     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
3116   @type separator: string or None
3117   @param separator: String used to separate fields
3118   @type header: bool
3119   @param header: Whether to output header row
3120   @type verbose: boolean
3121   @param verbose: whether to use verbose field descriptions or not
3122
3123   """
3124   if unit is None:
3125     if separator:
3126       unit = "m"
3127     else:
3128       unit = "h"
3129
3130   if format_override is None:
3131     format_override = {}
3132
3133   stats = dict.fromkeys(constants.RS_ALL, 0)
3134
3135   def _RecordStatus(status):
3136     if status in stats:
3137       stats[status] += 1
3138
3139   columns = []
3140   for fdef in result.fields:
3141     assert fdef.title and fdef.name
3142     (fn, align_right) = _GetColumnFormatter(fdef, format_override, unit)
3143     columns.append(TableColumn(fdef.title,
3144                                _QueryColumnFormatter(fn, _RecordStatus,
3145                                                      verbose),
3146                                align_right))
3147
3148   table = FormatTable(result.data, columns, header, separator)
3149
3150   # Collect statistics
3151   assert len(stats) == len(constants.RS_ALL)
3152   assert compat.all(count >= 0 for count in stats.values())
3153
3154   # Determine overall status. If there was no data, unknown fields must be
3155   # detected via the field definitions.
3156   if (stats[constants.RS_UNKNOWN] or
3157       (not result.data and _GetUnknownFields(result.fields))):
3158     status = QR_UNKNOWN
3159   elif compat.any(count > 0 for key, count in stats.items()
3160                   if key != constants.RS_NORMAL):
3161     status = QR_INCOMPLETE
3162   else:
3163     status = QR_NORMAL
3164
3165   return (status, table)
3166
3167
3168 def _GetUnknownFields(fdefs):
3169   """Returns list of unknown fields included in C{fdefs}.
3170
3171   @type fdefs: list of L{objects.QueryFieldDefinition}
3172
3173   """
3174   return [fdef for fdef in fdefs
3175           if fdef.kind == constants.QFT_UNKNOWN]
3176
3177
3178 def _WarnUnknownFields(fdefs):
3179   """Prints a warning to stderr if a query included unknown fields.
3180
3181   @type fdefs: list of L{objects.QueryFieldDefinition}
3182
3183   """
3184   unknown = _GetUnknownFields(fdefs)
3185   if unknown:
3186     ToStderr("Warning: Queried for unknown fields %s",
3187              utils.CommaJoin(fdef.name for fdef in unknown))
3188     return True
3189
3190   return False
3191
3192
3193 def GenericList(resource, fields, names, unit, separator, header, cl=None,
3194                 format_override=None, verbose=False, force_filter=False,
3195                 namefield=None, qfilter=None, isnumeric=False):
3196   """Generic implementation for listing all items of a resource.
3197
3198   @param resource: One of L{constants.QR_VIA_LUXI}
3199   @type fields: list of strings
3200   @param fields: List of fields to query for
3201   @type names: list of strings
3202   @param names: Names of items to query for
3203   @type unit: string or None
3204   @param unit: Unit used for formatting fields of type L{constants.QFT_UNIT} or
3205     None for automatic choice (human-readable for non-separator usage,
3206     otherwise megabytes); this is a one-letter string
3207   @type separator: string or None
3208   @param separator: String used to separate fields
3209   @type header: bool
3210   @param header: Whether to show header row
3211   @type force_filter: bool
3212   @param force_filter: Whether to always treat names as filter
3213   @type format_override: dict
3214   @param format_override: Dictionary for overriding field formatting functions,
3215     indexed by field name, contents like L{_DEFAULT_FORMAT_QUERY}
3216   @type verbose: boolean
3217   @param verbose: whether to use verbose field descriptions or not
3218   @type namefield: string
3219   @param namefield: Name of field to use for simple filters (see
3220     L{qlang.MakeFilter} for details)
3221   @type qfilter: list or None
3222   @param qfilter: Query filter (in addition to names)
3223   @param isnumeric: bool
3224   @param isnumeric: Whether the namefield's type is numeric, and therefore
3225     any simple filters built by namefield should use integer values to
3226     reflect that
3227
3228   """
3229   if not names:
3230     names = None
3231
3232   namefilter = qlang.MakeFilter(names, force_filter, namefield=namefield,
3233                                 isnumeric=isnumeric)
3234
3235   if qfilter is None:
3236     qfilter = namefilter
3237   elif namefilter is not None:
3238     qfilter = [qlang.OP_AND, namefilter, qfilter]
3239
3240   if cl is None:
3241     cl = GetClient()
3242
3243   response = cl.Query(resource, fields, qfilter)
3244
3245   found_unknown = _WarnUnknownFields(response.fields)
3246
3247   (status, data) = FormatQueryResult(response, unit=unit, separator=separator,
3248                                      header=header,
3249                                      format_override=format_override,
3250                                      verbose=verbose)
3251
3252   for line in data:
3253     ToStdout(line)
3254
3255   assert ((found_unknown and status == QR_UNKNOWN) or
3256           (not found_unknown and status != QR_UNKNOWN))
3257
3258   if status == QR_UNKNOWN:
3259     return constants.EXIT_UNKNOWN_FIELD
3260
3261   # TODO: Should the list command fail if not all data could be collected?
3262   return constants.EXIT_SUCCESS
3263
3264
3265 def _FieldDescValues(fdef):
3266   """Helper function for L{GenericListFields} to get query field description.
3267
3268   @type fdef: L{objects.QueryFieldDefinition}
3269   @rtype: list
3270
3271   """
3272   return [
3273     fdef.name,
3274     _QFT_NAMES.get(fdef.kind, fdef.kind),
3275     fdef.title,
3276     fdef.doc,
3277     ]
3278
3279
3280 def GenericListFields(resource, fields, separator, header, cl=None):
3281   """Generic implementation for listing fields for a resource.
3282
3283   @param resource: One of L{constants.QR_VIA_LUXI}
3284   @type fields: list of strings
3285   @param fields: List of fields to query for
3286   @type separator: string or None
3287   @param separator: String used to separate fields
3288   @type header: bool
3289   @param header: Whether to show header row
3290
3291   """
3292   if cl is None:
3293     cl = GetClient()
3294
3295   if not fields:
3296     fields = None
3297
3298   response = cl.QueryFields(resource, fields)
3299
3300   found_unknown = _WarnUnknownFields(response.fields)
3301
3302   columns = [
3303     TableColumn("Name", str, False),
3304     TableColumn("Type", str, False),
3305     TableColumn("Title", str, False),
3306     TableColumn("Description", str, False),
3307     ]
3308
3309   rows = map(_FieldDescValues, response.fields)
3310
3311   for line in FormatTable(rows, columns, header, separator):
3312     ToStdout(line)
3313
3314   if found_unknown:
3315     return constants.EXIT_UNKNOWN_FIELD
3316
3317   return constants.EXIT_SUCCESS
3318
3319
3320 class TableColumn:
3321   """Describes a column for L{FormatTable}.
3322
3323   """
3324   def __init__(self, title, fn, align_right):
3325     """Initializes this class.
3326
3327     @type title: string
3328     @param title: Column title
3329     @type fn: callable
3330     @param fn: Formatting function
3331     @type align_right: bool
3332     @param align_right: Whether to align values on the right-hand side
3333
3334     """
3335     self.title = title
3336     self.format = fn
3337     self.align_right = align_right
3338
3339
3340 def _GetColFormatString(width, align_right):
3341   """Returns the format string for a field.
3342
3343   """
3344   if align_right:
3345     sign = ""
3346   else:
3347     sign = "-"
3348
3349   return "%%%s%ss" % (sign, width)
3350
3351
3352 def FormatTable(rows, columns, header, separator):
3353   """Formats data as a table.
3354
3355   @type rows: list of lists
3356   @param rows: Row data, one list per row
3357   @type columns: list of L{TableColumn}
3358   @param columns: Column descriptions
3359   @type header: bool
3360   @param header: Whether to show header row
3361   @type separator: string or None
3362   @param separator: String used to separate columns
3363
3364   """
3365   if header:
3366     data = [[col.title for col in columns]]
3367     colwidth = [len(col.title) for col in columns]
3368   else:
3369     data = []
3370     colwidth = [0 for _ in columns]
3371
3372   # Format row data
3373   for row in rows:
3374     assert len(row) == len(columns)
3375
3376     formatted = [col.format(value) for value, col in zip(row, columns)]
3377
3378     if separator is None:
3379       # Update column widths
3380       for idx, (oldwidth, value) in enumerate(zip(colwidth, formatted)):
3381         # Modifying a list's items while iterating is fine
3382         colwidth[idx] = max(oldwidth, len(value))
3383
3384     data.append(formatted)
3385
3386   if separator is not None:
3387     # Return early if a separator is used
3388     return [separator.join(row) for row in data]
3389
3390   if columns and not columns[-1].align_right:
3391     # Avoid unnecessary spaces at end of line
3392     colwidth[-1] = 0
3393
3394   # Build format string
3395   fmt = " ".join([_GetColFormatString(width, col.align_right)
3396                   for col, width in zip(columns, colwidth)])
3397
3398   return [fmt % tuple(row) for row in data]
3399
3400
3401 def FormatTimestamp(ts):
3402   """Formats a given timestamp.
3403
3404   @type ts: timestamp
3405   @param ts: a timeval-type timestamp, a tuple of seconds and microseconds
3406
3407   @rtype: string
3408   @return: a string with the formatted timestamp
3409
3410   """
3411   if not isinstance(ts, (tuple, list)) or len(ts) != 2:
3412     return "?"
3413
3414   (sec, usecs) = ts
3415   return utils.FormatTime(sec, usecs=usecs)
3416
3417
3418 def ParseTimespec(value):
3419   """Parse a time specification.
3420
3421   The following suffixed will be recognized:
3422
3423     - s: seconds
3424     - m: minutes
3425     - h: hours
3426     - d: day
3427     - w: weeks
3428
3429   Without any suffix, the value will be taken to be in seconds.
3430
3431   """
3432   value = str(value)
3433   if not value:
3434     raise errors.OpPrereqError("Empty time specification passed",
3435                                errors.ECODE_INVAL)
3436   suffix_map = {
3437     "s": 1,
3438     "m": 60,
3439     "h": 3600,
3440     "d": 86400,
3441     "w": 604800,
3442     }
3443   if value[-1] not in suffix_map:
3444     try:
3445       value = int(value)
3446     except (TypeError, ValueError):
3447       raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3448                                  errors.ECODE_INVAL)
3449   else:
3450     multiplier = suffix_map[value[-1]]
3451     value = value[:-1]
3452     if not value: # no data left after stripping the suffix
3453       raise errors.OpPrereqError("Invalid time specification (only"
3454                                  " suffix passed)", errors.ECODE_INVAL)
3455     try:
3456       value = int(value) * multiplier
3457     except (TypeError, ValueError):
3458       raise errors.OpPrereqError("Invalid time specification '%s'" % value,
3459                                  errors.ECODE_INVAL)
3460   return value
3461
3462
3463 def GetOnlineNodes(nodes, cl=None, nowarn=False, secondary_ips=False,
3464                    filter_master=False, nodegroup=None):
3465   """Returns the names of online nodes.
3466
3467   This function will also log a warning on stderr with the names of
3468   the online nodes.
3469
3470   @param nodes: if not empty, use only this subset of nodes (minus the
3471       offline ones)
3472   @param cl: if not None, luxi client to use
3473   @type nowarn: boolean
3474   @param nowarn: by default, this function will output a note with the
3475       offline nodes that are skipped; if this parameter is True the
3476       note is not displayed
3477   @type secondary_ips: boolean
3478   @param secondary_ips: if True, return the secondary IPs instead of the
3479       names, useful for doing network traffic over the replication interface
3480       (if any)
3481   @type filter_master: boolean
3482   @param filter_master: if True, do not return the master node in the list
3483       (useful in coordination with secondary_ips where we cannot check our
3484       node name against the list)
3485   @type nodegroup: string
3486   @param nodegroup: If set, only return nodes in this node group
3487
3488   """
3489   if cl is None:
3490     cl = GetClient()
3491
3492   qfilter = []
3493
3494   if nodes:
3495     qfilter.append(qlang.MakeSimpleFilter("name", nodes))
3496
3497   if nodegroup is not None:
3498     qfilter.append([qlang.OP_OR, [qlang.OP_EQUAL, "group", nodegroup],
3499                                  [qlang.OP_EQUAL, "group.uuid", nodegroup]])
3500
3501   if filter_master:
3502     qfilter.append([qlang.OP_NOT, [qlang.OP_TRUE, "master"]])
3503
3504   if qfilter:
3505     if len(qfilter) > 1:
3506       final_filter = [qlang.OP_AND] + qfilter
3507     else:
3508       assert len(qfilter) == 1
3509       final_filter = qfilter[0]
3510   else:
3511     final_filter = None
3512
3513   result = cl.Query(constants.QR_NODE, ["name", "offline", "sip"], final_filter)
3514
3515   def _IsOffline(row):
3516     (_, (_, offline), _) = row
3517     return offline
3518
3519   def _GetName(row):
3520     ((_, name), _, _) = row
3521     return name
3522
3523   def _GetSip(row):
3524     (_, _, (_, sip)) = row
3525     return sip
3526
3527   (offline, online) = compat.partition(result.data, _IsOffline)
3528
3529   if offline and not nowarn:
3530     ToStderr("Note: skipping offline node(s): %s" %
3531              utils.CommaJoin(map(_GetName, offline)))
3532
3533   if secondary_ips:
3534     fn = _GetSip
3535   else:
3536     fn = _GetName
3537
3538   return map(fn, online)
3539
3540
3541 def _ToStream(stream, txt, *args):
3542   """Write a message to a stream, bypassing the logging system
3543
3544   @type stream: file object
3545   @param stream: the file to which we should write
3546   @type txt: str
3547   @param txt: the message
3548
3549   """
3550   try:
3551     if args:
3552       args = tuple(args)
3553       stream.write(txt % args)
3554     else:
3555       stream.write(txt)
3556     stream.write("\n")
3557     stream.flush()
3558   except IOError, err:
3559     if err.errno == errno.EPIPE:
3560       # our terminal went away, we'll exit
3561       sys.exit(constants.EXIT_FAILURE)
3562     else:
3563       raise
3564
3565
3566 def ToStdout(txt, *args):
3567   """Write a message to stdout only, bypassing the logging system
3568
3569   This is just a wrapper over _ToStream.
3570
3571   @type txt: str
3572   @param txt: the message
3573
3574   """
3575   _ToStream(sys.stdout, txt, *args)
3576
3577
3578 def ToStderr(txt, *args):
3579   """Write a message to stderr only, bypassing the logging system
3580
3581   This is just a wrapper over _ToStream.
3582
3583   @type txt: str
3584   @param txt: the message
3585
3586   """
3587   _ToStream(sys.stderr, txt, *args)
3588
3589
3590 class JobExecutor(object):
3591   """Class which manages the submission and execution of multiple jobs.
3592
3593   Note that instances of this class should not be reused between
3594   GetResults() calls.
3595
3596   """
3597   def __init__(self, cl=None, verbose=True, opts=None, feedback_fn=None):
3598     self.queue = []
3599     if cl is None:
3600       cl = GetClient()
3601     self.cl = cl
3602     self.verbose = verbose
3603     self.jobs = []
3604     self.opts = opts
3605     self.feedback_fn = feedback_fn
3606     self._counter = itertools.count()
3607
3608   @staticmethod
3609   def _IfName(name, fmt):
3610     """Helper function for formatting name.
3611
3612     """
3613     if name:
3614       return fmt % name
3615
3616     return ""
3617
3618   def QueueJob(self, name, *ops):
3619     """Record a job for later submit.
3620
3621     @type name: string
3622     @param name: a description of the job, will be used in WaitJobSet
3623
3624     """
3625     SetGenericOpcodeOpts(ops, self.opts)
3626     self.queue.append((self._counter.next(), name, ops))
3627
3628   def AddJobId(self, name, status, job_id):
3629     """Adds a job ID to the internal queue.
3630
3631     """
3632     self.jobs.append((self._counter.next(), status, job_id, name))
3633
3634   def SubmitPending(self, each=False):
3635     """Submit all pending jobs.
3636
3637     """
3638     if each:
3639       results = []
3640       for (_, _, ops) in self.queue:
3641         # SubmitJob will remove the success status, but raise an exception if
3642         # the submission fails, so we'll notice that anyway.
3643         results.append([True, self.cl.SubmitJob(ops)[0]])
3644     else:
3645       results = self.cl.SubmitManyJobs([ops for (_, _, ops) in self.queue])
3646     for ((status, data), (idx, name, _)) in zip(results, self.queue):
3647       self.jobs.append((idx, status, data, name))
3648
3649   def _ChooseJob(self):
3650     """Choose a non-waiting/queued job to poll next.
3651
3652     """
3653     assert self.jobs, "_ChooseJob called with empty job list"
3654
3655     result = self.cl.QueryJobs([i[2] for i in self.jobs[:_CHOOSE_BATCH]],
3656                                ["status"])
3657     assert result
3658
3659     for job_data, status in zip(self.jobs, result):
3660       if (isinstance(status, list) and status and
3661           status[0] in (constants.JOB_STATUS_QUEUED,
3662                         constants.JOB_STATUS_WAITING,
3663                         constants.JOB_STATUS_CANCELING)):
3664         # job is still present and waiting
3665         continue
3666       # good candidate found (either running job or lost job)
3667       self.jobs.remove(job_data)
3668       return job_data
3669
3670     # no job found
3671     return self.jobs.pop(0)
3672
3673   def GetResults(self):
3674     """Wait for and return the results of all jobs.
3675
3676     @rtype: list
3677     @return: list of tuples (success, job results), in the same order
3678         as the submitted jobs; if a job has failed, instead of the result
3679         there will be the error message
3680
3681     """
3682     if not self.jobs:
3683       self.SubmitPending()
3684     results = []
3685     if self.verbose:
3686       ok_jobs = [row[2] for row in self.jobs if row[1]]
3687       if ok_jobs:
3688         ToStdout("Submitted jobs %s", utils.CommaJoin(ok_jobs))
3689
3690     # first, remove any non-submitted jobs
3691     self.jobs, failures = compat.partition(self.jobs, lambda x: x[1])
3692     for idx, _, jid, name in failures:
3693       ToStderr("Failed to submit job%s: %s", self._IfName(name, " for %s"), jid)
3694       results.append((idx, False, jid))
3695
3696     while self.jobs:
3697       (idx, _, jid, name) = self._ChooseJob()
3698       ToStdout("Waiting for job %s%s ...", jid, self._IfName(name, " for %s"))
3699       try:
3700         job_result = PollJob(jid, cl=self.cl, feedback_fn=self.feedback_fn)
3701         success = True
3702       except errors.JobLost, err:
3703         _, job_result = FormatError(err)
3704         ToStderr("Job %s%s has been archived, cannot check its result",
3705                  jid, self._IfName(name, " for %s"))
3706         success = False
3707       except (errors.GenericError, luxi.ProtocolError), err:
3708         _, job_result = FormatError(err)
3709         success = False
3710         # the error message will always be shown, verbose or not
3711         ToStderr("Job %s%s has failed: %s",
3712                  jid, self._IfName(name, " for %s"), job_result)
3713
3714       results.append((idx, success, job_result))
3715
3716     # sort based on the index, then drop it
3717     results.sort()
3718     results = [i[1:] for i in results]
3719
3720     return results
3721
3722   def WaitOrShow(self, wait):
3723     """Wait for job results or only print the job IDs.
3724
3725     @type wait: boolean
3726     @param wait: whether to wait or not
3727
3728     """
3729     if wait:
3730       return self.GetResults()
3731     else:
3732       if not self.jobs:
3733         self.SubmitPending()
3734       for _, status, result, name in self.jobs:
3735         if status:
3736           ToStdout("%s: %s", result, name)
3737         else:
3738           ToStderr("Failure for %s: %s", name, result)
3739       return [row[1:3] for row in self.jobs]
3740
3741
3742 def FormatParamsDictInfo(param_dict, actual):
3743   """Formats a parameter dictionary.
3744
3745   @type param_dict: dict
3746   @param param_dict: the own parameters
3747   @type actual: dict
3748   @param actual: the current parameter set (including defaults)
3749   @rtype: dict
3750   @return: dictionary where the value of each parameter is either a fully
3751       formatted string or a dictionary containing formatted strings
3752
3753   """
3754   ret = {}
3755   for (key, data) in actual.items():
3756     if isinstance(data, dict) and data:
3757       ret[key] = FormatParamsDictInfo(param_dict.get(key, {}), data)
3758     else:
3759       ret[key] = str(param_dict.get(key, "default (%s)" % data))
3760   return ret
3761
3762
3763 def _FormatListInfoDefault(data, def_data):
3764   if data is not None:
3765     ret = utils.CommaJoin(data)
3766   else:
3767     ret = "default (%s)" % utils.CommaJoin(def_data)
3768   return ret
3769
3770
3771 def FormatPolicyInfo(custom_ipolicy, eff_ipolicy, iscluster):
3772   """Formats an instance policy.
3773
3774   @type custom_ipolicy: dict
3775   @param custom_ipolicy: own policy
3776   @type eff_ipolicy: dict
3777   @param eff_ipolicy: effective policy (including defaults); ignored for
3778       cluster
3779   @type iscluster: bool
3780   @param iscluster: the policy is at cluster level
3781   @rtype: list of pairs
3782   @return: formatted data, suitable for L{PrintGenericInfo}
3783
3784   """
3785   if iscluster:
3786     eff_ipolicy = custom_ipolicy
3787
3788   minmax_out = []
3789   custom_minmax = custom_ipolicy.get(constants.ISPECS_MINMAX)
3790   if custom_minmax:
3791     for (k, minmax) in enumerate(custom_minmax):
3792       minmax_out.append([
3793         ("%s/%s" % (key, k),
3794          FormatParamsDictInfo(minmax[key], minmax[key]))
3795         for key in constants.ISPECS_MINMAX_KEYS
3796         ])
3797   else:
3798     for (k, minmax) in enumerate(eff_ipolicy[constants.ISPECS_MINMAX]):
3799       minmax_out.append([
3800         ("%s/%s" % (key, k),
3801          FormatParamsDictInfo({}, minmax[key]))
3802         for key in constants.ISPECS_MINMAX_KEYS
3803         ])
3804   ret = [("bounds specs", minmax_out)]
3805
3806   if iscluster:
3807     stdspecs = custom_ipolicy[constants.ISPECS_STD]
3808     ret.append(
3809       (constants.ISPECS_STD,
3810        FormatParamsDictInfo(stdspecs, stdspecs))
3811       )
3812
3813   ret.append(
3814     ("allowed disk templates",
3815      _FormatListInfoDefault(custom_ipolicy.get(constants.IPOLICY_DTS),
3816                             eff_ipolicy[constants.IPOLICY_DTS]))
3817     )
3818   ret.extend([
3819     (key, str(custom_ipolicy.get(key, "default (%s)" % eff_ipolicy[key])))
3820     for key in constants.IPOLICY_PARAMETERS
3821     ])
3822   return ret
3823
3824
3825 def _PrintSpecsParameters(buf, specs):
3826   values = ("%s=%s" % (par, val) for (par, val) in sorted(specs.items()))
3827   buf.write(",".join(values))
3828
3829
3830 def PrintIPolicyCommand(buf, ipolicy, isgroup):
3831   """Print the command option used to generate the given instance policy.
3832
3833   Currently only the parts dealing with specs are supported.
3834
3835   @type buf: StringIO
3836   @param buf: stream to write into
3837   @type ipolicy: dict
3838   @param ipolicy: instance policy
3839   @type isgroup: bool
3840   @param isgroup: whether the policy is at group level
3841
3842   """
3843   if not isgroup:
3844     stdspecs = ipolicy.get("std")
3845     if stdspecs:
3846       buf.write(" %s " % IPOLICY_STD_SPECS_STR)
3847       _PrintSpecsParameters(buf, stdspecs)
3848   minmaxes = ipolicy.get("minmax", [])
3849   first = True
3850   for minmax in minmaxes:
3851     minspecs = minmax.get("min")
3852     maxspecs = minmax.get("max")
3853     if minspecs and maxspecs:
3854       if first:
3855         buf.write(" %s " % IPOLICY_BOUNDS_SPECS_STR)
3856         first = False
3857       else:
3858         buf.write("//")
3859       buf.write("min:")
3860       _PrintSpecsParameters(buf, minspecs)
3861       buf.write("/max:")
3862       _PrintSpecsParameters(buf, maxspecs)
3863
3864
3865 def ConfirmOperation(names, list_type, text, extra=""):
3866   """Ask the user to confirm an operation on a list of list_type.
3867
3868   This function is used to request confirmation for doing an operation
3869   on a given list of list_type.
3870
3871   @type names: list
3872   @param names: the list of names that we display when
3873       we ask for confirmation
3874   @type list_type: str
3875   @param list_type: Human readable name for elements in the list (e.g. nodes)
3876   @type text: str
3877   @param text: the operation that the user should confirm
3878   @rtype: boolean
3879   @return: True or False depending on user's confirmation.
3880
3881   """
3882   count = len(names)
3883   msg = ("The %s will operate on %d %s.\n%s"
3884          "Do you want to continue?" % (text, count, list_type, extra))
3885   affected = (("\nAffected %s:\n" % list_type) +
3886               "\n".join(["  %s" % name for name in names]))
3887
3888   choices = [("y", True, "Yes, execute the %s" % text),
3889              ("n", False, "No, abort the %s" % text)]
3890
3891   if count > 20:
3892     choices.insert(1, ("v", "v", "View the list of affected %s" % list_type))
3893     question = msg
3894   else:
3895     question = msg + affected
3896
3897   choice = AskUser(question, choices)
3898   if choice == "v":
3899     choices.pop(1)
3900     choice = AskUser(msg + affected, choices)
3901   return choice
3902
3903
3904 def _MaybeParseUnit(elements):
3905   """Parses and returns an array of potential values with units.
3906
3907   """
3908   parsed = {}
3909   for k, v in elements.items():
3910     if v == constants.VALUE_DEFAULT:
3911       parsed[k] = v
3912     else:
3913       parsed[k] = utils.ParseUnit(v)
3914   return parsed
3915
3916
3917 def _InitISpecsFromSplitOpts(ipolicy, ispecs_mem_size, ispecs_cpu_count,
3918                              ispecs_disk_count, ispecs_disk_size,
3919                              ispecs_nic_count, group_ipolicy, fill_all):
3920   try:
3921     if ispecs_mem_size:
3922       ispecs_mem_size = _MaybeParseUnit(ispecs_mem_size)
3923     if ispecs_disk_size:
3924       ispecs_disk_size = _MaybeParseUnit(ispecs_disk_size)
3925   except (TypeError, ValueError, errors.UnitParseError), err:
3926     raise errors.OpPrereqError("Invalid disk (%s) or memory (%s) size"
3927                                " in policy: %s" %
3928                                (ispecs_disk_size, ispecs_mem_size, err),
3929                                errors.ECODE_INVAL)
3930
3931   # prepare ipolicy dict
3932   ispecs_transposed = {
3933     constants.ISPEC_MEM_SIZE: ispecs_mem_size,
3934     constants.ISPEC_CPU_COUNT: ispecs_cpu_count,
3935     constants.ISPEC_DISK_COUNT: ispecs_disk_count,
3936     constants.ISPEC_DISK_SIZE: ispecs_disk_size,
3937     constants.ISPEC_NIC_COUNT: ispecs_nic_count,
3938     }
3939
3940   # first, check that the values given are correct
3941   if group_ipolicy:
3942     forced_type = TISPECS_GROUP_TYPES
3943   else:
3944     forced_type = TISPECS_CLUSTER_TYPES
3945   for specs in ispecs_transposed.values():
3946     assert type(specs) is dict
3947     utils.ForceDictType(specs, forced_type)
3948
3949   # then transpose
3950   ispecs = {
3951     constants.ISPECS_MIN: {},
3952     constants.ISPECS_MAX: {},
3953     constants.ISPECS_STD: {},
3954     }
3955   for (name, specs) in ispecs_transposed.iteritems():
3956     assert name in constants.ISPECS_PARAMETERS
3957     for key, val in specs.items(): # {min: .. ,max: .., std: ..}
3958       assert key in ispecs
3959       ispecs[key][name] = val
3960   minmax_out = {}
3961   for key in constants.ISPECS_MINMAX_KEYS:
3962     if fill_all:
3963       minmax_out[key] = \
3964         objects.FillDict(constants.ISPECS_MINMAX_DEFAULTS[key], ispecs[key])
3965     else:
3966       minmax_out[key] = ispecs[key]
3967   ipolicy[constants.ISPECS_MINMAX] = [minmax_out]
3968   if fill_all:
3969     ipolicy[constants.ISPECS_STD] = \
3970         objects.FillDict(constants.IPOLICY_DEFAULTS[constants.ISPECS_STD],
3971                          ispecs[constants.ISPECS_STD])
3972   else:
3973     ipolicy[constants.ISPECS_STD] = ispecs[constants.ISPECS_STD]
3974
3975
3976 def _ParseSpecUnit(spec, keyname):
3977   ret = spec.copy()
3978   for k in [constants.ISPEC_DISK_SIZE, constants.ISPEC_MEM_SIZE]:
3979     if k in ret:
3980       try:
3981         ret[k] = utils.ParseUnit(ret[k])
3982       except (TypeError, ValueError, errors.UnitParseError), err:
3983         raise errors.OpPrereqError(("Invalid parameter %s (%s) in %s instance"
3984                                     " specs: %s" % (k, ret[k], keyname, err)),
3985                                    errors.ECODE_INVAL)
3986   return ret
3987
3988
3989 def _ParseISpec(spec, keyname, required):
3990   ret = _ParseSpecUnit(spec, keyname)
3991   utils.ForceDictType(ret, constants.ISPECS_PARAMETER_TYPES)
3992   missing = constants.ISPECS_PARAMETERS - frozenset(ret.keys())
3993   if required and missing:
3994     raise errors.OpPrereqError("Missing parameters in ipolicy spec %s: %s" %
3995                                (keyname, utils.CommaJoin(missing)),
3996                                errors.ECODE_INVAL)
3997   return ret
3998
3999
4000 def _GetISpecsInAllowedValues(minmax_ispecs, allowed_values):
4001   ret = None
4002   if (minmax_ispecs and allowed_values and len(minmax_ispecs) == 1 and
4003       len(minmax_ispecs[0]) == 1):
4004     for (key, spec) in minmax_ispecs[0].items():
4005       # This loop is executed exactly once
4006       if key in allowed_values and not spec:
4007         ret = key
4008   return ret
4009
4010
4011 def _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs,
4012                             group_ipolicy, allowed_values):
4013   found_allowed = _GetISpecsInAllowedValues(minmax_ispecs, allowed_values)
4014   if found_allowed is not None:
4015     ipolicy_out[constants.ISPECS_MINMAX] = found_allowed
4016   elif minmax_ispecs is not None:
4017     minmax_out = []
4018     for mmpair in minmax_ispecs:
4019       mmpair_out = {}
4020       for (key, spec) in mmpair.items():
4021         if key not in constants.ISPECS_MINMAX_KEYS:
4022           msg = "Invalid key in bounds instance specifications: %s" % key
4023           raise errors.OpPrereqError(msg, errors.ECODE_INVAL)
4024         mmpair_out[key] = _ParseISpec(spec, key, True)
4025       minmax_out.append(mmpair_out)
4026     ipolicy_out[constants.ISPECS_MINMAX] = minmax_out
4027   if std_ispecs is not None:
4028     assert not group_ipolicy # This is not an option for gnt-group
4029     ipolicy_out[constants.ISPECS_STD] = _ParseISpec(std_ispecs, "std", False)
4030
4031
4032 def CreateIPolicyFromOpts(ispecs_mem_size=None,
4033                           ispecs_cpu_count=None,
4034                           ispecs_disk_count=None,
4035                           ispecs_disk_size=None,
4036                           ispecs_nic_count=None,
4037                           minmax_ispecs=None,
4038                           std_ispecs=None,
4039                           ipolicy_disk_templates=None,
4040                           ipolicy_vcpu_ratio=None,
4041                           ipolicy_spindle_ratio=None,
4042                           group_ipolicy=False,
4043                           allowed_values=None,
4044                           fill_all=False):
4045   """Creation of instance policy based on command line options.
4046
4047   @param fill_all: whether for cluster policies we should ensure that
4048     all values are filled
4049
4050   """
4051   assert not (fill_all and allowed_values)
4052
4053   split_specs = (ispecs_mem_size or ispecs_cpu_count or ispecs_disk_count or
4054                  ispecs_disk_size or ispecs_nic_count)
4055   if (split_specs and (minmax_ispecs is not None or std_ispecs is not None)):
4056     raise errors.OpPrereqError("A --specs-xxx option cannot be specified"
4057                                " together with any --ipolicy-xxx-specs option",
4058                                errors.ECODE_INVAL)
4059
4060   ipolicy_out = objects.MakeEmptyIPolicy()
4061   if split_specs:
4062     assert fill_all
4063     _InitISpecsFromSplitOpts(ipolicy_out, ispecs_mem_size, ispecs_cpu_count,
4064                              ispecs_disk_count, ispecs_disk_size,
4065                              ispecs_nic_count, group_ipolicy, fill_all)
4066   elif (minmax_ispecs is not None or std_ispecs is not None):
4067     _InitISpecsFromFullOpts(ipolicy_out, minmax_ispecs, std_ispecs,
4068                             group_ipolicy, allowed_values)
4069
4070   if ipolicy_disk_templates is not None:
4071     if allowed_values and ipolicy_disk_templates in allowed_values:
4072       ipolicy_out[constants.IPOLICY_DTS] = ipolicy_disk_templates
4073     else:
4074       ipolicy_out[constants.IPOLICY_DTS] = list(ipolicy_disk_templates)
4075   if ipolicy_vcpu_ratio is not None:
4076     ipolicy_out[constants.IPOLICY_VCPU_RATIO] = ipolicy_vcpu_ratio
4077   if ipolicy_spindle_ratio is not None:
4078     ipolicy_out[constants.IPOLICY_SPINDLE_RATIO] = ipolicy_spindle_ratio
4079
4080   assert not (frozenset(ipolicy_out.keys()) - constants.IPOLICY_ALL_KEYS)
4081
4082   if not group_ipolicy and fill_all:
4083     ipolicy_out = objects.FillIPolicy(constants.IPOLICY_DEFAULTS, ipolicy_out)
4084
4085   return ipolicy_out
4086
4087
4088 def _SerializeGenericInfo(buf, data, level, afterkey=False):
4089   """Formatting core of L{PrintGenericInfo}.
4090
4091   @param buf: (string) stream to accumulate the result into
4092   @param data: data to format
4093   @type level: int
4094   @param level: depth in the data hierarchy, used for indenting
4095   @type afterkey: bool
4096   @param afterkey: True when we are in the middle of a line after a key (used
4097       to properly add newlines or indentation)
4098
4099   """
4100   baseind = "  "
4101   if isinstance(data, dict):
4102     if not data:
4103       buf.write("\n")
4104     else:
4105       if afterkey:
4106         buf.write("\n")
4107         doindent = True
4108       else:
4109         doindent = False
4110       for key in sorted(data):
4111         if doindent:
4112           buf.write(baseind * level)
4113         else:
4114           doindent = True
4115         buf.write(key)
4116         buf.write(": ")
4117         _SerializeGenericInfo(buf, data[key], level + 1, afterkey=True)
4118   elif isinstance(data, list) and len(data) > 0 and isinstance(data[0], tuple):
4119     # list of tuples (an ordered dictionary)
4120     if afterkey:
4121       buf.write("\n")
4122       doindent = True
4123     else:
4124       doindent = False
4125     for (key, val) in data:
4126       if doindent:
4127         buf.write(baseind * level)
4128       else:
4129         doindent = True
4130       buf.write(key)
4131       buf.write(": ")
4132       _SerializeGenericInfo(buf, val, level + 1, afterkey=True)
4133   elif isinstance(data, list):
4134     if not data:
4135       buf.write("\n")
4136     else:
4137       if afterkey:
4138         buf.write("\n")
4139         doindent = True
4140       else:
4141         doindent = False
4142       for item in data:
4143         if doindent:
4144           buf.write(baseind * level)
4145         else:
4146           doindent = True
4147         buf.write("-")
4148         buf.write(baseind[1:])
4149         _SerializeGenericInfo(buf, item, level + 1)
4150   else:
4151     # This branch should be only taken for strings, but it's practically
4152     # impossible to guarantee that no other types are produced somewhere
4153     buf.write(str(data))
4154     buf.write("\n")
4155
4156
4157 def PrintGenericInfo(data):
4158   """Print information formatted according to the hierarchy.
4159
4160   The output is a valid YAML string.
4161
4162   @param data: the data to print. It's a hierarchical structure whose elements
4163       can be:
4164         - dictionaries, where keys are strings and values are of any of the
4165           types listed here
4166         - lists of pairs (key, value), where key is a string and value is of
4167           any of the types listed here; it's a way to encode ordered
4168           dictionaries
4169         - lists of any of the types listed here
4170         - strings
4171
4172   """
4173   buf = StringIO()
4174   _SerializeGenericInfo(buf, data, 0)
4175   ToStdout(buf.getvalue().rstrip("\n"))