Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 00430f8e

History | View | Annotate | Download (47.2 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
      "disk.count": "Disks",
225
      "nic.count": "NICs",
226
      }
227
  else:
228
    headers = None
229

    
230
  if opts.human_readable:
231
    unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk.size/.*"]
232
  else:
233
    unitfields = None
234

    
235
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
236
               "serial_no", "(disk|nic).count", "disk.size/.*"]
237

    
238
  list_type_fields = ("tags",)
239
  # change raw values to nicer strings
240
  for row in output:
241
    for idx, field in enumerate(selected_fields):
242
      val = row[idx]
243
      if field == "snodes":
244
        val = ",".join(val) or "-"
245
      elif field == "admin_state":
246
        if val:
247
          val = "yes"
248
        else:
249
          val = "no"
250
      elif field == "oper_state":
251
        if val is None:
252
          val = "(node down)"
253
        elif val: # True
254
          val = "running"
255
        else:
256
          val = "stopped"
257
      elif field == "oper_ram":
258
        if val is None:
259
          val = "(node down)"
260
      elif field == "sda_size" or field == "sdb_size":
261
        if val is None:
262
          val = "N/A"
263
      elif field in list_type_fields:
264
        val = ",".join(val)
265
      elif val is None:
266
        val = "-"
267
      row[idx] = str(val)
268

    
269
  data = GenerateTable(separator=opts.separator, headers=headers,
270
                       fields=selected_fields, unitfields=unitfields,
271
                       numfields=numfields, data=output)
272

    
273
  for line in data:
274
    ToStdout(line)
275

    
276
  return 0
277

    
278

    
279
def AddInstance(opts, args):
280
  """Add an instance to the cluster.
281

    
282
  @param opts: the command line options selected by the user
283
  @type args: list
284
  @param args: should contain only one element, the new instance name
285
  @rtype: int
286
  @return: the desired exit code
287

    
288
  """
289
  instance = args[0]
290

    
291
  (pnode, snode) = SplitNodeOption(opts.node)
292

    
293
  hypervisor = None
294
  hvparams = {}
295
  if opts.hypervisor:
296
    hypervisor, hvparams = opts.hypervisor
297

    
298
  if opts.nics:
299
    try:
300
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
301
    except ValueError, err:
302
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
303
    nics = [{}] * nic_max
304
    for nidx, ndict in opts.nics.items():
305
      nidx = int(nidx)
306
      nics[nidx] = ndict
307
  else:
308
    # default of one nic, all auto
309
    nics = [{}]
310

    
311
  if not opts.disks and opts.disk_template != constants.DT_DISKLESS:
312
    raise errors.OpPrereqError("No disk information specified")
313
  elif opts.disks and opts.disk_template == constants.DT_DISKLESS:
314
    raise errors.OpPrereqError("Diskless instance but disk information passeD")
315
  else:
316
    try:
317
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
318
    except ValueError, err:
319
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
320
    disks = [{}] * disk_max
321
    for didx, ddict in opts.disks:
322
      didx = int(didx)
323
      if "size" not in ddict:
324
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
325
      try:
326
        ddict["size"] = utils.ParseUnit(ddict["size"])
327
      except ValueError, err:
328
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
329
                                   (didx, err))
330
      disks[didx] = ddict
331

    
332
  ValidateBeParams(opts.beparams)
333

    
334
##  kernel_path = _TransformPath(opts.kernel_path)
335
##  initrd_path = _TransformPath(opts.initrd_path)
336

    
337
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
338
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
339

    
340
##  if ((opts.hvm_cdrom_image_path is not None) and
341
##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
342
##    hvm_cdrom_image_path = None
343
##  else:
344
##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
345

    
346
  op = opcodes.OpCreateInstance(instance_name=instance,
347
                                disks=disks,
348
                                disk_template=opts.disk_template,
349
                                nics=nics,
350
                                mode=constants.INSTANCE_CREATE,
351
                                os_type=opts.os, pnode=pnode,
352
                                snode=snode,
353
                                start=opts.start, ip_check=opts.ip_check,
354
                                wait_for_sync=opts.wait_for_sync,
355
                                hypervisor=hypervisor,
356
                                hvparams=hvparams,
357
                                beparams=opts.beparams,
358
                                iallocator=opts.iallocator,
359
                                file_storage_dir=opts.file_storage_dir,
360
                                file_driver=opts.file_driver,
361
                                )
362

    
363
  SubmitOrSend(op, opts)
364
  return 0
365

    
366

    
367
def BatchCreate(opts, args):
368
  """Create instances using a definition file.
369

    
370
  This function reads a json file with instances defined
371
  in the form::
372

    
373
    {"instance-name":{
374
      "disk_size": 25,
375
      "swap_size": 1024,
376
      "template": "drbd",
377
      "backend": {
378
        "memory": 512,
379
        "vcpus": 1 },
380
      "os": "etch-image",
381
      "primary_node": "firstnode",
382
      "secondary_node": "secondnode",
383
      "iallocator": "dumb"}
384
    }
385

    
386
  Note that I{primary_node} and I{secondary_node} have precedence over
387
  I{iallocator}.
388

    
389
  @param opts: the command line options selected by the user
390
  @type args: list
391
  @param args: should contain one element, the json filename
392
  @rtype: int
393
  @return: the desired exit code
394

    
395
  """
