Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 691744c4

History | View | Annotate | Download (55.1 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, extra=""):
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%s"
137
         "Do you want to continue?" % (text, count, extra))
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{ganeti.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 (TypeError, 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 (TypeError, 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 (TypeError, 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 (TypeError, 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
  # first, compute the desired name list
510
  if opts.multi_mode is None:
511
    opts.multi_mode = _SHUTDOWN_INSTANCES
512

    
513
  inames = _ExpandMultiNames(opts.multi_mode, args)
514
  if not inames:
515
    raise errors.OpPrereqError("Selection filter does not match any instances")
516

    
517
  # second, if requested, ask for an OS
518
  if opts.select_os is True:
519
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
520
    result = SubmitOpCode(op)
521

    
522
    if not result:
523
      ToStdout("Can't get the OS list")
524
      return 1
525

    
526
    ToStdout("Available OS templates:")
527
    number = 0
528
    choices = []
529
    for entry in result:
530
      ToStdout("%3s: %s", number, entry[0])
531
      choices.append(("%s" % number, entry[0], entry[0]))
532
      number = number + 1
533

    
534
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
535
    selected = AskUser("Enter OS template number (or x to abort):",
536
                       choices)
537

    
538
    if selected == 'exit':
539
      ToStderr("User aborted reinstall, exiting")
540
      return 1
541

    
542
    os_name = selected
543
  else:
544
    os_name = opts.os
545

    
546
  # third, get confirmation: multi-reinstall requires --force-multi
547
  # *and* --force, single-reinstall just --force
548
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
549
  if multi_on:
550
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
551
    if not ((opts.force_multi and opts.force) or
552
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
553
      return 1
554
  else:
555
    if not opts.force:
556
      usertext = ("This will reinstall the instance %s and remove"
557
                  " all data. Continue?") % inames[0]
558
      if not AskUser(usertext):
559
        return 1
560

    
561
  jex = JobExecutor(verbose=multi_on)
562
  for instance_name in inames:
563
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
564
                                     os_type=os_name)
565
    jex.QueueJob(instance_name, op)
566

    
567
  jex.WaitOrShow(not opts.submit_only)
568
  return 0
569

    
570

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

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

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

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

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

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

    
600

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

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

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

    
618

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

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

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

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

    
642

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

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

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

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

    
661

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

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

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

    
685

    
686
def StartupInstance(opts, args):
687
  """Startup instances.
688

    
689
  Depending on the options given, this will start one or more
690
  instances.
691

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

    
700
  """
701
  cl = GetClient()
702
  if opts.multi_mode is None:
703
    opts.multi_mode = _SHUTDOWN_INSTANCES
704
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
705
  if not inames:
706
    raise errors.OpPrereqError("Selection filter does not match any instances")
707
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
708
  if not (opts.force_multi or not multi_on
709
          or _ConfirmOperation(inames, "startup")):
710
    return 1
711
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
712
  for name in inames:
713
    op = opcodes.OpStartupInstance(instance_name=name,
714
                                   force=opts.force)
715
    # do not add these parameters to the opcode unless they're defined
716
    if opts.hvparams:
717
      op.hvparams = opts.hvparams
718
    if opts.beparams:
719
      op.beparams = opts.beparams
720
    jex.QueueJob(name, op)
721
  jex.WaitOrShow(not opts.submit_only)
722
  return 0
723

    
724

    
725
def RebootInstance(opts, args):
726
  """Reboot instance(s).
727

    
728
  Depending on the parameters given, this will reboot one or more
729
  instances.
730

    
731
  @param opts: the command line options selected by the user
732
  @type args: list
733
  @param args: the instance or node names based on which we
734
      create the final selection (in conjunction with the
735
      opts argument)
736
  @rtype: int
737
  @return: the desired exit code
738

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

    
759

    
760
def ShutdownInstance(opts, args):
761
  """Shutdown an instance.
762

    
763
  @param opts: the command line options selected by the user
764
  @type args: list
765
  @param args: the instance or node names based on which we
766
      create the final selection (in conjunction with the
767
      opts argument)
768
  @rtype: int
769
  @return: the desired exit code
770

    
771
  """
772
  cl = GetClient()
773
  if opts.multi_mode is None:
774
    opts.multi_mode = _SHUTDOWN_INSTANCES
775
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
776
  if not inames:
777
    raise errors.OpPrereqError("Selection filter does not match any instances")
778
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
779
  if not (opts.force_multi or not multi_on
780
          or _ConfirmOperation(inames, "shutdown")):
781
    return 1
782

    
783
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
784
  for name in inames:
785
    op = opcodes.OpShutdownInstance(instance_name=name)
786
    jex.QueueJob(name, op)
787
  jex.WaitOrShow(not opts.submit_only)
788
  return 0
789

    
790

    
791
def ReplaceDisks(opts, args):
792
  """Replace the disks of an instance
793

    
794
  @param opts: the command line options selected by the user
795
  @type args: list
796
  @param args: should contain only one element, the instance name
797
  @rtype: int
798
  @return: the desired exit code
799

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

    
824
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
825
                              remote_node=new_2ndary, mode=mode,
826
                              iallocator=iallocator)
827
  SubmitOrSend(op, opts)
828
  return 0
829

    
830

    
831
def FailoverInstance(opts, args):
832
  """Failover an instance.
833

    
834
  The failover is done by shutting it down on its present node and
835
  starting it on the secondary.
836

    
837
  @param opts: the command line options selected by the user
838
  @type args: list
839
  @param args: should contain only one element, the instance name
840
  @rtype: int
841
  @return: the desired exit code
842

    
843
  """
844
  cl = GetClient()
845
  instance_name = args[0]
846
  force = opts.force
847

    
848
  if not force:
849
    _EnsureInstancesExist(cl, [instance_name])
850

    
851
    usertext = ("Failover will happen to image %s."
852
                " This requires a shutdown of the instance. Continue?" %
853
                (instance_name,))
854
    if not AskUser(usertext):
855
      return 1
856

    
857
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
858
                                  ignore_consistency=opts.ignore_consistency)
859
  SubmitOrSend(op, opts, cl=cl)
860
  return 0
861

    
862

    
863
def MigrateInstance(opts, args):
864
  """Migrate an instance.
865

    
866
  The migrate is done without shutdown.
867

    
868
  @param opts: the command line options selected by the user
869
  @type args: list
870
  @param args: should contain only one element, the instance name
871
  @rtype: int
872
  @return: the desired exit code
873

    
874
  """
875
  cl = GetClient()
876
  instance_name = args[0]
877
  force = opts.force
878

    
879
  if not force:
880
    _EnsureInstancesExist(cl, [instance_name])
881

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

    
895
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
896
                                 cleanup=opts.cleanup)
897
  SubmitOpCode(op, cl=cl)
898
  return 0
899

    
900

    
901
def ConnectToInstanceConsole(opts, args):
902
  """Connect to the console of an instance.
903

    
904
  @param opts: the command line options selected by the user
905
  @type args: list
906
  @param args: should contain only one element, the instance name
907
  @rtype: int
908
  @return: the desired exit code
909

    
910
  """
911
  instance_name = args[0]
912

    
913
  op = opcodes.OpConnectConsole(instance_name=instance_name)
914
  cmd = SubmitOpCode(op)
915

    
916
  if opts.show_command:
917
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
918
  else:
919
    try:
920
      os.execvp(cmd[0], cmd)
921
    finally:
922
      ToStderr("Can't run console command %s with arguments:\n'%s'",
923
               cmd[0], " ".join(cmd))
924
      os._exit(1)
925

    
926

    
927
def _FormatLogicalID(dev_type, logical_id):
928
  """Formats the logical_id of a disk.
929

    
930
  """
931
  if dev_type == constants.LD_DRBD8:
932
    node_a, node_b, port, minor_a, minor_b, key = logical_id
933
    data = [
934
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
935
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
936
      ("port", port),
937
      ("auth key", key),
938
      ]
939
  elif dev_type == constants.LD_LV:
940
    vg_name, lv_name = logical_id
941
    data = ["%s/%s" % (vg_name, lv_name)]
942
  else:
943
    data = [str(logical_id)]
944

    
945
  return data
946

    
947

    
948
def _FormatBlockDevInfo(idx, top_level, dev, static):
949
  """Show block device information.
950

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

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

    
966
  """
967
  def helper(dtype, status):
968
    """Format one line for physical device status.
969

    
970
    @type dtype: str
971
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
972
    @type status: tuple
973
    @param status: a tuple as returned from L{backend.FindBlockDevice}
974
    @return: the string representing the status
975

    
976
    """
977
    if not status:
978
      return "not active"
979
    txt = ""
980
    (path, major, minor, syncp, estt, degr, ldisk) = status
981
    if major is None:
982
      major_string = "N/A"
983
    else:
984
      major_string = str(major)
985

    
986
    if minor is None:
987
      minor_string = "N/A"
988
    else:
989
      minor_string = str(minor)
990

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

    
1018
  # the header
1019
  if top_level:
1020
    if dev["iv_name"] is not None:
1021
      txt = dev["iv_name"]
1022
    else:
1023
      txt = "disk %d" % idx
1024
  else:
1025
    txt = "child %d" % idx
1026
  if isinstance(dev["size"], int):
1027
    nice_size = utils.FormatUnit(dev["size"], "h")
1028
  else:
1029
    nice_size = dev["size"]
1030
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1031
  data = []
1032
  if top_level:
1033
    data.append(("access mode", dev["mode"]))
1034
  if dev["logical_id"] is not None:
1035
    try:
1036
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1037
    except ValueError:
1038
      l_id = [str(dev["logical_id"])]
1039
    if len(l_id) == 1:
1040
      data.append(("logical_id", l_id[0]))
1041
    else:
1042
      data.extend(l_id)
1043
  elif dev["physical_id"] is not None:
1044
    data.append("physical_id:")
1045
    data.append([dev["physical_id"]])
1046
  if not static:
1047
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1048
  if dev["sstatus"] and not static:
1049
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1050

    
1051
  if dev["children"]:
1052
    data.append("child devices:")
1053
    for c_idx, child in enumerate(dev["children"]):
1054
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1055
  d1.append(data)
1056
  return d1
1057

    
1058

    
1059
def _FormatList(buf, data, indent_level):
1060
  """Formats a list of data at a given indent level.
1061

    
1062
  If the element of the list is:
1063
    - a string, it is simply formatted as is
1064
    - a tuple, it will be split into key, value and the all the
1065
      values in a list will be aligned all at the same start column
1066
    - a list, will be recursively formatted
1067

    
1068
  @type buf: StringIO
1069
  @param buf: the buffer into which we write the output
1070
  @param data: the list to format
1071
  @type indent_level: int
1072
  @param indent_level: the indent level to format at
1073

    
1074
  """
1075
  max_tlen = max([len(elem[0]) for elem in data
1076
                 if isinstance(elem, tuple)] or [0])
1077
  for elem in data:
1078
    if isinstance(elem, basestring):
1079
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1080
    elif isinstance(elem, tuple):
1081
      key, value = elem
1082
      spacer = "%*s" % (max_tlen - len(key), "")
1083
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1084
    elif isinstance(elem, list):
1085
      _FormatList(buf, elem, indent_level+1)
1086

    
1087
def ShowInstanceConfig(opts, args):
1088
  """Compute instance run-time status.
1089

    
1090
  @param opts: the command line options selected by the user
1091
  @type args: list
1092
  @param args: either an empty list, and then we query all
1093
      instances, or should contain a list of instance names
1094
  @rtype: int
1095
  @return: the desired exit code
1096

    
1097
  """
1098
  if not args and not opts.show_all:
1099
    ToStderr("No instance selected."
1100
             " Please pass in --all if you want to query all instances.\n"
1101
             "Note that this can take a long time on a big cluster.")
1102
    return 1
1103
  elif args and opts.show_all:
1104
    ToStderr("Cannot use --all if you specify instance names.")
1105
    return 1
1106

    
1107
  retcode = 0
1108
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1109
  result = SubmitOpCode(op)
1110
  if not result:
1111
    ToStdout("No instances.")
1112
    return 1
1113

    
1114
  buf = StringIO()
1115
  retcode = 0
1116
  for instance_name in result:
1117
    instance = result[instance_name]
1118
    buf.write("Instance name: %s\n" % instance["name"])
1119
    buf.write("State: configured to be %s" % instance["config_state"])
1120
    if not opts.static:
1121
      buf.write(", actual state is %s" % instance["run_state"])
1122
    buf.write("\n")
1123
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1124
    ##          instance["auto_balance"])
1125
    buf.write("  Nodes:\n")
1126
    buf.write("    - primary: %s\n" % instance["pnode"])
1127
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1128
    buf.write("  Operating system: %s\n" % instance["os"])
1129
    if instance.has_key("network_port"):
1130
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1131
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1132

    
1133
    # custom VNC console information
1134
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1135
                                                 None)
1136
    if vnc_bind_address:
1137
      port = instance["network_port"]
1138
      display = int(port) - constants.VNC_BASE_PORT
1139
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1140
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1141
                                                   port,
1142
                                                   display)
