Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 6633774e

History | View | Annotate | Download (53.3 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os
28
import itertools
29
import simplejson
30
from optparse import make_option
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import cli
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import utils
38
from ganeti import errors
39

    
40

    
41
_SHUTDOWN_CLUSTER = "cluster"
42
_SHUTDOWN_NODES_BOTH = "nodes"
43
_SHUTDOWN_NODES_PRI = "nodes-pri"
44
_SHUTDOWN_NODES_SEC = "nodes-sec"
45
_SHUTDOWN_INSTANCES = "instances"
46

    
47

    
48
_VALUE_TRUE = "true"
49

    
50
#: default list of options for L{ListInstances}
51
_LIST_DEF_FIELDS = [
52
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
53
  ]
54

    
55

    
56
def _ExpandMultiNames(mode, names, 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
                              True)
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 _TransformPath(user_input):
158
  """Transform a user path into a canonical value.
159

    
160
  This function transforms the a path passed as textual information
161
  into the constants that the LU code expects.
162

    
163
  """
164
  if user_input:
165
    if user_input.lower() == "default":
166
      result_path = constants.VALUE_DEFAULT
167
    elif user_input.lower() == "none":
168
      result_path = constants.VALUE_NONE
169
    else:
170
      if not os.path.isabs(user_input):
171
        raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
172
                                   user_input)
173
      result_path = user_input
174
  else:
175
    result_path = constants.VALUE_DEFAULT
176

    
177
  return result_path
178

    
179

    
180
def _EnsureInstancesExist(client, names):
181
  """Check for and ensure the given instance names exist.
182

    
183
  This function will raise an OpPrereqError in case they don't
184
  exist. Otherwise it will exit cleanly.
185

    
186
  @type client: L{luxi.Client}
187
  @param client: the client to use for the query
188
  @type names: list
189
  @param names: the list of instance names to query
190
  @raise errors.OpPrereqError: in case any instance is missing
191

    
192
  """
193
  # TODO: change LUQueryInstances to that it actually returns None
194
  # instead of raising an exception, or devise a better mechanism
195
  result = client.QueryInstances(names, ["name"], False)
196
  for orig_name, row in zip(names, result):
197
    if row[0] is None:
198
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
199

    
200

    
201
def ListInstances(opts, args):
202
  """List instances and their properties.
203

    
204
  @param opts: the command line options selected by the user
205
  @type args: list
206
  @param args: should be an empty list
207
  @rtype: int
208
  @return: the desired exit code
209

    
210
  """
211
  if opts.output is None:
212
    selected_fields = _LIST_DEF_FIELDS
213
  elif opts.output.startswith("+"):
214
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
215
  else:
216
    selected_fields = opts.output.split(",")
217

    
218
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
219

    
220
  if not opts.no_headers:
221
    headers = {
222
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
223
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
224
      "oper_state": "Running",
225
      "oper_ram": "Memory", "disk_template": "Disk_template",
226
      "ip": "IP_address", "mac": "MAC_address",
227
      "bridge": "Bridge",
228
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
229
      "disk_usage": "DiskUsage",
230
      "status": "Status", "tags": "Tags",
231
      "network_port": "Network_port",
232
      "hv/kernel_path": "Kernel_path",
233
      "hv/initrd_path": "Initrd_path",
234
      "hv/boot_order": "HVM_boot_order",
235
      "hv/acpi": "HVM_ACPI",
236
      "hv/pae": "HVM_PAE",
237
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
238
      "hv/nic_type": "HVM_NIC_type",
239
      "hv/disk_type": "HVM_Disk_type",
240
      "hv/vnc_bind_address": "VNC_bind_address",
241
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
242
      "hvparams": "Hypervisor_parameters",
243
      "be/memory": "Configured_memory",
244
      "be/vcpus": "VCPUs",
245
      "be/auto_balance": "Auto_balance",
246
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
247
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
248
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
249
      }
250
  else:
251
    headers = None
252

    
253
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
254
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
255
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
256

    
257
  list_type_fields = ("tags", "disk.sizes",
258
                      "nic.macs", "nic.ips", "nic.bridges")
259
  # change raw values to nicer strings
260
  for row in output:
261
    for idx, field in enumerate(selected_fields):
262
      val = row[idx]
263
      if field == "snodes":
264
        val = ",".join(val) or "-"