396
  _DEFAULT_SPECS = {"disk_size": 20 * 1024,
397
                    "swap_size": 4 * 1024,
398
                    "backend": {},
399
                    "iallocator": None,
400
                    "primary_node": None,
401
                    "secondary_node": None,
402
                    "ip": 'none',
403
                    "mac": 'auto',
404
                    "bridge": None,
405
                    "start": True,
406
                    "ip_check": True,
407
                    "hypervisor": None,
408
                    "file_storage_dir": None,
409
                    "file_driver": 'loop'}
410

    
411
  def _PopulateWithDefaults(spec):
412
    """Returns a new hash combined with default values."""
413
    mydict = _DEFAULT_SPECS.copy()
414
    mydict.update(spec)
415
    return mydict
416

    
417
  def _Validate(spec):
418
    """Validate the instance specs."""
419
    # Validate fields required under any circumstances
420
    for required_field in ('os', 'template'):
421
      if required_field not in spec:
422
        raise errors.OpPrereqError('Required field "%s" is missing.' %
423
                                   required_field)
424
    # Validate special fields
425
    if spec['primary_node'] is not None:
426
      if (spec['template'] in constants.DTS_NET_MIRROR and
427
          spec['secondary_node'] is None):
428
        raise errors.OpPrereqError('Template requires secondary node, but'
429
                                   ' there was no secondary provided.')
430
    elif spec['iallocator'] is None:
431
      raise errors.OpPrereqError('You have to provide at least a primary_node'
432
                                 ' or an iallocator.')
433

    
434
    if (spec['hypervisor'] and
435
        not isinstance(spec['hypervisor'], dict)):
436
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
437

    
438
  json_filename = args[0]
439
  fd = open(json_filename, 'r')
440
  try:
441
    instance_data = simplejson.load(fd)
442
  finally:
443
    fd.close()
444

    
445
  # Iterate over the instances and do:
446
  #  * Populate the specs with default value
447
  #  * Validate the instance specs
448
  for (name, specs) in instance_data.iteritems():
449
    specs = _PopulateWithDefaults(specs)
450
    _Validate(specs)
451

    
452
    hypervisor = None
453
    hvparams = {}
454
    if specs['hypervisor']:
455
      hypervisor, hvparams = specs['hypervisor'].iteritems()
456

    
457
    op = opcodes.OpCreateInstance(instance_name=name,
458
                                  disk_size=specs['disk_size'],
459
                                  swap_size=specs['swap_size'],
460
                                  disk_template=specs['template'],
461
                                  mode=constants.INSTANCE_CREATE,
462
                                  os_type=specs['os'],
463
                                  pnode=specs['primary_node'],
464
                                  snode=specs['secondary_node'],
465
                                  ip=specs['ip'], bridge=specs['bridge'],
466
                                  start=specs['start'],
467
                                  ip_check=specs['ip_check'],
468
                                  wait_for_sync=True,
469
                                  mac=specs['mac'],
470
                                  iallocator=specs['iallocator'],
471
                                  hypervisor=hypervisor,
472
                                  hvparams=hvparams,
473
                                  beparams=specs['backend'],
474
                                  file_storage_dir=specs['file_storage_dir'],
475
                                  file_driver=specs['file_driver'])
476

    
477
    ToStdout("%s: %s", name, cli.SendJob([op]))
478

    
479
  return 0
480

    
481

    
482
def ReinstallInstance(opts, args):
483
  """Reinstall an instance.
484

    
485
  @param opts: the command line options selected by the user
486
  @type args: list
487
  @param args: should contain only one element, the name of the
488
      instance to be reinstalled
489
  @rtype: int
490
  @return: the desired exit code
491

    
492
  """
493
  instance_name = args[0]
494

    
495
  if opts.select_os is True:
496
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
497
    result = SubmitOpCode(op)
498

    
499
    if not result:
500
      ToStdout("Can't get the OS list")
501
      return 1
502

    
503
    ToStdout("Available OS templates:")
504
    number = 0
505
    choices = []
506
    for entry in result:
507
      ToStdout("%3s: %s", number, entry[0])
508
      choices.append(("%s" % number, entry[0], entry[0]))
509
      number = number + 1
510

    
511
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
512
    selected = AskUser("Enter OS template name or number (or x to abort):",
513
                       choices)
514

    
515
    if selected == 'exit':
516
      ToStdout("User aborted reinstall, exiting")
517
      return 1
518

    
519
    os_name = selected
520
  else:
521
    os_name = opts.os
522

    
523
  if not opts.force:
524
    usertext = ("This will reinstall the instance %s and remove"
525
                " all data. Continue?") % instance_name
526
    if not AskUser(usertext):
527
      return 1
528

    
529
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
530
                                   os_type=os_name)