1143
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1144
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1145
                             (vnc_bind_address, port,
1146
                              instance["pnode"], display))
1147
      else:
1148
        # vnc bind address is a file
1149
        vnc_console_port = "%s:%s" % (instance["pnode"],
1150
                                      vnc_bind_address)
1151
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1152

    
1153
    for key in instance["hv_actual"]:
1154
      if key in instance["hv_instance"]:
1155
        val = instance["hv_instance"][key]
1156
      else:
1157
        val = "default (%s)" % instance["hv_actual"][key]
1158
      buf.write("    - %s: %s\n" % (key, val))
1159
    buf.write("  Hardware:\n")
1160
    buf.write("    - VCPUs: %d\n" %
1161
              instance["be_actual"][constants.BE_VCPUS])
1162
    buf.write("    - memory: %dMiB\n" %
1163
              instance["be_actual"][constants.BE_MEMORY])
1164
    buf.write("    - NICs:\n")
1165
    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1166
      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1167
                (idx, mac, ip, bridge))
1168
    buf.write("  Disks:\n")
1169

    
1170
    for idx, device in enumerate(instance["disks"]):
1171
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1172

    
1173
  ToStdout(buf.getvalue().rstrip('\n'))
1174
  return retcode
1175

    
1176

    
1177
def SetInstanceParams(opts, args):
1178
  """Modifies an instance.
1179

    
1180
  All parameters take effect only at the next restart of the instance.
1181

    
1182
  @param opts: the command line options selected by the user
1183
  @type args: list
1184
  @param args: should contain only one element, the instance name
1185
  @rtype: int
1186
  @return: the desired exit code
1187

    
1188
  """
