Let gnt-cluster support prealloc_wipe_disks
[ganeti-local] / scripts / gnt-instance
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007, 2008, 2009, 2010 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 """Instance related commands"""
22
23 # pylint: disable-msg=W0401,W0614,C0103
24 # W0401: Wildcard import ganeti.cli
25 # W0614: Unused import %s from wildcard import (since we need cli)
26 # C0103: Invalid name gnt-instance
27
28 import sys
29 import os
30 import itertools
31 import simplejson
32 from cStringIO import StringIO
33
34 from ganeti.cli import *
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import compat
38 from ganeti import utils
39 from ganeti import errors
40 from ganeti import netutils
41
42
43 _SHUTDOWN_CLUSTER = "cluster"
44 _SHUTDOWN_NODES_BOTH = "nodes"
45 _SHUTDOWN_NODES_PRI = "nodes-pri"
46 _SHUTDOWN_NODES_SEC = "nodes-sec"
47 _SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
48 _SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
49 _SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
50 _SHUTDOWN_INSTANCES = "instances"
51 _SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
52
53 _SHUTDOWN_NODES_TAGS_MODES = (
54     _SHUTDOWN_NODES_BOTH_BY_TAGS,
55     _SHUTDOWN_NODES_PRI_BY_TAGS,
56     _SHUTDOWN_NODES_SEC_BY_TAGS)
57
58
59 _VALUE_TRUE = "true"
60
61 #: default list of options for L{ListInstances}
62 _LIST_DEF_FIELDS = [
63   "name", "hypervisor", "os", "pnode", "status", "oper_ram",
64   ]
65
66
67 def _ExpandMultiNames(mode, names, client=None):
68   """Expand the given names using the passed mode.
69
70   For _SHUTDOWN_CLUSTER, all instances will be returned. For
71   _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
72   primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
73   instances having those nodes as either primary or secondary will be
74   returned. For _SHUTDOWN_INSTANCES, the given instances will be
75   returned.
76
77   @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
78       L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
79       L{_SHUTDOWN_INSTANCES}
80   @param names: a list of names; for cluster, it must be empty,
81       and for node and instance it must be a list of valid item
82       names (short names are valid as usual, e.g. node1 instead of
83       node1.example.com)
84   @rtype: list
85   @return: the list of names after the expansion
86   @raise errors.ProgrammerError: for unknown selection type
87   @raise errors.OpPrereqError: for invalid input parameters
88
89   """
90   # pylint: disable-msg=W0142
91
92   if client is None:
93     client = GetClient()
94   if mode == _SHUTDOWN_CLUSTER:
95     if names:
96       raise errors.OpPrereqError("Cluster filter mode takes no arguments",
97                                  errors.ECODE_INVAL)
98     idata = client.QueryInstances([], ["name"], False)
99     inames = [row[0] for row in idata]
100
101   elif mode in (_SHUTDOWN_NODES_BOTH,
102                 _SHUTDOWN_NODES_PRI,
103                 _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
104     if mode in _SHUTDOWN_NODES_TAGS_MODES:
105       if not names:
106         raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
107       ndata = client.QueryNodes([], ["name", "pinst_list",
108                                      "sinst_list", "tags"], False)
109       ndata = [row for row in ndata if set(row[3]).intersection(names)]
110     else:
111       if not names:
112         raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
113       ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
114                               False)
115
116     ipri = [row[1] for row in ndata]
117     pri_names = list(itertools.chain(*ipri))
118     isec = [row[2] for row in ndata]
119     sec_names = list(itertools.chain(*isec))
120     if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
121       inames = pri_names + sec_names
122     elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
123       inames = pri_names
124     elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
125       inames = sec_names
126     else:
127       raise errors.ProgrammerError("Unhandled shutdown type")
128   elif mode == _SHUTDOWN_INSTANCES:
129     if not names:
130       raise errors.OpPrereqError("No instance names passed",
131                                  errors.ECODE_INVAL)
132     idata = client.QueryInstances(names, ["name"], False)
133     inames = [row[0] for row in idata]
134   elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
135     if not names:
136       raise errors.OpPrereqError("No instance tags passed",
137                                  errors.ECODE_INVAL)
138     idata = client.QueryInstances([], ["name", "tags"], False)
139     inames = [row[0] for row in idata if set(row[1]).intersection(names)]
140   else:
141     raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
142
143   return inames
144
145
146 def _ConfirmOperation(inames, text, extra=""):
147   """Ask the user to confirm an operation on a list of instances.
148
149   This function is used to request confirmation for doing an operation
150   on a given list of instances.
151
152   @type inames: list
153   @param inames: the list of names that we display when
154       we ask for confirmation
155   @type text: str
156   @param text: the operation that the user should confirm
157       (e.g. I{shutdown} or I{startup})
158   @rtype: boolean
159   @return: True or False depending on user's confirmation.
160
161   """
162   count = len(inames)
163   msg = ("The %s will operate on %d instances.\n%s"
164          "Do you want to continue?" % (text, count, extra))
165   affected = ("\nAffected instances:\n" +
166               "\n".join(["  %s" % name for name in inames]))
167
168   choices = [('y', True, 'Yes, execute the %s' % text),
169              ('n', False, 'No, abort the %s' % text)]
170
171   if count > 20:
172     choices.insert(1, ('v', 'v', 'View the list of affected instances'))
173     ask = msg
174   else:
175     ask = msg + affected
176
177   choice = AskUser(ask, choices)
178   if choice == 'v':
179     choices.pop(1)
180     choice = AskUser(msg + affected, choices)
181   return choice
182
183
184 def _EnsureInstancesExist(client, names):
185   """Check for and ensure the given instance names exist.
186
187   This function will raise an OpPrereqError in case they don't
188   exist. Otherwise it will exit cleanly.
189
190   @type client: L{ganeti.luxi.Client}
191   @param client: the client to use for the query
192   @type names: list
193   @param names: the list of instance names to query
194   @raise errors.OpPrereqError: in case any instance is missing
195
196   """
197   # TODO: change LUQueryInstances to that it actually returns None
198   # instead of raising an exception, or devise a better mechanism
199   result = client.QueryInstances(names, ["name"], False)
200   for orig_name, row in zip(names, result):
201     if row[0] is None:
202       raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
203                                  errors.ECODE_NOENT)
204
205
206 def GenericManyOps(operation, fn):
207   """Generic multi-instance operations.
208
209   The will return a wrapper that processes the options and arguments
210   given, and uses the passed function to build the opcode needed for
211   the specific operation. Thus all the generic loop/confirmation code
212   is abstracted into this function.
213
214   """
215   def realfn(opts, args):
216     if opts.multi_mode is None:
217       opts.multi_mode = _SHUTDOWN_INSTANCES
218     cl = GetClient()
219     inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
220     if not inames:
221       raise errors.OpPrereqError("Selection filter does not match"
222                                  " any instances", errors.ECODE_INVAL)
223     multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
224     if not (opts.force_multi or not multi_on
225             or _ConfirmOperation(inames, operation)):
226       return 1
227     jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
228     for name in inames:
229       op = fn(name, opts)
230       jex.QueueJob(name, op)
231     results = jex.WaitOrShow(not opts.submit_only)
232     rcode = compat.all(row[0] for row in results)
233     return int(not rcode)
234   return realfn
235
236
237 def ListInstances(opts, args):
238   """List instances and their properties.
239
240   @param opts: the command line options selected by the user
241   @type args: list
242   @param args: should be an empty list
243   @rtype: int
244   @return: the desired exit code
245
246   """
247   selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
248
249   output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
250
251   if not opts.no_headers:
252     headers = {
253       "name": "Instance", "os": "OS", "pnode": "Primary_node",
254       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
255       "oper_state": "Running",
256       "oper_ram": "Memory", "disk_template": "Disk_template",
257       "oper_vcpus": "VCPUs",
258       "ip": "IP_address", "mac": "MAC_address",
259       "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
260       "bridge": "Bridge",
261       "sda_size": "Disk/0", "sdb_size": "Disk/1",
262       "disk_usage": "DiskUsage",
263       "status": "Status", "tags": "Tags",
264       "network_port": "Network_port",
265       "hv/kernel_path": "Kernel_path",
266       "hv/initrd_path": "Initrd_path",
267       "hv/boot_order": "Boot_order",
268       "hv/acpi": "ACPI",
269       "hv/pae": "PAE",
270       "hv/cdrom_image_path": "CDROM_image_path",
271       "hv/nic_type": "NIC_type",
272       "hv/disk_type": "Disk_type",
273       "hv/vnc_bind_address": "VNC_bind_address",
274       "serial_no": "SerialNo", "hypervisor": "Hypervisor",
275       "hvparams": "Hypervisor_parameters",
276       "be/memory": "Configured_memory",
277       "be/vcpus": "VCPUs",
278       "vcpus": "VCPUs",
279       "be/auto_balance": "Auto_balance",
280       "disk.count": "Disks", "disk.sizes": "Disk_sizes",
281       "nic.count": "NICs", "nic.ips": "NIC_IPs",
282       "nic.modes": "NIC_modes", "nic.links": "NIC_links",
283       "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
284       "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
285       }
286   else:
287     headers = None
288
289   unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
290   numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
291                "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
292
293   list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
294                       "nic.modes", "nic.links", "nic.bridges")
295   # change raw values to nicer strings
296   for row in output:
297     for idx, field in enumerate(selected_fields):
298       val = row[idx]
299       if field == "snodes":
300         val = ",".join(val) or "-"
301       elif field == "admin_state":
302         if val:
303           val = "yes"
304         else:
305           val = "no"
306       elif field == "oper_state":
307         if val is None:
308           val = "(node down)"
309         elif val: # True
310           val = "running"
311         else:
312           val = "stopped"
313       elif field == "oper_ram":
314         if val is None:
315           val = "(node down)"
316       elif field == "oper_vcpus":
317         if val is None:
318           val = "(node down)"
319       elif field == "sda_size" or field == "sdb_size":
320         if val is None:
321           val = "N/A"
322       elif field == "ctime" or field == "mtime":
323         val = utils.FormatTime(val)
324       elif field in list_type_fields:
325         val = ",".join(str(item) for item in val)
326       elif val is None:
327         val = "-"
328       if opts.roman_integers and isinstance(val, int):
329         val = compat.TryToRoman(val)
330       row[idx] = str(val)
331
332   data = GenerateTable(separator=opts.separator, headers=headers,
333                        fields=selected_fields, unitfields=unitfields,
334                        numfields=numfields, data=output, units=opts.units)
335
336   for line in data:
337     ToStdout(line)
338
339   return 0
340
341
342 def AddInstance(opts, args):
343   """Add an instance to the cluster.
344
345   This is just a wrapper over GenericInstanceCreate.
346
347   """
348   return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
349
350
351 def BatchCreate(opts, args):
352   """Create instances using a definition file.
353
354   This function reads a json file with instances defined
355   in the form::
356
357     {"instance-name":{
358       "disk_size": [20480],
359       "template": "drbd",
360       "backend": {
361         "memory": 512,
362         "vcpus": 1 },
363       "os": "debootstrap",
364       "primary_node": "firstnode",
365       "secondary_node": "secondnode",
366       "iallocator": "dumb"}
367     }
368
369   Note that I{primary_node} and I{secondary_node} have precedence over
370   I{iallocator}.
371
372   @param opts: the command line options selected by the user
373   @type args: list
374   @param args: should contain one element, the json filename
375   @rtype: int
376   @return: the desired exit code
377
378   """
379   _DEFAULT_SPECS = {"disk_size": [20 * 1024],
380                     "backend": {},
381                     "iallocator": None,
382                     "primary_node": None,
383                     "secondary_node": None,
384                     "nics": None,
385                     "start": True,
386                     "ip_check": True,
387                     "name_check": True,
388                     "hypervisor": None,
389                     "hvparams": {},
390                     "file_storage_dir": None,
391                     "force_variant": False,
392                     "file_driver": 'loop'}
393
394   def _PopulateWithDefaults(spec):
395     """Returns a new hash combined with default values."""
396     mydict = _DEFAULT_SPECS.copy()
397     mydict.update(spec)
398     return mydict
399
400   def _Validate(spec):
401     """Validate the instance specs."""
402     # Validate fields required under any circumstances
403     for required_field in ('os', 'template'):
404       if required_field not in spec:
405         raise errors.OpPrereqError('Required field "%s" is missing.' %
406                                    required_field, errors.ECODE_INVAL)
407     # Validate special fields
408     if spec['primary_node'] is not None:
409       if (spec['template'] in constants.DTS_NET_MIRROR and
410           spec['secondary_node'] is None):
411         raise errors.OpPrereqError('Template requires secondary node, but'
412                                    ' there was no secondary provided.',
413                                    errors.ECODE_INVAL)
414     elif spec['iallocator'] is None:
415       raise errors.OpPrereqError('You have to provide at least a primary_node'
416                                  ' or an iallocator.',
417                                  errors.ECODE_INVAL)
418
419     if (spec['hvparams'] and
420         not isinstance(spec['hvparams'], dict)):
421       raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
422                                  errors.ECODE_INVAL)
423
424   json_filename = args[0]
425   try:
426     instance_data = simplejson.loads(utils.ReadFile(json_filename))
427   except Exception, err: # pylint: disable-msg=W0703
428     ToStderr("Can't parse the instance definition file: %s" % str(err))
429     return 1
430
431   if not isinstance(instance_data, dict):
432     ToStderr("The instance definition file is not in dict format.")
433     return 1
434
435   jex = JobExecutor(opts=opts)
436
437   # Iterate over the instances and do:
438   #  * Populate the specs with default value
439   #  * Validate the instance specs
440   i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
441   for name in i_names:
442     specs = instance_data[name]
443     specs = _PopulateWithDefaults(specs)
444     _Validate(specs)
445
446     hypervisor = specs['hypervisor']
447     hvparams = specs['hvparams']
448
449     disks = []
450     for elem in specs['disk_size']:
451       try:
452         size = utils.ParseUnit(elem)
453       except (TypeError, ValueError), err:
454         raise errors.OpPrereqError("Invalid disk size '%s' for"
455                                    " instance %s: %s" %
456                                    (elem, name, err), errors.ECODE_INVAL)
457       disks.append({"size": size})
458
459     utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
460     utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
461
462     tmp_nics = []
463     for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
464       if field in specs:
465         if not tmp_nics:
466           tmp_nics.append({})
467         tmp_nics[0][field] = specs[field]
468
469     if specs['nics'] is not None and tmp_nics:
470       raise errors.OpPrereqError("'nics' list incompatible with using"
471                                  " individual nic fields as well",
472                                  errors.ECODE_INVAL)
473     elif specs['nics'] is not None:
474       tmp_nics = specs['nics']
475     elif not tmp_nics:
476       tmp_nics = [{}]
477
478     op = opcodes.OpCreateInstance(instance_name=name,
479                                   disks=disks,
480                                   disk_template=specs['template'],
481                                   mode=constants.INSTANCE_CREATE,
482                                   os_type=specs['os'],
483                                   force_variant=specs["force_variant"],
484                                   pnode=specs['primary_node'],
485                                   snode=specs['secondary_node'],
486                                   nics=tmp_nics,
487                                   start=specs['start'],
488                                   ip_check=specs['ip_check'],
489                                   name_check=specs['name_check'],
490                                   wait_for_sync=True,
491                                   iallocator=specs['iallocator'],
492                                   hypervisor=hypervisor,
493                                   hvparams=hvparams,
494                                   beparams=specs['backend'],
495                                   file_storage_dir=specs['file_storage_dir'],
496                                   file_driver=specs['file_driver'])
497
498     jex.QueueJob(name, op)
499   # we never want to wait, just show the submitted job IDs
500   jex.WaitOrShow(False)
501
502   return 0
503
504
505 def ReinstallInstance(opts, args):
506   """Reinstall an instance.
507
508   @param opts: the command line options selected by the user
509   @type args: list
510   @param args: should contain only one element, the name of the
511       instance to be reinstalled
512   @rtype: int
513   @return: the desired exit code
514
515   """
516   # first, compute the desired name list
517   if opts.multi_mode is None:
518     opts.multi_mode = _SHUTDOWN_INSTANCES
519
520   inames = _ExpandMultiNames(opts.multi_mode, args)
521   if not inames:
522     raise errors.OpPrereqError("Selection filter does not match any instances",
523                                errors.ECODE_INVAL)
524
525   # second, if requested, ask for an OS
526   if opts.select_os is True:
527     op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
528     result = SubmitOpCode(op, opts=opts)
529
530     if not result:
531       ToStdout("Can't get the OS list")
532       return 1
533
534     ToStdout("Available OS templates:")
535     number = 0
536     choices = []
537     for (name, variants) in result:
538       for entry in CalculateOSNames(name, variants):
539         ToStdout("%3s: %s", number, entry)
540         choices.append(("%s" % number, entry, entry))
541         number += 1
542
543     choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
544     selected = AskUser("Enter OS template number (or x to abort):",
545                        choices)
546
547     if selected == 'exit':
548       ToStderr("User aborted reinstall, exiting")
549       return 1
550
551     os_name = selected
552   else:
553     os_name = opts.os
554
555   # third, get confirmation: multi-reinstall requires --force-multi,
556   # single-reinstall either --force or --force-multi (--force-multi is
557   # a stronger --force)
558   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
559   if multi_on:
560     warn_msg = "Note: this will remove *all* data for the below instances!\n"
561     if not (opts.force_multi or
562             _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
563       return 1
564   else:
565     if not (opts.force or opts.force_multi):
566       usertext = ("This will reinstall the instance %s and remove"
567                   " all data. Continue?") % inames[0]
568       if not AskUser(usertext):
569         return 1
570
571   jex = JobExecutor(verbose=multi_on, opts=opts)
572   for instance_name in inames:
573     op = opcodes.OpReinstallInstance(instance_name=instance_name,
574                                      os_type=os_name,
575                                      force_variant=opts.force_variant)
576     jex.QueueJob(instance_name, op)
577
578   jex.WaitOrShow(not opts.submit_only)
579   return 0
580
581
582 def RemoveInstance(opts, args):
583   """Remove an instance.
584
585   @param opts: the command line options selected by the user
586   @type args: list
587   @param args: should contain only one element, the name of
588       the instance to be removed
589   @rtype: int
590   @return: the desired exit code
591
592   """
593   instance_name = args[0]
594   force = opts.force
595   cl = GetClient()
596
597   if not force:
598     _EnsureInstancesExist(cl, [instance_name])
599
600     usertext = ("This will remove the volumes of the instance %s"
601                 " (including mirrors), thus removing all the data"
602                 " of the instance. Continue?") % instance_name
603     if not AskUser(usertext):
604       return 1
605
606   op = opcodes.OpRemoveInstance(instance_name=instance_name,
607                                 ignore_failures=opts.ignore_failures,
608                                 shutdown_timeout=opts.shutdown_timeout)
609   SubmitOrSend(op, opts, cl=cl)
610   return 0
611
612
613 def RenameInstance(opts, args):
614   """Rename an instance.
615
616   @param opts: the command line options selected by the user
617   @type args: list
618   @param args: should contain two elements, the old and the
619       new instance names
620   @rtype: int
621   @return: the desired exit code
622
623   """
624   if not opts.name_check:
625     if not AskUser("As you disabled the check of the DNS entry, please verify"
626                    " that '%s' is a FQDN. Continue?" % args[1]):
627       return 1
628
629   op = opcodes.OpRenameInstance(instance_name=args[0],
630                                 new_name=args[1],
631                                 ip_check=opts.ip_check,
632                                 name_check=opts.name_check)
633   result = SubmitOrSend(op, opts)
634
635   if result:
636     ToStdout("Instance '%s' renamed to '%s'", args[0], result)
637
638   return 0
639
640
641 def ActivateDisks(opts, args):
642   """Activate an instance's disks.
643
644   This serves two purposes:
645     - it allows (as long as the instance is not running)
646       mounting the disks and modifying them from the node
647     - it repairs inactive secondary drbds
648
649   @param opts: the command line options selected by the user
650   @type args: list
651   @param args: should contain only one element, the instance name
652   @rtype: int
653   @return: the desired exit code
654
655   """
656   instance_name = args[0]
657   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
658                                        ignore_size=opts.ignore_size)
659   disks_info = SubmitOrSend(op, opts)
660   for host, iname, nname in disks_info:
661     ToStdout("%s:%s:%s", host, iname, nname)
662   return 0
663
664
665 def DeactivateDisks(opts, args):
666   """Deactivate an instance's disks.
667
668   This function takes the instance name, looks for its primary node
669   and the tries to shutdown its block devices on that node.
670
671   @param opts: the command line options selected by the user
672   @type args: list
673   @param args: should contain only one element, the instance name
674   @rtype: int
675   @return: the desired exit code
676
677   """
678   instance_name = args[0]
679   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
680   SubmitOrSend(op, opts)
681   return 0
682
683
684 def RecreateDisks(opts, args):
685   """Recreate an instance's disks.
686
687   @param opts: the command line options selected by the user
688   @type args: list
689   @param args: should contain only one element, the instance name
690   @rtype: int
691   @return: the desired exit code
692
693   """
694   instance_name = args[0]
695   if opts.disks:
696     try:
697       opts.disks = [int(v) for v in opts.disks.split(",")]
698     except (ValueError, TypeError), err:
699       ToStderr("Invalid disks value: %s" % str(err))
700       return 1
701   else:
702     opts.disks = []
703
704   op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
705                                        disks=opts.disks)
706   SubmitOrSend(op, opts)
707   return 0
708
709
710 def GrowDisk(opts, args):
711   """Grow an instance's disks.
712
713   @param opts: the command line options selected by the user
714   @type args: list
715   @param args: should contain two elements, the instance name
716       whose disks we grow and the disk name, e.g. I{sda}
717   @rtype: int
718   @return: the desired exit code
719
720   """
721   instance = args[0]
722   disk = args[1]
723   try:
724     disk = int(disk)
725   except (TypeError, ValueError), err:
726     raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
727                                errors.ECODE_INVAL)
728   amount = utils.ParseUnit(args[2])
729   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
730                           wait_for_sync=opts.wait_for_sync)
731   SubmitOrSend(op, opts)
732   return 0
733
734
735 def _StartupInstance(name, opts):
736   """Startup instances.
737
738   This returns the opcode to start an instance, and its decorator will
739   wrap this into a loop starting all desired instances.
740
741   @param name: the name of the instance to act on
742   @param opts: the command line options selected by the user
743   @return: the opcode needed for the operation
744
745   """
746   op = opcodes.OpStartupInstance(instance_name=name,
747                                  force=opts.force)
748   # do not add these parameters to the opcode unless they're defined
749   if opts.hvparams:
750     op.hvparams = opts.hvparams
751   if opts.beparams:
752     op.beparams = opts.beparams
753   return op
754
755
756 def _RebootInstance(name, opts):
757   """Reboot instance(s).
758
759   This returns the opcode to reboot an instance, and its decorator
760   will wrap this into a loop rebooting all desired instances.
761
762   @param name: the name of the instance to act on
763   @param opts: the command line options selected by the user
764   @return: the opcode needed for the operation
765
766   """
767   return opcodes.OpRebootInstance(instance_name=name,
768                                   reboot_type=opts.reboot_type,
769                                   ignore_secondaries=opts.ignore_secondaries,
770                                   shutdown_timeout=opts.shutdown_timeout)
771
772
773 def _ShutdownInstance(name, opts):
774   """Shutdown an instance.
775
776   This returns the opcode to shutdown an instance, and its decorator
777   will wrap this into a loop shutting down all desired instances.
778
779   @param name: the name of the instance to act on
780   @param opts: the command line options selected by the user
781   @return: the opcode needed for the operation
782
783   """
784   return opcodes.OpShutdownInstance(instance_name=name,
785                                     timeout=opts.timeout)
786
787
788 def ReplaceDisks(opts, args):
789   """Replace the disks of an instance
790
791   @param opts: the command line options selected by the user
792   @type args: list
793   @param args: should contain only one element, the instance name
794   @rtype: int
795   @return: the desired exit code
796
797   """
798   new_2ndary = opts.dst_node
799   iallocator = opts.iallocator
800   if opts.disks is None:
801     disks = []
802   else:
803     try:
804       disks = [int(i) for i in opts.disks.split(",")]
805     except (TypeError, ValueError), err:
806       raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
807                                  errors.ECODE_INVAL)
808   cnt = [opts.on_primary, opts.on_secondary, opts.auto,
809          new_2ndary is not None, iallocator is not None].count(True)
810   if cnt != 1:
811     raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
812                                " options must be passed", errors.ECODE_INVAL)
813   elif opts.on_primary:
814     mode = constants.REPLACE_DISK_PRI
815   elif opts.on_secondary:
816     mode = constants.REPLACE_DISK_SEC
817   elif opts.auto:
818     mode = constants.REPLACE_DISK_AUTO
819     if disks:
820       raise errors.OpPrereqError("Cannot specify disks when using automatic"
821                                  " mode", errors.ECODE_INVAL)
822   elif new_2ndary is not None or iallocator is not None:
823     # replace secondary
824     mode = constants.REPLACE_DISK_CHG
825
826   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
827                               remote_node=new_2ndary, mode=mode,
828                               iallocator=iallocator,
829                               early_release=opts.early_release)
830   SubmitOrSend(op, opts)
831   return 0
832
833
834 def FailoverInstance(opts, args):
835   """Failover an instance.
836
837   The failover is done by shutting it down on its present node and
838   starting it on the secondary.
839
840   @param opts: the command line options selected by the user
841   @type args: list
842   @param args: should contain only one element, the instance name
843   @rtype: int
844   @return: the desired exit code
845
846   """
847   cl = GetClient()
848   instance_name = args[0]
849   force = opts.force
850
851   if not force:
852     _EnsureInstancesExist(cl, [instance_name])
853
854     usertext = ("Failover will happen to image %s."
855                 " This requires a shutdown of the instance. Continue?" %
856                 (instance_name,))
857     if not AskUser(usertext):
858       return 1
859
860   op = opcodes.OpFailoverInstance(instance_name=instance_name,
861                                   ignore_consistency=opts.ignore_consistency,
862                                   shutdown_timeout=opts.shutdown_timeout)
863   SubmitOrSend(op, opts, cl=cl)
864   return 0
865
866
867 def MigrateInstance(opts, args):
868   """Migrate an instance.
869
870   The migrate is done without shutdown.
871
872   @param opts: the command line options selected by the user
873   @type args: list
874   @param args: should contain only one element, the instance name
875   @rtype: int
876   @return: the desired exit code
877
878   """
879   cl = GetClient()
880   instance_name = args[0]
881   force = opts.force
882
883   if not force:
884     _EnsureInstancesExist(cl, [instance_name])
885
886     if opts.cleanup:
887       usertext = ("Instance %s will be recovered from a failed migration."
888                   " Note that the migration procedure (including cleanup)" %
889                   (instance_name,))
890     else:
891       usertext = ("Instance %s will be migrated. Note that migration" %
892                   (instance_name,))
893     usertext += (" might impact the instance if anything goes wrong"
894                  " (e.g. due to bugs in the hypervisor). Continue?")
895     if not AskUser(usertext):
896       return 1
897
898   # this should be removed once --non-live is deprecated
899   if not opts.live and opts.migration_mode is not None:
900     raise errors.OpPrereqError("Only one of the --non-live and "
901                                "--migration-mode options can be passed",
902                                errors.ECODE_INVAL)
903   if not opts.live: # --non-live passed
904     mode = constants.HT_MIGRATION_NONLIVE
905   else:
906     mode = opts.migration_mode
907
908   op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
909                                  cleanup=opts.cleanup)
910   SubmitOpCode(op, cl=cl, opts=opts)
911   return 0
912
913
914 def MoveInstance(opts, args):
915   """Move an instance.
916
917   @param opts: the command line options selected by the user
918   @type args: list
919   @param args: should contain only one element, the instance name
920   @rtype: int
921   @return: the desired exit code
922
923   """
924   cl = GetClient()
925   instance_name = args[0]
926   force = opts.force
927
928   if not force:
929     usertext = ("Instance %s will be moved."
930                 " This requires a shutdown of the instance. Continue?" %
931                 (instance_name,))
932     if not AskUser(usertext):
933       return 1
934
935   op = opcodes.OpMoveInstance(instance_name=instance_name,
936                               target_node=opts.node,
937                               shutdown_timeout=opts.shutdown_timeout)
938   SubmitOrSend(op, opts, cl=cl)
939   return 0
940
941
942 def ConnectToInstanceConsole(opts, args):
943   """Connect to the console of an instance.
944
945   @param opts: the command line options selected by the user
946   @type args: list
947   @param args: should contain only one element, the instance name
948   @rtype: int
949   @return: the desired exit code
950
951   """
952   instance_name = args[0]
953
954   op = opcodes.OpConnectConsole(instance_name=instance_name)
955   cmd = SubmitOpCode(op, opts=opts)
956
957   if opts.show_command:
958     ToStdout("%s", utils.ShellQuoteArgs(cmd))
959   else:
960     try:
961       os.execvp(cmd[0], cmd)
962     finally:
963       ToStderr("Can't run console command %s with arguments:\n'%s'",
964                cmd[0], " ".join(cmd))
965       os._exit(1) # pylint: disable-msg=W0212
966
967
968 def _FormatLogicalID(dev_type, logical_id, roman):
969   """Formats the logical_id of a disk.
970
971   """
972   if dev_type == constants.LD_DRBD8:
973     node_a, node_b, port, minor_a, minor_b, key = logical_id
974     data = [
975       ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
976                                                             convert=roman))),
977       ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
978                                                             convert=roman))),
979       ("port", compat.TryToRoman(port, convert=roman)),
980       ("auth key", key),
981       ]
982   elif dev_type == constants.LD_LV:
983     vg_name, lv_name = logical_id
984     data = ["%s/%s" % (vg_name, lv_name)]
985   else:
986     data = [str(logical_id)]
987
988   return data
989
990
991 def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
992   """Show block device information.
993
994   This is only used by L{ShowInstanceConfig}, but it's too big to be
995   left for an inline definition.
996
997   @type idx: int
998   @param idx: the index of the current disk
999   @type top_level: boolean
1000   @param top_level: if this a top-level disk?
1001   @type dev: dict
1002   @param dev: dictionary with disk information
1003   @type static: boolean
1004   @param static: wheter the device information doesn't contain
1005       runtime information but only static data
1006   @type roman: boolean
1007   @param roman: whether to try to use roman integers
1008   @return: a list of either strings, tuples or lists
1009       (which should be formatted at a higher indent level)
1010
1011   """
1012   def helper(dtype, status):
1013     """Format one line for physical device status.
1014
1015     @type dtype: str
1016     @param dtype: a constant from the L{constants.LDS_BLOCK} set
1017     @type status: tuple
1018     @param status: a tuple as returned from L{backend.FindBlockDevice}
1019     @return: the string representing the status
1020
1021     """
1022     if not status:
1023       return "not active"
1024     txt = ""
1025     (path, major, minor, syncp, estt, degr, ldisk_status) = status
1026     if major is None:
1027       major_string = "N/A"
1028     else:
1029       major_string = str(compat.TryToRoman(major, convert=roman))
1030
1031     if minor is None:
1032       minor_string = "N/A"
1033     else:
1034       minor_string = str(compat.TryToRoman(minor, convert=roman))
1035
1036     txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1037     if dtype in (constants.LD_DRBD8, ):
1038       if syncp is not None:
1039         sync_text = "*RECOVERING* %5.2f%%," % syncp
1040         if estt:
1041           sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1042         else:
1043           sync_text += " ETA unknown"
1044       else:
1045         sync_text = "in sync"
1046       if degr:
1047         degr_text = "*DEGRADED*"
1048       else:
1049         degr_text = "ok"
1050       if ldisk_status == constants.LDS_FAULTY:
1051         ldisk_text = " *MISSING DISK*"
1052       elif ldisk_status == constants.LDS_UNKNOWN:
1053         ldisk_text = " *UNCERTAIN STATE*"
1054       else:
1055         ldisk_text = ""
1056       txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1057     elif dtype == constants.LD_LV:
1058       if ldisk_status == constants.LDS_FAULTY:
1059         ldisk_text = " *FAILED* (failed drive?)"
1060       else:
1061         ldisk_text = ""
1062       txt += ldisk_text
1063     return txt
1064
1065   # the header
1066   if top_level:
1067     if dev["iv_name"] is not None:
1068       txt = dev["iv_name"]
1069     else:
1070       txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1071   else:
1072     txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1073   if isinstance(dev["size"], int):
1074     nice_size = utils.FormatUnit(dev["size"], "h")
1075   else:
1076     nice_size = dev["size"]
1077   d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1078   data = []
1079   if top_level:
1080     data.append(("access mode", dev["mode"]))
1081   if dev["logical_id"] is not None:
1082     try:
1083       l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1084     except ValueError:
1085       l_id = [str(dev["logical_id"])]
1086     if len(l_id) == 1:
1087       data.append(("logical_id", l_id[0]))
1088     else:
1089       data.extend(l_id)
1090   elif dev["physical_id"] is not None:
1091     data.append("physical_id:")
1092     data.append([dev["physical_id"]])
1093   if not static:
1094     data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1095   if dev["sstatus"] and not static:
1096     data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1097
1098   if dev["children"]:
1099     data.append("child devices:")
1100     for c_idx, child in enumerate(dev["children"]):
1101       data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1102   d1.append(data)
1103   return d1
1104
1105
1106 def _FormatList(buf, data, indent_level):
1107   """Formats a list of data at a given indent level.
1108
1109   If the element of the list is:
1110     - a string, it is simply formatted as is
1111     - a tuple, it will be split into key, value and the all the
1112       values in a list will be aligned all at the same start column
1113     - a list, will be recursively formatted
1114
1115   @type buf: StringIO
1116   @param buf: the buffer into which we write the output
1117   @param data: the list to format
1118   @type indent_level: int
1119   @param indent_level: the indent level to format at
1120
1121   """
1122   max_tlen = max([len(elem[0]) for elem in data
1123                  if isinstance(elem, tuple)] or [0])
1124   for elem in data:
1125     if isinstance(elem, basestring):
1126       buf.write("%*s%s\n" % (2*indent_level, "", elem))
1127     elif isinstance(elem, tuple):
1128       key, value = elem
1129       spacer = "%*s" % (max_tlen - len(key), "")
1130       buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1131     elif isinstance(elem, list):
1132       _FormatList(buf, elem, indent_level+1)
1133
1134
1135 def _FormatParameterDict(buf, per_inst, actual):
1136   """Formats a parameter dictionary.
1137
1138   @type buf: L{StringIO}
1139   @param buf: the buffer into which to write
1140   @type per_inst: dict
1141   @param per_inst: the instance's own parameters
1142   @type actual: dict
1143   @param actual: the current parameter set (including defaults)
1144
1145   """
1146   for key in sorted(actual):
1147     val = per_inst.get(key, "default (%s)" % actual[key])
1148     buf.write("    - %s: %s\n" % (key, val))
1149
1150 def ShowInstanceConfig(opts, args):
1151   """Compute instance run-time status.
1152
1153   @param opts: the command line options selected by the user
1154   @type args: list
1155   @param args: either an empty list, and then we query all
1156       instances, or should contain a list of instance names
1157   @rtype: int
1158   @return: the desired exit code
1159
1160   """
1161   if not args and not opts.show_all:
1162     ToStderr("No instance selected."
1163              " Please pass in --all if you want to query all instances.\n"
1164              "Note that this can take a long time on a big cluster.")
1165     return 1
1166   elif args and opts.show_all:
1167     ToStderr("Cannot use --all if you specify instance names.")
1168     return 1
1169
1170   retcode = 0
1171   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1172   result = SubmitOpCode(op, opts=opts)
1173   if not result:
1174     ToStdout("No instances.")
1175     return 1
1176
1177   buf = StringIO()
1178   retcode = 0
1179   for instance_name in result:
1180     instance = result[instance_name]
1181     buf.write("Instance name: %s\n" % instance["name"])
1182     buf.write("UUID: %s\n" % instance["uuid"])
1183     buf.write("Serial number: %s\n" %
1184               compat.TryToRoman(instance["serial_no"],
1185                                 convert=opts.roman_integers))
1186     buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1187     buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1188     buf.write("State: configured to be %s" % instance["config_state"])
1189     if not opts.static:
1190       buf.write(", actual state is %s" % instance["run_state"])
1191     buf.write("\n")
1192     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1193     ##          instance["auto_balance"])
1194     buf.write("  Nodes:\n")
1195     buf.write("    - primary: %s\n" % instance["pnode"])
1196     buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1197     buf.write("  Operating system: %s\n" % instance["os"])
1198     _FormatParameterDict(buf, instance["os_instance"], instance["os_actual"])
1199     if instance.has_key("network_port"):
1200       buf.write("  Allocated network port: %s\n" %
1201                 compat.TryToRoman(instance["network_port"],
1202                                   convert=opts.roman_integers))
1203     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1204
1205     # custom VNC console information
1206     vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1207                                                  None)
1208     if vnc_bind_address:
1209       port = instance["network_port"]
1210       display = int(port) - constants.VNC_BASE_PORT
1211       if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1212         vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1213                                                    port,
1214                                                    display)
1215       elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1216         vnc_console_port = ("%s:%s (node %s) (display %s)" %
1217                              (vnc_bind_address, port,
1218                               instance["pnode"], display))
1219       else:
1220         # vnc bind address is a file
1221         vnc_console_port = "%s:%s" % (instance["pnode"],
1222                                       vnc_bind_address)
1223       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1224
1225     _FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"])
1226     buf.write("  Hardware:\n")
1227     buf.write("    - VCPUs: %s\n" %
1228               compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1229                                 convert=opts.roman_integers))
1230     buf.write("    - memory: %sMiB\n" %
1231               compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1232                                 convert=opts.roman_integers))
1233     buf.write("    - NICs:\n")
1234     for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1235       buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1236                 (idx, mac, ip, mode, link))
1237     buf.write("  Disks:\n")
1238
1239     for idx, device in enumerate(instance["disks"]):
1240       _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1241                   opts.roman_integers), 2)
1242
1243   ToStdout(buf.getvalue().rstrip('\n'))
1244   return retcode
1245
1246
1247 def SetInstanceParams(opts, args):
1248   """Modifies an instance.
1249
1250   All parameters take effect only at the next restart of the instance.
1251
1252   @param opts: the command line options selected by the user
1253   @type args: list
1254   @param args: should contain only one element, the instance name
1255   @rtype: int
1256   @return: the desired exit code
1257
1258   """
1259   if not (opts.nics or opts.disks or opts.disk_template or
1260           opts.hvparams or opts.beparams or opts.os or opts.osparams):
1261     ToStderr("Please give at least one of the parameters.")
1262     return 1
1263
1264   for param in opts.beparams:
1265     if isinstance(opts.beparams[param], basestring):
1266       if opts.beparams[param].lower() == "default":
1267         opts.beparams[param] = constants.VALUE_DEFAULT
1268
1269   utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1270                       allowed_values=[constants.VALUE_DEFAULT])
1271
1272   for param in opts.hvparams:
1273     if isinstance(opts.hvparams[param], basestring):
1274       if opts.hvparams[param].lower() == "default":
1275         opts.hvparams[param] = constants.VALUE_DEFAULT
1276
1277   utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1278                       allowed_values=[constants.VALUE_DEFAULT])
1279
1280   for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1281     try:
1282       nic_op = int(nic_op)
1283       opts.nics[idx] = (nic_op, nic_dict)
1284     except (TypeError, ValueError):
1285       pass
1286
1287   for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1288     try:
1289       disk_op = int(disk_op)
1290       opts.disks[idx] = (disk_op, disk_dict)
1291     except (TypeError, ValueError):
1292       pass
1293     if disk_op == constants.DDM_ADD:
1294       if 'size' not in disk_dict:
1295         raise errors.OpPrereqError("Missing required parameter 'size'",
1296                                    errors.ECODE_INVAL)
1297       disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1298
1299   if (opts.disk_template and
1300       opts.disk_template in constants.DTS_NET_MIRROR and
1301       not opts.node):
1302     ToStderr("Changing the disk template to a mirrored one requires"
1303              " specifying a secondary node")
1304     return 1
1305
1306   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1307                                    nics=opts.nics,
1308                                    disks=opts.disks,
1309                                    disk_template=opts.disk_template,
1310                                    remote_node=opts.node,
1311                                    hvparams=opts.hvparams,
1312                                    beparams=opts.beparams,
1313                                    os_name=opts.os,
1314                                    osparams=opts.osparams,
1315                                    force_variant=opts.force_variant,
1316                                    force=opts.force)
1317
1318   # even if here we process the result, we allow submit only
1319   result = SubmitOrSend(op, opts)
1320
1321   if result:
1322     ToStdout("Modified instance %s", args[0])
1323     for param, data in result:
1324       ToStdout(" - %-5s -> %s", param, data)
1325     ToStdout("Please don't forget that most parameters take effect"
1326              " only at the next start of the instance.")
1327   return 0
1328
1329
1330 # multi-instance selection options
1331 m_force_multi = cli_option("--force-multiple", dest="force_multi",
1332                            help="Do not ask for confirmation when more than"
1333                            " one instance is affected",
1334                            action="store_true", default=False)
1335
1336 m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1337                             help="Filter by nodes (primary only)",
1338                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1339
1340 m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1341                             help="Filter by nodes (secondary only)",
1342                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1343
1344 m_node_opt = cli_option("--node", dest="multi_mode",
1345                         help="Filter by nodes (primary and secondary)",
1346                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1347
1348 m_clust_opt = cli_option("--all", dest="multi_mode",
1349                          help="Select all instances in the cluster",
1350                          const=_SHUTDOWN_CLUSTER, action="store_const")
1351
1352 m_inst_opt = cli_option("--instance", dest="multi_mode",
1353                         help="Filter by instance name [default]",
1354                         const=_SHUTDOWN_INSTANCES, action="store_const")
1355
1356 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1357                              help="Filter by node tag",
1358                              const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1359                              action="store_const")
1360
1361 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1362                                  help="Filter by primary node tag",
1363                                  const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1364                                  action="store_const")
1365
1366 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1367                                  help="Filter by secondary node tag",
1368                                  const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1369                                  action="store_const")
1370
1371 m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1372                              help="Filter by instance tag",
1373                              const=_SHUTDOWN_INSTANCES_BY_TAGS,
1374                              action="store_const")
1375
1376 # this is defined separately due to readability only
1377 add_opts = [
1378   BACKEND_OPT,
1379   DISK_OPT,
1380   DISK_TEMPLATE_OPT,
1381   FILESTORE_DIR_OPT,
1382   FILESTORE_DRIVER_OPT,
1383   HYPERVISOR_OPT,
1384   IALLOCATOR_OPT,
1385   NET_OPT,
1386   NODE_PLACEMENT_OPT,
1387   NOIPCHECK_OPT,
1388   NONAMECHECK_OPT,
1389   NONICS_OPT,
1390   NOSTART_OPT,
1391   NWSYNC_OPT,
1392   OSPARAMS_OPT,
1393   OS_OPT,
1394   FORCE_VARIANT_OPT,
1395   NO_INSTALL_OPT,
1396   OS_SIZE_OPT,
1397   SUBMIT_OPT,
1398   DRY_RUN_OPT,
1399   PRIORITY_OPT,
1400   ]
1401
1402 commands = {
1403   'add': (
1404     AddInstance, [ArgHost(min=1, max=1)], add_opts,
1405     "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1406     "Creates and adds a new instance to the cluster"),
1407   'batch-create': (
1408     BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1409     "<instances.json>",
1410     "Create a bunch of instances based on specs in the file."),
1411   'console': (
1412     ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1413     [SHOWCMD_OPT, PRIORITY_OPT],
1414     "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1415   'failover': (
1416     FailoverInstance, ARGS_ONE_INSTANCE,
1417     [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1418      DRY_RUN_OPT, PRIORITY_OPT],
1419     "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1420     " using the remote mirror (only for instances of type drbd)"),
1421   'migrate': (
1422     MigrateInstance, ARGS_ONE_INSTANCE,
1423     [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1424      PRIORITY_OPT],
1425     "[-f] <instance>", "Migrate instance to its secondary node"
1426     " (only for instances of type drbd)"),
1427   'move': (
1428     MoveInstance, ARGS_ONE_INSTANCE,
1429     [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1430      DRY_RUN_OPT, PRIORITY_OPT],
1431     "[-f] <instance>", "Move instance to an arbitrary node"
1432     " (only for instances of type file and lv)"),
1433   'info': (
1434     ShowInstanceConfig, ARGS_MANY_INSTANCES,
1435     [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1436     "[-s] {--all | <instance>...}",
1437     "Show information on the specified instance(s)"),
1438   'list': (
1439     ListInstances, ARGS_MANY_INSTANCES,
1440     [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
1441     "[<instance>...]",
1442     "Lists the instances and their status. The available fields are"
1443     " (see the man page for details): status, oper_state, oper_ram,"
1444     " oper_vcpus, name, os, pnode, snodes, admin_state, admin_ram,"
1445     " disk_template, ip, mac, nic_mode, nic_link, sda_size, sdb_size,"
1446     " vcpus, serial_no,"
1447     " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
1448     " nic.macs, nic.ips, nic.modes, nic.links,"
1449     " disk.count, disk.size/N, disk.sizes,"
1450     " hv/NAME, be/memory, be/vcpus, be/auto_balance,"
1451     " hypervisor."
1452     " The default field"
1453     " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS),
1454     ),
1455   'reinstall': (
1456     ReinstallInstance, [ArgInstance()],
1457     [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1458      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1459      m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1460      SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1461     "[-f] <instance>", "Reinstall a stopped instance"),
1462   'remove': (
1463     RemoveInstance, ARGS_ONE_INSTANCE,
1464     [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1465      DRY_RUN_OPT, PRIORITY_OPT],
1466     "[-f] <instance>", "Shuts down the instance and removes it"),
1467   'rename': (
1468     RenameInstance,
1469     [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1470     [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1471     "<instance> <new_name>", "Rename the instance"),
1472   'replace-disks': (
1473     ReplaceDisks, ARGS_ONE_INSTANCE,
1474     [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1475      NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1476      DRY_RUN_OPT, PRIORITY_OPT],
1477     "[-s|-p|-n NODE|-I NAME] <instance>",
1478     "Replaces all disks for the instance"),
1479   'modify': (
1480     SetInstanceParams, ARGS_ONE_INSTANCE,
1481     [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1482      DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1483      OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1484     "<instance>", "Alters the parameters of an instance"),
1485   'shutdown': (
1486     GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1487     [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1488      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1489      m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1490      DRY_RUN_OPT, PRIORITY_OPT],
1491     "<instance>", "Stops an instance"),
1492   'startup': (
1493     GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1494     [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1495      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1496      m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1497      BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1498     "<instance>", "Starts an instance"),
1499   'reboot': (
1500     GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1501     [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1502      m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1503      m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1504      m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1505     "<instance>", "Reboots an instance"),
1506   'activate-disks': (
1507     ActivateDisks, ARGS_ONE_INSTANCE,
1508     [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1509     "<instance>", "Activate an instance's disks"),
1510   'deactivate-disks': (
1511     DeactivateDisks, ARGS_ONE_INSTANCE,
1512     [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1513     "<instance>", "Deactivate an instance's disks"),
1514   'recreate-disks': (
1515     RecreateDisks, ARGS_ONE_INSTANCE,
1516     [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1517     "<instance>", "Recreate an instance's disks"),
1518   'grow-disk': (
1519     GrowDisk,
1520     [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1521      ArgUnknown(min=1, max=1)],
1522     [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1523     "<instance> <disk> <size>", "Grow an instance's disk"),
1524   'list-tags': (
1525     ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1526     "<instance_name>", "List the tags of the given instance"),
1527   'add-tags': (
1528     AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1529     [TAG_SRC_OPT, PRIORITY_OPT],
1530     "<instance_name> tag...", "Add tags to the given instance"),
1531   'remove-tags': (
1532     RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1533     [TAG_SRC_OPT, PRIORITY_OPT],
1534     "<instance_name> tag...", "Remove tags from given instance"),
1535   }
1536
1537 #: dictionary with aliases for commands
1538 aliases = {
1539   'start': 'startup',
1540   'stop': 'shutdown',
1541   }
1542
1543
1544 if __name__ == '__main__':
1545   sys.exit(GenericMain(commands, aliases=aliases,
1546                        override={"tag_type": constants.TAG_INSTANCE}))