Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 5ffaa51d

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"])
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
    ipri = [row[1] for row in ndata]
94
    pri_names = list(itertools.chain(*ipri))
95
    isec = [row[2] for row in ndata]
96
    sec_names = list(itertools.chain(*isec))
97
    if mode == _SHUTDOWN_NODES_BOTH:
98
      inames = pri_names + sec_names
99
    elif mode == _SHUTDOWN_NODES_PRI:
100
      inames = pri_names
101
    elif mode == _SHUTDOWN_NODES_SEC:
102
      inames = sec_names
103
    else:
104
      raise errors.ProgrammerError("Unhandled shutdown type")
105

    
106
  elif mode == _SHUTDOWN_INSTANCES:
107
    if not names:
108
      raise errors.OpPrereqError("No instance names passed")
109
    idata = client.QueryInstances(names, ["name"])
110
    inames = [row[0] for row in idata]
111

    
112
  else:
113
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
114

    
115
  return inames
116

    
117

    
118
def _ConfirmOperation(inames, text):
119
  """Ask the user to confirm an operation on a list of instances.
120

    
121
  This function is used to request confirmation for doing an operation
122
  on a given list of instances.
123

    
124
  @type inames: list
125
  @param inames: the list of names that we display when
126
      we ask for confirmation
127
  @type text: str
128
  @param text: the operation that the user should confirm
129
      (e.g. I{shutdown} or I{startup})
130
  @rtype: boolean
131
  @return: True or False depending on user's confirmation.
132

    
133
  """
134
  count = len(inames)
135
  msg = ("The %s will operate on %d instances.\n"
136
         "Do you want to continue?" % (text, count))
137
  affected = ("\nAffected instances:\n" +
138
              "\n".join(["  %s" % name for name in inames]))
139

    
140
  choices = [('y', True, 'Yes, execute the %s' % text),
141
             ('n', False, 'No, abort the %s' % text)]
142

    
143
  if count > 20:
144
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
145
    ask = msg
146
  else:
147
    ask = msg + affected
148

    
149
  choice = AskUser(ask, choices)
150
  if choice == 'v':
151
    choices.pop(1)
152
    choice = AskUser(msg + affected, choices)
153
  return choice
154

    
155

    
156
def _TransformPath(user_input):
157
  """Transform a user path into a canonical value.
158

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

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

    
176
  return result_path
177

    
178

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

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

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

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

    
199

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

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

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

    
217
  output = GetClient().QueryInstances(args, selected_fields)
218

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

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

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

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

    
291
  for line in data:
292
    ToStdout(line)
293

    
294
  return 0
295

    
296

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

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

    
306
  """
307
  instance = args[0]
308

    
309
  (pnode, snode) = SplitNodeOption(opts.node)
310

    
311
  hypervisor = None
312
  hvparams = {}
313
  if opts.hypervisor:
314
    hypervisor, hvparams = opts.hypervisor
315

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

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

    
356
  ValidateBeParams(opts.beparams)
357

    
358
##  kernel_path = _TransformPath(opts.kernel_path)
359
##  initrd_path = _TransformPath(opts.initrd_path)
360

    
361
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
362
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
363

    
364
##  if ((opts.hvm_cdrom_image_path is not None) and
365
##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
366
##    hvm_cdrom_image_path = None
367
##  else:
368
##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
369

    
370
  op = opcodes.OpCreateInstance(instance_name=instance,
371
                                disks=disks,
372
                                disk_template=opts.disk_template,
373
                                nics=nics,
374
                                mode=constants.INSTANCE_CREATE,
375
                                os_type=opts.os, pnode=pnode,
376
                                snode=snode,
377
                                start=opts.start, ip_check=opts.ip_check,
378
                                wait_for_sync=opts.wait_for_sync,
379
                                hypervisor=hypervisor,
380
                                hvparams=hvparams,
381
                                beparams=opts.beparams,
382
                                iallocator=opts.iallocator,
383
                                file_storage_dir=opts.file_storage_dir,
384
                                file_driver=opts.file_driver,
385
                                )
