Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 7312b33d

History | View | Annotate | Download (52.3 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
  elif opts.no_nics:
306
    # no nics
307
    nics = []
308
  else:
309
    # default of one nic, all auto
310
    nics = [{}]
311

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

    
336
  ValidateBeParams(opts.beparams)
337

    
338
##  kernel_path = _TransformPath(opts.kernel_path)
339
##  initrd_path = _TransformPath(opts.initrd_path)
340

    
341
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
342
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
343

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

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

    
367
  SubmitOrSend(op, opts)
368
  return 0
369

    
370

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

    
374
  This function reads a json file with instances defined
375
  in the form::
376

    
377
    {"instance-name":{
378
      "disk_size": [20480],
379
      "template": "drbd",
380
      "backend": {
381
        "memory": 512,
382
        "vcpus": 1 },
383
      "os": "debootstrap",
384
      "primary_node": "firstnode",
385
      "secondary_node": "secondnode",
386
      "iallocator": "dumb"}
387
    }
388

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

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

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

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

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

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

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

    
447
  # Iterate over the instances and do:
448
  #  * Populate the specs with default value
449
  #  * Validate the instance specs
450
  i_names = utils.NiceSort(instance_data.keys())
451
  for name in i_names:
452
    specs = instance_data[name]
453
    specs = _PopulateWithDefaults(specs)
454
    _Validate(specs)
455

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

    
461
    disks = []
462
    for elem in specs['disk_size']:
463
      try:
464
        size = utils.ParseUnit(elem)
465
      except ValueError, err:
466
        raise errors.OpPrereqError("Invalid disk size '%s' for"
467
                                   " instance %s: %s" %
468
                                   (elem, name, err))
469
      disks.append({"size": size})
470

    
471
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
472

    
473
    op = opcodes.OpCreateInstance(instance_name=name,
474
                                  disks=disks,
475
                                  disk_template=specs['template'],
476
                                  mode=constants.INSTANCE_CREATE,
477
                                  os_type=specs['os'],
478
                                  pnode=specs['primary_node'],
479
                                  snode=specs['secondary_node'],
480
                                  nics=[nic0],
481
                                  start=specs['start'],
482
                                  ip_check=specs['ip_check'],
483
                                  wait_for_sync=True,
484
                                  iallocator=specs['iallocator'],
485
                                  hypervisor=hypervisor,
486
                                  hvparams=hvparams,
487
                                  beparams=specs['backend'],
488
                                  file_storage_dir=specs['file_storage_dir'],
489
                                  file_driver=specs['file_driver'])
490

    
491
    ToStdout("%s: %s", name, cli.SendJob([op]))
492

    
493
  return 0
494

    
495

    
496
def ReinstallInstance(opts, args):
497
  """Reinstall an instance.
498

    
499
  @param opts: the command line options selected by the user
500
  @type args: list
501
  @param args: should contain only one element, the name of the
502
      instance to be reinstalled
503
  @rtype: int
504
  @return: the desired exit code
505

    
506
  """
507
  instance_name = args[0]
508

    
509
  if opts.select_os is True:
510
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
511
    result = SubmitOpCode(op)
512

    
513
    if not result:
514
      ToStdout("Can't get the OS list")
515
      return 1
516

    
517
    ToStdout("Available OS templates:")
518
    number = 0
519
    choices = []
520
    for entry in result:
521
      ToStdout("%3s: %s", number, entry[0])
522
      choices.append(("%s" % number, entry[0], entry[0]))
523
      number = number + 1
524

    
525
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
526
    selected = AskUser("Enter OS template name or number (or x to abort):",
527
                       choices)
528

    
529
    if selected == 'exit':
530
      ToStdout("User aborted reinstall, exiting")
531
      return 1
532

    
533
    os_name = selected
534
  else:
535
    os_name = opts.os
536

    
537
  if not opts.force:
538
    usertext = ("This will reinstall the instance %s and remove"
539
                " all data. Continue?") % instance_name
540
    if not AskUser(usertext):
541
      return 1
542

    
543
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
544
                                   os_type=os_name)
545
  SubmitOrSend(op, opts)
546

    
547
  return 0
548

    
549

    
550
def RemoveInstance(opts, args):
551
  """Remove an instance.
552

    
553
  @param opts: the command line options selected by the user
554
  @type args: list
555
  @param args: should contain only one element, the name of
556
      the instance to be removed
557
  @rtype: int
558
  @return: the desired exit code
559

    
560
  """
561
  instance_name = args[0]
562
  force = opts.force
563

    
564
  if not force:
565
    usertext = ("This will remove the volumes of the instance %s"
566
                " (including mirrors), thus removing all the data"
567
                " of the instance. Continue?") % instance_name
568
    if not AskUser(usertext):
569
      return 1
570

    
571
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
572
                                ignore_failures=opts.ignore_failures)
