Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ b4ec07f8

History | View | Annotate | Download (54 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, client=None):
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 client is None:
80
    client = GetClient()
81
  if mode == _SHUTDOWN_CLUSTER:
82
    if names:
83
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
84
    idata = client.QueryInstances([], ["name"], False)
85
    inames = [row[0] for row in idata]
86

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

    
107
  elif mode == _SHUTDOWN_INSTANCES:
108
    if not names:
109
      raise errors.OpPrereqError("No instance names passed")
110
    idata = client.QueryInstances(names, ["name"], False)
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 _EnsureInstancesExist(client, names):
158
  """Check for and ensure the given instance names exist.
159

    
160
  This function will raise an OpPrereqError in case they don't
161
  exist. Otherwise it will exit cleanly.
162

    
163
  @type client: L{luxi.Client}
164
  @param client: the client to use for the query
165
  @type names: list
166
  @param names: the list of instance names to query
167
  @raise errors.OpPrereqError: in case any instance is missing
168

    
169
  """
170
  # TODO: change LUQueryInstances to that it actually returns None
171
  # instead of raising an exception, or devise a better mechanism
172
  result = client.QueryInstances(names, ["name"], False)
173
  for orig_name, row in zip(names, result):
174
    if row[0] is None:
175
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
176

    
177

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

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

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

    
195
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
196

    
197
  if not opts.no_headers:
198
    headers = {
199
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
200
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
201
      "oper_state": "Running",
202
      "oper_ram": "Memory", "disk_template": "Disk_template",
203
      "ip": "IP_address", "mac": "MAC_address",
204
      "bridge": "Bridge",
205
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
206
      "disk_usage": "DiskUsage",
207
      "status": "Status", "tags": "Tags",
208
      "network_port": "Network_port",
209
      "hv/kernel_path": "Kernel_path",
210
      "hv/initrd_path": "Initrd_path",
211
      "hv/boot_order": "HVM_boot_order",
212
      "hv/acpi": "HVM_ACPI",
213
      "hv/pae": "HVM_PAE",
214
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
215
      "hv/nic_type": "HVM_NIC_type",
216
      "hv/disk_type": "HVM_Disk_type",
217
      "hv/vnc_bind_address": "VNC_bind_address",
218
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
219
      "hvparams": "Hypervisor_parameters",
220
      "be/memory": "Configured_memory",
221
      "be/vcpus": "VCPUs",
222
      "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:
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 or opts.sd_size is not None:
314
      raise errors.OpPrereqError("Diskless instance but disk"
315
                                 " information passed")
316
    disks = []
317
  else:
318
    if not opts.disks and not opts.sd_size:
319
      raise errors.OpPrereqError("No disk information specified")
320
    if opts.disks and opts.sd_size is not None:
321
      raise errors.OpPrereqError("Please use either the '--disk' or"
322
                                 " '-s' option")
323
    if opts.sd_size is not None:
324
      opts.disks = [(0, {"size": opts.sd_size})]
325
    try:
326
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
327
    except ValueError, err:
328
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
329
    disks = [{}] * disk_max
330
    for didx, ddict in opts.disks:
331
      didx = int(didx)
332
      if "size" not in ddict:
333
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
334
      try:
335
        ddict["size"] = utils.ParseUnit(ddict["size"])
336
      except ValueError, err:
337
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
338
                                   (didx, err))
339
      disks[didx] = ddict
340

    
341
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
342
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
343

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

    
361
  SubmitOrSend(op, opts)
362
  return 0
363

    
364

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

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

    
371
    {"instance-name":{
372
      "disk_size": [20480],
373
      "template": "drbd",
374
      "backend": {
375
        "memory": 512,
376
        "vcpus": 1 },
377
      "os": "debootstrap",
378
      "primary_node": "firstnode",
379
      "secondary_node": "secondnode",
380
      "iallocator": "dumb"}
381
    }
382

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

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

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

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

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

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

    
435
  json_filename = args[0]
436
  try:
437
    fd = open(json_filename, 'r')
438
    instance_data = simplejson.load(fd)
439
    fd.close()
440
  except Exception, err:
441
    ToStderr("Can't parse the instance definition file: %s" % str(err))
442
    return 1
443

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

    
453
    hypervisor = specs['hypervisor']
454
    hvparams = specs['hvparams']
455

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

    
466
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
467

    
468
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
469
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
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 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
  cl = GetClient()
562

    
563
  if not force:
564
    _EnsureInstancesExist(cl, [instance_name])