386

    
387
  SubmitOrSend(op, opts)
388
  return 0
389

    
390

    
391
def BatchCreate(opts, args):
392
  """Create instances using a definition file.
393

    
394
  This function reads a json file with instances defined
395
  in the form::
396

    
397
    {"instance-name":{
398
      "disk_size": [20480],
399
      "template": "drbd",
400
      "backend": {
401
        "memory": 512,
402
        "vcpus": 1 },
403
      "os": "debootstrap",
404
      "primary_node": "firstnode",
405
      "secondary_node": "secondnode",
406
      "iallocator": "dumb"}
407
    }
408

    
409
  Note that I{primary_node} and I{secondary_node} have precedence over
410
  I{iallocator}.
411

    
412
  @param opts: the command line options selected by the user
413
  @type args: list
414
  @param args: should contain one element, the json filename
415
  @rtype: int
416
  @return: the desired exit code
417

    
418
  """
419
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
420
                    "backend": {},
421
                    "iallocator": None,
422
                    "primary_node": None,
423
                    "secondary_node": None,
424
                    "ip": 'none',
425
                    "mac": 'auto',
426
                    "bridge": None,
427
                    "start": True,
428
                    "ip_check": True,
429
                    "hypervisor": None,
430
                    "file_storage_dir": None,
431
                    "file_driver": 'loop'}
432

    
433
  def _PopulateWithDefaults(spec):
434
    """Returns a new hash combined with default values."""
435
    mydict = _DEFAULT_SPECS.copy()
436
    mydict.update(spec)
437
    return mydict
438

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

    
456
    if (spec['hypervisor'] and
457
        not isinstance(spec['hypervisor'], dict)):
458
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
459

    
460
  json_filename = args[0]
461
  fd = open(json_filename, 'r')
462
  try:
463
    instance_data = simplejson.load(fd)
464
  finally:
465
    fd.close()
466

    
467
  # Iterate over the instances and do:
468
  #  * Populate the specs with default value
469
  #  * Validate the instance specs
470
  i_names = utils.NiceSort(instance_data.keys())
471
  for name in i_names:
472
    specs = instance_data[name]
473
    specs = _PopulateWithDefaults(specs)
474
    _Validate(specs)
475

    
476
    hypervisor = None
477
    hvparams = {}
478
    if specs['hypervisor']:
479
      hypervisor, hvparams = specs['hypervisor'].iteritems()
480

    
481
    disks = []
482
    for elem in specs['disk_size']:
483
      try:
484
        size = utils.ParseUnit(elem)
485
      except ValueError, err:
486
        raise errors.OpPrereqError("Invalid disk size '%s' for"
487
                                   " instance %s: %s" %
488
                                   (elem, name, err))
489
      disks.append({"size": size})
490

    
491
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
492

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

    
511
    ToStdout("%s: %s", name, cli.SendJob([op]))
512

    
513
  return 0
514

    
515

    
516
def ReinstallInstance(opts, args):
517
  """Reinstall an instance.
518

    
519
  @param opts: the command line options selected by the user
520
  @type args: list
521
  @param args: should contain only one element, the name of the
522
      instance to be reinstalled
523
  @rtype: int
524
  @return: the desired exit code
525

    
526
  """
527
  instance_name = args[0]
528

    
529
  if opts.select_os is True:
530
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
531
    result = SubmitOpCode(op)
532

    
533
    if not result:
534
      ToStdout("Can't get the OS list")
535
      return 1
536

    
537
    ToStdout("Available OS templates:")
538
    number = 0
539
    choices = []
540
    for entry in result:
541
      ToStdout("%3s: %s", number, entry[0])
542
      choices.append(("%s" % number, entry[0], entry[0]))
543
      number = number + 1
