Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 23b8c8d6

History | View | Annotate | Download (47.4 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", "disk.sizes": "Disk_sizes",
225
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
226
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
227
      }
228
  else:
229
    headers = None
230

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

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

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

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

    
275
  for line in data:
276
    ToStdout(line)
277

    
278
  return 0
279

    
280

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

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

    
290
  """
291
  instance = args[0]
292

    
293
  (pnode, snode) = SplitNodeOption(opts.node)
294

    
295
  hypervisor = None
296
  hvparams = {}
297
  if opts.hypervisor:
298
    hypervisor, hvparams = opts.hypervisor
299

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

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

    
334
  ValidateBeParams(opts.beparams)
335

    
336
##  kernel_path = _TransformPath(opts.kernel_path)
337
##  initrd_path = _TransformPath(opts.initrd_path)
338

    
339
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
340
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
341

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

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

    
365
  SubmitOrSend(op, opts)
366
  return 0
367

    
368

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

    
372
  This function reads a json file with instances defined
373
  in the form::
374

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

    
388
  Note that I{primary_node} and I{secondary_node} have precedence over
389
  I{iallocator}.
390

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

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

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

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

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

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

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

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

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

    
479
    ToStdout("%s: %s", name, cli.SendJob([op]))
480

    
481
  return 0
482

    
483

    
484
def ReinstallInstance(opts, args):
485
  """Reinstall an instance.
486

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

    
494
  """
495
  instance_name = args[0]
496

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

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

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

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

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

    
521
    os_name = selected
522
  else:
523
    os_name = opts.os
524

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

    
531
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
532
                                   os_type=os_name)
533
  SubmitOrSend(op, opts)
534

    
535
  return 0
536

    
537

    
538
def RemoveInstance(opts, args):
539
  """Remove an instance.
540

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

    
548
  """
549
  instance_name = args[0]
550
  force = opts.force
551

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

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

    
564

    
565
def RenameInstance(opts, args):
566
  """Rename an instance.
567

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

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

    
582

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

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

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

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

    
605

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

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

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

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

    
624

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

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

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

    
648

    
649
def StartupInstance(opts, args):
650
  """Startup instances.
651

    
652
  Depending on the options given, this will start one or more
653
  instances.
654

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

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

    
686

    
687
def RebootInstance(opts, args):
688
  """Reboot instance(s).
689

    
690
  Depending on the parameters given, this will reboot one or more
691
  instances.
692

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

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

    
716
    SubmitOrSend(op, opts)
717
  return 0
718

    
719

    
720
def ShutdownInstance(opts, args):
721
  """Shutdown an instance.
722

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

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

    
752

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

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

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

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

    
790

    
791
def FailoverInstance(opts, args):
792
  """Failover an instance.
793

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

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

    
803
  """
804
  instance_name = args[0]
805
  force = opts.force
806

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

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

    
819

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

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

    
829
  """
830
  instance_name = args[0]
831

    
832
  op = opcodes.OpConnectConsole(instance_name=instance_name)
833
  cmd = SubmitOpCode(op)
834

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

    
845

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

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

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

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

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

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

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

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

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

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

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

    
938

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

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

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

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

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

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

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

    
1025
  ToStdout(buf.getvalue().rstrip('\n'))
1026
  return retcode
1027

    
1028

    
1029
def SetInstanceParams(opts, args):
1030
  """Modifies an instance.
1031

    
1032
  All parameters take effect only at the next restart of the instance.
1033

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

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

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

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

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

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

    
1068

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

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

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

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

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

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

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

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

    
1102

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

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

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

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

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