Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 54155f52

History | View | Annotate | Download (47 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os
28
import itertools
29
import simplejson
30
from optparse import make_option
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import cli
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import utils
38
from ganeti import errors
39

    
40

    
41
_SHUTDOWN_CLUSTER = "cluster"
42
_SHUTDOWN_NODES_BOTH = "nodes"
43
_SHUTDOWN_NODES_PRI = "nodes-pri"
44
_SHUTDOWN_NODES_SEC = "nodes-sec"
45
_SHUTDOWN_INSTANCES = "instances"
46

    
47

    
48
_VALUE_TRUE = "true"
49

    
50
#: default list of options for L{ListInstances}
51
_LIST_DEF_FIELDS = [
52
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
53
  ]
54

    
55

    
56
def _ExpandMultiNames(mode, names):
57
  """Expand the given names using the passed mode.
58

    
59
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
60
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
61
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
62
  instances having those nodes as either primary or secondary will be
63
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
64
  returned.
65

    
66
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
67
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
68
      L{_SHUTDOWN_INSTANCES}
69
  @param names: a list of names; for cluster, it must be empty,
70
      and for node and instance it must be a list of valid item
71
      names (short names are valid as usual, e.g. node1 instead of
72
      node1.example.com)
73
  @rtype: list
74
  @return: the list of names after the expansion
75
  @raise errors.ProgrammerError: for unknown selection type
76
  @raise errors.OpPrereqError: for invalid input parameters
77

    
78
  """
79
  if mode == _SHUTDOWN_CLUSTER:
80
    if names:
81
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
82
    client = GetClient()
83
    idata = client.QueryInstances([], ["name"])
84
    inames = [row[0] for row in idata]
85

    
86
  elif mode in (_SHUTDOWN_NODES_BOTH,
87
                _SHUTDOWN_NODES_PRI,
88
                _SHUTDOWN_NODES_SEC):
89
    if not names:
90
      raise errors.OpPrereqError("No node names passed")
91
    client = GetClient()
92
    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"])
93
    ipri = [row[1] for row in ndata]
94
    pri_names = list(itertools.chain(*ipri))
95
    isec = [row[2] for row in ndata]
96
    sec_names = list(itertools.chain(*isec))
97
    if mode == _SHUTDOWN_NODES_BOTH:
98
      inames = pri_names + sec_names
99
    elif mode == _SHUTDOWN_NODES_PRI:
100
      inames = pri_names
101
    elif mode == _SHUTDOWN_NODES_SEC:
102
      inames = sec_names
103
    else:
104
      raise errors.ProgrammerError("Unhandled shutdown type")
105

    
106
  elif mode == _SHUTDOWN_INSTANCES:
107
    if not names:
108
      raise errors.OpPrereqError("No instance names passed")
109
    client = GetClient()
110
    idata = client.QueryInstances(names, ["name"])
111
    inames = [row[0] for row in idata]
112

    
113
  else:
114
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
115

    
116
  return inames
117

    
118

    
119
def _ConfirmOperation(inames, text):
120
  """Ask the user to confirm an operation on a list of instances.
121

    
122
  This function is used to request confirmation for doing an operation
123
  on a given list of instances.
124

    
125
  @type inames: list
126
  @param inames: the list of names that we display when
127
      we ask for confirmation
128
  @type text: str
129
  @param text: the operation that the user should confirm
130
      (e.g. I{shutdown} or I{startup})
131
  @rtype: boolean
132
  @return: True or False depending on user's confirmation.
133

    
134
  """
135
  count = len(inames)
136
  msg = ("The %s will operate on %d instances.\n"
137
         "Do you want to continue?" % (text, count))
138
  affected = ("\nAffected instances:\n" +
139
              "\n".join(["  %s" % name for name in inames]))
140

    
141
  choices = [('y', True, 'Yes, execute the %s' % text),
142
             ('n', False, 'No, abort the %s' % text)]
143

    
144
  if count > 20:
145
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
146
    ask = msg
147
  else:
148
    ask = msg + affected
149

    
150
  choice = AskUser(ask, choices)
151
  if choice == 'v':
152
    choices.pop(1)
153
    choice = AskUser(msg + affected, choices)
154
  return choice
155

    
156

    
157
def _TransformPath(user_input):
158
  """Transform a user path into a canonical value.
159

    
160
  This function transforms the a path passed as textual information
161
  into the constants that the LU code expects.
162

    
163
  """
164
  if user_input:
165
    if user_input.lower() == "default":
166
      result_path = constants.VALUE_DEFAULT
167
    elif user_input.lower() == "none":
168
      result_path = constants.VALUE_NONE
169
    else:
170
      if not os.path.isabs(user_input):
171
        raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
172
                                   user_input)
