Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 9939547b

History | View | Annotate | Download (52.2 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
  for (name, specs) in instance_data.iteritems():
451
    specs = _PopulateWithDefaults(specs)
452
    _Validate(specs)
453

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

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

    
469
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
470

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

    
489
    ToStdout("%s: %s", name, cli.SendJob([op]))
490

    
491
  return 0
492

    
493

    
494
def ReinstallInstance(opts, args):
495
  """Reinstall an instance.
496

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

    
504
  """
505
  instance_name = args[0]
506

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

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

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

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

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

    
531
    os_name = selected
532
  else:
533
    os_name = opts.os
534

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

    
541
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
542
                                   os_type=os_name)
543
  SubmitOrSend(op, opts)
544

    
545
  return 0
546

    
547

    
548
def RemoveInstance(opts, args):
549
  """Remove an instance.
550

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

    
558
  """
559
  instance_name = args[0]
560
  force = opts.force
561

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

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

    
574

    
575
def RenameInstance(opts, args):
576
  """Rename an instance.
577

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

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

    
592

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

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

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

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

    
615

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

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

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

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

    
634

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

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

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

    
658

    
659
def StartupInstance(opts, args):
660
  """Startup instances.
661

    
662
  Depending on the options given, this will start one or more
663
  instances.
664

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

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

    
696

    
697
def RebootInstance(opts, args):
698
  """Reboot instance(s).
699

    
700
  Depending on the parameters given, this will reboot one or more
701
  instances.
702

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

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

    
726
    SubmitOrSend(op, opts)
727
  return 0
728

    
729

    
730
def ShutdownInstance(opts, args):
731
  """Shutdown an instance.
732

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

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

    
762

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

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

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

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

    
802

    
803
def FailoverInstance(opts, args):
804
  """Failover an instance.
805

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

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

    
815
  """
816
  instance_name = args[0]
817
  force = opts.force
818

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

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

    
831

    
832
def MigrateInstance(opts, args):
833
  """Migrate an instance.
834

    
835
  The migrate is done without shutdown.
836

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

    
843
  """
844
  instance_name = args[0]
845
  force = opts.force
846

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

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

    
866

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

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

    
876
  """
877
  instance_name = args[0]
878

    
879
  op = opcodes.OpConnectConsole(instance_name=instance_name)
880
  cmd = SubmitOpCode(op)
881

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

    
892

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

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

    
911
  return data
912

    
913

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

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

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

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

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

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

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

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

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

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

    
1020

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

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

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

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

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

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

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

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

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

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

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

    
1135
  ToStdout(buf.getvalue().rstrip('\n'))
1136
  return retcode
1137

    
1138

    
1139
def SetInstanceParams(opts, args):
1140
  """Modifies an instance.
1141

    
1142
  All parameters take effect only at the next restart of the instance.
1143

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

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

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

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

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

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

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

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

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

    
1207

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

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

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

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

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

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

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

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

    
1241

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

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

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

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

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