480232834b7eeb83b32d3db7b7354bcd0ac32b5e
[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 # pylint: disable-msg=W0401,W0614
23 # W0401: Wildcard import ganeti.cli
24 # W0614: Unused import %s from wildcard import (since we need cli)
25
26 import sys
27 import os
28 import itertools
29 import simplejson
30 from optparse import make_option
31 from cStringIO import StringIO
32
33 from ganeti.cli import *
34 from ganeti import cli
35 from ganeti import opcodes
36 from ganeti import constants
37 from ganeti import utils
38 from ganeti import errors
39
40
41 _SHUTDOWN_CLUSTER = "cluster"
42 _SHUTDOWN_NODES_BOTH = "nodes"
43 _SHUTDOWN_NODES_PRI = "nodes-pri"
44 _SHUTDOWN_NODES_SEC = "nodes-sec"
45 _SHUTDOWN_INSTANCES = "instances"
46
47
48 _VALUE_TRUE = "true"
49
50 #: default list of options for L{ListInstances}
51 _LIST_DEF_FIELDS = [
52   "name", "hypervisor", "os", "pnode", "status", "oper_ram",
53   ]
54
55
56 def _ExpandMultiNames(mode, names):
57   """Expand the given names using the passed mode.
58
59   For _SHUTDOWN_CLUSTER, all instances will be returned. For
60   _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
61   primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
62   instances having those nodes as either primary or secondary will be
63   returned. For _SHUTDOWN_INSTANCES, the given instances will be
64   returned.
65
66   @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
67       L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
68       L{_SHUTDOWN_INSTANCES}
69   @param names: a list of names; for cluster, it must be empty,
70       and for node and instance it must be a list of valid item
71       names (short names are valid as usual, e.g. node1 instead of
72       node1.example.com)
73   @rtype: list
74   @return: the list of names after the expansion
75   @raise errors.ProgrammerError: for unknown selection type
76   @raise errors.OpPrereqError: for invalid input parameters
77
78   """
79   if mode == _SHUTDOWN_CLUSTER:
80     if names:
81       raise errors.OpPrereqError("Cluster filter mode takes no arguments")
82     client = GetClient()
83     idata = client.QueryInstances([], ["name"])
84     inames = [row[0] for row in idata]
85
86   elif mode in (_SHUTDOWN_NODES_BOTH,
87                 _SHUTDOWN_NODES_PRI,
88                 _SHUTDOWN_NODES_SEC):
89     if not names:
90       raise errors.OpPrereqError("No node names passed")
91     client = GetClient()
92     ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"])
93     ipri = [row[1] for row in ndata]
94     pri_names = list(itertools.chain(*ipri))
95     isec = [row[2] for row in ndata]
96     sec_names = list(itertools.chain(*isec))
97     if mode == _SHUTDOWN_NODES_BOTH:
98       inames = pri_names + sec_names
99     elif mode == _SHUTDOWN_NODES_PRI:
100       inames = pri_names
101     elif mode == _SHUTDOWN_NODES_SEC:
102       inames = sec_names
103     else:
104       raise errors.ProgrammerError("Unhandled shutdown type")
105
106   elif mode == _SHUTDOWN_INSTANCES:
107     if not names:
108       raise errors.OpPrereqError("No instance names passed")
109     client = GetClient()
110     idata = client.QueryInstances(names, ["name"])
111     inames = [row[0] for row in idata]
112
113   else:
114     raise errors.OpPrereqError("Unknown mode '%s'" % mode)
115
116   return inames
117
118
119 def _ConfirmOperation(inames, text):
120   """Ask the user to confirm an operation on a list of instances.
121
122   This function is used to request confirmation for doing an operation
123   on a given list of instances.
124
125   @type inames: list
126   @param inames: the list of names that we display when
127       we ask for confirmation
128   @type text: str
129   @param text: the operation that the user should confirm
130       (e.g. I{shutdown} or I{startup})
131   @rtype: boolean
132   @return: True or False depending on user's confirmation.
133
134   """
135   count = len(inames)
136   msg = ("The %s will operate on %d instances.\n"
137          "Do you want to continue?" % (text, count))
138   affected = ("\nAffected instances:\n" +
139               "\n".join(["  %s" % name for name in inames]))
140
141   choices = [('y', True, 'Yes, execute the %s' % text),
142              ('n', False, 'No, abort the %s' % text)]
143
144   if count > 20:
145     choices.insert(1, ('v', 'v', 'View the list of affected instances'))
146     ask = msg
147   else:
148     ask = msg + affected
149
150   choice = AskUser(ask, choices)
151   if choice == 'v':
152     choices.pop(1)
153     choice = AskUser(msg + affected, choices)
154   return choice
155
156
157 def _TransformPath(user_input):
158   """Transform a user path into a canonical value.
159
160   This function transforms the a path passed as textual information
161   into the constants that the LU code expects.
162
163   """
164   if user_input:
165     if user_input.lower() == "default":
166       result_path = constants.VALUE_DEFAULT
167     elif user_input.lower() == "none":
168       result_path = constants.VALUE_NONE
169     else:
170       if not os.path.isabs(user_input):
171         raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
172                                    user_input)
173       result_path = user_input
174   else:
175     result_path = constants.VALUE_DEFAULT
176
177   return result_path
178
179
180 def ListInstances(opts, args):
181   """List instances and their properties.
182
183   @param opts: the command line options selected by the user
184   @type args: list
185   @param args: should be an empty list
186   @rtype: int
187   @return: the desired exit code
188
189   """
190   if opts.output is None:
191     selected_fields = _LIST_DEF_FIELDS
192   elif opts.output.startswith("+"):
193     selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
194   else:
195     selected_fields = opts.output.split(",")
196
197   output = GetClient().QueryInstances([], selected_fields)
198
199   if not opts.no_headers:
200     headers = {
201       "name": "Instance", "os": "OS", "pnode": "Primary_node",
202       "snodes": "Secondary_Nodes", "admin_state": "Autostart",
203       "oper_state": "Running",
204       "oper_ram": "Memory", "disk_template": "Disk_template",
205       "ip": "IP_address", "mac": "MAC_address",
206       "bridge": "Bridge",
207       "sda_size": "Disk/0", "sdb_size": "Disk/1",
208       "status": "Status", "tags": "Tags",
209       "network_port": "Network_port",
210       "hv/kernel_path": "Kernel_path",
211       "hv/initrd_path": "Initrd_path",
212       "hv/boot_order": "HVM_boot_order",
213       "hv/acpi": "HVM_ACPI",
214       "hv/pae": "HVM_PAE",
215       "hv/cdrom_image_path": "HVM_CDROM_image_path",
216       "hv/nic_type": "HVM_NIC_type",
217       "hv/disk_type": "HVM_Disk_type",
218       "hv/vnc_bind_address": "VNC_bind_address",
219       "serial_no": "SerialNo", "hypervisor": "Hypervisor",
220       "hvparams": "Hypervisor_parameters",
221       "be/memory": "Configured_memory",
222       "be/vcpus": "VCPUs",
223       "be/auto_balance": "Auto_balance",
224       }
225   else:
226     headers = None
227
228   if opts.human_readable:
229     unitfields = ["be/memory", "oper_ram", "sda_size", "sdb_size"]
230   else:
231     unitfields = None
232
233   numfields = ["be/memory", "oper_ram", "sda_size", "sdb_size", "be/vcpus",
234                "serial_no"]
235
236   list_type_fields = ("tags",)
237   # change raw values to nicer strings
238   for row in output:
239     for idx, field in enumerate(selected_fields):
240       val = row[idx]
241       if field == "snodes":
242         val = ",".join(val) or "-"
243       elif field == "admin_state":
244         if val:
245           val = "yes"
246         else:
247           val = "no"
248       elif field == "oper_state":
249         if val is None:
250           val = "(node down)"
251         elif val: # True
252           val = "running"
253         else:
254           val = "stopped"
255       elif field == "oper_ram":
256         if val is None:
257           val = "(node down)"
258       elif field == "sda_size" or field == "sdb_size":
259         if val is None:
260           val = "N/A"
261       elif field in list_type_fields:
262         val = ",".join(val)
263       elif val is None:
264         val = "-"
265       row[idx] = str(val)
266
267   data = GenerateTable(separator=opts.separator, headers=headers,
268                        fields=selected_fields, unitfields=unitfields,
269                        numfields=numfields, data=output)
270
271   for line in data:
272     ToStdout(line)
273
274   return 0
275
276
277 def AddInstance(opts, args):
278   """Add an instance to the cluster.
279
280   @param opts: the command line options selected by the user
281   @type args: list
282   @param args: should contain only one element, the new instance name
283   @rtype: int
284   @return: the desired exit code
285
286   """
287   instance = args[0]
288
289   (pnode, snode) = SplitNodeOption(opts.node)
290
291   hypervisor = None
292   hvparams = {}
293   if opts.hypervisor:
294     hypervisor, hvparams = opts.hypervisor
295
296   ValidateBeParams(opts.beparams)
297
298 ##  kernel_path = _TransformPath(opts.kernel_path)
299 ##  initrd_path = _TransformPath(opts.initrd_path)
300
301 ##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
302 ##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
303
304 ##  if ((opts.hvm_cdrom_image_path is not None) and
305 ##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
306 ##    hvm_cdrom_image_path = None
307 ##  else:
308 ##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
309
310   op = opcodes.OpCreateInstance(instance_name=instance,
311                                 disk_size=opts.size, swap_size=opts.swap,
312                                 disk_template=opts.disk_template,
313                                 mode=constants.INSTANCE_CREATE,
314                                 os_type=opts.os, pnode=pnode,
315                                 snode=snode,
316                                 ip=opts.ip, bridge=opts.bridge,
317                                 start=opts.start, ip_check=opts.ip_check,
318                                 wait_for_sync=opts.wait_for_sync,
319                                 mac=opts.mac,
320                                 hypervisor=hypervisor,
321                                 hvparams=hvparams,
322                                 beparams=opts.beparams,
323                                 iallocator=opts.iallocator,
324                                 file_storage_dir=opts.file_storage_dir,
325                                 file_driver=opts.file_driver,
326                                 )
327
328   SubmitOrSend(op, opts)
329   return 0
330
331
332 def BatchCreate(opts, args):
333   """Create instances using a definition file.
334
335   This function reads a json file with instances defined
336   in the form::
337
338     {"instance-name":{
339       "disk_size": 25,
340       "swap_size": 1024,
341       "template": "drbd",
342       "backend": {
343         "memory": 512,
344         "vcpus": 1 },
345       "os": "etch-image",
346       "primary_node": "firstnode",
347       "secondary_node": "secondnode",
348       "iallocator": "dumb"}
349     }
350
351   Note that I{primary_node} and I{secondary_node} have precedence over
352   I{iallocator}.
353
354   @param opts: the command line options selected by the user
355   @type args: list
356   @param args: should contain one element, the json filename
357   @rtype: int
358   @return: the desired exit code
359
360   """
361   _DEFAULT_SPECS = {"disk_size": 20 * 1024,
362                     "swap_size": 4 * 1024,
363                     "backend": {},
364                     "iallocator": None,
365                     "primary_node": None,
366                     "secondary_node": None,
367                     "ip": 'none',
368                     "mac": 'auto',
369                     "bridge": None,
370                     "start": True,
371                     "ip_check": True,
372                     "hypervisor": None,
373                     "file_storage_dir": None,
374                     "file_driver": 'loop'}
375
376   def _PopulateWithDefaults(spec):
377     """Returns a new hash combined with default values."""
378     mydict = _DEFAULT_SPECS.copy()
379     mydict.update(spec)
380     return mydict
381
382   def _Validate(spec):
383     """Validate the instance specs."""
384     # Validate fields required under any circumstances
385     for required_field in ('os', 'template'):
386       if required_field not in spec:
387         raise errors.OpPrereqError('Required field "%s" is missing.' %
388                                    required_field)
389     # Validate special fields
390     if spec['primary_node'] is not None:
391       if (spec['template'] in constants.DTS_NET_MIRROR and
392           spec['secondary_node'] is None):
393         raise errors.OpPrereqError('Template requires secondary node, but'
394                                    ' there was no secondary provided.')
395     elif spec['iallocator'] is None:
396       raise errors.OpPrereqError('You have to provide at least a primary_node'
397                                  ' or an iallocator.')
398
399     if (spec['hypervisor'] and
400         not isinstance(spec['hypervisor'], dict)):
401       raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
402
403   json_filename = args[0]
404   fd = open(json_filename, 'r')
405   try:
406     instance_data = simplejson.load(fd)
407   finally:
408     fd.close()
409
410   # Iterate over the instances and do:
411   #  * Populate the specs with default value
412   #  * Validate the instance specs
413   for (name, specs) in instance_data.iteritems():
414     specs = _PopulateWithDefaults(specs)
415     _Validate(specs)
416
417     hypervisor = None
418     hvparams = {}
419     if specs['hypervisor']:
420       hypervisor, hvparams = specs['hypervisor'].iteritems()
421
422     op = opcodes.OpCreateInstance(instance_name=name,
423                                   disk_size=specs['disk_size'],
424                                   swap_size=specs['swap_size'],
425                                   disk_template=specs['template'],
426                                   mode=constants.INSTANCE_CREATE,
427                                   os_type=specs['os'],
428                                   pnode=specs['primary_node'],
429                                   snode=specs['secondary_node'],
430                                   ip=specs['ip'], bridge=specs['bridge'],
431                                   start=specs['start'],
432                                   ip_check=specs['ip_check'],
433                                   wait_for_sync=True,
434                                   mac=specs['mac'],
435                                   iallocator=specs['iallocator'],
436                                   hypervisor=hypervisor,
437                                   hvparams=hvparams,
438                                   beparams=specs['backend'],
439                                   file_storage_dir=specs['file_storage_dir'],
440                                   file_driver=specs['file_driver'])
441
442     ToStdout("%s: %s", name, cli.SendJob([op]))
443
444   return 0
445
446
447 def ReinstallInstance(opts, args):
448   """Reinstall an instance.
449
450   @param opts: the command line options selected by the user
451   @type args: list
452   @param args: should contain only one element, the name of the
453       instance to be reinstalled
454   @rtype: int
455   @return: the desired exit code
456
457   """
458   instance_name = args[0]
459
460   if opts.select_os is True:
461     op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
462     result = SubmitOpCode(op)
463
464     if not result:
465       ToStdout("Can't get the OS list")
466       return 1
467
468     ToStdout("Available OS templates:")
469     number = 0
470     choices = []
471     for entry in result:
472       ToStdout("%3s: %s", number, entry[0])
473       choices.append(("%s" % number, entry[0], entry[0]))
474       number = number + 1
475
476     choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
477     selected = AskUser("Enter OS template name or number (or x to abort):",
478                        choices)
479
480     if selected == 'exit':
481       ToStdout("User aborted reinstall, exiting")
482       return 1
483
484     os_name = selected
485   else:
486     os_name = opts.os
487
488   if not opts.force:
489     usertext = ("This will reinstall the instance %s and remove"
490                 " all data. Continue?") % instance_name
491     if not AskUser(usertext):
492       return 1
493
494   op = opcodes.OpReinstallInstance(instance_name=instance_name,
495                                    os_type=os_name)
496   SubmitOrSend(op, opts)
497
498   return 0
499
500
501 def RemoveInstance(opts, args):
502   """Remove an instance.
503
504   @param opts: the command line options selected by the user
505   @type args: list
506   @param args: should contain only one element, the name of
507       the instance to be removed
508   @rtype: int
509   @return: the desired exit code
510
511   """
512   instance_name = args[0]
513   force = opts.force
514
515   if not force:
516     usertext = ("This will remove the volumes of the instance %s"
517                 " (including mirrors), thus removing all the data"
518                 " of the instance. Continue?") % instance_name
519     if not AskUser(usertext):
520       return 1
521
522   op = opcodes.OpRemoveInstance(instance_name=instance_name,
523                                 ignore_failures=opts.ignore_failures)
524   SubmitOrSend(op, opts)
525   return 0
526
527
528 def RenameInstance(opts, args):
529   """Rename an instance.
530
531   @param opts: the command line options selected by the user
532   @type args: list
533   @param args: should contain two elements, the old and the
534       new instance names
535   @rtype: int
536   @return: the desired exit code
537
538   """
539   op = opcodes.OpRenameInstance(instance_name=args[0],
540                                 new_name=args[1],
541                                 ignore_ip=opts.ignore_ip)
542   SubmitOrSend(op, opts)
543   return 0
544
545
546 def ActivateDisks(opts, args):
547   """Activate an instance's disks.
548
549   This serves two purposes:
550     - it allows (as long as the instance is not running)
551       mounting the disks and modifying them from the node
552     - it repairs inactive secondary drbds
553
554   @param opts: the command line options selected by the user
555   @type args: list
556   @param args: should contain only one element, the instance name
557   @rtype: int
558   @return: the desired exit code
559
560   """
561   instance_name = args[0]
562   op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
563   disks_info = SubmitOrSend(op, opts)
564   for host, iname, nname in disks_info:
565     ToStdout("%s:%s:%s", host, iname, nname)
566   return 0
567
568
569 def DeactivateDisks(opts, args):
570   """Deactivate an instance's disks..
571
572   This function takes the instance name, looks for its primary node
573   and the tries to shutdown its block devices on that node.
574
575   @param opts: the command line options selected by the user
576   @type args: list
577   @param args: should contain only one element, the instance name
578   @rtype: int
579   @return: the desired exit code
580
581   """
582   instance_name = args[0]
583   op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
584   SubmitOrSend(op, opts)
585   return 0
586
587
588 def GrowDisk(opts, args):
589   """Grow an instance's disks.
590
591   @param opts: the command line options selected by the user
592   @type args: list
593   @param args: should contain two elements, the instance name
594       whose disks we grow and the disk name, e.g. I{sda}
595   @rtype: int
596   @return: the desired exit code
597
598   """
599   instance = args[0]
600   disk = args[1]
601   amount = utils.ParseUnit(args[2])
602   op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
603                           wait_for_sync=opts.wait_for_sync)
604   SubmitOrSend(op, opts)
605   return 0
606
607
608 def StartupInstance(opts, args):
609   """Startup instances.
610
611   Depending on the options given, this will start one or more
612   instances.
613
614   @param opts: the command line options selected by the user
615   @type args: list
616   @param args: the instance or node names based on which we
617       create the final selection (in conjunction with the
618       opts argument)
619   @rtype: int
620   @return: the desired exit code
621
622   """
623   if opts.multi_mode is None:
624     opts.multi_mode = _SHUTDOWN_INSTANCES
625   inames = _ExpandMultiNames(opts.multi_mode, args)
626   if not inames:
627     raise errors.OpPrereqError("Selection filter does not match any instances")
628   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
629   if not (opts.force_multi or not multi_on
630           or _ConfirmOperation(inames, "startup")):
631     return 1
632   for name in inames:
633     op = opcodes.OpStartupInstance(instance_name=name,
634                                    force=opts.force,
635                                    extra_args=opts.extra_args)
636     if multi_on:
637       ToStdout("Starting up %s", name)
638     try:
639       SubmitOrSend(op, opts)
640     except JobSubmittedException, err:
641       _, txt = FormatError(err)
642       ToStdout("%s", txt)
643   return 0
644
645
646 def RebootInstance(opts, args):
647   """Reboot instance(s).
648
649   Depending on the parameters given, this will reboot one or more
650   instances.
651
652   @param opts: the command line options selected by the user
653   @type args: list
654   @param args: the instance or node names based on which we
655       create the final selection (in conjunction with the
656       opts argument)
657   @rtype: int
658   @return: the desired exit code
659
660   """
661   if opts.multi_mode is None:
662     opts.multi_mode = _SHUTDOWN_INSTANCES
663   inames = _ExpandMultiNames(opts.multi_mode, args)
664   if not inames:
665     raise errors.OpPrereqError("Selection filter does not match any instances")
666   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
667   if not (opts.force_multi or not multi_on
668           or _ConfirmOperation(inames, "reboot")):
669     return 1
670   for name in inames:
671     op = opcodes.OpRebootInstance(instance_name=name,
672                                   reboot_type=opts.reboot_type,
673                                   ignore_secondaries=opts.ignore_secondaries)
674
675     SubmitOrSend(op, opts)
676   return 0
677
678
679 def ShutdownInstance(opts, args):
680   """Shutdown an instance.
681
682   @param opts: the command line options selected by the user
683   @type args: list
684   @param args: the instance or node names based on which we
685       create the final selection (in conjunction with the
686       opts argument)
687   @rtype: int
688   @return: the desired exit code
689
690   """
691   if opts.multi_mode is None:
692     opts.multi_mode = _SHUTDOWN_INSTANCES
693   inames = _ExpandMultiNames(opts.multi_mode, args)
694   if not inames:
695     raise errors.OpPrereqError("Selection filter does not match any instances")
696   multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
697   if not (opts.force_multi or not multi_on
698           or _ConfirmOperation(inames, "shutdown")):
699     return 1
700   for name in inames:
701     op = opcodes.OpShutdownInstance(instance_name=name)
702     if multi_on:
703       ToStdout("Shutting down %s", name)
704     try:
705       SubmitOrSend(op, opts)
706     except JobSubmittedException, err:
707       _, txt = FormatError(err)
708       ToStdout("%s", txt)
709   return 0
710
711
712 def ReplaceDisks(opts, args):
713   """Replace the disks of an instance
714
715   @param opts: the command line options selected by the user
716   @type args: list
717   @param args: should contain only one element, the instance name
718   @rtype: int
719   @return: the desired exit code
720
721   """
722   instance_name = args[0]
723   new_2ndary = opts.new_secondary
724   iallocator = opts.iallocator
725   if opts.disks is None:
726     disks = ["sda", "sdb"]
727   else:
728     disks = opts.disks.split(",")
729   if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
730     mode = constants.REPLACE_DISK_ALL
731   elif opts.on_primary: # only on primary:
732     mode = constants.REPLACE_DISK_PRI
733     if new_2ndary is not None or iallocator is not None:
734       raise errors.OpPrereqError("Can't change secondary node on primary disk"
735                                  " replacement")
736   elif opts.on_secondary is not None or iallocator is not None:
737     # only on secondary
738     mode = constants.REPLACE_DISK_SEC
739
740   op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
741                               remote_node=new_2ndary, mode=mode,
742                               iallocator=iallocator)
743   SubmitOrSend(op, opts)
744   return 0
745
746
747 def FailoverInstance(opts, args):
748   """Failover an instance.
749
750   The failover is done by shutting it down on its present node and
751   starting it on the secondary.
752
753   @param opts: the command line options selected by the user
754   @type args: list
755   @param args: should contain only one element, the instance name
756   @rtype: int
757   @return: the desired exit code
758
759   """
760   instance_name = args[0]
761   force = opts.force
762
763   if not force:
764     usertext = ("Failover will happen to image %s."
765                 " This requires a shutdown of the instance. Continue?" %
766                 (instance_name,))
767     if not AskUser(usertext):
768       return 1
769
770   op = opcodes.OpFailoverInstance(instance_name=instance_name,
771                                   ignore_consistency=opts.ignore_consistency)
772   SubmitOrSend(op, opts)
773   return 0
774
775
776 def ConnectToInstanceConsole(opts, args):
777   """Connect to the console of an instance.
778
779   @param opts: the command line options selected by the user
780   @type args: list
781   @param args: should contain only one element, the instance name
782   @rtype: int
783   @return: the desired exit code
784
785   """
786   instance_name = args[0]
787
788   op = opcodes.OpConnectConsole(instance_name=instance_name)
789   cmd = SubmitOpCode(op)
790
791   if opts.show_command:
792     ToStdout("%s", utils.ShellQuoteArgs(cmd))
793   else:
794     try:
795       os.execvp(cmd[0], cmd)
796     finally:
797       ToStderr("Can't run console command %s with arguments:\n'%s'",
798                cmd[0], " ".join(cmd))
799       os._exit(1)
800
801
802 def _FormatBlockDevInfo(buf, dev, indent_level, static):
803   """Show block device information.
804
805   This is only used by L{ShowInstanceConfig}, but it's too big to be
806   left for an inline definition.
807
808   @type buf: StringIO
809   @param buf: buffer that will accumulate the output
810   @type dev: dict
811   @param dev: dictionary with disk information
812   @type indent_level: int
813   @param indent_level: the indendation level we are at, used for
814       the layout of the device tree
815   @type static: boolean
816   @param static: wheter the device information doesn't contain
817       runtime information but only static data
818
819   """
820   def helper(buf, dtype, status):
821     """Format one line for physical device status.
822
823     @type buf: StringIO
824     @param buf: buffer that will accumulate the output
825     @type dtype: str
826     @param dtype: a constant from the L{constants.LDS_BLOCK} set
827     @type status: tuple
828     @param status: a tuple as returned from L{backend.FindBlockDevice}
829
830     """
831     if not status:
832       buf.write("not active\n")
833     else:
834       (path, major, minor, syncp, estt, degr, ldisk) = status
835       if major is None:
836         major_string = "N/A"
837       else:
838         major_string = str(major)
839
840       if minor is None:
841         minor_string = "N/A"
842       else:
843         minor_string = str(minor)
844
845       buf.write("%s (%s:%s)" % (path, major_string, minor_string))
846       if dtype in (constants.LD_DRBD8, ):
847         if syncp is not None:
848           sync_text = "*RECOVERING* %5.2f%%," % syncp
849           if estt:
850             sync_text += " ETA %ds" % estt
851           else:
852             sync_text += " ETA unknown"
853         else:
854           sync_text = "in sync"
855         if degr:
856           degr_text = "*DEGRADED*"
857         else:
858           degr_text = "ok"
859         if ldisk:
860           ldisk_text = " *MISSING DISK*"
861         else:
862           ldisk_text = ""
863         buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
864       elif dtype == constants.LD_LV:
865         if ldisk:
866           ldisk_text = " *FAILED* (failed drive?)"
867         else:
868           ldisk_text = ""
869         buf.write(ldisk_text)
870       buf.write("\n")
871
872   if dev["iv_name"] is not None:
873     data = "  - %s, " % dev["iv_name"]
874   else:
875     data = "  - "
876   data += "type: %s" % dev["dev_type"]
877   if dev["logical_id"] is not None:
878     data += ", logical_id: %s" % (dev["logical_id"],)
879   elif dev["physical_id"] is not None:
880     data += ", physical_id: %s" % (dev["physical_id"],)
881   buf.write("%*s%s\n" % (2*indent_level, "", data))
882   if not static:
883     buf.write("%*s    primary:   " % (2*indent_level, ""))
884     helper(buf, dev["dev_type"], dev["pstatus"])
885
886   if dev["sstatus"] and not static:
887     buf.write("%*s    secondary: " % (2*indent_level, ""))
888     helper(buf, dev["dev_type"], dev["sstatus"])
889
890   if dev["children"]:
891     for child in dev["children"]:
892       _FormatBlockDevInfo(buf, child, indent_level+1, static)
893
894
895 def ShowInstanceConfig(opts, args):
896   """Compute instance run-time status.
897
898   @param opts: the command line options selected by the user
899   @type args: list
900   @param args: either an empty list, and then we query all
901       instances, or should contain a list of instance names
902   @rtype: int
903   @return: the desired exit code
904
905   """
906   retcode = 0
907   op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
908   result = SubmitOpCode(op)
909   if not result:
910     ToStdout("No instances.")
911     return 1
912
913   buf = StringIO()
914   retcode = 0
915   for instance_name in result:
916     instance = result[instance_name]
917     buf.write("Instance name: %s\n" % instance["name"])
918     buf.write("State: configured to be %s" % instance["config_state"])
919     if not opts.static:
920       buf.write(", actual state is %s" % instance["run_state"])
921     buf.write("\n")
922     ##buf.write("Considered for memory checks in cluster verify: %s\n" %
923     ##          instance["auto_balance"])
924     buf.write("  Nodes:\n")
925     buf.write("    - primary: %s\n" % instance["pnode"])
926     buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
927     buf.write("  Operating system: %s\n" % instance["os"])
928     if instance.has_key("network_port"):
929       buf.write("  Allocated network port: %s\n" % instance["network_port"])
930     buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
931     if instance["hypervisor"] == constants.HT_XEN_PVM:
932       hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
933                  (constants.HV_INITRD_PATH, "initrd path"))
934     elif instance["hypervisor"] == constants.HT_XEN_HVM:
935       hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
936                  (constants.HV_ACPI, "ACPI"),
937                  (constants.HV_PAE, "PAE"),
938                  (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
939                  (constants.HV_NIC_TYPE, "NIC type"),
940                  (constants.HV_DISK_TYPE, "Disk type"),
941                  (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
942                  )
943       # custom console information for HVM
944       vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
945       if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
946         vnc_console_port = "%s:%s" % (instance["pnode"],
947                                       instance["network_port"])
948       elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
949         vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
950                                                  instance["network_port"],
951                                                  instance["pnode"])
952       else:
953         vnc_console_port = "%s:%s" % (vnc_bind_address,
954                                       instance["network_port"])
955       buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
956
957     else:
958       # auto-handle other hypervisor types
959       hvattrs = [(key, key) for key in instance["hv_actual"]]
960
961     for key, desc in hvattrs:
962       if key in instance["hv_instance"]:
963         val = instance["hv_instance"][key]
964       else:
965         val = "default (%s)" % instance["hv_actual"][key]
966       buf.write("    - %s: %s\n" % (desc, val))
967     buf.write("  Hardware:\n")
968     buf.write("    - VCPUs: %d\n" %
969               instance["be_actual"][constants.BE_VCPUS])
970     buf.write("    - memory: %dMiB\n" %
971               instance["be_actual"][constants.BE_MEMORY])
972     buf.write("    - NICs: %s\n" %
973               ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
974                          (mac, ip, bridge)
975                          for mac, ip, bridge in instance["nics"]]))
976     buf.write("  Block devices:\n")
977
978     for device in instance["disks"]:
979       _FormatBlockDevInfo(buf, device, 1, opts.static)
980
981   ToStdout(buf.getvalue().rstrip('\n'))
982   return retcode
983
984
985 def SetInstanceParams(opts, args):
986   """Modifies an instance.
987
988   All parameters take effect only at the next restart of the instance.
989
990   @param opts: the command line options selected by the user
991   @type args: list
992   @param args: should contain only one element, the instance name
993   @rtype: int
994   @return: the desired exit code
995
996   """
997   if not (opts.ip or opts.bridge or opts.mac or
998           opts.hypervisor or opts.beparams):
999     ToStderr("Please give at least one of the parameters.")
1000     return 1
1001
1002   if constants.BE_MEMORY in opts.beparams:
1003     opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
1004       opts.beparams[constants.BE_MEMORY])
1005
1006   op = opcodes.OpSetInstanceParams(instance_name=args[0],
1007                                    ip=opts.ip,
1008                                    bridge=opts.bridge, mac=opts.mac,
1009                                    hvparams=opts.hypervisor,
1010                                    beparams=opts.beparams,
1011                                    force=opts.force)
1012
1013   # even if here we process the result, we allow submit only
1014   result = SubmitOrSend(op, opts)
1015
1016   if result:
1017     ToStdout("Modified instance %s", args[0])
1018     for param, data in result:
1019       ToStdout(" - %-5s -> %s", param, data)
1020     ToStdout("Please don't forget that these parameters take effect"
1021              " only at the next start of the instance.")
1022   return 0
1023
1024
1025 # options used in more than one cmd
1026 node_opt = make_option("-n", "--node", dest="node", help="Target node",
1027                        metavar="<node>")
1028
1029 os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1030                     metavar="<os>")
1031
1032 # multi-instance selection options
1033 m_force_multi = make_option("--force-multiple", dest="force_multi",
1034                             help="Do not ask for confirmation when more than"
1035                             " one instance is affected",
1036                             action="store_true", default=False)
1037
1038 m_pri_node_opt = make_option("--primary", dest="multi_mode",
1039                              help="Filter by nodes (primary only)",
1040                              const=_SHUTDOWN_NODES_PRI, action="store_const")
1041
1042 m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1043                              help="Filter by nodes (secondary only)",
1044                              const=_SHUTDOWN_NODES_SEC, action="store_const")
1045
1046 m_node_opt = make_option("--node", dest="multi_mode",
1047                          help="Filter by nodes (primary and secondary)",
1048                          const=_SHUTDOWN_NODES_BOTH, action="store_const")
1049
1050 m_clust_opt = make_option("--all", dest="multi_mode",
1051                           help="Select all instances in the cluster",
1052                           const=_SHUTDOWN_CLUSTER, action="store_const")
1053
1054 m_inst_opt = make_option("--instance", dest="multi_mode",
1055                          help="Filter by instance name [default]",
1056                          const=_SHUTDOWN_INSTANCES, action="store_const")
1057
1058
1059 # this is defined separately due to readability only
1060 add_opts = [
1061   DEBUG_OPT,
1062   make_option("-n", "--node", dest="node",
1063               help="Target node and optional secondary node",
1064               metavar="<pnode>[:<snode>]"),
1065   cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
1066              " a suffix is used",
1067              default=20 * 1024, type="unit", metavar="<size>"),
1068   cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
1069              " suffix is used",
1070              default=4 * 1024, type="unit", metavar="<size>"),
1071   os_opt,
1072   keyval_option("-B", "--backend", dest="beparams",
1073                 type="keyval", default={},
1074                 help="Backend parameters"),
1075   make_option("-t", "--disk-template", dest="disk_template",
1076               help="Custom disk setup (diskless, file, plain or drbd)",
1077               default=None, metavar="TEMPL"),
1078   make_option("-i", "--ip", dest="ip",
1079               help="IP address ('none' [default], 'auto', or specify address)",
1080               default='none', type="string", metavar="<ADDRESS>"),
1081   make_option("--mac", dest="mac",
1082               help="MAC address ('auto' [default], or specify address)",
1083               default='auto', type="string", metavar="<MACADDRESS>"),
1084   make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1085               action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1086   make_option("-b", "--bridge", dest="bridge",
1087               help="Bridge to connect this instance to",
1088               default=None, metavar="<bridge>"),
1089   make_option("--no-start", dest="start", default=True,
1090               action="store_false", help="Don't start the instance after"
1091               " creation"),
1092   make_option("--no-ip-check", dest="ip_check", default=True,
1093               action="store_false", help="Don't check that the instance's IP"
1094               " is alive (only valid with --no-start)"),
1095   make_option("--file-storage-dir", dest="file_storage_dir",
1096               help="Relative path under default cluster-wide file storage dir"
1097               " to store file-based disks", default=None,
1098               metavar="<DIR>"),
1099   make_option("--file-driver", dest="file_driver", help="Driver to use"
1100               " for image files", default="loop", metavar="<DRIVER>"),
1101   make_option("--iallocator", metavar="<NAME>",
1102               help="Select nodes for the instance automatically using the"
1103               " <NAME> iallocator plugin", default=None, type="string"),
1104   ikv_option("-H", "--hypervisor", dest="hypervisor",
1105               help="Hypervisor and hypervisor options, in the format"
1106               " hypervisor:option=value,option=value,...", default=None,
1107               type="identkeyval"),
1108   SUBMIT_OPT,
1109   ]
1110
1111 commands = {
1112   'add': (AddInstance, ARGS_ONE, add_opts,
1113           "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1114           "Creates and adds a new instance to the cluster"),
1115   'batch-create': (BatchCreate, ARGS_ONE,
1116                    [DEBUG_OPT],
1117                    "<instances_file.json>",
1118                    "Create a bunch of instances based on specs in the file."),
1119   'console': (ConnectToInstanceConsole, ARGS_ONE,
1120               [DEBUG_OPT,
1121                make_option("--show-cmd", dest="show_command",
1122                            action="store_true", default=False,
1123                            help=("Show command instead of executing it"))],
1124               "[--show-cmd] <instance>",
1125               "Opens a console on the specified instance"),
1126   'failover': (FailoverInstance, ARGS_ONE,
1127                [DEBUG_OPT, FORCE_OPT,
1128                 make_option("--ignore-consistency", dest="ignore_consistency",
1129                             action="store_true", default=False,
1130                             help="Ignore the consistency of the disks on"
1131                             " the secondary"),
1132                 SUBMIT_OPT,
1133                 ],
1134                "[-f] <instance>",
1135                "Stops the instance and starts it on the backup node, using"
1136                " the remote mirror (only for instances of type drbd)"),
1137   'info': (ShowInstanceConfig, ARGS_ANY,
1138            [DEBUG_OPT,
1139             make_option("-s", "--static", dest="static",
1140                         action="store_true", default=False,
1141                         help="Only show configuration data, not runtime data"),
1142             ], "[-s] [<instance>...]",
1143            "Show information on the specified instance(s)"),
1144   'list': (ListInstances, ARGS_NONE,
1145            [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1146            "Lists the instances and their status. The available fields are"
1147            " (see the man page for details): status, oper_state, oper_ram,"
1148            " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1149            " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1150            " hypervisor."
1151            " The default field"
1152            " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1153            ),
1154   'reinstall': (ReinstallInstance, ARGS_ONE,
1155                 [DEBUG_OPT, FORCE_OPT, os_opt,
1156                  make_option("--select-os", dest="select_os",
1157                              action="store_true", default=False,
1158                              help="Interactive OS reinstall, lists available"
1159                              " OS templates for selection"),
1160                  SUBMIT_OPT,
1161                  ],
1162                 "[-f] <instance>", "Reinstall a stopped instance"),
1163   'remove': (RemoveInstance, ARGS_ONE,
1164              [DEBUG_OPT, FORCE_OPT,
1165               make_option("--ignore-failures", dest="ignore_failures",
1166                           action="store_true", default=False,
1167                           help=("Remove the instance from the cluster even"
1168                                 " if there are failures during the removal"
1169                                 " process (shutdown, disk removal, etc.)")),
1170               SUBMIT_OPT,
1171               ],
1172              "[-f] <instance>", "Shuts down the instance and removes it"),
1173   'rename': (RenameInstance, ARGS_FIXED(2),
1174              [DEBUG_OPT,
1175               make_option("--no-ip-check", dest="ignore_ip",
1176                           help="Do not check that the IP of the new name"
1177                           " is alive",
1178                           default=False, action="store_true"),
1179               SUBMIT_OPT,
1180               ],
1181              "<instance> <new_name>", "Rename the instance"),
1182   'replace-disks': (ReplaceDisks, ARGS_ONE,
1183                     [DEBUG_OPT,
1184                      make_option("-n", "--new-secondary", dest="new_secondary",
1185                                  help=("New secondary node (for secondary"
1186                                        " node change)"), metavar="NODE"),
1187                      make_option("-p", "--on-primary", dest="on_primary",
1188                                  default=False, action="store_true",
1189                                  help=("Replace the disk(s) on the primary"
1190                                        " node (only for the drbd template)")),
1191                      make_option("-s", "--on-secondary", dest="on_secondary",
1192                                  default=False, action="store_true",
1193                                  help=("Replace the disk(s) on the secondary"
1194                                        " node (only for the drbd template)")),
1195                      make_option("--disks", dest="disks", default=None,
1196                                  help=("Comma-separated list of disks"
1197                                        " to replace (e.g. sda) (optional,"
1198                                        " defaults to all disks")),
1199                      make_option("--iallocator", metavar="<NAME>",
1200                                  help="Select new secondary for the instance"
1201                                  " automatically using the"
1202                                  " <NAME> iallocator plugin (enables"
1203                                  " secondary node replacement)",
1204                                  default=None, type="string"),
1205                      SUBMIT_OPT,
1206                      ],
1207                     "[-s|-p|-n NODE] <instance>",
1208                     "Replaces all disks for the instance"),
1209   'modify': (SetInstanceParams, ARGS_ONE,
1210              [DEBUG_OPT, FORCE_OPT,
1211               make_option("-i", "--ip", dest="ip",
1212                           help="IP address ('none' or numeric IP)",
1213                           default=None, type="string", metavar="<ADDRESS>"),
1214               make_option("-b", "--bridge", dest="bridge",
1215                           help="Bridge to connect this instance to",
1216                           default=None, type="string", metavar="<bridge>"),
1217               make_option("--mac", dest="mac",
1218                           help="MAC address", default=None,
1219                           type="string", metavar="<MACADDRESS>"),
1220               keyval_option("-H", "--hypervisor", type="keyval",
1221                             default={}, dest="hypervisor",
1222                             help="Change hypervisor parameters"),
1223               keyval_option("-B", "--backend", type="keyval",
1224                             default={}, dest="beparams",
1225                             help="Change backend parameters"),
1226               SUBMIT_OPT,
1227               ],
1228              "<instance>", "Alters the parameters of an instance"),
1229   'shutdown': (ShutdownInstance, ARGS_ANY,
1230                [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1231                 m_clust_opt, m_inst_opt, m_force_multi,
1232                 SUBMIT_OPT,
1233                 ],
1234                "<instance>", "Stops an instance"),
1235   'startup': (StartupInstance, ARGS_ANY,
1236               [DEBUG_OPT, FORCE_OPT, m_force_multi,
1237                make_option("-e", "--extra", dest="extra_args",
1238                            help="Extra arguments for the instance's kernel",
1239                            default=None, type="string", metavar="<PARAMS>"),
1240                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1241                m_clust_opt, m_inst_opt,
1242                SUBMIT_OPT,
1243                ],
1244             "<instance>", "Starts an instance"),
1245
1246   'reboot': (RebootInstance, ARGS_ANY,
1247               [DEBUG_OPT, m_force_multi,
1248                make_option("-e", "--extra", dest="extra_args",
1249                            help="Extra arguments for the instance's kernel",
1250                            default=None, type="string", metavar="<PARAMS>"),
1251                make_option("-t", "--type", dest="reboot_type",
1252                            help="Type of reboot: soft/hard/full",
1253                            default=constants.INSTANCE_REBOOT_HARD,
1254                            type="string", metavar="<REBOOT>"),
1255                make_option("--ignore-secondaries", dest="ignore_secondaries",
1256                            default=False, action="store_true",
1257                            help="Ignore errors from secondaries"),
1258                m_node_opt, m_pri_node_opt, m_sec_node_opt,
1259                m_clust_opt, m_inst_opt,
1260                SUBMIT_OPT,
1261                ],
1262             "<instance>", "Reboots an instance"),
1263   'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1264                      "<instance>",
1265                      "Activate an instance's disks"),
1266   'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1267                        "<instance>",
1268                        "Deactivate an instance's disks"),
1269   'grow-disk': (GrowDisk, ARGS_FIXED(3),
1270                 [DEBUG_OPT, SUBMIT_OPT,
1271                  make_option("--no-wait-for-sync",
1272                              dest="wait_for_sync", default=True,
1273                              action="store_false",
1274                              help="Don't wait for sync (DANGEROUS!)"),
1275                  ],
1276                 "<instance> <disk> <size>", "Grow an instance's disk"),
1277   'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1278                 "<instance_name>", "List the tags of the given instance"),
1279   'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1280                "<instance_name> tag...", "Add tags to the given instance"),
1281   'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1282                   "<instance_name> tag...", "Remove tags from given instance"),
1283   }
1284
1285 #: dictionary with aliases for commands
1286 aliases = {
1287   'activate_block_devs': 'activate-disks',
1288   'replace_disks': 'replace-disks',
1289   'start': 'startup',
1290   'stop': 'shutdown',
1291   }
1292
1293 if __name__ == '__main__':
1294   sys.exit(GenericMain(commands, aliases=aliases,
1295                        override={"tag_type": constants.TAG_INSTANCE}))