544

    
545
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
546
    selected = AskUser("Enter OS template name or number (or x to abort):",
547
                       choices)
548

    
549
    if selected == 'exit':
550
      ToStdout("User aborted reinstall, exiting")
551
      return 1
552

    
553
    os_name = selected
554
  else:
555
    os_name = opts.os
556

    
557
  if not opts.force:
558
    usertext = ("This will reinstall the instance %s and remove"
559
                " all data. Continue?") % instance_name
560
    if not AskUser(usertext):
561
      return 1
562

    
563
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
564
                                   os_type=os_name)
565
  SubmitOrSend(op, opts)
566

    
567
  return 0
568

    
569

    
570
def RemoveInstance(opts, args):
571
  """Remove an instance.
572

    
573
  @param opts: the command line options selected by the user
574
  @type args: list
575
  @param args: should contain only one element, the name of
576
      the instance to be removed
577
  @rtype: int
578
  @return: the desired exit code
579

    
580
  """
581
  instance_name = args[0]
582
  force = opts.force
583
  cl = GetClient()
584

    
585
  if not force:
586
    _EnsureInstancesExist(cl, [instance_name])
587

    
588
    usertext = ("This will remove the volumes of the instance %s"
589
                " (including mirrors), thus removing all the data"
590
                " of the instance. Continue?") % instance_name
591
    if not AskUser(usertext):
592
      return 1
593

    
594
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
595
                                ignore_failures=opts.ignore_failures)
596
  SubmitOrSend(op, opts, cl=cl)
597
  return 0
598

    
599

    
600
def RenameInstance(opts, args):
601
  """Rename an instance.
602

    
603
  @param opts: the command line options selected by the user
604
  @type args: list
605
  @param args: should contain two elements, the old and the
606
      new instance names
607
  @rtype: int
608
  @return: the desired exit code
609

    
610
  """
611
  op = opcodes.OpRenameInstance(instance_name=args[0],
612
                                new_name=args[1],
613
                                ignore_ip=opts.ignore_ip)
614
  SubmitOrSend(op, opts)
615
  return 0
616

    
617

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

    
621
  This serves two purposes:
622
    - it allows (as long as the instance is not running)
623
      mounting the disks and modifying them from the node
624
    - it repairs inactive secondary drbds
625

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

    
632
  """
633
  instance_name = args[0]
634
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
635
  disks_info = SubmitOrSend(op, opts)
636
  for host, iname, nname in disks_info:
637
    ToStdout("%s:%s:%s", host, iname, nname)
638
  return 0
639

    
640

    
641
def DeactivateDisks(opts, args):
642
  """Deactivate an instance's disks..
643

    
644
  This function takes the instance name, looks for its primary node
645
  and the tries to shutdown its block devices on that node.
646

    
647
  @param opts: the command line options selected by the user
648
  @type args: list
649
  @param args: should contain only one element, the instance name
650
  @rtype: int
651
  @return: the desired exit code
652

    
653
  """
654
  instance_name = args[0]
655
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
656
  SubmitOrSend(op, opts)
657
  return 0
658

    
659

    
660
def GrowDisk(opts, args):
661
  """Grow an instance's disks.
662

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

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

    
683

    
684
def StartupInstance(opts, args):
685
  """Startup instances.
686

    
687
  Depending on the options given, this will start one or more
688
  instances.
689

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

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

    
718

    
719
def RebootInstance(opts, args):
720
  """Reboot instance(s).
721

    
722
  Depending on the parameters given, this will reboot one or more
723
  instances.
724

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

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

    
753

    
754
def ShutdownInstance(opts, args):
755
  """Shutdown an instance.
756

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

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

    
777
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
778
  for name in inames:
779
    op = opcodes.OpShutdownInstance(instance_name=name)
780
    jex.QueueJob(name, op)
781
  jex.WaitOrShow(not opts.submit_only)
782
  return 0
783

    
784

    
785
def ReplaceDisks(opts, args):
786
  """Replace the disks of an instance