265
      elif field == "admin_state":
266
        if val:
267
          val = "yes"
268
        else:
269
          val = "no"
270
      elif field == "oper_state":
271
        if val is None:
272
          val = "(node down)"
273
        elif val: # True
274
          val = "running"
275
        else:
276
          val = "stopped"
277
      elif field == "oper_ram":
278
        if val is None:
279
          val = "(node down)"
280
      elif field == "sda_size" or field == "sdb_size":
281
        if val is None:
282
          val = "N/A"
283
      elif field in list_type_fields:
284
        val = ",".join(str(item) for item in val)
285
      elif val is None:
286
        val = "-"
287
      row[idx] = str(val)
288

    
289
  data = GenerateTable(separator=opts.separator, headers=headers,
290
                       fields=selected_fields, unitfields=unitfields,
291
                       numfields=numfields, data=output, units=opts.units)
292

    
293
  for line in data:
294
    ToStdout(line)
295

    
296
  return 0
297

    
298

    
299
def AddInstance(opts, args):
300
  """Add an instance to the cluster.
301

    
302
  @param opts: the command line options selected by the user
303
  @type args: list
304
  @param args: should contain only one element, the new instance name
305
  @rtype: int
306
  @return: the desired exit code
307

    
308
  """
309
  instance = args[0]
310

    
311
  (pnode, snode) = SplitNodeOption(opts.node)
312

    
313
  hypervisor = None
314
  hvparams = {}
315
  if opts.hypervisor:
316
    hypervisor, hvparams = opts.hypervisor
317

    
318
  if opts.nics:
319
    try:
320
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
321
    except ValueError, err:
322
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
323
    nics = [{}] * nic_max
324
    for nidx, ndict in opts.nics.items():
325
      nidx = int(nidx)
326
      nics[nidx] = ndict
327
  elif opts.no_nics:
328
    # no nics
329
    nics = []
330
  else:
331
    # default of one nic, all auto
332
    nics = [{}]
333

    
334
  if opts.disk_template == constants.DT_DISKLESS:
335
    if opts.disks:
336
      raise errors.OpPrereqError("Diskless instance but disk"
337
                                 " information passed")
338
    disks = []
339
  else:
340
    if not opts.disks:
341
      raise errors.OpPrereqError("No disk information specified")
342
    try:
343
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
344
    except ValueError, err:
345
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
346
    disks = [{}] * disk_max
347
    for didx, ddict in opts.disks:
348
      didx = int(didx)
349
      if "size" not in ddict:
350
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
351
      try:
352
        ddict["size"] = utils.ParseUnit(ddict["size"])
353
      except ValueError, err:
354
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
355
                                   (didx, err))
356
      disks[didx] = ddict
357

    
358
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
359
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
360

    
361
  op = opcodes.OpCreateInstance(instance_name=instance,
362
                                disks=disks,
363
                                disk_template=opts.disk_template,
364
                                nics=nics,
365
                                mode=constants.INSTANCE_CREATE,
366
                                os_type=opts.os, pnode=pnode,
367
                                snode=snode,
368
                                start=opts.start, ip_check=opts.ip_check,
369
                                wait_for_sync=opts.wait_for_sync,
370
                                hypervisor=hypervisor,
371
                                hvparams=hvparams,
372
                                beparams=opts.beparams,
373
                                iallocator=opts.iallocator,
374
                                file_storage_dir=opts.file_storage_dir,
375
                                file_driver=opts.file_driver,
376
                                )
377

    
378
  SubmitOrSend(op, opts)
379
  return 0
380

    
381

    
382
def BatchCreate(opts, args):
383
  """Create instances using a definition file.
384

    
385
  This function reads a json file with instances defined
386
  in the form::
387

    
388
    {"instance-name":{
389
      "disk_size": [20480],
390
      "template": "drbd",
391
      "backend": {
392
        "memory": 512,
393
        "vcpus": 1 },
394
      "os": "debootstrap",
395
      "primary_node": "firstnode",
396
      "secondary_node": "secondnode",
397
      "iallocator": "dumb"}
398
    }
399

    
400
  Note that I{primary_node} and I{secondary_node} have precedence over
401
  I{iallocator}.
402

    
403
  @param opts: the command line options selected by the user
404
  @type args: list
405
  @param args: should contain one element, the json filename
406
  @rtype: int
407
  @return: the desired exit code
408

    
409
  """