1189
  if not (opts.nics or opts.disks or
1190
          opts.hypervisor or opts.beparams):
1191
    ToStderr("Please give at least one of the parameters.")
1192
    return 1
1193

    
1194
  for param in opts.beparams:
1195
    if isinstance(opts.beparams[param], basestring):
1196
      if opts.beparams[param].lower() == "default":
1197
        opts.beparams[param] = constants.VALUE_DEFAULT
1198

    
1199
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1200
                      allowed_values=[constants.VALUE_DEFAULT])
1201

    
1202
  for param in opts.hypervisor:
1203
    if isinstance(opts.hypervisor[param], basestring):
1204
      if opts.hypervisor[param].lower() == "default":
1205
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1206

    
1207
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1208
                      allowed_values=[constants.VALUE_DEFAULT])
1209

    
1210
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1211
    try:
1212
      nic_op = int(nic_op)
1213
      opts.nics[idx] = (nic_op, nic_dict)
1214
    except (TypeError, ValueError):
1215
      pass
1216

    
1217
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1218
    try:
1219
      disk_op = int(disk_op)
1220
      opts.disks[idx] = (disk_op, disk_dict)
1221
    except (TypeError, ValueError):
1222
      pass
1223
    if disk_op == constants.DDM_ADD:
1224
      if 'size' not in disk_dict:
