Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 53c776b5

History | View | Annotate | Download (52 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": 25,
379
      "swap_size": 1024,
380
      "template": "drbd",
381
      "backend": {
382
        "memory": 512,
383
        "vcpus": 1 },
384
      "os": "etch-image",
385
      "primary_node": "firstnode",
386
      "secondary_node": "secondnode",
387
      "iallocator": "dumb"}
388
    }
389

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

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

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

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

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

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

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

    
449
  # Iterate over the instances and do:
450
  #  * Populate the specs with default value
451
  #  * Validate the instance specs
452
  for (name, specs) in instance_data.iteritems():
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
    op = opcodes.OpCreateInstance(instance_name=name,
462
                                  disk_size=specs['disk_size'],
463
                                  swap_size=specs['swap_size'],
464
                                  disk_template=specs['template'],
465
                                  mode=constants.INSTANCE_CREATE,
466
                                  os_type=specs['os'],
467
                                  pnode=specs['primary_node'],
468
                                  snode=specs['secondary_node'],
469
                                  ip=specs['ip'], bridge=specs['bridge'],
470
                                  start=specs['start'],
471
                                  ip_check=specs['ip_check'],
472
                                  wait_for_sync=True,
473
                                  mac=specs['mac'],
474
                                  iallocator=specs['iallocator'],
475
                                  hypervisor=hypervisor,
476
                                  hvparams=hvparams,
477
                                  beparams=specs['backend'],
478
                                  file_storage_dir=specs['file_storage_dir'],
479
                                  file_driver=specs['file_driver'])
480

    
481
    ToStdout("%s: %s", name, cli.SendJob([op]))
482

    
483
  return 0
484

    
485

    
486
def ReinstallInstance(opts, args):
487
  """Reinstall an instance.
488

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

    
496
  """
497
  instance_name = args[0]
498

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

    
503
    if not result:
504
      ToStdout("Can't get the OS list")
505
      return 1
506

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

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

    
519
    if selected == 'exit':
520
      ToStdout("User aborted reinstall, exiting")
521
      return 1
522

    
523
    os_name = selected
524
  else:
525
    os_name = opts.os
526

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

    
533
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
534
                                   os_type=os_name)
535
  SubmitOrSend(op, opts)
536

    
537
  return 0
538

    
539

    
540
def RemoveInstance(opts, args):
541
  """Remove an instance.
542

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

    
550
  """
551
  instance_name = args[0]
552
  force = opts.force
553

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

    
561
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
562
                                ignore_failures=opts.ignore_failures)
563
  SubmitOrSend(op, opts)
564
  return 0
565

    
566

    
567
def RenameInstance(opts, args):
568
  """Rename an instance.
569

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

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

    
584

    
585
def ActivateDisks(opts, args):
586
  """Activate an instance's disks.
587

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

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

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

    
607

    
608
def DeactivateDisks(opts, args):
609
  """Deactivate an instance's disks..
610

    
611
  This function takes the instance name, looks for its primary node
612
  and the tries to shutdown its block devices on that node.
613

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

    
620
  """
621
  instance_name = args[0]
622
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
623
  SubmitOrSend(op, opts)
624
  return 0
625

    
626

    
627
def GrowDisk(opts, args):
628
  """Grow an instance's disks.
629

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

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

    
650

    
651
def StartupInstance(opts, args):
652
  """Startup instances.
653

    
654
  Depending on the options given, this will start one or more
655
  instances.
656

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

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

    
688

    
689
def RebootInstance(opts, args):
690
  """Reboot instance(s).
691

    
692
  Depending on the parameters given, this will reboot one or more
693
  instances.
694

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

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

    
718
    SubmitOrSend(op, opts)
719
  return 0
720

    
721

    
722
def ShutdownInstance(opts, args):
723
  """Shutdown an instance.
