Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ dd7dcca7

History | View | Annotate | Download (53.6 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
  jex = JobExecutor()
445

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

    
455
    hypervisor = specs['hypervisor']
456
    hvparams = specs['hvparams']
457

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

    
468
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
469

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

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

    
491
    jex.QueueJob(name, op)
492
  # we never want to wait, just show the submitted job IDs
493
  jex.WaitOrShow(False)
494

    
495
  return 0
496

    
497

    
498
def ReinstallInstance(opts, args):
499
  """Reinstall an instance.
500

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

    
508
  """
509
  instance_name = args[0]
510

    
511
  if opts.select_os is True:
512
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
513
    result = SubmitOpCode(op)
514

    
515
    if not result:
516
      ToStdout("Can't get the OS list")
517
      return 1
518

    
519
    ToStdout("Available OS templates:")
520
    number = 0
521
    choices = []
522
    for entry in result:
523
      ToStdout("%3s: %s", number, entry[0])
524
      choices.append(("%s" % number, entry[0], entry[0]))
525
      number = number + 1
526

    
527
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
528
    selected = AskUser("Enter OS template number (or x to abort):",
529
                       choices)
530

    
531
    if selected == 'exit':
532
      ToStdout("User aborted reinstall, exiting")
533
      return 1
534

    
535
    os_name = selected
536
  else:
537
    os_name = opts.os
538

    
539
  if not opts.force:
540
    usertext = ("This will reinstall the instance %s and remove"
541
                " all data. Continue?") % instance_name
542
    if not AskUser(usertext):
543
      return 1
544

    
545
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
546
                                   os_type=os_name)
547
  SubmitOrSend(op, opts)
548

    
549
  return 0
550

    
551

    
552
def RemoveInstance(opts, args):
553
  """Remove an instance.
554

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

    
562
  """
563
  instance_name = args[0]
564
  force = opts.force
565
  cl = GetClient()
566

    
567
  if not force:
568
    _EnsureInstancesExist(cl, [instance_name])
569

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

    
576
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
577
                                ignore_failures=opts.ignore_failures)
578
  SubmitOrSend(op, opts, cl=cl)
579
  return 0
580

    
581

    
582
def RenameInstance(opts, args):
583
  """Rename an instance.
584

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

    
592
  """
593
  op = opcodes.OpRenameInstance(instance_name=args[0],
594
                                new_name=args[1],
595
                                ignore_ip=opts.ignore_ip)
596
  SubmitOrSend(op, opts)
597
  return 0
598

    
599

    
600
def ActivateDisks(opts, args):
601
  """Activate an instance's disks.
602

    
603
  This serves two purposes:
604
    - it allows (as long as the instance is not running)
605
      mounting the disks and modifying them from the node
606
    - it repairs inactive secondary drbds
607

    
608
  @param opts: the command line options selected by the user
609
  @type args: list
610
  @param args: should contain only one element, the instance name
611
  @rtype: int
612
  @return: the desired exit code
613

    
614
  """
615
  instance_name = args[0]
616
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
617
  disks_info = SubmitOrSend(op, opts)
618
  for host, iname, nname in disks_info:
619
    ToStdout("%s:%s:%s", host, iname, nname)
620
  return 0
621

    
622

    
623
def DeactivateDisks(opts, args):
624
  """Deactivate an instance's disks..
625

    
626
  This function takes the instance name, looks for its primary node
627
  and the tries to shutdown its block devices on that node.
628

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

    
635
  """
636
  instance_name = args[0]
637
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
638
  SubmitOrSend(op, opts)
639
  return 0
640

    
641

    
642
def GrowDisk(opts, args):
643
  """Grow an instance's disks.
644

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

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

    
665

    
666
def StartupInstance(opts, args):
667
  """Startup instances.
668

    
669
  Depending on the options given, this will start one or more
670
  instances.
671

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

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

    
704

    
705
def RebootInstance(opts, args):
706
  """Reboot instance(s).
707

    
708
  Depending on the parameters given, this will reboot one or more
709
  instances.
710

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

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

    
739

    
740
def ShutdownInstance(opts, args):
741
  """Shutdown an instance.
742

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

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

    
763
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
764
  for name in inames:
765
    op = opcodes.OpShutdownInstance(instance_name=name)
766
    jex.QueueJob(name, op)
767
  jex.WaitOrShow(not opts.submit_only)
768
  return 0
769

    
770

    
771
def ReplaceDisks(opts, args):
772
  """Replace the disks of an instance
773

    
774
  @param opts: the command line options selected by the user
775
  @type args: list
776
  @param args: should contain only one element, the instance name
777
  @rtype: int
778
  @return: the desired exit code
779

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

    
804
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
805
                              remote_node=new_2ndary, mode=mode,
806
                              iallocator=iallocator)
807
  SubmitOrSend(op, opts)
808
  return 0
809

    
810

    
811
def FailoverInstance(opts, args):
812
  """Failover an instance.
813

    
814
  The failover is done by shutting it down on its present node and
815
  starting it on the secondary.
816

    
817
  @param opts: the command line options selected by the user
818
  @type args: list
819
  @param args: should contain only one element, the instance name
820
  @rtype: int
821
  @return: the desired exit code
822

    
823
  """
824
  cl = GetClient()
825
  instance_name = args[0]
826
  force = opts.force
827

    
828
  if not force:
829
    _EnsureInstancesExist(cl, [instance_name])
830

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

    
837
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
838
                                  ignore_consistency=opts.ignore_consistency)
839
  SubmitOrSend(op, opts, cl=cl)
840
  return 0
841

    
842

    
843
def MigrateInstance(opts, args):
844
  """Migrate an instance.