173
      result_path = user_input
174
  else:
175
    result_path = constants.VALUE_DEFAULT
176

    
177
  return result_path
178

    
179

    
180
def ListInstances(opts, args):
181
  """List instances and their properties.
182

    
183
  @param opts: the command line options selected by the user
184
  @type args: list
185
  @param args: should be an empty list
186
  @rtype: int
187
  @return: the desired exit code
188

    
189
  """
190
  if opts.output is None:
191
    selected_fields = _LIST_DEF_FIELDS
192
  elif opts.output.startswith("+"):
193
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
194
  else:
195
    selected_fields = opts.output.split(",")
196

    
197
  output = GetClient().QueryInstances([], selected_fields)
198

    
199
  if not opts.no_headers:
200
    headers = {
201
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
202
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
203
      "oper_state": "Running",
204
      "oper_ram": "Memory", "disk_template": "Disk_template",
205
      "ip": "IP_address", "mac": "MAC_address",
206
      "bridge": "Bridge",
207
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
208
      "status": "Status", "tags": "Tags",
209
      "network_port": "Network_port",
210
      "hv/kernel_path": "Kernel_path",
211
      "hv/initrd_path": "Initrd_path",
212
      "hv/boot_order": "HVM_boot_order",
213
      "hv/acpi": "HVM_ACPI",
214
      "hv/pae": "HVM_PAE",
215
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
216
      "hv/nic_type": "HVM_NIC_type",
217
      "hv/disk_type": "HVM_Disk_type",
218
      "hv/vnc_bind_address": "VNC_bind_address",
219
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
220
      "hvparams": "Hypervisor_parameters",
221
      "be/memory": "Configured_memory",
222
      "be/vcpus": "VCPUs",
223
      "be/auto_balance": "Auto_balance",
224
      }
225
  else:
226
    headers = None
227

    
228
  if opts.human_readable:
229
    unitfields = ["be/memory", "oper_ram", "sda_size", "sdb_size"]
230
  else:
231
    unitfields = None
232

    
233
  numfields = ["be/memory", "oper_ram", "sda_size", "sdb_size", "be/vcpus",
234
               "serial_no"]
235

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

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

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

    
274
  return 0
275

    
276

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

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

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

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

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

    
296
  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
  amount = utils.ParseUnit(args[2])
635
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
636
                          wait_for_sync=opts.wait_for_sync)
637
  SubmitOrSend(op, opts)
638
  return 0
639

    
640

    
641
def StartupInstance(opts, args):
642
  """Startup instances.
643

    
644
  Depending on the options given, this will start one or more
645
  instances.
646

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

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

    
678

    
679
def RebootInstance(opts, args):
680
  """Reboot instance(s).
681

    
682
  Depending on the parameters given, this will reboot one or more
683
  instances.
684

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

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

    
708
    SubmitOrSend(op, opts)
709
  return 0
710

    
711

    
712
def ShutdownInstance(opts, args):
713
  """Shutdown an instance.
714

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

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

    
744

    
745
def ReplaceDisks(opts, args):
746
  """Replace the disks of an instance
747

    
748
  @param opts: the command line options selected by the user
749
  @type args: list
750
  @param args: should contain only one element, the instance name
751
  @rtype: int
752
  @return: the desired exit code
753

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

    
776
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
777
                              remote_node=new_2ndary, mode=mode,
778
                              iallocator=iallocator)
779
  SubmitOrSend(op, opts)
780
  return 0
781

    
782

    
783
def FailoverInstance(opts, args):
784
  """Failover an instance.
785

    
786
  The failover is done by shutting it down on its present node and
787
  starting it on the secondary.
788

    
789
  @param opts: the command line options selected by the user
790
  @type args: list
791
  @param args: should contain only one element, the instance name
792
  @rtype: int
793
  @return: the desired exit code
794

    
795
  """
796
  instance_name = args[0]
797
  force = opts.force
798

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

    
806
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
807
                                  ignore_consistency=opts.ignore_consistency)
808
  SubmitOrSend(op, opts)
809
  return 0
810

    
811

    
812
def ConnectToInstanceConsole(opts, args):
813
  """Connect to the console of an instance.
814

    
815
  @param opts: the command line options selected by the user
816
  @type args: list
817
  @param args: should contain only one element, the instance name
818
  @rtype: int
819
  @return: the desired exit code
820

    
821
  """
822
  instance_name = args[0]
823

    
824
  op = opcodes.OpConnectConsole(instance_name=instance_name)
825
  cmd = SubmitOpCode(op)