573
  SubmitOrSend(op, opts)
574
  return 0
575

    
576

    
577
def RenameInstance(opts, args):
578
  """Rename an instance.
579

    
580
  @param opts: the command line options selected by the user
581
  @type args: list
582
  @param args: should contain two elements, the old and the
583
      new instance names
584
  @rtype: int
585
  @return: the desired exit code
586

    
587
  """
588
  op = opcodes.OpRenameInstance(instance_name=args[0],
589
                                new_name=args[1],
590
                                ignore_ip=opts.ignore_ip)
591
  SubmitOrSend(op, opts)
592
  return 0
593

    
594

    
595
def ActivateDisks(opts, args):
596
  """Activate an instance's disks.
597

    
598
  This serves two purposes:
599
    - it allows (as long as the instance is not running)
600
      mounting the disks and modifying them from the node
601
    - it repairs inactive secondary drbds
602

    
603
  @param opts: the command line options selected by the user
604
  @type args: list
605
  @param args: should contain only one element, the instance name
606
  @rtype: int
607
  @return: the desired exit code
608

    
609
  """
610
  instance_name = args[0]
611
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
612
  disks_info = SubmitOrSend(op, opts)
613
  for host, iname, nname in disks_info:
614
    ToStdout("%s:%s:%s", host, iname, nname)
615
  return 0
616

    
617

    
618
def DeactivateDisks(opts, args):
619
  """Deactivate an instance's disks..
620

    
621
  This function takes the instance name, looks for its primary node
622
  and the tries to shutdown its block devices on that node.
623

    
624
  @param opts: the command line options selected by the user
625
  @type args: list
626
  @param args: should contain only one element, the instance name
627
  @rtype: int
628
  @return: the desired exit code
629

    
630
  """
631
  instance_name = args[0]
632
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
633
  SubmitOrSend(op, opts)
634
  return 0
635

    
636

    
637
def GrowDisk(opts, args):
638
  """Grow an instance's disks.
639

    
640
  @param opts: the command line options selected by the user
641
  @type args: list
642
  @param args: should contain two elements, the instance name
643
      whose disks we grow and the disk name, e.g. I{sda}
644
  @rtype: int
645
  @return: the desired exit code
646

    
647
  """
648
  instance = args[0]
649
  disk = args[1]
650
  try:
651
    disk = int(disk)
652
  except ValueError, err:
653
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
654
  amount = utils.ParseUnit(args[2])
655
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
656
                          wait_for_sync=opts.wait_for_sync)
657
  SubmitOrSend(op, opts)
658
  return 0
659

    
660

    
661
def StartupInstance(opts, args):
662
  """Startup instances.
663

    
664
  Depending on the options given, this will start one or more
665
  instances.
666

    
667
  @param opts: the command line options selected by the user
668
  @type args: list
669
  @param args: the instance or node names based on which we
670
      create the final selection (in conjunction with the
671
      opts argument)
672
  @rtype: int
673
  @return: the desired exit code
674

    
675
  """
676
  if opts.multi_mode is None:
677
    opts.multi_mode = _SHUTDOWN_INSTANCES
678
  inames = _ExpandMultiNames(opts.multi_mode, args)
679
  if not inames:
680
    raise errors.OpPrereqError("Selection filter does not match any instances")
681
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
682
  if not (opts.force_multi or not multi_on
683
          or _ConfirmOperation(inames, "startup")):
684
    return 1
685
  for name in inames:
686
    op = opcodes.OpStartupInstance(instance_name=name,
687
                                   force=opts.force,
688
                                   extra_args=opts.extra_args)
689
    if multi_on:
690
      ToStdout("Starting up %s", name)
691
    try:
692
      SubmitOrSend(op, opts)
693
    except JobSubmittedException, err:
694
      _, txt = FormatError(err)
695
      ToStdout("%s", txt)
696
  return 0
697

    
698

    
699
def RebootInstance(opts, args):
700
  """Reboot instance(s).
701

    
702
  Depending on the parameters given, this will reboot one or more
703
  instances.
704

    
705
  @param opts: the command line options selected by the user
706
  @type args: list
707
  @param args: the instance or node names based on which we
708
      create the final selection (in conjunction with the
709
      opts argument)
710
  @rtype: int
711
  @return: the desired exit code
712

    
713
  """
714
  if opts.multi_mode is None:
715
    opts.multi_mode = _SHUTDOWN_INSTANCES
716
  inames = _ExpandMultiNames(opts.multi_mode, args)
717
  if not inames:
718
    raise errors.OpPrereqError("Selection filter does not match any instances")
719
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
720
  if not (opts.force_multi or not multi_on
721
          or _ConfirmOperation(inames, "reboot")):
722
    return 1
723
  for name in inames:
724
    op = opcodes.OpRebootInstance(instance_name=name,
725
                                  reboot_type=opts.reboot_type,
726
                                  ignore_secondaries=opts.ignore_secondaries)
