Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ c98162a7

History | View | Annotate | Download (53.5 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
      "be/auto_balance": "Auto_balance",
223
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
224
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
225
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
226
      }
227
  else:
228
    headers = None
229

    
230
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
231
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
232
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
233

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

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

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

    
273
  return 0
274

    
275

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

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

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

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

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

    
295
  if opts.nics:
296
    try:
297
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
298
    except ValueError, err:
299
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
300
    nics = [{}] * nic_max
301
    for nidx, ndict in opts.nics:
302
      nidx = int(nidx)
303
      nics[nidx] = ndict
304
  elif opts.no_nics:
305
    # no nics
306
    nics = []
307
  else:
308
    # default of one nic, all auto
309
    nics = [{}]
310

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

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

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

    
360
  SubmitOrSend(op, opts)
361
  return 0
362

    
363

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
467
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
468
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
469

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

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

    
490
  return 0
491

    
492

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

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

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

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

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

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

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

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

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

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

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

    
544
  return 0
545

    
546

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

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

    
557
  """
558
  instance_name = args[0]
559
  force = opts.force
560
  cl = GetClient()
561

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

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

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

    
576

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

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

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

    
594

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

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

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

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

    
617

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

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

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

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

    
636

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

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

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

    
660

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

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

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

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

    
699

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

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

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

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

    
734

    
735
def ShutdownInstance(opts, args):
736
  """Shutdown an instance.
737

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

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

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

    
765

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

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

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

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

    
805

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

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

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

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

    
823
  if not force:
824
    _EnsureInstancesExist(cl, [instance_name])
825

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

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

    
837

    
838
def MigrateInstance(opts, args):
839
  """Migrate an instance.
840

    
841
  The migrate is done without shutdown.
842

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

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

    
854
  if not force:
855
    _EnsureInstancesExist(cl, [instance_name])
856

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

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

    
875

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

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

    
885
  """
886
  instance_name = args[0]
887

    
888
  op = opcodes.OpConnectConsole(instance_name=instance_name)
889
  cmd = SubmitOpCode(op)
890

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

    
901

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

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

    
920
  return data
921

    
922

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

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

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

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

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

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

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

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

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

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

    
1033

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

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

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

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

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

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

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

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

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

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

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

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

    
1148
  ToStdout(buf.getvalue().rstrip('\n'))
1149
  return retcode
1150

    
1151

    
1152
def SetInstanceParams(opts, args):
1153
  """Modifies an instance.
1154

    
1155
  All parameters take effect only at the next restart of the instance.
1156

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

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

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

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

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

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

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

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

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

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

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

    
1221

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

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

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

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

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

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

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

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

    
1255

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

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

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

    
1506
#: dictionary with aliases for commands
1507
aliases = {
1508
  'activate_block_devs': 'activate-disks',
1509
  'replace_disks': 'replace-disks',
1510
  'start': 'startup',
1511
  'stop': 'shutdown',
1512
  }
1513

    
1514
if __name__ == '__main__':
1515
  sys.exit(GenericMain(commands, aliases=aliases,
1516
                       override={"tag_type": constants.TAG_INSTANCE}))