CLI: remove command opts/args in "gnt-X"
[ganeti-local] / scripts / gnt-instance
1 #!/usr/bin/python
2 #
3
4 # Copyright (C) 2006, 2007 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 import sys
23 import os
24 import itertools
25 from optparse import make_option
26 from cStringIO import StringIO
27
28 from ganeti.cli import *
29 from ganeti import opcodes
30 from ganeti import logger
31 from ganeti import constants
32 from ganeti import utils
33 from ganeti import errors
34
35
36 _SHUTDOWN_CLUSTER = "cluster"
37 _SHUTDOWN_NODES_BOTH = "nodes"
38 _SHUTDOWN_NODES_PRI = "nodes-pri"
39 _SHUTDOWN_NODES_SEC = "nodes-sec"
40 _SHUTDOWN_INSTANCES = "instances"
41
42
43 _LIST_DEF_FIELDS = [
44   "name", "os", "pnode", "status", "oper_ram",
45   ]
46
47 def _ExpandMultiNames(mode, names):
48   """Expand the given names using the passed mode.
49
50   Args:
51     - mode, which can be one of _SHUTDOWN_CLUSTER, _SHUTDOWN_NODES_BOTH,
52       _SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_SEC or _SHUTDOWN_INSTANCES
53     - names, which is a list of names; for cluster, it must be empty,
54       and for node and instance it must be a list of valid item
55       names (short names are valid as usual, e.g. node1 instead of
56       node1.example.com)
57
58   For _SHUTDOWN_CLUSTER, all instances will be returned. For
59   _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
60   primary/secondary will be shutdown. For _SHUTDOWN_NODES_BOTH, all
61   instances having those nodes as either primary or secondary will be
62   returned. For _SHUTDOWN_INSTANCES, the given instances will be
63   returned.
64
65   """
66   if mode == _SHUTDOWN_CLUSTER:
67     if names:
68       raise errors.OpPrereqError("Cluster filter mode takes no arguments")
69     op = opcodes.OpQueryInstances(output_fields=["name"], names=[])
70     idata = SubmitOpCode(op)
71     inames = [row[0] for row in idata]
72
73   elif mode in (_SHUTDOWN_NODES_BOTH,
74                 _SHUTDOWN_NODES_PRI,
75                 _SHUTDOWN_NODES_SEC):
76     if not names:
77       raise errors.OpPrereqError("No node names passed")
78     op = opcodes.OpQueryNodes(output_fields=["name", "pinst_list",
79                                              "sinst_list"], names=names)
80     ndata = SubmitOpCode(op)
81     ipri = [row[1] for row in ndata]
82     pri_names = list(itertools.chain(*ipri))
83     isec = [row[2] for row in ndata]
84     sec_names = list(itertools.chain(*isec))
85     if mode == _SHUTDOWN_NODES_BOTH:
86       inames = pri_names + sec_names
87     elif mode == _SHUTDOWN_NODES_PRI:
88       inames = pri_names
89     elif mode == _SHUTDOWN_NODES_SEC:
90       inames = sec_names
91     else:
92       raise errors.ProgrammerError("Unhandled shutdown type")
93
94   elif mode == _SHUTDOWN_INSTANCES:
95     if not names:
96       raise errors.OpPrereqError("No instance names passed")
97     op = opcodes.OpQueryInstances(output_fields=["name"], names=names)
98     idata = SubmitOpCode(op)
99     inames = [row[0] for row in idata]
100
101   else:
102     raise errors.OpPrereqError("Unknown mode '%s'" % mode)
103
104   return inames
105
106
107 def _ConfirmOperation(inames, text):
108   """Ask the user to confirm an operation on a list of instances.
109
110   This function is used to request confirmation for doing an operation
111   on a given list of instances.
112
113   The inames argument is what the selection algorithm computed, and
114   the text argument is the operation we should tell the user to
115   confirm (e.g. 'shutdown' or 'startup').
116
117   Returns: boolean depending on user's confirmation.
118
119   """
120   count = len(inames)
121   msg = ("The %s will operate on %d instances.\n"
122          "Do you want to continue?" % (text, count))
123   affected = ("\nAffected instances:\n" +
124               "\n".join(["  %s" % name for name in inames]))
125
126   choices = [('y', True, 'Yes, execute the %s' % text),
127              ('n', False, 'No, abort the %s' % text)]
128
129   if count > 20:
130     choices.insert(1, ('v', 'v', 'View the list of affected instances'))
131     ask = msg
132   else:
133     ask = msg + affected
134
135   choice = AskUser(ask, choices)
136   if choice == 'v':
137     choices.pop(1)
138     choice = AskUser(msg + affected, choices)
139   return choice
140
141
142 def _TransformPath(user_input):
143   """Transform a user path into a canonical value.
144
145   This function transforms the a path passed as textual information
146   into the constants that the LU code expects.
147
148   """
149   if user_input:
150     if user_input.lower() == "default":
151       result_path = constants.VALUE_DEFAULT
152     elif user_input.lower() == "none":
153       result_path = constants.VALUE_NONE
154     else:
155       if not os.path.isabs(user_input):
156         raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
157                                    user_input)
158       result_path = user_input
159   else:
160     result_path = constants.VALUE_DEFAULT
161
162   return result_path
163
164
165 def ListInstances(opts, args):
166   """List instances and their properties.
167
168   """
169   if opts.output is None:
170     selected_fields = _LIST_DEF_FIELDS
171   elif opts.output.startswith("+"):
172     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
173   else:
174     selected_fields = opts.output.split(",")
175
176   op = opcodes.OpQueryInstances(output_fields=selected_fields, names=[])
177   output = SubmitOpCode(op)
178
179   if not opts.no_headers:
180     headers = {
181       "name": "Instance", "os": "OS", "pnode": "Primary_node",
182       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
183       "oper_state": "Running", "admin_ram": "Configured_memory",
184       "oper_ram": "Memory", "disk_template": "Disk_template",
185       "ip": "IP Address", "mac": "MAC Address",
186       "bridge": "Bridge", "vcpus": "VCPUs",
187       "sda_size": "Disk/0", "sdb_size": "Disk/1",
188       "status": "Status",
189       }
190   else:
191     headers = None
192
193   if opts.human_readable:
194     unitfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
195   else:
196     unitfields = None
197
198   numfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size", "vcpus"]
199
200   # change raw values to nicer strings
201   for row in output:
202     for idx, field in enumerate(selected_fields):
203       val = row[idx]
204       if field == "snodes":
205         val = ",".join(val) or "-"
206       elif field == "admin_state":
207         if val:
208           val = "yes"
209         else:
210           val = "no"
211       elif field == "oper_state":
212         if val is None:
213           val = "(node down)"
214         elif val: # True
215           val = "running"
216         else:
217           val = "stopped"
218       elif field == "oper_ram":
219         if val is None:
220           val = "(node down)"
221       elif field == "sda_size" or field == "sdb_size":
222         if val is None:
223           val = "N/A"
224       row[idx] = str(val)
225
226   data = GenerateTable(separator=opts.separator, headers=headers,
227                        fields=selected_fields, unitfields=unitfields,
228                        numfields=numfields, data=output)
229
230   for line in data:
231     logger.ToStdout(line)
232
233   return 0
234
235
236 def AddInstance(opts, args):
237   """Add an instance to the cluster.
238
239   Args:
240     opts - class with options as members
241     args - list with a single element, the instance name
242   Opts used:
243     mem - amount of memory to allocate to instance (MiB)
244     size - amount of disk space to allocate to instance (MiB)
245     os - which OS to run on instance
246     node - node to run new instance on
247
248   """
249   instance = args[0]
250
251   (pnode, snode) = SplitNodeOption(opts.node)
252
253   kernel_path = _TransformPath(opts.kernel_path)
254   initrd_path = _TransformPath(opts.initrd_path)
255
256   op = opcodes.OpCreateInstance(instance_name=instance, mem_size=opts.mem,
257                                 disk_size=opts.size, swap_size=opts.swap,
258                                 disk_template=opts.disk_template,
259                                 mode=constants.INSTANCE_CREATE,
260                                 os_type=opts.os, pnode=pnode,
261                                 snode=snode, vcpus=opts.vcpus,
262                                 ip=opts.ip, bridge=opts.bridge,
263                                 start=opts.start, ip_check=opts.ip_check,
264                                 wait_for_sync=opts.wait_for_sync,
265                                 mac=opts.mac,
266                                 kernel_path=kernel_path,
267                                 initrd_path=initrd_path,
268                                 hvm_boot_order=opts.hvm_boot_order,
269                                 file_storage_dir=opts.file_storage_dir,
270                                 file_driver=opts.file_driver,
271                                 iallocator=opts.iallocator)
272   SubmitOpCode(op)
273   return 0
274
275
276 def ReinstallInstance(opts, args):
277   """Reinstall an instance.
278
279   Args:
280     opts - class with options as members
281     args - list containing a single element, the instance name
282
283   """
284   instance_name = args[0]
285
286   if not opts.force:
287     usertext = ("This will reinstall the instance %s and remove"
288                 " all data. Continue?") % instance_name
289     if not AskUser(usertext):
290       return 1
291
292   op = opcodes.OpReinstallInstance(instance_name=instance_name,
293                                    os_type=opts.os)
294   SubmitOpCode(op)
295
296   return 0
297
298
299 def RemoveInstance(opts, args):
300   """Remove an instance.
301
302   Args:
303     opts - class with options as members
304     args - list containing a single element, the instance name
305
306   """
307   instance_name = args[0]
308   force = opts.force
309
310   if not force:
311     usertext = ("This will remove the volumes of the instance %s"
312                 " (including mirrors), thus removing all the data"
313                 " of the instance. Continue?") % instance_name
314     if not AskUser(usertext):
315       return 1
316
317   op = opcodes.OpRemoveInstance(instance_name=instance_name,
318                                 ignore_failures=opts.ignore_failures)
319   SubmitOpCode(op)
320   return 0
321
322
323 def RenameInstance(opts, args):
324   """Rename an instance.
325
326   Args:
327     opts - class with options as members
328     args - list containing two elements, the instance name and the new name
329
330   """
331   op = opcodes.OpRenameInstance(instance_name=args[0],
332                                 new_name=args[1],
333                                 ignore_ip=opts.ignore_ip)
334   SubmitOpCode(op)
335
336   return 0
337
338
339 def ActivateDisks(opts, args):
340   """Activate an instance's disks.
341
342   This serves two purposes:
343     - it allows one (as long as the instance is not running) to mount
344     the disks and modify them from the node
345     - it repairs inactive secondary drbds
346
347   """
348   instance_name = args[0]
349   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
350   disks_info = SubmitOpCode(op)
351   for host, iname, nname in disks_info:
352     print "%s:%s:%s" % (host, iname, nname)
353   return 0
354
355
356 def DeactivateDisks(opts, args):
357   """Command-line interface for _ShutdownInstanceBlockDevices.
358
359   This function takes the instance name, looks for its primary node
360   and the tries to shutdown its block devices on that node.
361
362   """
363   instance_name = args[0]
364   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
365   SubmitOpCode(op)
366   return 0
367
368
369 def StartupInstance(opts, args):
370   """Startup an instance.
371
372   Args:
373     opts - class with options as members
374     args - list containing a single element, the instance name
375
376   """
377   if opts.multi_mode is None:
378     opts.multi_mode = _SHUTDOWN_INSTANCES
379   inames = _ExpandMultiNames(opts.multi_mode, args)
380   if not inames:
381     raise errors.OpPrereqError("Selection filter does not match any instances")
382   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
383   if not (opts.force_multi or not multi_on
384           or _ConfirmOperation(inames, "startup")):
385     return 1
386   for name in inames:
387     op = opcodes.OpStartupInstance(instance_name=name,
388                                    force=opts.force,
389                                    extra_args=opts.extra_args)
390     if multi_on:
391       logger.ToStdout("Starting up %s" % name)
392     SubmitOpCode(op)
393   return 0
394
395
396 def RebootInstance(opts, args):
397   """Reboot an instance
398
399   Args:
400     opts - class with options as members
401     args - list containing a single element, the instance name
402
403   """
404   if opts.multi_mode is None:
405     opts.multi_mode = _SHUTDOWN_INSTANCES
406   inames = _ExpandMultiNames(opts.multi_mode, args)
407   if not inames:
408     raise errors.OpPrereqError("Selection filter does not match any instances")
409   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
410   if not (opts.force_multi or not multi_on
411           or _ConfirmOperation(inames, "reboot")):
412     return 1
413   for name in inames:
414     op = opcodes.OpRebootInstance(instance_name=name,
415                                   reboot_type=opts.reboot_type,
416                                   ignore_secondaries=opts.ignore_secondaries)
417
418     SubmitOpCode(op)
419   return 0
420
421
422 def ShutdownInstance(opts, args):
423   """Shutdown an instance.
424
425   Args:
426     opts - class with options as members
427     args - list containing a single element, the instance name
428
429   """
430   if opts.multi_mode is None:
431     opts.multi_mode = _SHUTDOWN_INSTANCES
432   inames = _ExpandMultiNames(opts.multi_mode, args)
433   if not inames:
434     raise errors.OpPrereqError("Selection filter does not match any instances")
435   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
436   if not (opts.force_multi or not multi_on
437           or _ConfirmOperation(inames, "shutdown")):
438     return 1
439   for name in inames:
440     op = opcodes.OpShutdownInstance(instance_name=name)
441     if multi_on:
442       logger.ToStdout("Shutting down %s" % name)
443     SubmitOpCode(op)
444   return 0
445
446
447 def ReplaceDisks(opts, args):
448   """Replace the disks of an instance
449
450   Args:
451     opts - class with options as members
452     args - list with a single element, the instance name
453
454   """
455   instance_name = args[0]
456   new_2ndary = opts.new_secondary
457   iallocator = opts.iallocator
458   if opts.disks is None:
459     disks = ["sda", "sdb"]
460   else:
461     disks = opts.disks.split(",")
462   if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
463     mode = constants.REPLACE_DISK_ALL
464   elif opts.on_primary: # only on primary:
465     mode = constants.REPLACE_DISK_PRI
466     if new_2ndary is not None or iallocator is not None:
467       raise errors.OpPrereqError("Can't change secondary node on primary disk"
468                                  " replacement")
469   elif opts.on_secondary is not None or iallocator is not None:
470     # only on secondary
471     mode = constants.REPLACE_DISK_SEC
472
473   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
474                               remote_node=new_2ndary, mode=mode,
475                               iallocator=iallocator)
476   SubmitOpCode(op)
477   return 0
478
479
480 def FailoverInstance(opts, args):
481   """Failover an instance.
482
483   The failover is done by shutting it down on its present node and
484   starting it on the secondary.
485
486   Args:
487     opts - class with options as members
488     args - list with a single element, the instance name
489   Opts used:
490     force - whether to failover without asking questions.
491
492   """
493   instance_name = args[0]
494   force = opts.force
495
496   if not force:
497     usertext = ("Failover will happen to image %s."
498                 " This requires a shutdown of the instance. Continue?" %
499                 (instance_name,))
500     if not AskUser(usertext):
501       return 1
502
503   op = opcodes.OpFailoverInstance(instance_name=instance_name,
504                                   ignore_consistency=opts.ignore_consistency)
505   SubmitOpCode(op)
506   return 0
507
508
509 def ConnectToInstanceConsole(opts, args):
510   """Connect to the console of an instance.
511
512   Args:
513     opts - class with options as members
514     args - list with a single element, the instance name
515
516   """
517   instance_name = args[0]
518
519   op = opcodes.OpConnectConsole(instance_name=instance_name)
520   cmd = SubmitOpCode(op)
521
522   if opts.show_command:
523     print utils.ShellQuoteArgs(cmd)
524   else:
525     # drop lock and exec so other commands can run while we have console
526     utils.Unlock("cmd")
527     try:
528       os.execvp(cmd[0], cmd)
529     finally:
530       sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
531                        (cmd, " ".join(argv)))
532       os._exit(1)
533
534
535 def _FormatBlockDevInfo(buf, dev, indent_level):
536   """Show block device information.
537
538   This is only used by ShowInstanceConfig(), but it's too big to be
539   left for an inline definition.
540
541   """
542   def helper(buf, dtype, status):
543     """Format one line for physical device status."""
544     if not status:
545       buf.write("not active\n")
546     else:
547       (path, major, minor, syncp, estt, degr, ldisk) = status
548       if major is None:
549         major_string = "N/A"
550       else:
551         major_string = str(major)
552
553       if minor is None:
554         minor_string = "N/A"
555       else:
556         minor_string = str(minor)
557
558       buf.write("%s (%s:%s)" % (path, major_string, minor_string))
559       if dtype in (constants.LD_MD_R1, constants.LD_DRBD7, constants.LD_DRBD8):
560         if syncp is not None:
561           sync_text = "*RECOVERING* %5.2f%%," % syncp
562           if estt:
563             sync_text += " ETA %ds" % estt
564           else:
565             sync_text += " ETA unknown"
566         else:
567           sync_text = "in sync"
568         if degr:
569           degr_text = "*DEGRADED*"
570         else:
571           degr_text = "ok"
572         if ldisk:
573           ldisk_text = " *MISSING DISK*"
574         else:
575           ldisk_text = ""
576         buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
577       elif dtype == constants.LD_LV:
578         if ldisk:
579           ldisk_text = " *FAILED* (failed drive?)"
580         else:
581           ldisk_text = ""
582         buf.write(ldisk_text)
583       buf.write("\n")
584
585   if dev["iv_name"] is not None:
586     data = "  - %s, " % dev["iv_name"]
587   else:
588     data = "  - "
589   data += "type: %s" % dev["dev_type"]
590   if dev["logical_id"] is not None:
591     data += ", logical_id: %s" % (dev["logical_id"],)
592   elif dev["physical_id"] is not None:
593     data += ", physical_id: %s" % (dev["physical_id"],)
594   buf.write("%*s%s\n" % (2*indent_level, "", data))
595   buf.write("%*s    primary:   " % (2*indent_level, ""))
596   helper(buf, dev["dev_type"], dev["pstatus"])
597
598   if dev["sstatus"]:
599     buf.write("%*s    secondary: " % (2*indent_level, ""))
600     helper(buf, dev["dev_type"], dev["sstatus"])
601
602   if dev["children"]:
603     for child in dev["children"]:
604       _FormatBlockDevInfo(buf, child, indent_level+1)
605
606
607 def ShowInstanceConfig(opts, args):
608   """Compute instance run-time status.
609
610   """
611   retcode = 0
612   op = opcodes.OpQueryInstanceData(instances=args)
613   result = SubmitOpCode(op)
614
615   if not result:
616     logger.ToStdout("No instances.")
617     return 1
618
619   buf = StringIO()
620   retcode = 0
621   for instance_name in result:
622     instance = result[instance_name]
623     buf.write("Instance name: %s\n" % instance["name"])
624     buf.write("State: configured to be %s, actual state is %s\n" %
625               (instance["config_state"], instance["run_state"]))
626     buf.write("  Nodes:\n")
627     buf.write("    - primary: %s\n" % instance["pnode"])
628     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
629     buf.write("  Operating system: %s\n" % instance["os"])
630     buf.write("  Allocated network port: %s\n" % instance["network_port"])
631     if instance["kernel_path"] in (None, constants.VALUE_DEFAULT):
632       kpath = "(default: %s)" % constants.XEN_KERNEL
633     else:
634       kpath = instance["kernel_path"]
635     buf.write("  Kernel path: %s\n" % kpath)
636     if instance["initrd_path"] in (None, constants.VALUE_DEFAULT):
637       initrd = "(default: %s)" % constants.XEN_INITRD
638     elif instance["initrd_path"] == constants.VALUE_NONE:
639       initrd = "(none)"
640     else:
641       initrd = instance["initrd_path"]
642     buf.write("       initrd: %s\n" % initrd)
643     buf.write("  HVM boot order: %s\n" % instance["hvm_boot_order"])
644     buf.write("  Hardware:\n")
645     buf.write("    - VCPUs: %d\n" % instance["vcpus"])
646     buf.write("    - memory: %dMiB\n" % instance["memory"])
647     buf.write("    - NICs: %s\n" %
648         ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
649                    (mac, ip, bridge)
650                      for mac, ip, bridge in instance["nics"]]))
651     buf.write("  Block devices:\n")
652
653     for device in instance["disks"]:
654       _FormatBlockDevInfo(buf, device, 1)
655
656   logger.ToStdout(buf.getvalue().rstrip('\n'))
657   return retcode
658
659
660 def SetInstanceParams(opts, args):
661   """Modifies an instance.
662
663   All parameters take effect only at the next restart of the instance.
664
665   Args:
666     opts - class with options as members
667     args - list with a single element, the instance name
668   Opts used:
669     memory - the new memory size
670     vcpus - the new number of cpus
671     mac - the new MAC address of the instance
672
673   """
674   if not (opts.mem or opts.vcpus or opts.ip or opts.bridge or opts.mac or
675           opts.kernel_path or opts.initrd_path or opts.hvm_boot_order):
676     logger.ToStdout("Please give at least one of the parameters.")
677     return 1
678
679   kernel_path = _TransformPath(opts.kernel_path)
680   initrd_path = _TransformPath(opts.initrd_path)
681   if opts.hvm_boot_order == 'default':
682     hvm_boot_order = constants.VALUE_DEFAULT
683   else:
684     hvm_boot_order = opts.hvm_boot_order
685
686   op = opcodes.OpSetInstanceParams(instance_name=args[0], mem=opts.mem,
687                                    vcpus=opts.vcpus, ip=opts.ip,
688                                    bridge=opts.bridge, mac=opts.mac,
689                                    kernel_path=opts.kernel_path,
690                                    initrd_path=opts.initrd_path,
691                                    hvm_boot_order=hvm_boot_order)
692   result = SubmitOpCode(op)
693
694   if result:
695     logger.ToStdout("Modified instance %s" % args[0])
696     for param, data in result:
697       logger.ToStdout(" - %-5s -> %s" % (param, data))
698     logger.ToStdout("Please don't forget that these parameters take effect"
699                     " only at the next start of the instance.")
700   return 0
701
702
703 # options used in more than one cmd
704 node_opt = make_option("-n", "--node", dest="node", help="Target node",
705                        metavar="<node>")
706
707 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
708                     metavar="<os>")
709
710 # multi-instance selection options
711 m_force_multi = make_option("--force-multiple", dest="force_multi",
712                             help="Do not ask for confirmation when more than"
713                             " one instance is affected",
714                             action="store_true", default=False)
715
716 m_pri_node_opt = make_option("--primary", dest="multi_mode",
717                              help="Filter by nodes (primary only)",
718                              const=_SHUTDOWN_NODES_PRI, action="store_const")
719
720 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
721                              help="Filter by nodes (secondary only)",
722                              const=_SHUTDOWN_NODES_SEC, action="store_const")
723
724 m_node_opt = make_option("--node", dest="multi_mode",
725                          help="Filter by nodes (primary and secondary)",
726                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
727
728 m_clust_opt = make_option("--all", dest="multi_mode",
729                           help="Select all instances in the cluster",
730                           const=_SHUTDOWN_CLUSTER, action="store_const")
731
732 m_inst_opt = make_option("--instance", dest="multi_mode",
733                          help="Filter by instance name [default]",
734                          const=_SHUTDOWN_INSTANCES, action="store_const")
735
736
737 # this is defined separately due to readability only
738 add_opts = [
739   DEBUG_OPT,
740   make_option("-n", "--node", dest="node",
741               help="Target node and optional secondary node",
742               metavar="<pnode>[:<snode>]"),
743   cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
744              " a suffix is used",
745              default=20 * 1024, type="unit", metavar="<size>"),
746   cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
747              " suffix is used",
748              default=4 * 1024, type="unit", metavar="<size>"),
749   os_opt,
750   cli_option("-m", "--memory", dest="mem", help="Memory size (in MiB)",
751               default=128, type="unit", metavar="<mem>"),
752   make_option("-p", "--cpu", dest="vcpus", help="Number of virtual CPUs",
753               default=1, type="int", metavar="<PROC>"),
754   make_option("-t", "--disk-template", dest="disk_template",
755               help="Custom disk setup (diskless, file, plain or drbd)",
756               default=None, metavar="TEMPL"),
757   make_option("-i", "--ip", dest="ip",
758               help="IP address ('none' [default], 'auto', or specify address)",
759               default='none', type="string", metavar="<ADDRESS>"),
760   make_option("--mac", dest="mac",
761               help="MAC address ('auto' [default], or specify address)",
762               default='auto', type="string", metavar="<MACADDRESS>"),
763   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
764               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
765   make_option("-b", "--bridge", dest="bridge",
766               help="Bridge to connect this instance to",
767               default=None, metavar="<bridge>"),
768   make_option("--no-start", dest="start", default=True,
769               action="store_false", help="Don't start the instance after"
770               " creation"),
771   make_option("--no-ip-check", dest="ip_check", default=True,
772               action="store_false", help="Don't check that the instance's IP"
773               " is alive (only valid with --no-start)"),
774   make_option("--kernel", dest="kernel_path",
775               help="Path to the instances' kernel (or 'default')",
776               default=None,
777               type="string", metavar="<FILENAME>"),
778   make_option("--initrd", dest="initrd_path",
779               help="Path to the instances' initrd (or 'none', or 'default')",
780               default=None,
781               type="string", metavar="<FILENAME>"),
782   make_option("--hvm-boot-order", dest="hvm_boot_order",
783               help="Boot device order for HVM (one or more of [acdn])",
784               default=None, type="string", metavar="<BOOTORDER>"),
785   make_option("--file-storage-dir", dest="file_storage_dir",
786               help="Relative path under default cluster-wide file storage dir"
787               " to store file-based disks", default=None,
788               metavar="<DIR>"),
789   make_option("--file-driver", dest="file_driver", help="Driver to use"
790               " for image files", default="loop", metavar="<DRIVER>"),
791   make_option("--iallocator", metavar="<NAME>",
792               help="Select nodes for the instance automatically using the"
793               " <NAME> iallocator plugin", default=None, type="string"),
794   ]
795
796 commands = {
797   'add': (AddInstance, ARGS_ONE, add_opts,
798           "Creates and adds a new instance to the cluster"),
799   'console': (ConnectToInstanceConsole, ARGS_ONE,
800               [DEBUG_OPT,
801                make_option("--show-cmd", dest="show_command",
802                            action="store_true", default=False,
803                            help=("Show command instead of executing it"))],
804               "Opens a console on the specified instance"),
805   'failover': (FailoverInstance, ARGS_ONE,
806                [DEBUG_OPT, FORCE_OPT,
807                 make_option("--ignore-consistency", dest="ignore_consistency",
808                             action="store_true", default=False,
809                             help="Ignore the consistency of the disks on"
810                             " the secondary"),
811                 ],
812                "Stops the instance and starts it on the backup node, using"
813                " the remote mirror (only for instances of type drbd)"),
814   'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT],
815            "Show information on the specified instance"),
816   'list': (ListInstances, ARGS_NONE,
817            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
818            "Lists the instances and their status. The available fields are"
819            " (see the man page for details): status, oper_state, oper_ram,"
820            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
821            " ip, mac, bridge, sda_size, sdb_size, vcpus. The default field"
822            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
823            ),
824   'reinstall': (ReinstallInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, os_opt],
825                 "Reinstall a stopped instance"),
826   'remove': (RemoveInstance, ARGS_ONE,
827              [DEBUG_OPT, FORCE_OPT,
828               make_option("--ignore-failures", dest="ignore_failures",
829                           action="store_true", default=False,
830                           help=("Remove the instance from the cluster even"
831                                 " if there are failures during the removal"
832                                 " process (shutdown, disk removal, etc.)")),
833               ],
834              "Shuts down the instance and removes it"),
835   'rename': (RenameInstance, ARGS_FIXED(2),
836              [DEBUG_OPT,
837               make_option("--no-ip-check", dest="ignore_ip",
838                           help="Do not check that the IP of the new name"
839                           " is alive",
840                           default=False, action="store_true"),
841               ],
842              "Rename the instance"),
843   'replace-disks': (ReplaceDisks, ARGS_ONE,
844                     [DEBUG_OPT,
845                      make_option("-n", "--new-secondary", dest="new_secondary",
846                                  help=("New secondary node (for secondary"
847                                        " node change)"), metavar="NODE"),
848                      make_option("-p", "--on-primary", dest="on_primary",
849                                  default=False, action="store_true",
850                                  help=("Replace the disk(s) on the primary"
851                                        " node (only for the drbd template)")),
852                      make_option("-s", "--on-secondary", dest="on_secondary",
853                                  default=False, action="store_true",
854                                  help=("Replace the disk(s) on the secondary"
855                                        " node (only for the drbd template)")),
856                      make_option("--disks", dest="disks", default=None,
857                                  help=("Comma-separated list of disks"
858                                        " to replace (e.g. sda) (optional,"
859                                        " defaults to all disks")),
860                      make_option("--iallocator", metavar="<NAME>",
861                                  help="Select new secondary for the instance"
862                                  " automatically using the"
863                                  " <NAME> iallocator plugin (enables"
864                                  " secondary node replacement)",
865                                  default=None, type="string"),
866                      ],
867                     "Replaces all disks for the instance"),
868   'modify': (SetInstanceParams, ARGS_ONE,
869              [DEBUG_OPT, FORCE_OPT,
870               cli_option("-m", "--memory", dest="mem",
871                          help="Memory size",
872                          default=None, type="unit", metavar="<mem>"),
873               make_option("-p", "--cpu", dest="vcpus",
874                           help="Number of virtual CPUs",
875                           default=None, type="int", metavar="<PROC>"),
876               make_option("-i", "--ip", dest="ip",
877                           help="IP address ('none' or numeric IP)",
878                           default=None, type="string", metavar="<ADDRESS>"),
879               make_option("-b", "--bridge", dest="bridge",
880                           help="Bridge to connect this instance to",
881                           default=None, type="string", metavar="<bridge>"),
882               make_option("--mac", dest="mac",
883                           help="MAC address", default=None,
884                           type="string", metavar="<MACADDRESS>"),
885               make_option("--kernel", dest="kernel_path",
886                           help="Path to the instances' kernel (or"
887                           " 'default')", default=None,
888                           type="string", metavar="<FILENAME>"),
889               make_option("--initrd", dest="initrd_path",
890                           help="Path to the instances' initrd (or 'none', or"
891                           " 'default')", default=None,
892                           type="string", metavar="<FILENAME>"),
893               make_option("--hvm-boot-order", dest="hvm_boot_order",
894                           help="boot device order for HVM"
895                           "(either one or more of [acdn] or 'default')",
896                           default=None, type="string", metavar="<BOOTORDER>"),
897               ],
898              "Alters the parameters of an instance"),
899   'shutdown': (ShutdownInstance, ARGS_ANY,
900                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
901                 m_clust_opt, m_inst_opt, m_force_multi],
902                "Stops an instance"),
903   'startup': (StartupInstance, ARGS_ANY,
904               [DEBUG_OPT, FORCE_OPT, m_force_multi,
905                make_option("-e", "--extra", dest="extra_args",
906                            help="Extra arguments for the instance's kernel",
907                            default=None, type="string", metavar="<PARAMS>"),
908                m_node_opt, m_pri_node_opt, m_sec_node_opt,
909                m_clust_opt, m_inst_opt,
910                ],
911               "Starts an instance"),
912
913   'reboot': (RebootInstance, ARGS_ANY,
914               [DEBUG_OPT, m_force_multi,
915                make_option("-e", "--extra", dest="extra_args",
916                            help="Extra arguments for the instance's kernel",
917                            default=None, type="string", metavar="<PARAMS>"),
918                make_option("-t", "--type", dest="reboot_type",
919                            help="Type of reboot: soft/hard/full",
920                            default=constants.INSTANCE_REBOOT_SOFT,
921                            type="string", metavar="<REBOOT>"),
922                make_option("--ignore-secondaries", dest="ignore_secondaries",
923                            default=False, action="store_true",
924                            help="Ignore errors from secondaries"),
925                m_node_opt, m_pri_node_opt, m_sec_node_opt,
926                m_clust_opt, m_inst_opt,
927                ],
928              "Reboots an instance"),
929   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT],
930                      "Activate an instance's disks"),
931   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
932                        "Deactivate an instance's disks"),
933   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
934                 "List the tags of the given instance"),
935   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
936                "Add tags to the given instance"),
937   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
938                   "Remove tags from given instance"),
939   }
940
941 aliases = {
942   'activate_block_devs': 'activate-disks',
943   'replace_disks': 'replace-disks',
944   'start': 'startup',
945   'stop': 'shutdown',
946   }
947
948 if __name__ == '__main__':
949   sys.exit(GenericMain(commands, aliases=aliases,
950                        override={"tag_type": constants.TAG_INSTANCE}))