727

    
728
    SubmitOrSend(op, opts)
729
  return 0
730

    
731

    
732
def ShutdownInstance(opts, args):
733
  """Shutdown an instance.
734

    
735
  @param opts: the command line options selected by the user
736
  @type args: list
737
  @param args: the instance or node names based on which we
738
      create the final selection (in conjunction with the
739
      opts argument)
740
  @rtype: int
741
  @return: the desired exit code
742

    
743
  """
744
  if opts.multi_mode is None:
745
    opts.multi_mode = _SHUTDOWN_INSTANCES
746
  inames = _ExpandMultiNames(opts.multi_mode, args)
747
  if not inames:
748
    raise errors.OpPrereqError("Selection filter does not match any instances")
749
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
750
  if not (opts.force_multi or not multi_on
751
          or _ConfirmOperation(inames, "shutdown")):
752
    return 1
753
  for name in inames:
754
    op = opcodes.OpShutdownInstance(instance_name=name)
755
    if multi_on:
756
      ToStdout("Shutting down %s", name)
757
    try:
758
      SubmitOrSend(op, opts)
759
    except JobSubmittedException, err:
760
      _, txt = FormatError(err)
761
      ToStdout("%s", txt)
762
  return 0
763

    
764

    
765
def ReplaceDisks(opts, args):
766
  """Replace the disks of an instance
767

    
768
  @param opts: the command line options selected by the user
769
  @type args: list
770
  @param args: should contain only one element, the instance name
771
  @rtype: int
772
  @return: the desired exit code
773

    
774
  """
775
  instance_name = args[0]
776
  new_2ndary = opts.new_secondary
777
  iallocator = opts.iallocator
778
  if opts.disks is None:
779
    disks = []
780
  else:
781
    try:
782
      disks = [int(i) for i in opts.disks.split(",")]
783
    except ValueError, err:
784
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
785
  cnt = [opts.on_primary, opts.on_secondary,
786
         new_2ndary is not None, iallocator is not None].count(True)
787
  if cnt != 1:
788
    raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
789
                               " options must be passed")
790
  elif opts.on_primary:
791
    mode = constants.REPLACE_DISK_PRI
792
  elif opts.on_secondary:
793
    mode = constants.REPLACE_DISK_SEC
794
  elif new_2ndary is not None or iallocator is not None:
795
    # replace secondary
796
    mode = constants.REPLACE_DISK_CHG
797

    
798
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
799
                              remote_node=new_2ndary, mode=mode,
800
                              iallocator=iallocator)
801
  SubmitOrSend(op, opts)
802
  return 0
803

    
804

    
805
def FailoverInstance(opts, args):
806
  """Failover an instance.
807

    
808
  The failover is done by shutting it down on its present node and
809
  starting it on the secondary.
810

    
811
  @param opts: the command line options selected by the user
812
  @type args: list
813
  @param args: should contain only one element, the instance name
814
  @rtype: int
815
  @return: the desired exit code
816

    
817
  """
818
  instance_name = args[0]
819
  force = opts.force
820

    
821
  if not force:
822
    usertext = ("Failover will happen to image %s."
823
                " This requires a shutdown of the instance. Continue?" %
824
                (instance_name,))
825
    if not AskUser(usertext):
826
      return 1
827

    
828
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
829
                                  ignore_consistency=opts.ignore_consistency)
830
  SubmitOrSend(op, opts)
831
  return 0
832

    
833

    
834
def MigrateInstance(opts, args):
835
  """Migrate an instance.
836

    
837
  The migrate is done without shutdown.
838

    
839
  @param opts: the command line options selected by the user
840
  @type args: list
841
  @param args: should contain only one element, the instance name
842
  @rtype: int
843
  @return: the desired exit code
844

    
845
  """
846
  instance_name = args[0]
847
  force = opts.force
848

    
849
  if not force:
850
    if opts.cleanup:
851
      usertext = ("Instance %s will be recovered from a failed migration."
852
                  " Note that the migration procedure (including cleanup)" %
853
                  (instance_name,))
854
    else:
855
      usertext = ("Instance %s will be migrated. Note that migration" %
856
                  (instance_name,))
857
    usertext += (" is **experimental** in this version."
858
                " This might impact the instance if anything goes wrong."
859
                " Continue?")
860
    if not AskUser(usertext):
861
      return 1
862

    
863
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
864
                                 cleanup=opts.cleanup)
865
  SubmitOpCode(op)
866
  return 0
867

    
868

    
869
def ConnectToInstanceConsole(opts, args):
870
  """Connect to the console of an instance.
871

    
872
  @param opts: the command line options selected by the user
873
  @type args: list
874
  @param args: should contain only one element, the instance name
875
  @rtype: int
876
  @return: the desired exit code
877

    
878
  """
879
  instance_name = args[0]