565

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

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

    
577

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

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

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

    
595

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

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

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

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

    
619

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

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

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

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

    
638

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

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

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

    
662

    
663
def StartupInstance(opts, args):
664
  """Startup instances.
665

    
666
  Depending on the options given, this will start one or more
667
  instances.
668

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

    
677
  """
678
  cl = GetClient()
679
  if opts.multi_mode is None:
680
    opts.multi_mode = _SHUTDOWN_INSTANCES
681
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
682
  if not inames:
683
    raise errors.OpPrereqError("Selection filter does not match any instances")
684
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
685
  if not (opts.force_multi or not multi_on
686
          or _ConfirmOperation(inames, "startup")):
687
    return 1
688
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
689
  for name in inames:
690
    op = opcodes.OpStartupInstance(instance_name=name,
691
                                   force=opts.force)
692
    # do not add these parameters to the opcode unless they're defined
693
    if opts.hvparams:
694
      op.hvparams = opts.hvparams
695
    if opts.beparams:
696
      op.beparams = opts.beparams
697
    jex.QueueJob(name, op)
698
  jex.WaitOrShow(not opts.submit_only)
699
  return 0
700

    
701

    
702
def RebootInstance(opts, args):
703
  """Reboot instance(s).
704

    
705
  Depending on the parameters given, this will reboot one or more
706
  instances.
707

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

    
716
  """
717
  cl = GetClient()
718
  if opts.multi_mode is None:
719
    opts.multi_mode = _SHUTDOWN_INSTANCES
720
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
721
  if not inames:
722
    raise errors.OpPrereqError("Selection filter does not match any instances")
723
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
724
  if not (opts.force_multi or not multi_on
725
          or _ConfirmOperation(inames, "reboot")):
726
    return 1
727
  jex = JobExecutor(verbose=multi_on, cl=cl)
728
  for name in inames:
729
    op = opcodes.OpRebootInstance(instance_name=name,
730
                                  reboot_type=opts.reboot_type,
731
                                  ignore_secondaries=opts.ignore_secondaries)
732
    jex.QueueJob(name, op)
733
  jex.WaitOrShow(not opts.submit_only)
734
  return 0
735

    
736

    
737
def ShutdownInstance(opts, args):
738
  """Shutdown an instance.
739

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

    
748
  """
749
  cl = GetClient()
750
  if opts.multi_mode is None:
751
    opts.multi_mode = _SHUTDOWN_INSTANCES
752
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
753
  if not inames:
754
    raise errors.OpPrereqError("Selection filter does not match any instances")
755
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
756
  if not (opts.force_multi or not multi_on
757
          or _ConfirmOperation(inames, "shutdown")):
758
    return 1
759

    
760
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
761
  for name in inames:
762
    op = opcodes.OpShutdownInstance(instance_name=name)
763
    jex.QueueJob(name, op)
764
  jex.WaitOrShow(not opts.submit_only)
765
  return 0
766

    
767

    
768
def ReplaceDisks(opts, args):
769
  """Replace the disks of an instance
770

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

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

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

    
807

    
808
def FailoverInstance(opts, args):
809
  """Failover an instance.
810

    
811
  The failover is done by shutting it down on its present node and
812
  starting it on the secondary.
813

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

    
820
  """
821
  cl = GetClient()
822
  instance_name = args[0]
823
  force = opts.force
824

    
825
  if not force:
826
    _EnsureInstancesExist(cl, [instance_name])
827

    
828
    usertext = ("Failover will happen to image %s."
829
                " This requires a shutdown of the instance. Continue?" %
830
                (instance_name,))
831
    if not AskUser(usertext):
832
      return 1
833

    
834
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
835
                                  ignore_consistency=opts.ignore_consistency)
836
  SubmitOrSend(op, opts, cl=cl)
837
  return 0
838

    
839

    
840
def MigrateInstance(opts, args):
841
  """Migrate an instance.
842

    
843
  The migrate is done without shutdown.
844

    
845
  @param opts: the command line options selected by the user
846
  @type args: list
847
  @param args: should contain only one element, the instance name
848
  @rtype: int
849
  @return: the desired exit code