826

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

    
837

    
838
def _FormatBlockDevInfo(buf, dev, indent_level, static):
839
  """Show block device information.
840

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

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

    
855
  """
856
  def helper(buf, dtype, status):
857
    """Format one line for physical device status.
858

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

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

    
876
      if minor is None:
877
        minor_string = "N/A"
878
      else:
879
        minor_string = str(minor)
880

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

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

    
922
  if dev["sstatus"] and not static:
923
    buf.write("%*s    secondary: " % (2*indent_level, ""))
924
    helper(buf, dev["dev_type"], dev["sstatus"])
925

    
926
  if dev["children"]:
927
    for child in dev["children"]:
928
      _FormatBlockDevInfo(buf, child, indent_level+1, static)
929

    
930

    
931
def ShowInstanceConfig(opts, args):
932
  """Compute instance run-time status.
933

    
934
  @param opts: the command line options selected by the user
935
  @type args: list
936
  @param args: either an empty list, and then we query all
937
      instances, or should contain a list of instance names
938
  @rtype: int
939
  @return: the desired exit code
940

    
941
  """
942
  retcode = 0
943
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
944
  result = SubmitOpCode(op)
945
  if not result:
946
    ToStdout("No instances.")
947
    return 1
948

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

    
993
    else:
994
      # auto-handle other hypervisor types
995
      hvattrs = [(key, key) for key in instance["hv_actual"]]
996

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

    
1014
    for device in instance["disks"]:
1015
      _FormatBlockDevInfo(buf, device, 1, opts.static)
1016

    
1017
  ToStdout(buf.getvalue().rstrip('\n'))
1018
  return retcode
1019

    
1020

    
1021
def SetInstanceParams(opts, args):
1022
  """Modifies an instance.
1023

    
1024
  All parameters take effect only at the next restart of the instance.
1025

    
1026
  @param opts: the command line options selected by the user
1027
  @type args: list
1028
  @param args: should contain only one element, the instance name
1029
  @rtype: int
1030
  @return: the desired exit code
1031

    
1032
  """
1033
  if not (opts.ip or opts.bridge or opts.mac or
1034
          opts.hypervisor or opts.beparams):
1035
    ToStderr("Please give at least one of the parameters.")
1036
    return 1
1037

    
1038
  if constants.BE_MEMORY in opts.beparams:
1039
    opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
1040
      opts.beparams[constants.BE_MEMORY])
1041

    
1042
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1043
                                   ip=opts.ip,
1044
                                   bridge=opts.bridge, mac=opts.mac,
1045
                                   hvparams=opts.hypervisor,
1046
                                   beparams=opts.beparams,
1047
                                   force=opts.force)
1048

    
1049
  # even if here we process the result, we allow submit only
1050
  result = SubmitOrSend(op, opts)
1051

    
1052
  if result:
1053
    ToStdout("Modified instance %s", args[0])
1054
    for param, data in result:
1055
      ToStdout(" - %-5s -> %s", param, data)
1056
    ToStdout("Please don't forget that these parameters take effect"
1057
             " only at the next start of the instance.")
1058
  return 0
1059

    
1060

    
1061
# options used in more than one cmd
1062
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1063
                       metavar="<node>")
1064

    
1065
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1066
                    metavar="<os>")
1067

    
1068
# multi-instance selection options
1069
m_force_multi = make_option("--force-multiple", dest="force_multi",
1070
                            help="Do not ask for confirmation when more than"
1071
                            " one instance is affected",
1072
                            action="store_true", default=False)
1073

    
1074
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1075
                             help="Filter by nodes (primary only)",
1076
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1077

    
1078
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1079
                             help="Filter by nodes (secondary only)",
1080
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1081

    
1082
m_node_opt = make_option("--node", dest="multi_mode",
1083
                         help="Filter by nodes (primary and secondary)",
1084
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1085

    
1086
m_clust_opt = make_option("--all", dest="multi_mode",
1087
                          help="Select all instances in the cluster",
1088
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1089

    
1090
m_inst_opt = make_option("--instance", dest="multi_mode",
1091
                         help="Filter by instance name [default]",
1092
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1093

    
1094

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

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

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

    
1320
#: dictionary with aliases for commands
1321
aliases = {
1322
  'activate_block_devs': 'activate-disks',
1323
  'replace_disks': 'replace-disks',
1324
  'start': 'startup',
1325
  'stop': 'shutdown',
1326
  }
1327

    
1328
if __name__ == '__main__':
1329
  sys.exit(GenericMain(commands, aliases=aliases,
1330
                       override={"tag_type": constants.TAG_INSTANCE}))