531
  SubmitOrSend(op, opts)
532

    
533
  return 0
534

    
535

    
536
def RemoveInstance(opts, args):
537
  """Remove an instance.
538

    
539
  @param opts: the command line options selected by the user
540
  @type args: list
541
  @param args: should contain only one element, the name of
542
      the instance to be removed
543
  @rtype: int
544
  @return: the desired exit code
545

    
546
  """
547
  instance_name = args[0]
548
  force = opts.force
549

    
550
  if not force:
551
    usertext = ("This will remove the volumes of the instance %s"
552
                " (including mirrors), thus removing all the data"
553
                " of the instance. Continue?") % instance_name
554
    if not AskUser(usertext):
555
      return 1
556

    
557
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
558
                                ignore_failures=opts.ignore_failures)
559
  SubmitOrSend(op, opts)
560
  return 0
561

    
562

    
563
def RenameInstance(opts, args):
564
  """Rename an instance.
565

    
566
  @param opts: the command line options selected by the user
567
  @type args: list
568
  @param args: should contain two elements, the old and the
569
      new instance names
570
  @rtype: int
571
  @return: the desired exit code
572

    
573
  """
574
  op = opcodes.OpRenameInstance(instance_name=args[0],
575
                                new_name=args[1],
576
                                ignore_ip=opts.ignore_ip)
577
  SubmitOrSend(op, opts)
578
  return 0
579

    
580

    
581
def ActivateDisks(opts, args):
582
  """Activate an instance's disks.
583

    
584
  This serves two purposes:
585
    - it allows (as long as the instance is not running)
586
      mounting the disks and modifying them from the node
587
    - it repairs inactive secondary drbds
588

    
589
  @param opts: the command line options selected by the user
590
  @type args: list
591
  @param args: should contain only one element, the instance name
592
  @rtype: int
593
  @return: the desired exit code
594

    
595
  """
596
  instance_name = args[0]
597
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
598
  disks_info = SubmitOrSend(op, opts)
599
  for host, iname, nname in disks_info:
600
    ToStdout("%s:%s:%s", host, iname, nname)
601
  return 0
602

    
603

    
604
def DeactivateDisks(opts, args):
605
  """Deactivate an instance's disks..
606

    
607
  This function takes the instance name, looks for its primary node
608
  and the tries to shutdown its block devices on that node.
609

    
610
  @param opts: the command line options selected by the user
611
  @type args: list
612
  @param args: should contain only one element, the instance name
613
  @rtype: int
614
  @return: the desired exit code
615

    
616
  """
617
  instance_name = args[0]
618
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
619
  SubmitOrSend(op, opts)
620
  return 0
621

    
622

    
623
def GrowDisk(opts, args):
624
  """Grow an instance's disks.
625

    
626
  @param opts: the command line options selected by the user
627
  @type args: list
628
  @param args: should contain two elements, the instance name
629
      whose disks we grow and the disk name, e.g. I{sda}
630
  @rtype: int
631
  @return: the desired exit code
632

    
633
  """
634
  instance = args[0]
635
  disk = args[1]
636
  try:
637
    disk = int(disk)
638
  except ValueError, err:
639
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
640
  amount = utils.ParseUnit(args[2])
641
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
642
                          wait_for_sync=opts.wait_for_sync)
643
  SubmitOrSend(op, opts)
644
  return 0
645

    
646

    
647
def StartupInstance(opts, args):
648
  """Startup instances.
649

    
650
  Depending on the options given, this will start one or more
651
  instances.
652

    
653
  @param opts: the command line options selected by the user
654
  @type args: list
655
  @param args: the instance or node names based on which we
656
      create the final selection (in conjunction with the
657
      opts argument)
658
  @rtype: int
659
  @return: the desired exit code
660

    
661
  """
662
  if opts.multi_mode is None:
663
    opts.multi_mode = _SHUTDOWN_INSTANCES
664
  inames = _ExpandMultiNames(opts.multi_mode, args)
665
  if not inames:
666
    raise errors.OpPrereqError("Selection filter does not match any instances")
667
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
668
  if not (opts.force_multi or not multi_on
669
          or _ConfirmOperation(inames, "startup")):
670
    return 1
671
  for name in inames:
672
    op = opcodes.OpStartupInstance(instance_name=name,
673
                                   force=opts.force,
674
                                   extra_args=opts.extra_args)
675
    if multi_on:
676
      ToStdout("Starting up %s", name)
677
    try:
678
      SubmitOrSend(op, opts)
679
    except JobSubmittedException, err:
680
      _, txt = FormatError(err)
681
      ToStdout("%s", txt)
682
  return 0
683

    
684

    
685
def RebootInstance(opts, args):
686
  """Reboot instance(s).
687

    
688
  Depending on the parameters given, this will reboot one or more
689
  instances.
690

    
691
  @param opts: the command line options selected by the user
692
  @type args: list
693
  @param args: the instance or node names based on which we
694
      create the final selection (in conjunction with the
695
      opts argument)
696
  @rtype: int
697
  @return: the desired exit code
698

    
699
  """