724

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

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

    
754

    
755
def ReplaceDisks(opts, args):
756
  """Replace the disks of an instance
757

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

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

    
788
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
789
                              remote_node=new_2ndary, mode=mode,
790
                              iallocator=iallocator)
791
  SubmitOrSend(op, opts)
792
  return 0
793

    
794

    
795
def FailoverInstance(opts, args):
796
  """Failover an instance.
797

    
798
  The failover is done by shutting it down on its present node and
799
  starting it on the secondary.
800

    
801
  @param opts: the command line options selected by the user
802
  @type args: list
803
  @param args: should contain only one element, the instance name
804
  @rtype: int
805
  @return: the desired exit code
806

    
807
  """
808
  instance_name = args[0]
809
  force = opts.force
810

    
811
  if not force:
812
    usertext = ("Failover will happen to image %s."
813
                " This requires a shutdown of the instance. Continue?" %
814
                (instance_name,))
815
    if not AskUser(usertext):
816
      return 1
817

    
818
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
819
                                  ignore_consistency=opts.ignore_consistency)
820
  SubmitOrSend(op, opts)
821
  return 0
822

    
823

    
824
def MigrateInstance(opts, args):
825
  """Migrate an instance.
826

    
827
  The migrate is done without shutdown.
828

    
829
  Args:
830
    opts - class with options as members
831
    args - list with a single element, the instance name
832
  Opts used:
833
    force - whether to migrate without asking questions.
834

    
835
  """
836
  instance_name = args[0]
837
  force = opts.force
838

    
839
  if not force:
840
    if opts.cleanup:
841
      usertext = ("Instance %s will be recovered from a failed migration."
842
                  " Note that the migration procedure (including cleanup)" %
843
                  (instance_name,))
844
    else:
845
      usertext = ("Instance %s will be migrated. Note that migration" %
846
                  (instance_name,))
847
    usertext += (" is **experimental** in this version."
848
                " This might impact the instance if anything goes wrong."
849
                " Continue?")
850
    if not AskUser(usertext):
851
      return 1
852

    
853
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
854
                                 cleanup=opts.cleanup)
855
  SubmitOpCode(op)
856
  return 0
857

    
858

    
859
def ConnectToInstanceConsole(opts, args):
860
  """Connect to the console of an instance.
861

    
862
  @param opts: the command line options selected by the user
863
  @type args: list
864
  @param args: should contain only one element, the instance name
865
  @rtype: int
866
  @return: the desired exit code
867

    
868
  """
869
  instance_name = args[0]
870

    
871
  op = opcodes.OpConnectConsole(instance_name=instance_name)
872
  cmd = SubmitOpCode(op)
873

    
874
  if opts.show_command:
875
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
876
  else:
877
    try:
878
      os.execvp(cmd[0], cmd)
879
    finally:
880
      ToStderr("Can't run console command %s with arguments:\n'%s'",
881
               cmd[0], " ".join(cmd))
882
      os._exit(1)
883

    
884

    
885
def _FormatLogicalID(dev_type, logical_id):
886
  """Formats the logical_id of a disk.
887

    
888
  """
889
  if dev_type == constants.LD_DRBD8:
890
    node_a, node_b, port, minor_a, minor_b, key = logical_id
891
    data = [
892
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
893
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
894
      ("port", port),
895
      ("auth key", key),
896
      ]
897
  elif dev_type == constants.LD_LV:
898
    vg_name, lv_name = logical_id
899
    data = ["%s/%s" % (vg_name, lv_name)]
900
  else:
901
    data = [str(logical_id)]
902

    
903
  return data
904

    
905

    
906
def _FormatBlockDevInfo(idx, top_level, dev, static):
907
  """Show block device information.
908

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

    
912
  @type idx: int
913
  @param idx: the index of the current disk
914
  @type top_level: boolean
915
  @param top_level: if this a top-level disk?
916
  @type dev: dict
917
  @param dev: dictionary with disk information
918
  @type static: boolean
919
  @param static: wheter the device information doesn't contain
920
      runtime information but only static data