410
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
411
                    "backend": {},
412
                    "iallocator": None,
413
                    "primary_node": None,
414
                    "secondary_node": None,
415
                    "ip": 'none',
416
                    "mac": 'auto',
417
                    "bridge": None,
418
                    "start": True,
419
                    "ip_check": True,
420
                    "hypervisor": None,
421
                    "file_storage_dir": None,
422
                    "file_driver": 'loop'}
423

    
424
  def _PopulateWithDefaults(spec):
425
    """Returns a new hash combined with default values."""
426
    mydict = _DEFAULT_SPECS.copy()
427
    mydict.update(spec)
428
    return mydict
429

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

    
447
    if (spec['hypervisor'] and
448
        not isinstance(spec['hypervisor'], dict)):
449
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
450

    
451
  json_filename = args[0]
452
  fd = open(json_filename, 'r')
453
  try:
454
    instance_data = simplejson.load(fd)
455
  finally:
456
    fd.close()
457

    
458
  # Iterate over the instances and do:
459
  #  * Populate the specs with default value
460
  #  * Validate the instance specs
461
  i_names = utils.NiceSort(instance_data.keys())
462
  for name in i_names:
463
    specs = instance_data[name]
464
    specs = _PopulateWithDefaults(specs)
465
    _Validate(specs)
466

    
467
    hypervisor = None
468
    hvparams = {}
469
    if specs['hypervisor']:
470
      hypervisor, hvparams = specs['hypervisor'].iteritems()
471

    
472
    disks = []
473
    for elem in specs['disk_size']:
474
      try:
475
        size = utils.ParseUnit(elem)
476
      except ValueError, err:
477
        raise errors.OpPrereqError("Invalid disk size '%s' for"
478
                                   " instance %s: %s" %
479
                                   (elem, name, err))
480
      disks.append({"size": size})
481

    
482
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
483

    
484
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
485
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
486

    
487
    op = opcodes.OpCreateInstance(instance_name=name,
488
                                  disks=disks,
489
                                  disk_template=specs['template'],
490
                                  mode=constants.INSTANCE_CREATE,
491
                                  os_type=specs['os'],
492
                                  pnode=specs['primary_node'],
493
                                  snode=specs['secondary_node'],
494
                                  nics=[nic0],
495
                                  start=specs['start'],
496
                                  ip_check=specs['ip_check'],
497
                                  wait_for_sync=True,
498
                                  iallocator=specs['iallocator'],
499
                                  hypervisor=hypervisor,
500
                                  hvparams=hvparams,
501
                                  beparams=specs['backend'],
502
                                  file_storage_dir=specs['file_storage_dir'],
503
                                  file_driver=specs['file_driver'])
504

    
505
    ToStdout("%s: %s", name, cli.SendJob([op]))
506

    
507
  return 0
508

    
509

    
510
def ReinstallInstance(opts, args):
511
  """Reinstall an instance.
512

    
513
  @param opts: the command line options selected by the user
514
  @type args: list
515
  @param args: should contain only one element, the name of the
516
      instance to be reinstalled
517
  @rtype: int
518
  @return: the desired exit code
519

    
520
  """
521
  instance_name = args[0]
522

    
523
  if opts.select_os is True:
524
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
525
    result = SubmitOpCode(op)
526

    
527
    if not result:
528
      ToStdout("Can't get the OS list")
529
      return 1
530

    
531
    ToStdout("Available OS templates:")
532
    number = 0
533
    choices = []
534
    for entry in result:
535
      ToStdout("%3s: %s", number, entry[0])
536
      choices.append(("%s" % number, entry[0], entry[0]))
537
      number = number + 1
538

    
539
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
540
    selected = AskUser("Enter OS template name or number (or x to abort):",
541
                       choices)
542

    
543
    if selected == 'exit':
544
      ToStdout("User aborted reinstall, exiting")
545
      return 1
546

    
547
    os_name = selected
548
  else:
549
    os_name = opts.os
550

    
551
  if not opts.force:
552
    usertext = ("This will reinstall the instance %s and remove"
553
                " all data. Continue?") % instance_name
554
    if not AskUser(usertext):
555
      return 1
556

    
557
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
558
                                   os_type=os_name)