845

    
846
  The migrate is done without shutdown.
847

    
848
  @param opts: the command line options selected by the user
849
  @type args: list
850
  @param args: should contain only one element, the instance name
851
  @rtype: int
852
  @return: the desired exit code
853

    
854
  """
855
  cl = GetClient()
856
  instance_name = args[0]
857
  force = opts.force
858

    
859
  if not force:
860
    _EnsureInstancesExist(cl, [instance_name])
861

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

    
875
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
876
                                 cleanup=opts.cleanup)
877
  SubmitOpCode(op, cl=cl)
878
  return 0
879

    
880

    
881
def ConnectToInstanceConsole(opts, args):
882
  """Connect to the console of an instance.
883

    
884
  @param opts: the command line options selected by the user
885
  @type args: list
886
  @param args: should contain only one element, the instance name
887
  @rtype: int
888
  @return: the desired exit code
889

    
890
  """
891
  instance_name = args[0]
892

    
893
  op = opcodes.OpConnectConsole(instance_name=instance_name)
894
  cmd = SubmitOpCode(op)
895

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

    
906

    
907
def _FormatLogicalID(dev_type, logical_id):
908
  """Formats the logical_id of a disk.
909

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

    
925
  return data
926

    
927

    
928
def _FormatBlockDevInfo(idx, top_level, dev, static):
929
  """Show block device information.
930

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

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

    
946
  """
947
  def helper(dtype, status):
948
    """Format one line for physical device status.
949

    
950
    @type dtype: str
951
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
952
    @type status: tuple
953
    @param status: a tuple as returned from L{backend.FindBlockDevice}
954
    @return: the string representing the status
955

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

    
966
    if minor is None:
967
      minor_string = "N/A"
968
    else:
969
      minor_string = str(minor)
970

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

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

    
1031
  if dev["children"]:
1032
    data.append("child devices:")
1033
    for c_idx, child in enumerate(dev["children"]):
1034
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1035
  d1.append(data)
1036
  return d1
1037

    
1038

    
1039
def _FormatList(buf, data, indent_level):
1040
  """Formats a list of data at a given indent level.
1041

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

    
1048
  @type buf: StringIO
1049
  @param buf: the buffer into which we write the output
1050
  @param data: the list to format
1051
  @type indent_level: int
1052
  @param indent_level: the indent level to format at
1053

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

    
1067
def ShowInstanceConfig(opts, args):
1068
  """Compute instance run-time status.
1069

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

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

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

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

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

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

    
1150
    for idx, device in enumerate(instance["disks"]):
1151
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1152

    
1153
  ToStdout(buf.getvalue().rstrip('\n'))
1154
  return retcode
1155

    
1156

    
1157
def SetInstanceParams(opts, args):
1158
  """Modifies an instance.
1159

    
1160
  All parameters take effect only at the next restart of the instance.
1161

    
1162
  @param opts: the command line options selected by the user
1163
  @type args: list
1164
  @param args: should contain only one element, the instance name
1165
  @rtype: int
1166
  @return: the desired exit code
1167

    
1168
  """
1169
  if not (opts.nics or opts.disks or
1170
          opts.hypervisor or opts.beparams):
1171
    ToStderr("Please give at least one of the parameters.")
1172
    return 1
1173

    
1174
  for param in opts.beparams:
1175
    if isinstance(opts.beparams[param], basestring):
1176
      if opts.beparams[param].lower() == "default":
1177
        opts.beparams[param] = constants.VALUE_DEFAULT
1178

    
1179
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1180
                      allowed_values=[constants.VALUE_DEFAULT])
1181

    
1182
  for param in opts.hypervisor:
1183
    if isinstance(opts.hypervisor[param], basestring):
1184
      if opts.hypervisor[param].lower() == "default":
1185
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1186

    
1187
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1188
                      allowed_values=[constants.VALUE_DEFAULT])
1189

    
1190
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1191
    try:
1192
      nic_op = int(nic_op)
1193
      opts.nics[idx] = (nic_op, nic_dict)
1194
    except ValueError:
1195
      pass
1196

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

    
1208
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1209
                                   nics=opts.nics,
1210
                                   disks=opts.disks,
1211
                                   hvparams=opts.hypervisor,
1212
                                   beparams=opts.beparams,
1213
                                   force=opts.force)
1214

    
1215
  # even if here we process the result, we allow submit only
1216
  result = SubmitOrSend(op, opts)
1217

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

    
1226

    
1227
# options used in more than one cmd
1228
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1229
                       metavar="<node>")
1230

    
1231
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1232
                    metavar="<os>")
1233

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

    
1240
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1241
                             help="Filter by nodes (primary only)",
1242
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1243

    
1244
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1245
                             help="Filter by nodes (secondary only)",
1246
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1247

    
1248
m_node_opt = make_option("--node", dest="multi_mode",
1249
                         help="Filter by nodes (primary and secondary)",
1250
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1251

    
1252
m_clust_opt = make_option("--all", dest="multi_mode",
1253
                          help="Select all instances in the cluster",
1254
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1255

    
1256
m_inst_opt = make_option("--instance", dest="multi_mode",
1257
                         help="Filter by instance name [default]",
1258
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1259

    
1260

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

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

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

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

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