700
  if opts.multi_mode is None:
701
    opts.multi_mode = _SHUTDOWN_INSTANCES
702
  inames = _ExpandMultiNames(opts.multi_mode, args)
703
  if not inames:
704
    raise errors.OpPrereqError("Selection filter does not match any instances")
705
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
706
  if not (opts.force_multi or not multi_on
707
          or _ConfirmOperation(inames, "reboot")):
708
    return 1
709
  for name in inames:
710
    op = opcodes.OpRebootInstance(instance_name=name,
711
                                  reboot_type=opts.reboot_type,
712
                                  ignore_secondaries=opts.ignore_secondaries)
713

    
714
    SubmitOrSend(op, opts)
715
  return 0
716

    
717

    
718
def ShutdownInstance(opts, args):
719
  """Shutdown an instance.
720

    
721
  @param opts: the command line options selected by the user
722
  @type args: list
723
  @param args: the instance or node names based on which we
724
      create the final selection (in conjunction with the
725
      opts argument)
726
  @rtype: int
727
  @return: the desired exit code
728

    
729
  """
730
  if opts.multi_mode is None:
731
    opts.multi_mode = _SHUTDOWN_INSTANCES
732
  inames = _ExpandMultiNames(opts.multi_mode, args)
733
  if not inames:
734
    raise errors.OpPrereqError("Selection filter does not match any instances")
735
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
736
  if not (opts.force_multi or not multi_on
737
          or _ConfirmOperation(inames, "shutdown")):
738
    return 1
739
  for name in inames:
740
    op = opcodes.OpShutdownInstance(instance_name=name)
741
    if multi_on:
742
      ToStdout("Shutting down %s", name)
743
    try:
744
      SubmitOrSend(op, opts)
745
    except JobSubmittedException, err:
746
      _, txt = FormatError(err)
747
      ToStdout("%s", txt)
748
  return 0
749

    
750

    
751
def ReplaceDisks(opts, args):
752
  """Replace the disks of an instance
753

    
754
  @param opts: the command line options selected by the user
755
  @type args: list
756
  @param args: should contain only one element, the instance name
757
  @rtype: int
758
  @return: the desired exit code
759

    
760
  """
761
  instance_name = args[0]
762
  new_2ndary = opts.new_secondary
763
  iallocator = opts.iallocator
764
  if opts.disks is None:
765
    disks = []
766
  else:
767
    try:
768
      disks = [int(i) for i in opts.disks.split(",")]
769
    except ValueError, err:
770
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
771
  if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
772
    mode = constants.REPLACE_DISK_ALL
773
  elif opts.on_primary: # only on primary:
774
    mode = constants.REPLACE_DISK_PRI
775
    if new_2ndary is not None or iallocator is not None:
776
      raise errors.OpPrereqError("Can't change secondary node on primary disk"
777
                                 " replacement")
778
  elif opts.on_secondary is not None or iallocator is not None:
779
    # only on secondary
780
    mode = constants.REPLACE_DISK_SEC
781

    
782
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
783
                              remote_node=new_2ndary, mode=mode,
784
                              iallocator=iallocator)
785
  SubmitOrSend(op, opts)
786
  return 0
787

    
788

    
789
def FailoverInstance(opts, args):
790
  """Failover an instance.
791

    
792
  The failover is done by shutting it down on its present node and
793
  starting it on the secondary.
794

    
795
  @param opts: the command line options selected by the user
796
  @type args: list
797
  @param args: should contain only one element, the instance name
798
  @rtype: int
799
  @return: the desired exit code
800

    
801
  """
802
  instance_name = args[0]
803
  force = opts.force
804

    
805
  if not force:
806
    usertext = ("Failover will happen to image %s."
807
                " This requires a shutdown of the instance. Continue?" %
808
                (instance_name,))
809
    if not AskUser(usertext):
810
      return 1
811

    
812
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
813
                                  ignore_consistency=opts.ignore_consistency)
814
  SubmitOrSend(op, opts)
815
  return 0
816

    
817

    
818
def ConnectToInstanceConsole(opts, args):
819
  """Connect to the console of an instance.
820

    
821
  @param opts: the command line options selected by the user
822
  @type args: list
823
  @param args: should contain only one element, the instance name
824
  @rtype: int
825
  @return: the desired exit code
826

    
827
  """
828
  instance_name = args[0]
829

    
830
  op = opcodes.OpConnectConsole(instance_name=instance_name)
831
  cmd = SubmitOpCode(op)
832

    
833
  if opts.show_command:
834
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
835
  else:
836
    try:
837
      os.execvp(cmd[0], cmd)
838
    finally:
839
      ToStderr("Can't run console command %s with arguments:\n'%s'",
840
               cmd[0], " ".join(cmd))
841
      os._exit(1)