1225
        raise errors.OpPrereqError("Missing required parameter 'size'")
1226
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1227

    
1228
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1229
                                   nics=opts.nics,
1230
                                   disks=opts.disks,
1231
                                   hvparams=opts.hypervisor,
1232
                                   beparams=opts.beparams,
1233
                                   force=opts.force)
1234

    
1235
  # even if here we process the result, we allow submit only
1236
  result = SubmitOrSend(op, opts)
1237

    
1238
  if result:
1239
    ToStdout("Modified instance %s", args[0])
1240
    for param, data in result:
1241
      ToStdout(" - %-5s -> %s", param, data)
1242
    ToStdout("Please don't forget that these parameters take effect"
1243
             " only at the next start of the instance.")
1244
  return 0
1245

    
1246

    
1247
# options used in more than one cmd
1248
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1249
                       metavar="<node>")
1250

    
1251
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1252
                    metavar="<os>")
1253

    
1254
# multi-instance selection options
1255
m_force_multi = make_option("--force-multiple", dest="force_multi",
1256
                            help="Do not ask for confirmation when more than"
1257
                            " one instance is affected",
1258
                            action="store_true", default=False)
1259

    
1260
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1261
                             help="Filter by nodes (primary only)",
1262
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1263

    
1264
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1265
                             help="Filter by nodes (secondary only)",
