Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 24991749

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

    
330
  ValidateBeParams(opts.beparams)
331

    
332
##  kernel_path = _TransformPath(opts.kernel_path)
333
##  initrd_path = _TransformPath(opts.initrd_path)
334

    
335
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
336
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
337

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

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

    
361
  SubmitOrSend(op, opts)
362
  return 0
363

    
364

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

    
368
  This function reads a json file with instances defined
369
  in the form::
370

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

    
384
  Note that I{primary_node} and I{secondary_node} have precedence over
385
  I{iallocator}.
386

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

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

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

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

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

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

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

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

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

    
475
    ToStdout("%s: %s", name, cli.SendJob([op]))
476

    
477
  return 0
478

    
479

    
480
def ReinstallInstance(opts, args):
481
  """Reinstall an instance.
482

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

    
490
  """
491
  instance_name = args[0]
492

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

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

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

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

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

    
517
    os_name = selected
518
  else:
519
    os_name = opts.os
520

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

    
527
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
528
                                   os_type=os_name)
529
  SubmitOrSend(op, opts)
530

    
531
  return 0
532

    
533

    
534
def RemoveInstance(opts, args):
535
  """Remove an instance.
536

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

    
544
  """
545
  instance_name = args[0]
546
  force = opts.force
547

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

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

    
560

    
561
def RenameInstance(opts, args):
562
  """Rename an instance.
563

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

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

    
578

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

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

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

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

    
601

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

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

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

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

    
620

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

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

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

    
644

    
645
def StartupInstance(opts, args):
646
  """Startup instances.
647

    
648
  Depending on the options given, this will start one or more
649
  instances.
650

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

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

    
682

    
683
def RebootInstance(opts, args):
684
  """Reboot instance(s).
685

    
686
  Depending on the parameters given, this will reboot one or more
687
  instances.
688

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

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

    
712
    SubmitOrSend(op, opts)
713
  return 0
714

    
715

    
716
def ShutdownInstance(opts, args):
717
  """Shutdown an instance.
718

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

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

    
748

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

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

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

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

    
786

    
787
def FailoverInstance(opts, args):
788
  """Failover an instance.
789

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

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

    
799
  """
800
  instance_name = args[0]
801
  force = opts.force
802

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

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

    
815

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

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

    
825
  """
826
  instance_name = args[0]
827

    
828
  op = opcodes.OpConnectConsole(instance_name=instance_name)
829
  cmd = SubmitOpCode(op)
830

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

    
841

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

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

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

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

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

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

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

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

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

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

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

    
935

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

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

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

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

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

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

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

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

    
1025

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

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

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

    
1037
  """
1038
  if not (opts.nics or opts.disks or
1039
          opts.hypervisor or opts.beparams):
1040
    ToStderr("Please give at least one of the parameters.")
1041
    return 1
1042

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

    
1047
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1048
    try:
1049
      nic_op = int(nic_op)
1050
      opts.nics[idx] = (nic_op, nic_dict)
1051
    except ValueError:
1052
      pass
1053

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

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

    
1072
  # even if here we process the result, we allow submit only
1073
  result = SubmitOrSend(op, opts)
1074

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

    
1083

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

    
1088
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1089
                    metavar="<os>")
1090

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

    
1097
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1098
                             help="Filter by nodes (primary only)",
1099
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1100

    
1101
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1102
                             help="Filter by nodes (secondary only)",
1103
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1104

    
1105
m_node_opt = make_option("--node", dest="multi_mode",
1106
                         help="Filter by nodes (primary and secondary)",
1107
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1108

    
1109
m_clust_opt = make_option("--all", dest="multi_mode",
1110
                          help="Select all instances in the cluster",
1111
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1112

    
1113
m_inst_opt = make_option("--instance", dest="multi_mode",
1114
                         help="Filter by instance name [default]",
1115
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1116

    
1117

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

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

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

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

    
1350
if __name__ == '__main__':
1351
  sys.exit(GenericMain(commands, aliases=aliases,
1352
                       override={"tag_type": constants.TAG_INSTANCE}))