921
  @return: a list of either strings, tuples or lists
922
      (which should be formatted at a higher indent level)
923

    
924
  """
925
  def helper(dtype, status):
926
    """Format one line for physical device status.
927

    
928
    @type dtype: str
929
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
930
    @type status: tuple
931
    @param status: a tuple as returned from L{backend.FindBlockDevice}
932
    @return: the string representing the status
933

    
934
    """
935
    if not status:
936
      return "not active"
937
    txt = ""
938
    (path, major, minor, syncp, estt, degr, ldisk) = status
939
    if major is None:
940
      major_string = "N/A"
941
    else:
942
      major_string = str(major)
943

    
944
    if minor is None:
945
      minor_string = "N/A"
946
    else:
947
      minor_string = str(minor)
948

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

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

    
1005
  if dev["children"]:
1006
    data.append("child devices:")
1007
    for c_idx, child in enumerate(dev["children"]):
1008
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1009
  d1.append(data)
1010
  return d1
1011

    
1012

    
1013
def _FormatList(buf, data, indent_level):
1014
  """Formats a list of data at a given indent level.
1015

    
1016
  If the element of the list is:
1017
    - a string, it is simply formatted as is
1018
    - a tuple, it will be split into key, value and the all the
1019
      values in a list will be aligned all at the same start column
1020
    - a list, will be recursively formatted
1021

    
1022
  @type buf: StringIO
1023
  @param buf: the buffer into which we write the output
1024
  @param data: the list to format
1025
  @type indent_level: int
1026
  @param indent_level: the indent level to format at
1027

    
1028
  """
1029
  max_tlen = max([len(elem[0]) for elem in data
1030
                 if isinstance(elem, tuple)] or [0])
1031
  for elem in data:
1032
    if isinstance(elem, basestring):
1033
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1034
    elif isinstance(elem, tuple):
1035
      key, value = elem
1036
      spacer = "%*s" % (max_tlen - len(key), "")
1037
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1038
    elif isinstance(elem, list):
1039
      _FormatList(buf, elem, indent_level+1)
1040

    
1041
def ShowInstanceConfig(opts, args):
1042
  """Compute instance run-time status.
1043

    
1044
  @param opts: the command line options selected by the user
1045
  @type args: list
1046
  @param args: either an empty list, and then we query all
1047
      instances, or should contain a list of instance names
1048
  @rtype: int
1049
  @return: the desired exit code
1050

    
1051
  """
1052
  retcode = 0
1053
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1054
  result = SubmitOpCode(op)
1055
  if not result:
1056
    ToStdout("No instances.")
1057
    return 1
1058

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

    
1103
    else:
1104
      # auto-handle other hypervisor types
1105
      hvattrs = [(key, key) for key in instance["hv_actual"]]
1106

    
1107
    for key, desc in hvattrs:
1108
      if key in instance["hv_instance"]:
1109
        val = instance["hv_instance"][key]
1110
      else:
1111
        val = "default (%s)" % instance["hv_actual"][key]
1112
      buf.write("    - %s: %s\n" % (desc, val))
1113
    buf.write("  Hardware:\n")
1114
    buf.write("    - VCPUs: %d\n" %
1115
              instance["be_actual"][constants.BE_VCPUS])
1116
    buf.write("    - memory: %dMiB\n" %
1117
              instance["be_actual"][constants.BE_MEMORY])
1118
    buf.write("    - NICs:\n")
1119
    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1120
      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1121
                (idx, mac, ip, bridge))
1122
    buf.write("  Disks:\n")
1123

    
1124
    for idx, device in enumerate(instance["disks"]):
1125
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1126

    
1127
  ToStdout(buf.getvalue().rstrip('\n'))
1128
  return retcode
1129

    
1130

    
1131
def SetInstanceParams(opts, args):
1132
  """Modifies an instance.
1133

    
1134
  All parameters take effect only at the next restart of the instance.