787

    
788
  @param opts: the command line options selected by the user
789
  @type args: list
790
  @param args: should contain only one element, the instance name
791
  @rtype: int
792
  @return: the desired exit code
793

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

    
818
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
819
                              remote_node=new_2ndary, mode=mode,
820
                              iallocator=iallocator)
821
  SubmitOrSend(op, opts)
822
  return 0
823

    
824

    
825
def FailoverInstance(opts, args):
826
  """Failover an instance.
827

    
828
  The failover is done by shutting it down on its present node and
829
  starting it on the secondary.
830

    
831
  @param opts: the command line options selected by the user
832
  @type args: list
833
  @param args: should contain only one element, the instance name
834
  @rtype: int
835
  @return: the desired exit code
836

    
837
  """
838
  cl = GetClient()
839
  instance_name = args[0]
840
  force = opts.force
841

    
842
  if not force:
843
    _EnsureInstancesExist(cl, [instance_name])
844

    
845
    usertext = ("Failover will happen to image %s."
846
                " This requires a shutdown of the instance. Continue?" %
847
                (instance_name,))
848
    if not AskUser(usertext):
849
      return 1
850

    
851
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
852
                                  ignore_consistency=opts.ignore_consistency)
853
  SubmitOrSend(op, opts, cl=cl)
854
  return 0
855

    
856

    
857
def MigrateInstance(opts, args):
858
  """Migrate an instance.
859

    
860
  The migrate is done without shutdown.
861

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

    
868
  """
869
  cl = GetClient()
870
  instance_name = args[0]
871
  force = opts.force
872

    
873
  if not force:
874
    _EnsureInstancesExist(cl, [instance_name])
875

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

    
889
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
890
                                 cleanup=opts.cleanup)
891
  SubmitOpCode(op, cl=cl)
892
  return 0
893

    
894

    
895
def ConnectToInstanceConsole(opts, args):
896
  """Connect to the console of an instance.
897

    
898
  @param opts: the command line options selected by the user
899
  @type args: list
900
  @param args: should contain only one element, the instance name
901
  @rtype: int
902
  @return: the desired exit code
903

    
904
  """
905
  instance_name = args[0]
906

    
907
  op = opcodes.OpConnectConsole(instance_name=instance_name)
908
  cmd = SubmitOpCode(op)
909

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

    
920

    
921
def _FormatLogicalID(dev_type, logical_id):
922
  """Formats the logical_id of a disk.
923

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

    
939
  return data
940

    
941

    
942
def _FormatBlockDevInfo(idx, top_level, dev, static):
943
  """Show block device information.
944

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

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

    
960
  """
961
  def helper(dtype, status):
962
    """Format one line for physical device status.
963

    
964
    @type dtype: str
965
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
966
    @type status: tuple
967
    @param status: a tuple as returned from L{backend.FindBlockDevice}
968
    @return: the string representing the status
969

    
970
    """
971
    if not status:
972
      return "not active"
973
    txt = ""
974
    (path, major, minor, syncp, estt, degr, ldisk) = status
975
    if major is None:
976
      major_string = "N/A"
977
    else:
978
      major_string = str(major)
979

    
980
    if minor is None:
981
      minor_string = "N/A"
982
    else:
983
      minor_string = str(minor)
984

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

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

    
1041
  if dev["children"]:
1042
    data.append("child devices:")
1043
    for c_idx, child in enumerate(dev["children"]):
1044
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1045
  d1.append(data)
1046
  return d1
1047

    
1048

    
1049
def _FormatList(buf, data, indent_level):
1050
  """Formats a list of data at a given indent level.
1051

    
1052
  If the element of the list is:
1053
    - a string, it is simply formatted as is
1054
    - a tuple, it will be split into key, value and the all the
1055
      values in a list will be aligned all at the same start column
