Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ c1ce76bb

History | View | Annotate | Download (53.5 kB)

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

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

    
21

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

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

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

    
40

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

    
47

    
48
_VALUE_TRUE = "true"
49

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

    
55

    
56
def _ExpandMultiNames(mode, names, client=None):
57
  """Expand the given names using the passed mode.
58

    
59
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
60
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
61
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
62
  instances having those nodes as either primary or secondary will be
63
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
64
  returned.
65

    
66
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
67
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
68
      L{_SHUTDOWN_INSTANCES}
69
  @param names: a list of names; for cluster, it must be empty,
70
      and for node and instance it must be a list of valid item
71
      names (short names are valid as usual, e.g. node1 instead of
72
      node1.example.com)
73
  @rtype: list
74
  @return: the list of names after the expansion
75
  @raise errors.ProgrammerError: for unknown selection type
76
  @raise errors.OpPrereqError: for invalid input parameters
77

    
78
  """
79
  if client is None:
80
    client = GetClient()
81
  if mode == _SHUTDOWN_CLUSTER:
82
    if names:
83
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
84
    idata = client.QueryInstances([], ["name"], False)
85
    inames = [row[0] for row in idata]
86

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

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

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

    
116
  return inames
117

    
118

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

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

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

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

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

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

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

    
156

    
157
def _EnsureInstancesExist(client, names):
158
  """Check for and ensure the given instance names exist.
159

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

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

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

    
177

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

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

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

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

    
197
  if not opts.no_headers:
198
    headers = {
199
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
200
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
201
      "oper_state": "Running",
202
      "oper_ram": "Memory", "disk_template": "Disk_template",
203
      "ip": "IP_address", "mac": "MAC_address",
204
      "bridge": "Bridge",
205
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
206
      "disk_usage": "DiskUsage",
207
      "status": "Status", "tags": "Tags",
208
      "network_port": "Network_port",
209
      "hv/kernel_path": "Kernel_path",
210
      "hv/initrd_path": "Initrd_path",
211
      "hv/boot_order": "HVM_boot_order",
212
      "hv/acpi": "HVM_ACPI",
213
      "hv/pae": "HVM_PAE",
214
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
215
      "hv/nic_type": "HVM_NIC_type",
216
      "hv/disk_type": "HVM_Disk_type",
217
      "hv/vnc_bind_address": "VNC_bind_address",
218
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
219
      "hvparams": "Hypervisor_parameters",
220
      "be/memory": "Configured_memory",
221
      "be/vcpus": "VCPUs",
222
      "vcpus": "VCPUs",
223
      "be/auto_balance": "Auto_balance",
224
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
225
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
226
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
227
      }
228
  else:
229
    headers = None
230

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

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

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

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

    
274
  return 0
275

    
276

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

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

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

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

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

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

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

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

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

    
361
  SubmitOrSend(op, opts)
362
  return 0
363

    
364

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
491
  return 0
492

    
493

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

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

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

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

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

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

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

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

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

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

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

    
545
  return 0
546

    
547

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

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

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

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

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

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

    
577

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

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

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

    
595

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

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

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

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

    
618

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

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

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

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

    
637

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

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

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

    
661

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

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

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

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

    
700

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

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

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

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

    
735

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

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

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

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

    
766

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

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

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

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

    
806

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

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

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

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

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

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

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

    
838

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

    
842
  The migrate is done without shutdown.
843

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

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

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

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

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

    
876

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

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

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

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

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

    
902

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

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

    
921
  return data
922

    
923

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

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

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

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

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

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

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

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

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

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

    
1034

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

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

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

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

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

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

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

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

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

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

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

    
1152

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1222

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

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

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

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

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

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

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

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

    
1256

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

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

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

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

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