850

    
851
  """
852
  cl = GetClient()
853
  instance_name = args[0]
854
  force = opts.force
855

    
856
  if not force:
857
    _EnsureInstancesExist(cl, [instance_name])
858

    
859
    if opts.cleanup:
860
      usertext = ("Instance %s will be recovered from a failed migration."
861
                  " Note that the migration procedure (including cleanup)" %
862
                  (instance_name,))
863
    else:
864
      usertext = ("Instance %s will be migrated. Note that migration" %
865
                  (instance_name,))
866
    usertext += (" is **experimental** in this version."
867
                " This might impact the instance if anything goes wrong."
868
                " Continue?")
869
    if not AskUser(usertext):
870
      return 1
871

    
872
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
873
                                 cleanup=opts.cleanup)
874
  SubmitOpCode(op, cl=cl)
875
  return 0
876

    
877

    
878
def ConnectToInstanceConsole(opts, args):
879
  """Connect to the console of an instance.
880

    
881
  @param opts: the command line options selected by the user
882
  @type args: list
883
  @param args: should contain only one element, the instance name
884
  @rtype: int
885
  @return: the desired exit code
886

    
887
  """
888
  instance_name = args[0]
889

    
890
  op = opcodes.OpConnectConsole(instance_name=instance_name)
891
  cmd = SubmitOpCode(op)
892

    
893
  if opts.show_command:
894
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
895
  else:
896
    try:
897
      os.execvp(cmd[0], cmd)
898
    finally:
899
      ToStderr("Can't run console command %s with arguments:\n'%s'",
900
               cmd[0], " ".join(cmd))
901
      os._exit(1)
902

    
903

    
904
def _FormatLogicalID(dev_type, logical_id):
905
  """Formats the logical_id of a disk.
906

    
907
  """
908
  if dev_type == constants.LD_DRBD8:
909
    node_a, node_b, port, minor_a, minor_b, key = logical_id
910
    data = [
911
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
912
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
913
      ("port", port),
914
      ("auth key", key),
915
      ]
916
  elif dev_type == constants.LD_LV:
917
    vg_name, lv_name = logical_id
918
    data = ["%s/%s" % (vg_name, lv_name)]
919
  else:
920
    data = [str(logical_id)]
921

    
922
  return data
923

    
924

    
925
def _FormatBlockDevInfo(idx, top_level, dev, static):
926
  """Show block device information.
927

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

    
931
  @type idx: int
932
  @param idx: the index of the current disk
933
  @type top_level: boolean
934
  @param top_level: if this a top-level disk?
935
  @type dev: dict
936
  @param dev: dictionary with disk information
937
  @type static: boolean
938
  @param static: wheter the device information doesn't contain
939
      runtime information but only static data
940
  @return: a list of either strings, tuples or lists
941
      (which should be formatted at a higher indent level)
942

    
943
  """
944
  def helper(dtype, status):
945
    """Format one line for physical device status.
946

    
947
    @type dtype: str
948
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
949
    @type status: tuple
950
    @param status: a tuple as returned from L{backend.FindBlockDevice}
951
    @return: the string representing the status
952

    
953
    """
954
    if not status:
955
      return "not active"
956
    txt = ""
957
    (path, major, minor, syncp, estt, degr, ldisk) = status
958
    if major is None:
959
      major_string = "N/A"
960
    else:
961
      major_string = str(major)
962

    
963
    if minor is None:
964
      minor_string = "N/A"
965
    else:
966
      minor_string = str(minor)
967

    
968
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
969
    if dtype in (constants.LD_DRBD8, ):
970
      if syncp is not None:
971
        sync_text = "*RECOVERING* %5.2f%%," % syncp
972
        if estt:
973
          sync_text += " ETA %ds" % estt
974
        else:
975
          sync_text += " ETA unknown"
976
      else:
977
        sync_text = "in sync"
978
      if degr:
979
        degr_text = "*DEGRADED*"
980
      else:
981
        degr_text = "ok"
982
      if ldisk:
983
        ldisk_text = " *MISSING DISK*"
984
      else:
985
        ldisk_text = ""
986
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
987
    elif dtype == constants.LD_LV:
988
      if ldisk:
989
        ldisk_text = " *FAILED* (failed drive?)"
990
      else:
991
        ldisk_text = ""
992
      txt += ldisk_text
993
    return txt
994

    
995
  # the header
996
  if top_level:
997
    if dev["iv_name"] is not None:
998
      txt = dev["iv_name"]
999
    else:
1000
      txt = "disk %d" % idx
1001
  else:
1002
    txt = "child %d" % idx
1003
  if isinstance(dev["size"], int):
1004
    nice_size = utils.FormatUnit(dev["size"], "h")
1005
  else:
1006
    nice_size = dev["size"]
1007
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1008
  data = []
1009
  if top_level:
1010
    data.append(("access mode", dev["mode"]))
1011
  if dev["logical_id"] is not None:
1012
    try:
1013
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1014
    except ValueError:
1015
      l_id = [str(dev["logical_id"])]
1016
    if len(l_id) == 1:
1017
      data.append(("logical_id", l_id[0]))
1018
    else:
1019
      data.extend(l_id)
1020
  elif dev["physical_id"] is not None:
1021
    data.append("physical_id:")
1022
    data.append([dev["physical_id"]])
1023
  if not static:
1024
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1025
  if dev["sstatus"] and not static:
1026
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1027

    
1028
  if dev["children"]:
1029
    data.append("child devices:")
1030
    for c_idx, child in enumerate(dev["children"]):
1031
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1032
  d1.append(data)
1033
  return d1
1034

    
1035

    
1036
def _FormatList(buf, data, indent_level):
1037
  """Formats a list of data at a given indent level.
