Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 633b36db

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
                    "hvparams": {},
422
                    "file_storage_dir": None,
423
                    "file_driver": 'loop'}
424

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

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

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

    
452
  json_filename = args[0]
453
  try:
454
    fd = open(json_filename, 'r')
455
    instance_data = simplejson.load(fd)
456
    fd.close()
457
  except Exception, err:
458
    ToStderr("Can't parse the instance definition file: %s" % str(err))
459
    return 1
460

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

    
470
    hypervisor = specs['hypervisor']
471
    hvparams = specs['hvparams']
472

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

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

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

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

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

    
508
  return 0
509

    
510

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

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

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

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

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

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

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

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

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

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

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

    
562
  return 0
563

    
564

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

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

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

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

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

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

    
594

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

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

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

    
612

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

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

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

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

    
635

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

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

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

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

    
654

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

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

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

    
678

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

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

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

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

    
713

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

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

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

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

    
748

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

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

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

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

    
779

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

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

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

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

    
819

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

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

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

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

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

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

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

    
851

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

    
855
  The migrate is done without shutdown.
856

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

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

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

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

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

    
889

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

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

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

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

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

    
915

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

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

    
934
  return data
935

    
936

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

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

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

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

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

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

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

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

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

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

    
1043

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

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

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

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

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

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

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

    
1090
  buf = StringIO()
1091
  retcode = 0
1092
  for instance_name in result:
1093
    instance = result[instance_name]
1094
    buf.write("Instance name: %s\n" % instance["name"])
1095
    buf.write("State: configured to be %s" % instance["config_state"])
1096
    if not opts.static:
1097
      buf.write(", actual state is %s" % instance["run_state"])
1098
    buf.write("\n")
1099
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1100
    ##          instance["auto_balance"])
1101
    buf.write("  Nodes:\n")
1102
    buf.write("    - primary: %s\n" % instance["pnode"])
1103
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1104
    buf.write("  Operating system: %s\n" % instance["os"])
1105
    if instance.has_key("network_port"):
1106
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1107
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1108
    if instance["hypervisor"] == constants.HT_XEN_PVM:
1109
      hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
1110
                 (constants.HV_INITRD_PATH, "initrd path"))
1111
    elif instance["hypervisor"] == constants.HT_XEN_HVM:
1112
      hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
1113
                 (constants.HV_ACPI, "ACPI"),
1114
                 (constants.HV_PAE, "PAE"),
1115
                 (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
1116
                 (constants.HV_NIC_TYPE, "NIC type"),
1117
                 (constants.HV_DISK_TYPE, "Disk type"),
1118
                 (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
1119
                 )
1120
      # custom console information for HVM
1121
      vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
1122
      if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1123
        vnc_console_port = "%s:%s" % (instance["pnode"],
1124
                                      instance["network_port"])
1125
      elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
1126
        vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
1127
                                                 instance["network_port"],
1128
                                                 instance["pnode"])
1129
      else:
1130
        vnc_console_port = "%s:%s" % (vnc_bind_address,
1131
                                      instance["network_port"])
1132
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1133

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

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

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

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

    
1161

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1231

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

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

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

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

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

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

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

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

    
1265

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

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

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

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

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