842

    
843

    
844
def _FormatBlockDevInfo(buf, dev, indent_level, static):
845
  """Show block device information.
846

    
847
  This is only used by L{ShowInstanceConfig}, but it's too big to be
848
  left for an inline definition.
849

    
850
  @type buf: StringIO
851
  @param buf: buffer that will accumulate the output
852
  @type dev: dict
853
  @param dev: dictionary with disk information
854
  @type indent_level: int
855
  @param indent_level: the indendation level we are at, used for
856
      the layout of the device tree
857
  @type static: boolean
858
  @param static: wheter the device information doesn't contain
859
      runtime information but only static data
860

    
861
  """
862
  def helper(buf, dtype, status):
863
    """Format one line for physical device status.
864

    
865
    @type buf: StringIO
866
    @param buf: buffer that will accumulate the output
867
    @type dtype: str
868
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
869
    @type status: tuple
870
    @param status: a tuple as returned from L{backend.FindBlockDevice}
871

    
872
    """
873
    if not status:
874
      buf.write("not active\n")
875
    else:
876
      (path, major, minor, syncp, estt, degr, ldisk) = status
877
      if major is None:
878
        major_string = "N/A"
879
      else:
880
        major_string = str(major)
881

    
882
      if minor is None:
883
        minor_string = "N/A"
884
      else:
885
        minor_string = str(minor)
886

    
887
      buf.write("%s (%s:%s)" % (path, major_string, minor_string))
888
      if dtype in (constants.LD_DRBD8, ):
889
        if syncp is not None:
890
          sync_text = "*RECOVERING* %5.2f%%," % syncp
891
          if estt:
892
            sync_text += " ETA %ds" % estt
893
          else:
894
            sync_text += " ETA unknown"
895
        else:
896
          sync_text = "in sync"
897
        if degr:
898
          degr_text = "*DEGRADED*"
899
        else:
900
          degr_text = "ok"
901
        if ldisk:
902
          ldisk_text = " *MISSING DISK*"
903
        else:
904
          ldisk_text = ""
905
        buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
906
      elif dtype == constants.LD_LV:
907
        if ldisk:
908
          ldisk_text = " *FAILED* (failed drive?)"
909
        else:
910
          ldisk_text = ""
911
        buf.write(ldisk_text)
912
      buf.write("\n")
913

    
914
  if dev["iv_name"] is not None:
915
    data = "  - %s, " % dev["iv_name"]
916
  else:
917
    data = "  - "
918
  data += "type: %s" % dev["dev_type"]
919
  if dev["logical_id"] is not None:
920
    data += ", logical_id: %s" % (dev["logical_id"],)
921
  elif dev["physical_id"] is not None:
922
    data += ", physical_id: %s" % (dev["physical_id"],)
923
  buf.write("%*s%s\n" % (2*indent_level, "", data))
924
  if not static:
925
    buf.write("%*s    primary:   " % (2*indent_level, ""))
926
    helper(buf, dev["dev_type"], dev["pstatus"])
927

    
928
  if dev["sstatus"] and not static:
929
    buf.write("%*s    secondary: " % (2*indent_level, ""))
930
    helper(buf, dev["dev_type"], dev["sstatus"])
931

    
932
  if dev["children"]:
933
    for child in dev["children"]:
934
      _FormatBlockDevInfo(buf, child, indent_level+1, static)
935

    
936

    
937
def ShowInstanceConfig(opts, args):
938
  """Compute instance run-time status.
939

    
940
  @param opts: the command line options selected by the user
941
  @type args: list
942
  @param args: either an empty list, and then we query all
943
      instances, or should contain a list of instance names
944
  @rtype: int
945
  @return: the desired exit code
946

    
947
  """
948
  retcode = 0
949
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
950
  result = SubmitOpCode(op)
951
  if not result:
952
    ToStdout("No instances.")
953
    return 1
954

    
955
  buf = StringIO()
956
  retcode = 0
957
  for instance_name in result:
958
    instance = result[instance_name]
959
    buf.write("Instance name: %s\n" % instance["name"])
960
    buf.write("State: configured to be %s" % instance["config_state"])
961
    if not opts.static:
962
      buf.write(", actual state is %s" % instance["run_state"])
963
    buf.write("\n")
964
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
965
    ##          instance["auto_balance"])
966
    buf.write("  Nodes:\n")
967
    buf.write("    - primary: %s\n" % instance["pnode"])
968
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
969
    buf.write("  Operating system: %s\n" % instance["os"])
970
    if instance.has_key("network_port"):
971
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
972
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
973
    if instance["hypervisor"] == constants.HT_XEN_PVM:
974
      hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
975
                 (constants.HV_INITRD_PATH, "initrd path"))
976
    elif instance["hypervisor"] == constants.HT_XEN_HVM:
977
      hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
978
                 (constants.HV_ACPI, "ACPI"),
979
                 (constants.HV_PAE, "PAE"),
980
                 (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
981
                 (constants.HV_NIC_TYPE, "NIC type"),
982
                 (constants.HV_DISK_TYPE, "Disk type"),
983
                 (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
984
                 )
985
      # custom console information for HVM
986
      vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
987
      if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
988
        vnc_console_port = "%s:%s" % (instance["pnode"],
989
                                      instance["network_port"])
990
      elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
991
        vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
992
                                                 instance["network_port"],
993
                                                 instance["pnode"])