1038

    
1039
  If the element of the list is:
1040
    - a string, it is simply formatted as is
1041
    - a tuple, it will be split into key, value and the all the
1042
      values in a list will be aligned all at the same start column
1043
    - a list, will be recursively formatted
1044

    
1045
  @type buf: StringIO
1046
  @param buf: the buffer into which we write the output
1047
  @param data: the list to format
1048
  @type indent_level: int
1049
  @param indent_level: the indent level to format at
1050

    
1051
  """
1052
  max_tlen = max([len(elem[0]) for elem in data
1053
                 if isinstance(elem, tuple)] or [0])
1054
  for elem in data:
1055
    if isinstance(elem, basestring):
1056
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1057
    elif isinstance(elem, tuple):
1058
      key, value = elem
1059
      spacer = "%*s" % (max_tlen - len(key), "")
1060
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1061
    elif isinstance(elem, list):
1062
      _FormatList(buf, elem, indent_level+1)
1063

    
1064
def ShowInstanceConfig(opts, args):
1065
  """Compute instance run-time status.
1066

    
1067
  @param opts: the command line options selected by the user
1068
  @type args: list
1069
  @param args: either an empty list, and then we query all
1070
      instances, or should contain a list of instance names
1071
  @rtype: int
1072
  @return: the desired exit code
1073

    
1074
  """
1075
  if not args and not opts.show_all:
1076
    ToStderr("No instance selected."
1077
             " Please pass in --all if you want to query all instances.\n"
1078
             "Note that this can take a long time on a big cluster.")
1079
    return 1
1080
  elif args and opts.show_all:
1081
    ToStderr("Cannot use --all if you specify instance names.")
1082
    return 1
1083

    
1084
  retcode = 0
1085
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1086
  result = SubmitOpCode(op)
1087
  if not result:
1088
    ToStdout("No instances.")
1089
    return 1
1090

    
1091
  buf = StringIO()
1092
  retcode = 0
1093
  for instance_name in result:
1094
    instance = result[instance_name]
1095
    buf.write("Instance name: %s\n" % instance["name"])
1096
    buf.write("State: configured to be %s" % instance["config_state"])
1097
    if not opts.static:
1098
      buf.write(", actual state is %s" % instance["run_state"])
1099
    buf.write("\n")
1100
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1101
    ##          instance["auto_balance"])
1102
    buf.write("  Nodes:\n")
1103
    buf.write("    - primary: %s\n" % instance["pnode"])
1104
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1105
    buf.write("  Operating system: %s\n" % instance["os"])
1106
    if instance.has_key("network_port"):
1107
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1108
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1109

    
1110
    # custom VNC console information
1111
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1112
                                                 None)
1113
    if vnc_bind_address:
1114
      port = instance["network_port"]
1115
      display = int(port) - constants.VNC_BASE_PORT
1116
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1117
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1118
                                                   port,
1119
                                                   display)
1120
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1121
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1122
                             (vnc_bind_address, port,
1123
                              instance["pnode"], display))
1124
      else:
1125
        # vnc bind address is a file
1126
        vnc_console_port = "%s:%s" % (instance["pnode"],
1127
                                      vnc_bind_address)
1128
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1129

    
1130
    for key in instance["hv_actual"]:
1131
      if key in instance["hv_instance"]:
1132
        val = instance["hv_instance"][key]
1133
      else:
1134
        val = "default (%s)" % instance["hv_actual"][key]
1135
      buf.write("    - %s: %s\n" % (key, val))
1136
    buf.write("  Hardware:\n")
1137
    buf.write("    - VCPUs: %d\n" %
1138
              instance["be_actual"][constants.BE_VCPUS])
1139
    buf.write("    - memory: %dMiB\n" %
1140
              instance["be_actual"][constants.BE_MEMORY])
1141
    buf.write("    - NICs:\n")
1142
    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1143
      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1144
                (idx, mac, ip, bridge))
1145
    buf.write("  Disks:\n")
1146

    
1147
    for idx, device in enumerate(instance["disks"]):
1148
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1149

    
1150
  ToStdout(buf.getvalue().rstrip('\n'))
1151
  return retcode
1152

    
1153

    
1154
def SetInstanceParams(opts, args):
1155
  """Modifies an instance.