559
  SubmitOrSend(op, opts)
560

    
561
  return 0
562

    
563

    
564
def RemoveInstance(opts, args):
565
  """Remove an instance.
566

    
567
  @param opts: the command line options selected by the user
568
  @type args: list
569
  @param args: should contain only one element, the name of
570
      the instance to be removed
571
  @rtype: int
572
  @return: the desired exit code
573

    
574
  """
575
  instance_name = args[0]
576
  force = opts.force
577
  cl = GetClient()
578

    
579
  if not force:
580
    _EnsureInstancesExist(cl, [instance_name])
581

    
582
    usertext = ("This will remove the volumes of the instance %s"
583
                " (including mirrors), thus removing all the data"
584
                " of the instance. Continue?") % instance_name
585
    if not AskUser(usertext):
586
      return 1
587

    
588
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
589
                                ignore_failures=opts.ignore_failures)
590
  SubmitOrSend(op, opts, cl=cl)
591
  return 0
592

    
593

    
594
def RenameInstance(opts, args):
595
  """Rename an instance.
596

    
597
  @param opts: the command line options selected by the user
598
  @type args: list
599
  @param args: should contain two elements, the old and the
600
      new instance names
601
  @rtype: int
602
  @return: the desired exit code
603

    
604
  """
605
  op = opcodes.OpRenameInstance(instance_name=args[0],
606
                                new_name=args[1],
607
                                ignore_ip=opts.ignore_ip)
608
  SubmitOrSend(op, opts)
609
  return 0
610

    
611

    
612
def ActivateDisks(opts, args):
613
  """Activate an instance's disks.
614

    
615
  This serves two purposes:
616
    - it allows (as long as the instance is not running)
617
      mounting the disks and modifying them from the node
618
    - it repairs inactive secondary drbds
619

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

    
626
  """
627
  instance_name = args[0]
628
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
629
  disks_info = SubmitOrSend(op, opts)
630
  for host, iname, nname in disks_info:
631
    ToStdout("%s:%s:%s", host, iname, nname)
632
  return 0
633

    
634

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

    
638
  This function takes the instance name, looks for its primary node
639
  and the tries to shutdown its block devices on that node.
640

    
641
  @param opts: the command line options selected by the user
642
  @type args: list
643
  @param args: should contain only one element, the instance name
644
  @rtype: int
645
  @return: the desired exit code
646

    
647
  """
648
  instance_name = args[0]
649
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
650
  SubmitOrSend(op, opts)
651
  return 0
652

    
653

    
654
def GrowDisk(opts, args):
655
  """Grow an instance's disks.
656

    
657
  @param opts: the command line options selected by the user
658
  @type args: list
659
  @param args: should contain two elements, the instance name
660
      whose disks we grow and the disk name, e.g. I{sda}
661
  @rtype: int
662
  @return: the desired exit code
663

    
664
  """
665
  instance = args[0]
666
  disk = args[1]
667
  try:
668
    disk = int(disk)
669
  except ValueError, err:
670
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
671
  amount = utils.ParseUnit(args[2])
672
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
673
                          wait_for_sync=opts.wait_for_sync)
674
  SubmitOrSend(op, opts)
675
  return 0
676

    
677

    
678
def StartupInstance(opts, args):
679
  """Startup instances.
680

    
681
  Depending on the options given, this will start one or more
682
  instances.
683

    
684
  @param opts: the command line options selected by the user
685
  @type args: list
686
  @param args: the instance or node names based on which we
687
      create the final selection (in conjunction with the
688
      opts argument)
689
  @rtype: int
690
  @return: the desired exit code
691

    
692
  """
693
  cl = GetClient()
694
  if opts.multi_mode is None:
695
    opts.multi_mode = _SHUTDOWN_INSTANCES
696
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
697
  if not inames:
698
    raise errors.OpPrereqError("Selection filter does not match any instances")
699
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
700
  if not (opts.force_multi or not multi_on
701
          or _ConfirmOperation(inames, "startup")):
702
    return 1
703
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
704
  for name in inames:
705
    op = opcodes.OpStartupInstance(instance_name=name,
706
                                   force=opts.force,
707
                                   extra_args=opts.extra_args)
708
    jex.QueueJob(name, op)
709
  jex.WaitOrShow(not opts.submit_only)
710
  return 0
711

    
712

    
713
def RebootInstance(opts, args):
714
  """Reboot instance(s).