994
      else:
995
        vnc_console_port = "%s:%s" % (vnc_bind_address,
996
                                      instance["network_port"])
997
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
998

    
999
    else:
1000
      # auto-handle other hypervisor types
1001
      hvattrs = [(key, key) for key in instance["hv_actual"]]
1002

    
1003
    for key, desc in hvattrs:
1004
      if key in instance["hv_instance"]:
1005
        val = instance["hv_instance"][key]
1006
      else:
1007
        val = "default (%s)" % instance["hv_actual"][key]
1008
      buf.write("    - %s: %s\n" % (desc, val))
1009
    buf.write("  Hardware:\n")
1010
    buf.write("    - VCPUs: %d\n" %
1011
              instance["be_actual"][constants.BE_VCPUS])
1012
    buf.write("    - memory: %dMiB\n" %
1013
              instance["be_actual"][constants.BE_MEMORY])
1014
    buf.write("    - NICs: %s\n" %
1015
              ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
1016
                         (mac, ip, bridge)
1017
                         for mac, ip, bridge in instance["nics"]]))
1018
    buf.write("  Block devices:\n")
1019

    
1020
    for device in instance["disks"]:
1021
      _FormatBlockDevInfo(buf, device, 1, opts.static)
1022

    
1023
  ToStdout(buf.getvalue().rstrip('\n'))
1024
  return retcode
1025

    
1026

    
1027
def SetInstanceParams(opts, args):
1028
  """Modifies an instance.
1029

    
1030
  All parameters take effect only at the next restart of the instance.
1031

    
1032
  @param opts: the command line options selected by the user
1033
  @type args: list
1034
  @param args: should contain only one element, the instance name
1035
  @rtype: int
1036
  @return: the desired exit code
1037

    
1038
  """
1039
  if not (opts.ip or opts.bridge or opts.mac or
1040
          opts.hypervisor or opts.beparams):
1041
    ToStderr("Please give at least one of the parameters.")
1042
    return 1
1043

    
1044
  if constants.BE_MEMORY in opts.beparams:
1045
    opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
1046
      opts.beparams[constants.BE_MEMORY])
1047

    
1048
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1049
                                   ip=opts.ip,
1050
                                   bridge=opts.bridge, mac=opts.mac,
1051
                                   hvparams=opts.hypervisor,
1052
                                   beparams=opts.beparams,
1053
                                   force=opts.force)
1054

    
1055
  # even if here we process the result, we allow submit only
1056
  result = SubmitOrSend(op, opts)
1057

    
1058
  if result:
1059
    ToStdout("Modified instance %s", args[0])
1060
    for param, data in result:
1061
      ToStdout(" - %-5s -> %s", param, data)
1062
    ToStdout("Please don't forget that these parameters take effect"
1063
             " only at the next start of the instance.")
1064
  return 0
1065

    
1066

    
1067
# options used in more than one cmd
1068
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1069
                       metavar="<node>")
1070

    
1071
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1072
                    metavar="<os>")
1073

    
1074
# multi-instance selection options
1075
m_force_multi = make_option("--force-multiple", dest="force_multi",
1076
                            help="Do not ask for confirmation when more than"
1077
                            " one instance is affected",
1078
                            action="store_true", default=False)
1079

    
1080
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1081
                             help="Filter by nodes (primary only)",
1082
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1083

    
1084
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1085
                             help="Filter by nodes (secondary only)",
