Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 112050d9

History | View | Annotate | Download (47.8 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
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
232
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
233
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
234

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

    
267
  data = GenerateTable(separator=opts.separator, headers=headers,
268
                       fields=selected_fields, unitfields=unitfields,
269
                       numfields=numfields, data=output, units=opts.units)
270

    
271
  for line in data:
272
    ToStdout(line)
273

    
274
  return 0
275

    
276

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

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

    
286
  """
287
  instance = args[0]
288

    
289
  (pnode, snode) = SplitNodeOption(opts.node)
290

    
291
  hypervisor = None
292
  hvparams = {}
293
  if opts.hypervisor:
294
    hypervisor, hvparams = opts.hypervisor
295

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

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

    
333
  ValidateBeParams(opts.beparams)
334

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

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

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

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

    
364
  SubmitOrSend(op, opts)
365
  return 0
366

    
367

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
480
  return 0
481

    
482

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

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

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

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

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

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

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

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

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

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

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

    
534
  return 0
535

    
536

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

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

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

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

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

    
563

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

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

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

    
581

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

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

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

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

    
604

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

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

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

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

    
623

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

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

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

    
647

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

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

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

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

    
685

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

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

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

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

    
715
    SubmitOrSend(op, opts)
716
  return 0
717

    
718

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

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

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

    
751

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

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

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

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

    
789

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

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

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

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

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

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

    
818

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

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

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

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

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

    
844

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

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

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

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

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

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

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

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

    
915
  if dev["iv_name"] is not None:
916
    data = "  - %s, " % dev["iv_name"]
917
  else:
918
    data = "  - "
919
  data += "access mode: %s, " % dev["mode"]
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:\n")
1017
    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1018
      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1019
                (idx, mac, ip, bridge))
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.nics or opts.disks 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
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1051
    try:
1052
      nic_op = int(nic_op)
1053
      opts.nics[idx] = (nic_op, nic_dict)
1054
    except ValueError:
1055
      pass
1056

    
1057
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1058
    try:
1059
      disk_op = int(disk_op)
1060
      opts.disks[idx] = (disk_op, disk_dict)
1061
    except ValueError:
1062
      pass
1063
    if disk_op == constants.DDM_ADD:
1064
      if 'size' not in disk_dict:
1065
        raise errors.OpPrereqError("Missing required parameter 'size'")
1066
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1067

    
1068
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1069
                                   nics=opts.nics,
1070
                                   disks=opts.disks,
1071
                                   hvparams=opts.hypervisor,
1072
                                   beparams=opts.beparams,
1073
                                   force=opts.force)
1074

    
1075
  # even if here we process the result, we allow submit only
1076
  result = SubmitOrSend(op, opts)
1077

    
1078
  if result:
1079
    ToStdout("Modified instance %s", args[0])
1080
    for param, data in result:
1081
      ToStdout(" - %-5s -> %s", param, data)
1082
    ToStdout("Please don't forget that these parameters take effect"
1083
             " only at the next start of the instance.")
1084
  return 0
1085

    
1086

    
1087
# options used in more than one cmd
1088
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1089
                       metavar="<node>")
1090

    
1091
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1092
                    metavar="<os>")
1093

    
1094
# multi-instance selection options
1095
m_force_multi = make_option("--force-multiple", dest="force_multi",
1096
                            help="Do not ask for confirmation when more than"
1097
                            " one instance is affected",
1098
                            action="store_true", default=False)
1099

    
1100
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1101
                             help="Filter by nodes (primary only)",
1102
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1103

    
1104
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1105
                             help="Filter by nodes (secondary only)",
1106
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1107

    
1108
m_node_opt = make_option("--node", dest="multi_mode",
1109
                         help="Filter by nodes (primary and secondary)",
1110
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1111

    
1112
m_clust_opt = make_option("--all", dest="multi_mode",
1113
                          help="Select all instances in the cluster",
1114
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1115

    
1116
m_inst_opt = make_option("--instance", dest="multi_mode",
1117
                         help="Filter by instance name [default]",
1118
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1119

    
1120

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

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

    
1306
  'reboot': (RebootInstance, ARGS_ANY,
1307
              [DEBUG_OPT, m_force_multi,
1308
               make_option("-e", "--extra", dest="extra_args",
1309
                           help="Extra arguments for the instance's kernel",
1310
                           default=None, type="string", metavar="<PARAMS>"),
1311
               make_option("-t", "--type", dest="reboot_type",
1312
                           help="Type of reboot: soft/hard/full",
1313
                           default=constants.INSTANCE_REBOOT_HARD,
1314
                           type="string", metavar="<REBOOT>"),
1315
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1316
                           default=False, action="store_true",
1317
                           help="Ignore errors from secondaries"),
1318
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1319
               m_clust_opt, m_inst_opt,
1320
               SUBMIT_OPT,
1321
               ],
1322
            "<instance>", "Reboots an instance"),
1323
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1324
                     "<instance>",
1325
                     "Activate an instance's disks"),
1326
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1327
                       "<instance>",
1328
                       "Deactivate an instance's disks"),
1329
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1330
                [DEBUG_OPT, SUBMIT_OPT,
1331
                 make_option("--no-wait-for-sync",
1332
                             dest="wait_for_sync", default=True,
1333
                             action="store_false",
1334
                             help="Don't wait for sync (DANGEROUS!)"),
1335
                 ],
1336
                "<instance> <disk> <size>", "Grow an instance's disk"),
1337
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1338
                "<instance_name>", "List the tags of the given instance"),
1339
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1340
               "<instance_name> tag...", "Add tags to the given instance"),
1341
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1342
                  "<instance_name> tag...", "Remove tags from given instance"),
1343
  }
1344

    
1345
#: dictionary with aliases for commands
1346
aliases = {
1347
  'activate_block_devs': 'activate-disks',
1348
  'replace_disks': 'replace-disks',
1349
  'start': 'startup',
1350
  'stop': 'shutdown',
1351
  }
1352

    
1353
if __name__ == '__main__':
1354
  sys.exit(GenericMain(commands, aliases=aliases,
1355
                       override={"tag_type": constants.TAG_INSTANCE}))