715

    
716
  Depending on the parameters given, this will reboot one or more
717
  instances.
718

    
719
  @param opts: the command line options selected by the user
720
  @type args: list
721
  @param args: the instance or node names based on which we
722
      create the final selection (in conjunction with the
723
      opts argument)
724
  @rtype: int
725
  @return: the desired exit code
726

    
727
  """
728
  cl = GetClient()
729
  if opts.multi_mode is None:
730
    opts.multi_mode = _SHUTDOWN_INSTANCES
731
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
732
  if not inames:
733
    raise errors.OpPrereqError("Selection filter does not match any instances")
734
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
735
  if not (opts.force_multi or not multi_on
736
          or _ConfirmOperation(inames, "reboot")):
737
    return 1
738
  jex = JobExecutor(verbose=multi_on, cl=cl)
739
  for name in inames:
740
    op = opcodes.OpRebootInstance(instance_name=name,
741
                                  reboot_type=opts.reboot_type,
742
                                  ignore_secondaries=opts.ignore_secondaries)
743
    jex.QueueJob(name, op)
744
  jex.WaitOrShow(not opts.submit_only)
745
  return 0
746

    
747

    
748
def ShutdownInstance(opts, args):
749
  """Shutdown an instance.
750

    
751
  @param opts: the command line options selected by the user
752
  @type args: list
753
  @param args: the instance or node names based on which we
754
      create the final selection (in conjunction with the
755
      opts argument)
756
  @rtype: int
757
  @return: the desired exit code
758

    
759
  """
760
  cl = GetClient()
761
  if opts.multi_mode is None:
762
    opts.multi_mode = _SHUTDOWN_INSTANCES
763
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
764
  if not inames:
765
    raise errors.OpPrereqError("Selection filter does not match any instances")
766
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
767
  if not (opts.force_multi or not multi_on
768
          or _ConfirmOperation(inames, "shutdown")):
769
    return 1
770

    
771
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
772
  for name in inames:
773
    op = opcodes.OpShutdownInstance(instance_name=name)
774
    jex.QueueJob(name, op)
775
  jex.WaitOrShow(not opts.submit_only)
776
  return 0
777

    
778

    
779
def ReplaceDisks(opts, args):
780
  """Replace the disks of an instance
781

    
782
  @param opts: the command line options selected by the user
783
  @type args: list
784
  @param args: should contain only one element, the instance name
785
  @rtype: int
786
  @return: the desired exit code
787

    
788
  """
789
  instance_name = args[0]
790
  new_2ndary = opts.new_secondary
791
  iallocator = opts.iallocator
792
  if opts.disks is None:
793
    disks = []
794
  else:
795
    try:
796
      disks = [int(i) for i in opts.disks.split(",")]
797
    except ValueError, err:
798
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
799
  cnt = [opts.on_primary, opts.on_secondary,
800
         new_2ndary is not None, iallocator is not None].count(True)
801
  if cnt != 1:
802
    raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
803
                               " options must be passed")
804
  elif opts.on_primary:
805
    mode = constants.REPLACE_DISK_PRI
806
  elif opts.on_secondary:
807
    mode = constants.REPLACE_DISK_SEC
808
  elif new_2ndary is not None or iallocator is not None:
809
    # replace secondary
810
    mode = constants.REPLACE_DISK_CHG
811

    
812
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
813
                              remote_node=new_2ndary, mode=mode,
814
                              iallocator=iallocator)
815
  SubmitOrSend(op, opts)
816
  return 0
817

    
818

    
819
def FailoverInstance(opts, args):
820
  """Failover an instance.
821

    
822
  The failover is done by shutting it down on its present node and
823
  starting it on the secondary.
824

    
825
  @param opts: the command line options selected by the user
826
  @type args: list
827
  @param args: should contain only one element, the instance name
828
  @rtype: int
829
  @return: the desired exit code
830

    
831
  """
832
  cl = GetClient()
833
  instance_name = args[0]
834
  force = opts.force
835

    
836
  if not force:
837
    _EnsureInstancesExist(cl, [instance_name])
838

    
839
    usertext = ("Failover will happen to image %s."
840
                " This requires a shutdown of the instance. Continue?" %
841
                (instance_name,))
842
    if not AskUser(usertext):
843
      return 1
844

    
845
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
846
                                  ignore_consistency=opts.ignore_consistency)
847
  SubmitOrSend(op, opts, cl=cl)
848
  return 0
849

    
850

    
851
def MigrateInstance(opts, args):
852
  """Migrate an instance.
