Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 7232c04c

History | View | Annotate | Download (46 kB)

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}))