1156

    
1157
  All parameters take effect only at the next restart of the instance.
1158

    
1159
  @param opts: the command line options selected by the user
1160
  @type args: list
1161
  @param args: should contain only one element, the instance name
1162
  @rtype: int
1163
  @return: the desired exit code
1164

    
1165
  """
1166
  if not (opts.nics or opts.disks or
1167
          opts.hypervisor or opts.beparams):
1168
    ToStderr("Please give at least one of the parameters.")
1169
    return 1
1170

    
1171
  for param in opts.beparams:
1172
    if isinstance(opts.beparams[param], basestring):
1173
      if opts.beparams[param].lower() == "default":
1174
        opts.beparams[param] = constants.VALUE_DEFAULT
1175

    
1176
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1177
                      allowed_values=[constants.VALUE_DEFAULT])
1178

    
1179
  for param in opts.hypervisor:
1180
    if isinstance(opts.hypervisor[param], basestring):
1181
      if opts.hypervisor[param].lower() == "default":
1182
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1183

    
1184
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1185
                      allowed_values=[constants.VALUE_DEFAULT])
1186

    
1187
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1188
    try:
1189
      nic_op = int(nic_op)
1190
      opts.nics[idx] = (nic_op, nic_dict)
1191
    except ValueError:
1192
      pass
1193

    
1194
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1195
    try:
1196
      disk_op = int(disk_op)
1197
      opts.disks[idx] = (disk_op, disk_dict)
1198
    except ValueError:
1199
      pass
1200
    if disk_op == constants.DDM_ADD:
1201
      if 'size' not in disk_dict:
1202
        raise errors.OpPrereqError("Missing required parameter 'size'")
1203
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1204

    
1205
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1206
                                   nics=opts.nics,
1207
                                   disks=opts.disks,
1208
                                   hvparams=opts.hypervisor,
1209
                                   beparams=opts.beparams,
1210
                                   force=opts.force)
1211

    
1212
  # even if here we process the result, we allow submit only
1213
  result = SubmitOrSend(op, opts)
1214

    
1215
  if result:
1216
    ToStdout("Modified instance %s", args[0])
1217
    for param, data in result:
1218
      ToStdout(" - %-5s -> %s", param, data)
1219
    ToStdout("Please don't forget that these parameters take effect"
1220
             " only at the next start of the instance.")
1221
  return 0
1222

    
1223

    
1224
# options used in more than one cmd
1225
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1226
                       metavar="<node>")
1227

    
1228
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1229
                    metavar="<os>")
1230

    
1231
# multi-instance selection options
1232
m_force_multi = make_option("--force-multiple", dest="force_multi",
1233
                            help="Do not ask for confirmation when more than"
1234
                            " one instance is affected",
1235
                            action="store_true", default=False)
1236

    
1237
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1238
                             help="Filter by nodes (primary only)",
1239
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1240

    
1241
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1242
                             help="Filter by nodes (secondary only)",
1243
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1244

    
1245
m_node_opt = make_option("--node", dest="multi_mode",
1246
                         help="Filter by nodes (primary and secondary)",
1247
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1248

    
1249
m_clust_opt = make_option("--all", dest="multi_mode",
1250
                          help="Select all instances in the cluster",
1251
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1252

    
1253
m_inst_opt = make_option("--instance", dest="multi_mode",
1254
                         help="Filter by instance name [default]",
1255
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1256

    
1257

    
1258
# this is defined separately due to readability only
1259
add_opts = [
1260
  DEBUG_OPT,
1261
  make_option("-n", "--node", dest="node",
1262
              help="Target node and optional secondary node",
1263
              metavar="<pnode>[:<snode>]"),
1264
  os_opt,
1265
  keyval_option("-B", "--backend", dest="beparams",
1266
                type="keyval", default={},
1267
                help="Backend parameters"),
1268
  make_option("-t", "--disk-template", dest="disk_template",
1269
              help="Custom disk setup (diskless, file, plain or drbd)",
1270
              default=None, metavar="TEMPL"),
1271
  cli_option("-s", "--os-size", dest="sd_size", help="Disk size for a"
1272
             " single-disk configuration, when not using the --disk option,"
1273
             " in MiB unless a suffix is used",
1274
             default=None, type="unit", metavar="<size>"),
1275
  ikv_option("--disk", help="Disk information",
1276
             default=[], dest="disks",
1277
             action="append",
1278
             type="identkeyval"),
1279
  ikv_option("--net", help="NIC information",
1280
             default=[], dest="nics",
1281
             action="append",
1282
             type="identkeyval"),
1283
  make_option("--no-nics", default=False, action="store_true",
1284
              help="Do not create any network cards for the instance"),
1285
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1286
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1287
  make_option("--no-start", dest="start", default=True,
1288
              action="store_false", help="Don't start the instance after"
1289
              " creation"),
1290
  make_option("--no-ip-check", dest="ip_check", default=True,
1291
              action="store_false", help="Don't check that the instance's IP"
1292
              " is alive (only valid with --no-start)"),
1293
  make_option("--file-storage-dir", dest="file_storage_dir",
1294
              help="Relative path under default cluster-wide file storage dir"
1295
              " to store file-based disks", default=None,
1296
              metavar="<DIR>"),
1297
  make_option("--file-driver", dest="file_driver", help="Driver to use"
1298
              " for image files", default="loop", metavar="<DRIVER>"),
1299
  make_option("-I", "--iallocator", metavar="<NAME>",
1300
              help="Select nodes for the instance automatically using the"
1301
              " <NAME> iallocator plugin", default=None, type="string"),
1302
  ikv_option("-H", "--hypervisor", dest="hypervisor",
1303
              help="Hypervisor and hypervisor options, in the format"
1304
              " hypervisor:option=value,option=value,...", default=None,
1305
              type="identkeyval"),
1306
  SUBMIT_OPT,
1307
  ]
1308

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

    
1472
  'reboot': (RebootInstance, ARGS_ANY,
1473
              [DEBUG_OPT, m_force_multi,
1474
               make_option("-t", "--type", dest="reboot_type",
1475
                           help="Type of reboot: soft/hard/full",
1476
                           default=constants.INSTANCE_REBOOT_HARD,
1477
                           type="string", metavar="<REBOOT>"),
1478
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1479
                           default=False, action="store_true",
1480
                           help="Ignore errors from secondaries"),
1481
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1482
               m_clust_opt, m_inst_opt,
1483
               SUBMIT_OPT,
1484
               ],
1485
            "<instance>", "Reboots an instance"),
1486
  'activate-disks': (ActivateDisks, ARGS_ONE,
1487
                     [DEBUG_OPT, SUBMIT_OPT,
1488
                      make_option("--ignore-size", dest="ignore_size",
1489
                                  default=False, action="store_true",
1490
                                  help="Ignore current recorded size"
1491
                                  " (useful for forcing activation when"
1492
                                  " the recorded size is wrong)"),
1493
                      ],
1494
                     "<instance>",
1495
                     "Activate an instance's disks"),
1496
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1497
                       "<instance>",
1498
                       "Deactivate an instance's disks"),
1499
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1500
                [DEBUG_OPT, SUBMIT_OPT,
1501
                 make_option("--no-wait-for-sync",
1502
                             dest="wait_for_sync", default=True,
1503
                             action="store_false",
1504
                             help="Don't wait for sync (DANGEROUS!)"),
1505
                 ],
1506
                "<instance> <disk> <size>", "Grow an instance's disk"),
1507
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1508
                "<instance_name>", "List the tags of the given instance"),
1509
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1510
               "<instance_name> tag...", "Add tags to the given instance"),
1511
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1512
                  "<instance_name> tag...", "Remove tags from given instance"),
1513
  }
1514

    
1515
#: dictionary with aliases for commands
1516
aliases = {
1517
  'activate_block_devs': 'activate-disks',
1518
  'replace_disks': 'replace-disks',
1519
  'start': 'startup',
1520
  'stop': 'shutdown',
1521
  }
1522

    
1523
if __name__ == '__main__':
1524
  sys.exit(GenericMain(commands, aliases=aliases,
1525
                       override={"tag_type": constants.TAG_INSTANCE}))