880

    
881
  op = opcodes.OpConnectConsole(instance_name=instance_name)
882
  cmd = SubmitOpCode(op)
883

    
884
  if opts.show_command:
885
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
886
  else:
887
    try:
888
      os.execvp(cmd[0], cmd)
889
    finally:
890
      ToStderr("Can't run console command %s with arguments:\n'%s'",
891
               cmd[0], " ".join(cmd))
892
      os._exit(1)
893

    
894

    
895
def _FormatLogicalID(dev_type, logical_id):
896
  """Formats the logical_id of a disk.
897

    
898
  """
899
  if dev_type == constants.LD_DRBD8:
900
    node_a, node_b, port, minor_a, minor_b, key = logical_id
901
    data = [
902
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
903
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
904
      ("port", port),
905
      ("auth key", key),
906
      ]
907
  elif dev_type == constants.LD_LV:
908
    vg_name, lv_name = logical_id
909
    data = ["%s/%s" % (vg_name, lv_name)]
910
  else:
911
    data = [str(logical_id)]
912

    
913
  return data
914

    
915

    
916
def _FormatBlockDevInfo(idx, top_level, dev, static):
917
  """Show block device information.
918

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

    
922
  @type idx: int
923
  @param idx: the index of the current disk
924
  @type top_level: boolean
925
  @param top_level: if this a top-level disk?
926
  @type dev: dict
927
  @param dev: dictionary with disk information
928
  @type static: boolean
929
  @param static: wheter the device information doesn't contain
930
      runtime information but only static data
931
  @return: a list of either strings, tuples or lists
932
      (which should be formatted at a higher indent level)
933

    
934
  """
935
  def helper(dtype, status):
936
    """Format one line for physical device status.
937

    
938
    @type dtype: str
939
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
940
    @type status: tuple
941
    @param status: a tuple as returned from L{backend.FindBlockDevice}
942
    @return: the string representing the status
943

    
944
    """
945
    if not status:
946
      return "not active"
947
    txt = ""
948
    (path, major, minor, syncp, estt, degr, ldisk) = status
949
    if major is None:
950
      major_string = "N/A"
951
    else:
952
      major_string = str(major)
953

    
954
    if minor is None:
955
      minor_string = "N/A"
956
    else:
957
      minor_string = str(minor)
958

    
959
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
960
    if dtype in (constants.LD_DRBD8, ):
961
      if syncp is not None:
962
        sync_text = "*RECOVERING* %5.2f%%," % syncp
963
        if estt:
964
          sync_text += " ETA %ds" % estt
965
        else:
966
          sync_text += " ETA unknown"
967
      else:
968
        sync_text = "in sync"
969
      if degr:
970
        degr_text = "*DEGRADED*"
971
      else:
972
        degr_text = "ok"
973
      if ldisk:
974
        ldisk_text = " *MISSING DISK*"
975
      else:
976
        ldisk_text = ""
977
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
978
    elif dtype == constants.LD_LV:
979
      if ldisk:
980
        ldisk_text = " *FAILED* (failed drive?)"
981
      else:
982
        ldisk_text = ""
983
      txt += ldisk_text
984
    return txt
985

    
986
  # the header
987
  if top_level:
988
    if dev["iv_name"] is not None:
989
      txt = dev["iv_name"]
990
    else:
991
      txt = "disk %d" % idx
992
  else:
993
    txt = "child %d" % idx
994
  d1 = ["- %s: %s" % (txt, dev["dev_type"])]
995
  data = []
996
  if top_level:
997
    data.append(("access mode", dev["mode"]))
998
  if dev["logical_id"] is not None:
999
    try:
1000
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1001
    except ValueError:
1002
      l_id = [str(dev["logical_id"])]
1003
    if len(l_id) == 1:
1004
      data.append(("logical_id", l_id[0]))
1005
    else:
1006
      data.extend(l_id)
1007
  elif dev["physical_id"] is not None:
1008
    data.append("physical_id:")
1009
    data.append([dev["physical_id"]])
1010
  if not static:
1011
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1012
  if dev["sstatus"] and not static:
1013
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1014

    
1015
  if dev["children"]:
1016
    data.append("child devices:")
1017
    for c_idx, child in enumerate(dev["children"]):
1018
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1019
  d1.append(data)
1020
  return d1
1021

    
1022

    
1023
def _FormatList(buf, data, indent_level):
1024
  """Formats a list of data at a given indent level.
1025

    
1026
  If the element of the list is:
1027
    - a string, it is simply formatted as is
1028
    - a tuple, it will be split into key, value and the all the
1029
      values in a list will be aligned all at the same start column
1030
    - a list, will be recursively formatted
1031

    
1032
  @type buf: StringIO
1033
  @param buf: the buffer into which we write the output
1034
  @param data: the list to format
1035
  @type indent_level: int
1036
  @param indent_level: the indent level to format at
1037

    
1038
  """