853

    
854
  The migrate is done without shutdown.
855

    
856
  @param opts: the command line options selected by the user
857
  @type args: list
858
  @param args: should contain only one element, the instance name
859
  @rtype: int
860
  @return: the desired exit code
861

    
862
  """
863
  cl = GetClient()
864
  instance_name = args[0]
865
  force = opts.force
866

    
867
  if not force:
868
    _EnsureInstancesExist(cl, [instance_name])
869

    
870
    if opts.cleanup:
871
      usertext = ("Instance %s will be recovered from a failed migration."
872
                  " Note that the migration procedure (including cleanup)" %
873
                  (instance_name,))
874
    else:
875
      usertext = ("Instance %s will be migrated. Note that migration" %
876
                  (instance_name,))
877
    usertext += (" is **experimental** in this version."
878
                " This might impact the instance if anything goes wrong."
879
                " Continue?")
880
    if not AskUser(usertext):
881
      return 1
882

    
883
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
884
                                 cleanup=opts.cleanup)
885
  SubmitOpCode(op, cl=cl)
886
  return 0
887

    
888

    
889
def ConnectToInstanceConsole(opts, args):
890
  """Connect to the console of an instance.
891

    
892
  @param opts: the command line options selected by the user
893
  @type args: list
894
  @param args: should contain only one element, the instance name
895
  @rtype: int
896
  @return: the desired exit code
897

    
898
  """
899
  instance_name = args[0]
900

    
901
  op = opcodes.OpConnectConsole(instance_name=instance_name)
902
  cmd = SubmitOpCode(op)
903

    
904
  if opts.show_command:
905
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
906
  else:
907
    try:
908
      os.execvp(cmd[0], cmd)
909
    finally:
910
      ToStderr("Can't run console command %s with arguments:\n'%s'",
911
               cmd[0], " ".join(cmd))
912
      os._exit(1)
913

    
914

    
915
def _FormatLogicalID(dev_type, logical_id):
916
  """Formats the logical_id of a disk.
917

    
918
  """
919
  if dev_type == constants.LD_DRBD8:
920
    node_a, node_b, port, minor_a, minor_b, key = logical_id
921
    data = [
922
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
923
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
924
      ("port", port),
925
      ("auth key", key),
926
      ]
927
  elif dev_type == constants.LD_LV:
928
    vg_name, lv_name = logical_id
929
    data = ["%s/%s" % (vg_name, lv_name)]
930
  else:
931
    data = [str(logical_id)]
932

    
933
  return data
934

    
935

    
936
def _FormatBlockDevInfo(idx, top_level, dev, static):
937
  """Show block device information.
938

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

    
942
  @type idx: int
943
  @param idx: the index of the current disk
944
  @type top_level: boolean
945
  @param top_level: if this a top-level disk?
946
  @type dev: dict
947
  @param dev: dictionary with disk information
948
  @type static: boolean
949
  @param static: wheter the device information doesn't contain
950
      runtime information but only static data
951
  @return: a list of either strings, tuples or lists
952
      (which should be formatted at a higher indent level)
953

    
954
  """
955
  def helper(dtype, status):
956
    """Format one line for physical device status.
957

    
958
    @type dtype: str
959
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
960
    @type status: tuple
961
    @param status: a tuple as returned from L{backend.FindBlockDevice}
962
    @return: the string representing the status