1135

    
1136
  @param opts: the command line options selected by the user
1137
  @type args: list
1138
  @param args: should contain only one element, the instance name
1139
  @rtype: int
1140
  @return: the desired exit code
1141

    
1142
  """
1143
  if not (opts.nics or opts.disks or
1144
          opts.hypervisor or opts.beparams):
1145
    ToStderr("Please give at least one of the parameters.")
1146
    return 1
1147

    
1148
  for param in opts.beparams:
1149
    if opts.beparams[param].lower() == "default":
1150
      opts.beparams[param] = constants.VALUE_DEFAULT
1151
    elif opts.beparams[param].lower() == "none":
1152
      opts.beparams[param] = constants.VALUE_NONE
1153
    elif param == constants.BE_MEMORY:
1154
      opts.beparams[constants.BE_MEMORY] = \
1155
        utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1156

    
1157
  for param in opts.hypervisor:
1158
    if opts.hypervisor[param].lower() == "default":
1159
      opts.hypervisor[param] = constants.VALUE_DEFAULT
1160
    elif opts.hypervisor[param].lower() == "none":
1161
      opts.hypervisor[param] = constants.VALUE_NONE
1162

    
1163
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1164
    try:
1165
      nic_op = int(nic_op)
1166
      opts.nics[idx] = (nic_op, nic_dict)
1167
    except ValueError:
1168
      pass
1169

    
1170
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1171
    try:
1172
      disk_op = int(disk_op)
1173
      opts.disks[idx] = (disk_op, disk_dict)
1174
    except ValueError:
1175
      pass
1176
    if disk_op == constants.DDM_ADD:
1177
      if 'size' not in disk_dict:
1178
        raise errors.OpPrereqError("Missing required parameter 'size'")
1179
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1180

    
1181
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1182
                                   nics=opts.nics,
1183
                                   disks=opts.disks,
1184
                                   hvparams=opts.hypervisor,
1185
                                   beparams=opts.beparams,
1186
                                   force=opts.force)
1187

    
1188
  # even if here we process the result, we allow submit only
1189
  result = SubmitOrSend(op, opts)
1190

    
1191
  if result:
1192
    ToStdout("Modified instance %s", args[0])
1193
    for param, data in result:
1194
      ToStdout(" - %-5s -> %s", param, data)
1195
    ToStdout("Please don't forget that these parameters take effect"
1196
             " only at the next start of the instance.")
1197
  return 0
1198

    
1199

    
1200
# options used in more than one cmd
1201
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1202
                       metavar="<node>")
1203

    
1204
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1205
                    metavar="<os>")
1206

    
1207
# multi-instance selection options
1208
m_force_multi = make_option("--force-multiple", dest="force_multi",
1209
                            help="Do not ask for confirmation when more than"
1210
                            " one instance is affected",
1211
                            action="store_true", default=False)
1212

    
1213
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1214
                             help="Filter by nodes (primary only)",
1215
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1216

    
1217
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1218
                             help="Filter by nodes (secondary only)",
1219
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1220

    
1221
m_node_opt = make_option("--node", dest="multi_mode",
1222
                         help="Filter by nodes (primary and secondary)",
1223
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1224

    
1225
m_clust_opt = make_option("--all", dest="multi_mode",
1226
                          help="Select all instances in the cluster",
1227
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1228

    
1229
m_inst_opt = make_option("--instance", dest="multi_mode",
1230
                         help="Filter by instance name [default]",
1231
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1232

    
1233

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

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

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

    
1475
#: dictionary with aliases for commands
1476
aliases = {
1477
  'activate_block_devs': 'activate-disks',
1478
  'replace_disks': 'replace-disks',
1479
  'start': 'startup',
1480
  'stop': 'shutdown',
1481
  }
1482

    
1483
if __name__ == '__main__':
1484
  sys.exit(GenericMain(commands, aliases=aliases,
1485
                       override={"tag_type": constants.TAG_INSTANCE}))