1039
  max_tlen = max([len(elem[0]) for elem in data
1040
                 if isinstance(elem, tuple)] or [0])
1041
  for elem in data:
1042
    if isinstance(elem, basestring):
1043
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1044
    elif isinstance(elem, tuple):
1045
      key, value = elem
1046
      spacer = "%*s" % (max_tlen - len(key), "")
1047
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1048
    elif isinstance(elem, list):
1049
      _FormatList(buf, elem, indent_level+1)
1050

    
1051
def ShowInstanceConfig(opts, args):
1052
  """Compute instance run-time status.
1053

    
1054
  @param opts: the command line options selected by the user
1055
  @type args: list
1056
  @param args: either an empty list, and then we query all
1057
      instances, or should contain a list of instance names
1058
  @rtype: int
1059
  @return: the desired exit code
1060

    
1061
  """
1062
  retcode = 0
1063
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1064
  result = SubmitOpCode(op)
1065
  if not result:
1066
    ToStdout("No instances.")
1067
    return 1
1068

    
1069
  buf = StringIO()
1070
  retcode = 0
1071
  for instance_name in result:
1072
    instance = result[instance_name]
1073
    buf.write("Instance name: %s\n" % instance["name"])
1074
    buf.write("State: configured to be %s" % instance["config_state"])
1075
    if not opts.static:
1076
      buf.write(", actual state is %s" % instance["run_state"])
1077
    buf.write("\n")
1078
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1079
    ##          instance["auto_balance"])
1080
    buf.write("  Nodes:\n")
1081
    buf.write("    - primary: %s\n" % instance["pnode"])
1082
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1083
    buf.write("  Operating system: %s\n" % instance["os"])
1084
    if instance.has_key("network_port"):
1085
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1086
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1087
    if instance["hypervisor"] == constants.HT_XEN_PVM:
1088
      hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
1089
                 (constants.HV_INITRD_PATH, "initrd path"))
1090
    elif instance["hypervisor"] == constants.HT_XEN_HVM:
1091
      hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
1092
                 (constants.HV_ACPI, "ACPI"),
1093
                 (constants.HV_PAE, "PAE"),
1094
                 (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
1095
                 (constants.HV_NIC_TYPE, "NIC type"),
1096
                 (constants.HV_DISK_TYPE, "Disk type"),
1097
                 (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
1098
                 )
1099
      # custom console information for HVM
1100
      vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
1101
      if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1102
        vnc_console_port = "%s:%s" % (instance["pnode"],
1103
                                      instance["network_port"])
1104
      elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
1105
        vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
1106
                                                 instance["network_port"],
1107
                                                 instance["pnode"])
1108
      else:
1109
        vnc_console_port = "%s:%s" % (vnc_bind_address,
1110
                                      instance["network_port"])
1111
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1112

    
1113
    else:
1114
      # auto-handle other hypervisor types
1115
      hvattrs = [(key, key) for key in instance["hv_actual"]]
1116

    
1117
    for key, desc in hvattrs:
1118
      if key in instance["hv_instance"]:
1119
        val = instance["hv_instance"][key]
1120
      else:
1121
        val = "default (%s)" % instance["hv_actual"][key]
1122
      buf.write("    - %s: %s\n" % (desc, val))
1123
    buf.write("  Hardware:\n")
1124
    buf.write("    - VCPUs: %d\n" %
1125
              instance["be_actual"][constants.BE_VCPUS])
1126
    buf.write("    - memory: %dMiB\n" %
1127
              instance["be_actual"][constants.BE_MEMORY])
1128
    buf.write("    - NICs:\n")
1129
    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1130
      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1131
                (idx, mac, ip, bridge))
1132
    buf.write("  Disks:\n")
1133

    
1134
    for idx, device in enumerate(instance["disks"]):
1135
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1136

    
1137
  ToStdout(buf.getvalue().rstrip('\n'))
1138
  return retcode
1139

    
1140

    
1141
def SetInstanceParams(opts, args):
1142
  """Modifies an instance.
1143

    
1144
  All parameters take effect only at the next restart of the instance.
1145

    
1146
  @param opts: the command line options selected by the user
1147
  @type args: list
1148
  @param args: should contain only one element, the instance name
1149
  @rtype: int
1150
  @return: the desired exit code
1151

    
1152
  """
1153
  if not (opts.nics or opts.disks or
1154
          opts.hypervisor or opts.beparams):
1155
    ToStderr("Please give at least one of the parameters.")
1156
    return 1
1157

    
1158
  for param in opts.beparams:
1159
    if opts.beparams[param].lower() == "default":
1160
      opts.beparams[param] = constants.VALUE_DEFAULT
1161
    elif opts.beparams[param].lower() == "none":
1162
      opts.beparams[param] = constants.VALUE_NONE
1163
    elif param == constants.BE_MEMORY:
1164
      opts.beparams[constants.BE_MEMORY] = \
1165
        utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1166

    
1167
  for param in opts.hypervisor:
1168
    if opts.hypervisor[param].lower() == "default":
1169
      opts.hypervisor[param] = constants.VALUE_DEFAULT
1170
    elif opts.hypervisor[param].lower() == "none":
1171
      opts.hypervisor[param] = constants.VALUE_NONE
1172

    
1173
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1174
    try:
1175
      nic_op = int(nic_op)
1176
      opts.nics[idx] = (nic_op, nic_dict)
1177
    except ValueError:
1178
      pass
1179

    
1180
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1181
    try:
1182
      disk_op = int(disk_op)
1183
      opts.disks[idx] = (disk_op, disk_dict)
1184
    except ValueError:
1185
      pass
1186
    if disk_op == constants.DDM_ADD:
1187
      if 'size' not in disk_dict:
1188
        raise errors.OpPrereqError("Missing required parameter 'size'")
1189
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1190

    
1191
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1192
                                   nics=opts.nics,
1193
                                   disks=opts.disks,
1194
                                   hvparams=opts.hypervisor,
1195
                                   beparams=opts.beparams,
1196
                                   force=opts.force)
1197

    
1198
  # even if here we process the result, we allow submit only
1199
  result = SubmitOrSend(op, opts)
1200

    
1201
  if result:
1202
    ToStdout("Modified instance %s", args[0])
1203
    for param, data in result:
1204
      ToStdout(" - %-5s -> %s", param, data)
1205
    ToStdout("Please don't forget that these parameters take effect"
1206
             " only at the next start of the instance.")
1207
  return 0
1208

    
1209

    
1210
# options used in more than one cmd
1211
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1212
                       metavar="<node>")
1213

    
1214
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1215
                    metavar="<os>")
1216

    
1217
# multi-instance selection options
1218
m_force_multi = make_option("--force-multiple", dest="force_multi",
1219
                            help="Do not ask for confirmation when more than"
1220
                            " one instance is affected",
1221
                            action="store_true", default=False)
1222

    
1223
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1224
                             help="Filter by nodes (primary only)",
1225
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1226

    
1227
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1228
                             help="Filter by nodes (secondary only)",