963

    
964
    """
965
    if not status:
966
      return "not active"
967
    txt = ""
968
    (path, major, minor, syncp, estt, degr, ldisk) = status
969
    if major is None:
970
      major_string = "N/A"
971
    else:
972
      major_string = str(major)
973

    
974
    if minor is None:
975
      minor_string = "N/A"
976
    else:
977
      minor_string = str(minor)
978

    
979
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
980
    if dtype in (constants.LD_DRBD8, ):
981
      if syncp is not None:
982
        sync_text = "*RECOVERING* %5.2f%%," % syncp
983
        if estt:
984
          sync_text += " ETA %ds" % estt
985
        else:
986
          sync_text += " ETA unknown"
987
      else:
988
        sync_text = "in sync"
989
      if degr:
990
        degr_text = "*DEGRADED*"
991
      else:
992
        degr_text = "ok"
993
      if ldisk:
994
        ldisk_text = " *MISSING DISK*"
995
      else:
996
        ldisk_text = ""
997
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
998
    elif dtype == constants.LD_LV:
999
      if ldisk:
1000
        ldisk_text = " *FAILED* (failed drive?)"
1001
      else:
1002
        ldisk_text = ""
1003
      txt += ldisk_text
1004
    return txt
1005

    
1006
  # the header
1007
  if top_level:
1008
    if dev["iv_name"] is not None:
1009
      txt = dev["iv_name"]
1010
    else:
1011
      txt = "disk %d" % idx
1012
  else:
1013
    txt = "child %d" % idx
1014
  d1 = ["- %s: %s" % (txt, dev["dev_type"])]
1015
  data = []
1016
  if top_level:
1017
    data.append(("access mode", dev["mode"]))
1018
  if dev["logical_id"] is not None:
1019
    try:
1020
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1021
    except ValueError:
1022
      l_id = [str(dev["logical_id"])]
1023
    if len(l_id) == 1:
1024
      data.append(("logical_id", l_id[0]))
1025
    else:
1026
      data.extend(l_id)
1027
  elif dev["physical_id"] is not None:
1028
    data.append("physical_id:")
1029
    data.append([dev["physical_id"]])
1030
  if not static:
1031
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1032
  if dev["sstatus"] and not static:
1033
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1034

    
1035
  if dev["children"]:
1036
    data.append("child devices:")
1037
    for c_idx, child in enumerate(dev["children"]):
1038
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1039
  d1.append(data)
1040
  return d1
1041

    
1042

    
1043
def _FormatList(buf, data, indent_level):
1044
  """Formats a list of data at a given indent level.
1045

    
1046
  If the element of the list is:
1047
    - a string, it is simply formatted as is
1048
    - a tuple, it will be split into key, value and the all the
1049
      values in a list will be aligned all at the same start column
1050
    - a list, will be recursively formatted
1051

    
1052
  @type buf: StringIO
1053
  @param buf: the buffer into which we write the output
1054
  @param data: the list to format
1055
  @type indent_level: int
1056
  @param indent_level: the indent level to format at
1057

    
1058
  """
1059
  max_tlen = max([len(elem[0]) for elem in data
1060
                 if isinstance(elem, tuple)] or [0])
1061
  for elem in data:
1062
    if isinstance(elem, basestring):
1063
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1064
    elif isinstance(elem, tuple):
1065
      key, value = elem
1066
      spacer = "%*s" % (max_tlen - len(key), "")
1067
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1068
    elif isinstance(elem, list):
1069
      _FormatList(buf, elem, indent_level+1)
1070

    
1071
def ShowInstanceConfig(opts, args):
1072
  """Compute instance run-time status.
1073

    
1074
  @param opts: the command line options selected by the user
1075
  @type args: list
1076
  @param args: either an empty list, and then we query all
1077
      instances, or should contain a list of instance names
1078
  @rtype: int
1079
  @return: the desired exit code
1080

    
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
    if instance["hypervisor"] == constants.HT_XEN_PVM:
1108
      hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
1109
                 (constants.HV_INITRD_PATH, "initrd path"))
1110
    elif instance["hypervisor"] == constants.HT_XEN_HVM:
1111
      hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
1112
                 (constants.HV_ACPI, "ACPI"),
1113
                 (constants.HV_PAE, "PAE"),
1114
                 (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
1115
                 (constants.HV_NIC_TYPE, "NIC type"),
1116
                 (constants.HV_DISK_TYPE, "Disk type"),
1117
                 (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
1118
                 )
1119
      # custom console information for HVM
1120
      vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
1121
      if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1122
        vnc_console_port = "%s:%s" % (instance["pnode"],
1123
                                      instance["network_port"])
1124
      elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
1125
        vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
1126
                                                 instance["network_port"],
1127
                                                 instance["pnode"])
1128
      else:
1129
        vnc_console_port = "%s:%s" % (vnc_bind_address,
1130
                                      instance["network_port"])
1131
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1132

    
1133
    else:
1134
      # auto-handle other hypervisor types