1086
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1087

    
1088
m_node_opt = make_option("--node", dest="multi_mode",
1089
                         help="Filter by nodes (primary and secondary)",
1090
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1091

    
1092
m_clust_opt = make_option("--all", dest="multi_mode",
1093
                          help="Select all instances in the cluster",
1094
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1095

    
1096
m_inst_opt = make_option("--instance", dest="multi_mode",
1097
                         help="Filter by instance name [default]",
1098
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1099

    
1100

    
1101
# this is defined separately due to readability only
1102
add_opts = [
1103
  DEBUG_OPT,
1104
  make_option("-n", "--node", dest="node",
1105
              help="Target node and optional secondary node",
1106
              metavar="<pnode>[:<snode>]"),
1107
  cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
1108
             " a suffix is used",
1109
             default=20 * 1024, type="unit", metavar="<size>"),
1110
  cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
1111
             " suffix is used",
1112
             default=4 * 1024, type="unit", metavar="<size>"),
1113
  os_opt,
1114
  keyval_option("-B", "--backend", dest="beparams",
1115
                type="keyval", default={},
1116
                help="Backend parameters"),
1117
  make_option("-t", "--disk-template", dest="disk_template",
1118
              help="Custom disk setup (diskless, file, plain or drbd)",
1119
              default=None, metavar="TEMPL"),
1120
  ikv_option("--disk", help="Disk information",
1121
             default=[], dest="disks",
1122
             action="append",
1123
             type="identkeyval"),
1124
  ikv_option("--net", help="NIC information",
1125
             default=[], dest="nics",
1126
             action="append",
1127
             type="identkeyval"),
1128
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1129
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1130
  make_option("--no-start", dest="start", default=True,
1131
              action="store_false", help="Don't start the instance after"
1132
              " creation"),
1133
  make_option("--no-ip-check", dest="ip_check", default=True,
1134
              action="store_false", help="Don't check that the instance's IP"
1135
              " is alive (only valid with --no-start)"),
1136
  make_option("--file-storage-dir", dest="file_storage_dir",
1137
              help="Relative path under default cluster-wide file storage dir"
1138
              " to store file-based disks", default=None,
1139
              metavar="<DIR>"),
1140
  make_option("--file-driver", dest="file_driver", help="Driver to use"
1141
              " for image files", default="loop", metavar="<DRIVER>"),
1142
  make_option("--iallocator", metavar="<NAME>",
1143
              help="Select nodes for the instance automatically using the"
1144
              " <NAME> iallocator plugin", default=None, type="string"),
1145
  ikv_option("-H", "--hypervisor", dest="hypervisor",
1146
              help="Hypervisor and hypervisor options, in the format"
1147
              " hypervisor:option=value,option=value,...", default=None,
1148
              type="identkeyval"),
1149
  SUBMIT_OPT,
1150
  ]
1151

    
1152
commands = {
1153
  'add': (AddInstance, ARGS_ONE, add_opts,
1154
          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1155
          "Creates and adds a new instance to the cluster"),
1156
  'batch-create': (BatchCreate, ARGS_ONE,
1157
                   [DEBUG_OPT],
1158
                   "<instances_file.json>",
1159
                   "Create a bunch of instances based on specs in the file."),
1160
  'console': (ConnectToInstanceConsole, ARGS_ONE,
1161
              [DEBUG_OPT,
1162
               make_option("--show-cmd", dest="show_command",
1163
                           action="store_true", default=False,
1164
                           help=("Show command instead of executing it"))],
1165
              "[--show-cmd] <instance>",
1166
              "Opens a console on the specified instance"),
1167
  'failover': (FailoverInstance, ARGS_ONE,
1168
               [DEBUG_OPT, FORCE_OPT,
1169
                make_option("--ignore-consistency", dest="ignore_consistency",
1170
                            action="store_true", default=False,
1171
                            help="Ignore the consistency of the disks on"
1172
                            " the secondary"),
1173
                SUBMIT_OPT,
1174
                ],
1175
               "[-f] <instance>",
1176
               "Stops the instance and starts it on the backup node, using"
1177
               " the remote mirror (only for instances of type drbd)"),
1178
  'info': (ShowInstanceConfig, ARGS_ANY,
1179
           [DEBUG_OPT,
1180
            make_option("-s", "--static", dest="static",
1181
                        action="store_true", default=False,
1182
                        help="Only show configuration data, not runtime data"),
1183
            ], "[-s] [<instance>...]",
1184
           "Show information on the specified instance(s)"),
1185
  'list': (ListInstances, ARGS_NONE,
1186
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1187
           "Lists the instances and their status. The available fields are"
1188
           " (see the man page for details): status, oper_state, oper_ram,"
1189
           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1190
           " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1191
           " hypervisor."
1192
           " The default field"
1193
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1194
           ),
1195
  'reinstall': (ReinstallInstance, ARGS_ONE,
1196
                [DEBUG_OPT, FORCE_OPT, os_opt,
1197
                 make_option("--select-os", dest="select_os",
1198
                             action="store_true", default=False,
1199
                             help="Interactive OS reinstall, lists available"
1200
                             " OS templates for selection"),
1201
                 SUBMIT_OPT,
1202
                 ],
1203
                "[-f] <instance>", "Reinstall a stopped instance"),
1204
  'remove': (RemoveInstance, ARGS_ONE,
1205
             [DEBUG_OPT, FORCE_OPT,
1206
              make_option("--ignore-failures", dest="ignore_failures",
1207
                          action="store_true", default=False,
1208
                          help=("Remove the instance from the cluster even"
1209
                                " if there are failures during the removal"
1210
                                " process (shutdown, disk removal, etc.)")),
1211
              SUBMIT_OPT,
1212
              ],
1213
             "[-f] <instance>", "Shuts down the instance and removes it"),
1214
  'rename': (RenameInstance, ARGS_FIXED(2),
1215
             [DEBUG_OPT,
1216
              make_option("--no-ip-check", dest="ignore_ip",
1217
                          help="Do not check that the IP of the new name"
1218
                          " is alive",
1219
                          default=False, action="store_true"),
1220
              SUBMIT_OPT,
1221
              ],
1222
             "<instance> <new_name>", "Rename the instance"),
1223
  'replace-disks': (ReplaceDisks, ARGS_ONE,
1224
                    [DEBUG_OPT,
1225
                     make_option("-n", "--new-secondary", dest="new_secondary",
1226
                                 help=("New secondary node (for secondary"
1227
                                       " node change)"), metavar="NODE"),
1228
                     make_option("-p", "--on-primary", dest="on_primary",
1229
                                 default=False, action="store_true",
1230
                                 help=("Replace the disk(s) on the primary"
1231
                                       " node (only for the drbd template)")),
1232
                     make_option("-s", "--on-secondary", dest="on_secondary",
1233
                                 default=False, action="store_true",
1234
                                 help=("Replace the disk(s) on the secondary"
1235
                                       " node (only for the drbd template)")),
1236
                     make_option("--disks", dest="disks", default=None,
1237
                                 help=("Comma-separated list of disks"
1238
                                       " to replace (e.g. sda) (optional,"
1239
                                       " defaults to all disks")),
1240
                     make_option("--iallocator", metavar="<NAME>",
1241
                                 help="Select new secondary for the instance"
1242
                                 " automatically using the"
1243
                                 " <NAME> iallocator plugin (enables"
1244
                                 " secondary node replacement)",
1245
                                 default=None, type="string"),
1246
                     SUBMIT_OPT,
1247
                     ],
1248
                    "[-s|-p|-n NODE] <instance>",
1249
                    "Replaces all disks for the instance"),
1250
  'modify': (SetInstanceParams, ARGS_ONE,
1251
             [DEBUG_OPT, FORCE_OPT,
1252
              make_option("-i", "--ip", dest="ip",
1253
                          help="IP address ('none' or numeric IP)",
1254
                          default=None, type="string", metavar="<ADDRESS>"),
1255
              make_option("-b", "--bridge", dest="bridge",
1256
                          help="Bridge to connect this instance to",
1257
                          default=None, type="string", metavar="<bridge>"),
1258
              make_option("--mac", dest="mac",
1259
                          help="MAC address", default=None,
1260
                          type="string", metavar="<MACADDRESS>"),
1261
              keyval_option("-H", "--hypervisor", type="keyval",
1262
                            default={}, dest="hypervisor",
1263
                            help="Change hypervisor parameters"),
1264
              keyval_option("-B", "--backend", type="keyval",
1265
                            default={}, dest="beparams",
1266
                            help="Change backend parameters"),
1267
              SUBMIT_OPT,
1268
              ],
1269
             "<instance>", "Alters the parameters of an instance"),
1270
  'shutdown': (ShutdownInstance, ARGS_ANY,
1271
               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1272
                m_clust_opt, m_inst_opt, m_force_multi,
1273
                SUBMIT_OPT,
1274
                ],
1275
               "<instance>", "Stops an instance"),
1276
  'startup': (StartupInstance, ARGS_ANY,
1277
              [DEBUG_OPT, FORCE_OPT, m_force_multi,
1278
               make_option("-e", "--extra", dest="extra_args",
1279
                           help="Extra arguments for the instance's kernel",
1280
                           default=None, type="string", metavar="<PARAMS>"),
1281
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1282
               m_clust_opt, m_inst_opt,
1283
               SUBMIT_OPT,
1284
               ],
1285
            "<instance>", "Starts an instance"),
1286

    
1287
  'reboot': (RebootInstance, ARGS_ANY,
1288
              [DEBUG_OPT, m_force_multi,
1289
               make_option("-e", "--extra", dest="extra_args",
1290
                           help="Extra arguments for the instance's kernel",
1291
                           default=None, type="string", metavar="<PARAMS>"),
1292
               make_option("-t", "--type", dest="reboot_type",
1293
                           help="Type of reboot: soft/hard/full",
1294
                           default=constants.INSTANCE_REBOOT_HARD,
1295
                           type="string", metavar="<REBOOT>"),
1296
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1297
                           default=False, action="store_true",
1298
                           help="Ignore errors from secondaries"),
1299
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1300
               m_clust_opt, m_inst_opt,
1301
               SUBMIT_OPT,
1302
               ],
1303
            "<instance>", "Reboots an instance"),
1304
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1305
                     "<instance>",
1306
                     "Activate an instance's disks"),
1307
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1308
                       "<instance>",
1309
                       "Deactivate an instance's disks"),
1310
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1311
                [DEBUG_OPT, SUBMIT_OPT,
1312
                 make_option("--no-wait-for-sync",
1313
                             dest="wait_for_sync", default=True,
1314
                             action="store_false",
1315
                             help="Don't wait for sync (DANGEROUS!)"),
1316
                 ],
1317
                "<instance> <disk> <size>", "Grow an instance's disk"),
1318
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1319
                "<instance_name>", "List the tags of the given instance"),
1320
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1321
               "<instance_name> tag...", "Add tags to the given instance"),
1322
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1323
                  "<instance_name> tag...", "Remove tags from given instance"),
1324
  }
1325

    
1326
#: dictionary with aliases for commands
1327
aliases = {
1328
  'activate_block_devs': 'activate-disks',
1329
  'replace_disks': 'replace-disks',
1330
  'start': 'startup',
1331
  'stop': 'shutdown',
1332
  }
1333

    
1334
if __name__ == '__main__':
1335
  sys.exit(GenericMain(commands, aliases=aliases,
1336
                       override={"tag_type": constants.TAG_INSTANCE}))