1229
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1230

    
1231
m_node_opt = make_option("--node", dest="multi_mode",
1232
                         help="Filter by nodes (primary and secondary)",
1233
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1234

    
1235
m_clust_opt = make_option("--all", dest="multi_mode",
1236
                          help="Select all instances in the cluster",
1237
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1238

    
1239
m_inst_opt = make_option("--instance", dest="multi_mode",
1240
                         help="Filter by instance name [default]",
1241
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1242

    
1243

    
1244
# this is defined separately due to readability only
1245
add_opts = [
1246
  DEBUG_OPT,
1247
  make_option("-n", "--node", dest="node",
1248
              help="Target node and optional secondary node",
1249
              metavar="<pnode>[:<snode>]"),
1250
  os_opt,
1251
  keyval_option("-B", "--backend", dest="beparams",
1252
                type="keyval", default={},
1253
                help="Backend parameters"),
1254
  make_option("-t", "--disk-template", dest="disk_template",
1255
              help="Custom disk setup (diskless, file, plain or drbd)",
1256
              default=None, metavar="TEMPL"),
1257
  ikv_option("--disk", help="Disk information",
1258
             default=[], dest="disks",
1259
             action="append",
1260
             type="identkeyval"),
1261
  ikv_option("--net", help="NIC information",
1262
             default=[], dest="nics",
1263
             action="append",
1264
             type="identkeyval"),
1265
  make_option("--no-nics", default=False, action="store_true",
1266
              help="Do not create any network cards for the instance"),
1267
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1268
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1269
  make_option("--no-start", dest="start", default=True,
1270
              action="store_false", help="Don't start the instance after"
1271
              " creation"),
1272
  make_option("--no-ip-check", dest="ip_check", default=True,
1273
              action="store_false", help="Don't check that the instance's IP"
1274
              " is alive (only valid with --no-start)"),
1275
  make_option("--file-storage-dir", dest="file_storage_dir",
1276
              help="Relative path under default cluster-wide file storage dir"
1277
              " to store file-based disks", default=None,
1278
              metavar="<DIR>"),
1279
  make_option("--file-driver", dest="file_driver", help="Driver to use"
1280
              " for image files", default="loop", metavar="<DRIVER>"),
1281
  make_option("-I", "--iallocator", metavar="<NAME>",
1282
              help="Select nodes for the instance automatically using the"
1283
              " <NAME> iallocator plugin", default=None, type="string"),
1284
  ikv_option("-H", "--hypervisor", dest="hypervisor",
1285
              help="Hypervisor and hypervisor options, in the format"
1286
              " hypervisor:option=value,option=value,...", default=None,
1287
              type="identkeyval"),
1288
  SUBMIT_OPT,
1289
  ]
1290

    
1291
commands = {
1292
  'add': (AddInstance, ARGS_ONE, add_opts,
1293
          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1294
          "Creates and adds a new instance to the cluster"),
1295
  'batch-create': (BatchCreate, ARGS_ONE,
1296
                   [DEBUG_OPT],
1297
                   "<instances_file.json>",
1298
                   "Create a bunch of instances based on specs in the file."),
1299
  'console': (ConnectToInstanceConsole, ARGS_ONE,
1300
              [DEBUG_OPT,
1301
               make_option("--show-cmd", dest="show_command",
1302
                           action="store_true", default=False,
1303
                           help=("Show command instead of executing it"))],
1304
              "[--show-cmd] <instance>",
1305
              "Opens a console on the specified instance"),
1306
  'failover': (FailoverInstance, ARGS_ONE,
1307
               [DEBUG_OPT, FORCE_OPT,
1308
                make_option("--ignore-consistency", dest="ignore_consistency",
1309
                            action="store_true", default=False,
1310
                            help="Ignore the consistency of the disks on"
1311
                            " the secondary"),
1312
                SUBMIT_OPT,
1313
                ],
1314
               "[-f] <instance>",
1315
               "Stops the instance and starts it on the backup node, using"
1316
               " the remote mirror (only for instances of type drbd)"),
1317
  'migrate': (MigrateInstance, ARGS_ONE,
1318
               [DEBUG_OPT, FORCE_OPT,
1319
                make_option("--non-live", dest="live",
1320
                            default=True, action="store_false",
1321
                            help="Do a non-live migration (this usually means"
1322
                            " freeze the instance, save the state,"
1323
                            " transfer and only then resume running on the"
1324
                            " secondary node)"),
1325
                make_option("--cleanup", dest="cleanup",
1326
                            default=False, action="store_true",
1327
                            help="Instead of performing the migration, try to"
1328
                            " recover from a failed cleanup. This is safe"
1329
                            " to run even if the instance is healthy, but it"
1330
                            " will create extra replication traffic and "
1331
                            " disrupt briefly the replication (like during the"
1332
                            " migration"),
1333
                ],
1334
               "[-f] <instance>",
1335
               "Migrate instance to its secondary node"
1336
               " (only for instances of type drbd)"),
1337
  'info': (ShowInstanceConfig, ARGS_ANY,
1338
           [DEBUG_OPT,
1339
            make_option("-s", "--static", dest="static",
1340
                        action="store_true", default=False,
1341
                        help="Only show configuration data, not runtime data"),
1342
            ], "[-s] [<instance>...]",
1343
           "Show information on the specified instance(s)"),
1344
  'list': (ListInstances, ARGS_NONE,
1345
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1346
           "Lists the instances and their status. The available fields are"
1347
           " (see the man page for details): status, oper_state, oper_ram,"
1348
           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1349
           " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1350
           " hypervisor."
1351
           " The default field"
1352
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1353
           ),
1354
  'reinstall': (ReinstallInstance, ARGS_ONE,
1355
                [DEBUG_OPT, FORCE_OPT, os_opt,
1356
                 make_option("--select-os", dest="select_os",
1357
                             action="store_true", default=False,
1358
                             help="Interactive OS reinstall, lists available"
1359
                             " OS templates for selection"),
1360
                 SUBMIT_OPT,
1361
                 ],
1362
                "[-f] <instance>", "Reinstall a stopped instance"),
1363
  'remove': (RemoveInstance, ARGS_ONE,
1364
             [DEBUG_OPT, FORCE_OPT,
1365
              make_option("--ignore-failures", dest="ignore_failures",
1366
                          action="store_true", default=False,
1367
                          help=("Remove the instance from the cluster even"
1368
                                " if there are failures during the removal"
1369
                                " process (shutdown, disk removal, etc.)")),
1370
              SUBMIT_OPT,
1371
              ],
1372
             "[-f] <instance>", "Shuts down the instance and removes it"),
1373
  'rename': (RenameInstance, ARGS_FIXED(2),
1374
             [DEBUG_OPT,
1375
              make_option("--no-ip-check", dest="ignore_ip",
1376
                          help="Do not check that the IP of the new name"
1377
                          " is alive",
1378
                          default=False, action="store_true"),
1379
              SUBMIT_OPT,
1380
              ],
1381
             "<instance> <new_name>", "Rename the instance"),
1382
  'replace-disks': (ReplaceDisks, ARGS_ONE,
1383
                    [DEBUG_OPT,
1384
                     make_option("-n", "--new-secondary", dest="new_secondary",
1385
                                 help=("New secondary node (for secondary"
1386
                                       " node change)"), metavar="NODE",
1387
                                 default=None),
1388
                     make_option("-p", "--on-primary", dest="on_primary",
1389
                                 default=False, action="store_true",
1390
                                 help=("Replace the disk(s) on the primary"
1391
                                       " node (only for the drbd template)")),
1392
                     make_option("-s", "--on-secondary", dest="on_secondary",
1393
                                 default=False, action="store_true",
1394
                                 help=("Replace the disk(s) on the secondary"
1395
                                       " node (only for the drbd template)")),
1396
                     make_option("--disks", dest="disks", default=None,
1397
                                 help=("Comma-separated list of disks"
1398
                                       " to replace (e.g. sda) (optional,"
1399
                                       " defaults to all disks")),
1400
                     make_option("-i", "--iallocator", metavar="<NAME>",
1401
                                 help="Select new secondary for the instance"
1402
                                 " automatically using the"
1403
                                 " <NAME> iallocator plugin (enables"
1404
                                 " secondary node replacement)",
1405
                                 default=None, type="string"),
1406
                     SUBMIT_OPT,
1407
                     ],
1408
                    "[-s|-p|-n NODE] <instance>",
1409
                    "Replaces all disks for the instance"),
1410
  'modify': (SetInstanceParams, ARGS_ONE,
1411
             [DEBUG_OPT, FORCE_OPT,
1412
              keyval_option("-H", "--hypervisor", type="keyval",
1413
                            default={}, dest="hypervisor",
1414
                            help="Change hypervisor parameters"),
1415
              keyval_option("-B", "--backend", type="keyval",
1416
                            default={}, dest="beparams",
1417
                            help="Change backend parameters"),
1418
              ikv_option("--disk", help="Disk changes",
1419
                         default=[], dest="disks",
1420
                         action="append",
1421
                         type="identkeyval"),
1422
              ikv_option("--net", help="NIC changes",
1423
                         default=[], dest="nics",
1424
                         action="append",
1425
                         type="identkeyval"),
1426
              SUBMIT_OPT,
1427
              ],
1428
             "<instance>", "Alters the parameters of an instance"),
1429
  'shutdown': (ShutdownInstance, ARGS_ANY,
1430
               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1431
                m_clust_opt, m_inst_opt, m_force_multi,
1432
                SUBMIT_OPT,
1433
                ],
1434
               "<instance>", "Stops an instance"),
1435
  'startup': (StartupInstance, ARGS_ANY,
1436
              [DEBUG_OPT, FORCE_OPT, m_force_multi,
1437
               make_option("-e", "--extra", dest="extra_args",
1438
                           help="Extra arguments for the instance's kernel",
1439
                           default=None, type="string", metavar="<PARAMS>"),
1440
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1441
               m_clust_opt, m_inst_opt,
1442
               SUBMIT_OPT,
1443
               ],
1444
            "<instance>", "Starts an instance"),
1445

    
1446
  'reboot': (RebootInstance, ARGS_ANY,
1447
              [DEBUG_OPT, m_force_multi,
1448
               make_option("-e", "--extra", dest="extra_args",
1449
                           help="Extra arguments for the instance's kernel",
1450
                           default=None, type="string", metavar="<PARAMS>"),
1451
               make_option("-t", "--type", dest="reboot_type",
1452
                           help="Type of reboot: soft/hard/full",
1453
                           default=constants.INSTANCE_REBOOT_HARD,
1454
                           type="string", metavar="<REBOOT>"),
1455
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1456
                           default=False, action="store_true",
1457
                           help="Ignore errors from secondaries"),
1458
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1459
               m_clust_opt, m_inst_opt,
1460
               SUBMIT_OPT,
1461
               ],
1462
            "<instance>", "Reboots an instance"),
1463
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1464
                     "<instance>",
1465
                     "Activate an instance's disks"),
1466
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1467
                       "<instance>",
1468
                       "Deactivate an instance's disks"),
1469
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1470
                [DEBUG_OPT, SUBMIT_OPT,
1471
                 make_option("--no-wait-for-sync",
1472
                             dest="wait_for_sync", default=True,
1473
                             action="store_false",
1474
                             help="Don't wait for sync (DANGEROUS!)"),
1475
                 ],
1476
                "<instance> <disk> <size>", "Grow an instance's disk"),
1477
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1478
                "<instance_name>", "List the tags of the given instance"),
1479
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1480
               "<instance_name> tag...", "Add tags to the given instance"),
1481
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1482
                  "<instance_name> tag...", "Remove tags from given instance"),
1483
  }
1484

    
1485
#: dictionary with aliases for commands
1486
aliases = {
1487
  'activate_block_devs': 'activate-disks',
1488
  'replace_disks': 'replace-disks',
1489
  'start': 'startup',
1490
  'stop': 'shutdown',
1491
  }
1492

    
1493
if __name__ == '__main__':
1494
  sys.exit(GenericMain(commands, aliases=aliases,
1495
                       override={"tag_type": constants.TAG_INSTANCE}))