1266
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1267

    
1268
m_node_opt = make_option("--node", dest="multi_mode",
1269
                         help="Filter by nodes (primary and secondary)",
1270
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1271

    
1272
m_clust_opt = make_option("--all", dest="multi_mode",
1273
                          help="Select all instances in the cluster",
1274
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1275

    
1276
m_inst_opt = make_option("--instance", dest="multi_mode",
1277
                         help="Filter by instance name [default]",
1278
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1279

    
1280

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

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

    
1498
  'reboot': (RebootInstance, ARGS_ANY,
1499
              [DEBUG_OPT, m_force_multi,
1500
               make_option("-t", "--type", dest="reboot_type",
1501
                           help="Type of reboot: soft/hard/full",
1502
                           default=constants.INSTANCE_REBOOT_HARD,
1503
                           type="string", metavar="<REBOOT>"),
1504
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1505
                           default=False, action="store_true",
1506
                           help="Ignore errors from secondaries"),
1507
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1508
               m_clust_opt, m_inst_opt,
1509
               SUBMIT_OPT,
1510
               ],
1511
            "<instance>", "Reboots an instance"),
1512
  'activate-disks': (ActivateDisks, ARGS_ONE,
1513
                     [DEBUG_OPT, SUBMIT_OPT,
1514
                      make_option("--ignore-size", dest="ignore_size",
1515
                                  default=False, action="store_true",
1516
                                  help="Ignore current recorded size"
1517
                                  " (useful for forcing activation when"
1518
                                  " the recorded size is wrong)"),
1519
                      ],
1520
                     "<instance>",
1521
                     "Activate an instance's disks"),
1522
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1523
                       "<instance>",
1524
                       "Deactivate an instance's disks"),
1525
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1526
                [DEBUG_OPT, SUBMIT_OPT,
1527
                 make_option("--no-wait-for-sync",
1528
                             dest="wait_for_sync", default=True,
1529
                             action="store_false",
1530
                             help="Don't wait for sync (DANGEROUS!)"),
1531
                 ],
1532
                "<instance> <disk> <size>", "Grow an instance's disk"),
1533
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1534
                "<instance_name>", "List the tags of the given instance"),
1535
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1536
               "<instance_name> tag...", "Add tags to the given instance"),
1537
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1538
                  "<instance_name> tag...", "Remove tags from given instance"),
1539
  }
1540

    
1541
#: dictionary with aliases for commands
1542
aliases = {
1543
  'activate_block_devs': 'activate-disks',
1544
  'replace_disks': 'replace-disks',
1545
  'start': 'startup',
1546
  'stop': 'shutdown',
1547
  }
1548

    
1549
if __name__ == '__main__':
1550
  sys.exit(GenericMain(commands, aliases=aliases,
1551
                       override={"tag_type": constants.TAG_INSTANCE}))