1056
    - a list, will be recursively formatted
1057

    
1058
  @type buf: StringIO
1059
  @param buf: the buffer into which we write the output
1060
  @param data: the list to format
1061
  @type indent_level: int
1062
  @param indent_level: the indent level to format at
1063

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

    
1077
def ShowInstanceConfig(opts, args):
1078
  """Compute instance run-time status.
1079

    
1080
  @param opts: the command line options selected by the user
1081
  @type args: list
1082
  @param args: either an empty list, and then we query all
1083
      instances, or should contain a list of instance names
1084
  @rtype: int
1085
  @return: the desired exit code
1086

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

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

    
1139
    else:
1140
      # auto-handle other hypervisor types
1141
      hvattrs = [(key, key) for key in instance["hv_actual"]]
1142

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

    
1160
    for idx, device in enumerate(instance["disks"]):
1161
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1162

    
1163
  ToStdout(buf.getvalue().rstrip('\n'))
1164
  return retcode
1165

    
1166

    
1167
def SetInstanceParams(opts, args):
1168
  """Modifies an instance.
1169

    
1170
  All parameters take effect only at the next restart of the instance.
1171

    
1172
  @param opts: the command line options selected by the user
1173
  @type args: list
1174
  @param args: should contain only one element, the instance name
1175
  @rtype: int
1176
  @return: the desired exit code
1177

    
1178
  """
1179
  if not (opts.nics or opts.disks or
1180
          opts.hypervisor or opts.beparams):
1181
    ToStderr("Please give at least one of the parameters.")
1182
    return 1
1183

    
1184
  for param in opts.beparams:
1185
    if opts.beparams[param].lower() == "default":
1186
      opts.beparams[param] = constants.VALUE_DEFAULT
1187
    elif opts.beparams[param].lower() == "none":
1188
      opts.beparams[param] = constants.VALUE_NONE
1189
    elif param == constants.BE_MEMORY:
1190
      opts.beparams[constants.BE_MEMORY] = \
1191
        utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1192

    
1193
  for param in opts.hypervisor:
1194
    if opts.hypervisor[param].lower() == "default":
1195
      opts.hypervisor[param] = constants.VALUE_DEFAULT
1196
    elif opts.hypervisor[param].lower() == "none":
1197
      opts.hypervisor[param] = constants.VALUE_NONE
1198

    
1199
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1200
    try:
1201
      nic_op = int(nic_op)
1202
      opts.nics[idx] = (nic_op, nic_dict)
1203
    except ValueError:
1204
      pass
1205

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

    
1217
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1218
                                   nics=opts.nics,
1219
                                   disks=opts.disks,
1220
                                   hvparams=opts.hypervisor,
1221
                                   beparams=opts.beparams,
1222
                                   force=opts.force)
1223

    
1224
  # even if here we process the result, we allow submit only
1225
  result = SubmitOrSend(op, opts)
1226

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

    
1235

    
1236
# options used in more than one cmd
1237
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1238
                       metavar="<node>")
1239

    
1240
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1241
                    metavar="<os>")
1242

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

    
1249
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1250
                             help="Filter by nodes (primary only)",
1251
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1252

    
1253
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1254
                             help="Filter by nodes (secondary only)",
1255
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1256

    
1257
m_node_opt = make_option("--node", dest="multi_mode",
1258
                         help="Filter by nodes (primary and secondary)",
1259
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1260

    
1261
m_clust_opt = make_option("--all", dest="multi_mode",
1262
                          help="Select all instances in the cluster",
1263
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1264

    
1265
m_inst_opt = make_option("--instance", dest="multi_mode",
1266
                         help="Filter by instance name [default]",
1267
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1268

    
1269

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

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

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

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

    
1520
if __name__ == '__main__':
1521
  sys.exit(GenericMain(commands, aliases=aliases,
1522
                       override={"tag_type": constants.TAG_INSTANCE}))