1135
      hvattrs = [(key, key) for key in instance["hv_actual"]]
1136

    
1137
    for key, desc in hvattrs:
1138
      if key in instance["hv_instance"]:
1139
        val = instance["hv_instance"][key]
1140
      else:
1141
        val = "default (%s)" % instance["hv_actual"][key]
1142
      buf.write("    - %s: %s\n" % (desc, val))
1143
    buf.write("  Hardware:\n")
1144
    buf.write("    - VCPUs: %d\n" %
1145
              instance["be_actual"][constants.BE_VCPUS])
1146
    buf.write("    - memory: %dMiB\n" %
1147
              instance["be_actual"][constants.BE_MEMORY])
1148
    buf.write("    - NICs:\n")
1149
    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1150
      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1151
                (idx, mac, ip, bridge))
1152
    buf.write("  Disks:\n")
1153

    
1154
    for idx, device in enumerate(instance["disks"]):
1155
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1156

    
1157
  ToStdout(buf.getvalue().rstrip('\n'))
1158
  return retcode
1159

    
1160

    
1161
def SetInstanceParams(opts, args):
1162
  """Modifies an instance.
1163

    
1164
  All parameters take effect only at the next restart of the instance.
1165

    
1166
  @param opts: the command line options selected by the user
1167
  @type args: list
1168
  @param args: should contain only one element, the instance name
1169
  @rtype: int
1170
  @return: the desired exit code
1171

    
1172
  """
1173
  if not (opts.nics or opts.disks or
1174
          opts.hypervisor or opts.beparams):
1175
    ToStderr("Please give at least one of the parameters.")
1176
    return 1
1177

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

    
1183
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1184
                      allowed_values=[constants.VALUE_DEFAULT])
1185

    
1186
  for param in opts.hypervisor:
1187
    if isinstance(opts.hypervisor[param], basestring):
1188
      if opts.hypervisor[param].lower() == "default":
1189
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1190

    
1191
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1192
                      allowed_values=[constants.VALUE_DEFAULT])
1193

    
1194
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1195
    try:
1196
      nic_op = int(nic_op)
1197
      opts.nics[idx] = (nic_op, nic_dict)
1198
    except ValueError:
1199
      pass
1200

    
1201
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1202
    try:
1203
      disk_op = int(disk_op)
1204
      opts.disks[idx] = (disk_op, disk_dict)
1205
    except ValueError:
1206
      pass
1207
    if disk_op == constants.DDM_ADD:
1208
      if 'size' not in disk_dict:
1209
        raise errors.OpPrereqError("Missing required parameter 'size'")
1210
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1211

    
1212
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1213
                                   nics=opts.nics,
1214
                                   disks=opts.disks,
1215
                                   hvparams=opts.hypervisor,
1216
                                   beparams=opts.beparams,
1217
                                   force=opts.force)
1218

    
1219
  # even if here we process the result, we allow submit only
1220
  result = SubmitOrSend(op, opts)
1221

    
1222
  if result:
1223
    ToStdout("Modified instance %s", args[0])
1224
    for param, data in result:
1225
      ToStdout(" - %-5s -> %s", param, data)
1226
    ToStdout("Please don't forget that these parameters take effect"
1227
             " only at the next start of the instance.")
1228
  return 0
1229

    
1230

    
1231
# options used in more than one cmd
1232
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1233
                       metavar="<node>")
1234

    
1235
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1236
                    metavar="<os>")
1237

    
1238
# multi-instance selection options
1239
m_force_multi = make_option("--force-multiple", dest="force_multi",
1240
                            help="Do not ask for confirmation when more than"
1241
                            " one instance is affected",
1242
                            action="store_true", default=False)
1243

    
1244
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1245
                             help="Filter by nodes (primary only)",
1246
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1247

    
1248
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1249
                             help="Filter by nodes (secondary only)",
1250
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1251

    
1252
m_node_opt = make_option("--node", dest="multi_mode",
1253
                         help="Filter by nodes (primary and secondary)",
1254
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1255

    
1256
m_clust_opt = make_option("--all", dest="multi_mode",
1257
                          help="Select all instances in the cluster",
1258
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1259

    
1260
m_inst_opt = make_option("--instance", dest="multi_mode",
1261
                         help="Filter by instance name [default]",
1262
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1263

    
1264

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

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

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

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

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