Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ a379d9bd

History | View | Annotate | Download (54.9 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{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
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
205
      "bridge": "Bridge",
206
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
207
      "disk_usage": "DiskUsage",
208
      "status": "Status", "tags": "Tags",
209
      "network_port": "Network_port",
210
      "hv/kernel_path": "Kernel_path",
211
      "hv/initrd_path": "Initrd_path",
212
      "hv/boot_order": "HVM_boot_order",
213
      "hv/acpi": "HVM_ACPI",
214
      "hv/pae": "HVM_PAE",
215
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
216
      "hv/nic_type": "HVM_NIC_type",
217
      "hv/disk_type": "HVM_Disk_type",
218
      "hv/vnc_bind_address": "VNC_bind_address",
219
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
220
      "hvparams": "Hypervisor_parameters",
221
      "be/memory": "Configured_memory",
222
      "be/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.modes": "NIC_modes", "nic.links": "NIC_links",
227
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
228
      }
229
  else:
230
    headers = None
231

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

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

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

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

    
275
  return 0
276

    
277

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

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

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

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

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

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

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

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

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

    
362
  SubmitOrSend(op, opts)
363
  return 0
364

    
365

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

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

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

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

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

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

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

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

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

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

    
443
  jex = JobExecutor()
444

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

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

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

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

    
470
    tmp_nics = []
471
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
472
      if field in specs:
473
        if not tmp_nics:
474
          tmp_nics.append({})
475
        tmp_nics[0][field] = specs[field]
476

    
477
    if specs['nics'] is not None and tmp_nics:
478
      raise errors.OpPrereqError("'nics' list incompatible with using"
479
                                 " individual nic fields as well")
480
    elif specs['nics'] is not None:
481
      tmp_nics = specs['nics']
482
    elif not tmp_nics:
483
      tmp_nics = [{}]
484

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

    
503
    jex.QueueJob(name, op)
504
  # we never want to wait, just show the submitted job IDs
505
  jex.WaitOrShow(False)
506

    
507
  return 0
508

    
509

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

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

    
520
  """
521
  # first, compute the desired name list
522
  if opts.multi_mode is None:
523
    opts.multi_mode = _SHUTDOWN_INSTANCES
524

    
525
  inames = _ExpandMultiNames(opts.multi_mode, args)
526
  if not inames:
527
    raise errors.OpPrereqError("Selection filter does not match any instances")
528

    
529
  # second, if requested, ask for an OS
530
  if opts.select_os is True:
531
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
532
    result = SubmitOpCode(op)
533

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

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

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

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

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

    
558
  # third, get confirmation: multi-reinstall requires --force-multi
559
  # *and* --force, single-reinstall just --force
560
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
561
  if multi_on:
562
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
563
    if not ((opts.force_multi and opts.force) or
564
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
565
      return 1
566
  else:
567
    if not opts.force:
568
      usertext = ("This will reinstall the instance %s and remove"
569
                  " all data. Continue?") % inames[0]
570
      if not AskUser(usertext):
571
        return 1
572

    
573
  jex = JobExecutor(verbose=multi_on)
574
  for instance_name in inames:
575
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
576
                                     os_type=os_name)
577
    jex.QueueJob(instance_name, op)
578

    
579
  jex.WaitOrShow(not opts.submit_only)
580
  return 0
581

    
582

    
583
def RemoveInstance(opts, args):
584
  """Remove an instance.
585

    
586
  @param opts: the command line options selected by the user
587
  @type args: list
588
  @param args: should contain only one element, the name of
589
      the instance to be removed
590
  @rtype: int
591
  @return: the desired exit code
592

    
593
  """
594
  instance_name = args[0]
595
  force = opts.force
596
  cl = GetClient()
597

    
598
  if not force:
599
    _EnsureInstancesExist(cl, [instance_name])
600

    
601
    usertext = ("This will remove the volumes of the instance %s"
602
                " (including mirrors), thus removing all the data"
603
                " of the instance. Continue?") % instance_name
604
    if not AskUser(usertext):
605
      return 1
606

    
607
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
608
                                ignore_failures=opts.ignore_failures)
609
  SubmitOrSend(op, opts, cl=cl)
610
  return 0
611

    
612

    
613
def RenameInstance(opts, args):
614
  """Rename an instance.
615

    
616
  @param opts: the command line options selected by the user
617
  @type args: list
618
  @param args: should contain two elements, the old and the
619
      new instance names
620
  @rtype: int
621
  @return: the desired exit code
622

    
623
  """
624
  op = opcodes.OpRenameInstance(instance_name=args[0],
625
                                new_name=args[1],
626
                                ignore_ip=opts.ignore_ip)
627
  SubmitOrSend(op, opts)
628
  return 0
629

    
630

    
631
def ActivateDisks(opts, args):
632
  """Activate an instance's disks.
633

    
634
  This serves two purposes:
635
    - it allows (as long as the instance is not running)
636
      mounting the disks and modifying them from the node
637
    - it repairs inactive secondary drbds
638

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

    
645
  """
646
  instance_name = args[0]
647
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
648
  disks_info = SubmitOrSend(op, opts)
649
  for host, iname, nname in disks_info:
650
    ToStdout("%s:%s:%s", host, iname, nname)
651
  return 0
652

    
653

    
654
def DeactivateDisks(opts, args):
655
  """Deactivate an instance's disks..
656

    
657
  This function takes the instance name, looks for its primary node
658
  and the tries to shutdown its block devices on that node.
659

    
660
  @param opts: the command line options selected by the user
661
  @type args: list
662
  @param args: should contain only one element, the instance name
663
  @rtype: int
664
  @return: the desired exit code
665

    
666
  """
667
  instance_name = args[0]
668
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
669
  SubmitOrSend(op, opts)
670
  return 0
671

    
672

    
673
def GrowDisk(opts, args):
674
  """Grow an instance's disks.
675

    
676
  @param opts: the command line options selected by the user
677
  @type args: list
678
  @param args: should contain two elements, the instance name
679
      whose disks we grow and the disk name, e.g. I{sda}
680
  @rtype: int
681
  @return: the desired exit code
682

    
683
  """
684
  instance = args[0]
685
  disk = args[1]
686
  try:
687
    disk = int(disk)
688
  except ValueError, err:
689
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
690
  amount = utils.ParseUnit(args[2])
691
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
692
                          wait_for_sync=opts.wait_for_sync)
693
  SubmitOrSend(op, opts)
694
  return 0
695

    
696

    
697
def StartupInstance(opts, args):
698
  """Startup instances.
699

    
700
  Depending on the options given, this will start one or more
701
  instances.
702

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

    
711
  """
712
  cl = GetClient()
713
  if opts.multi_mode is None:
714
    opts.multi_mode = _SHUTDOWN_INSTANCES
715
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
716
  if not inames:
717
    raise errors.OpPrereqError("Selection filter does not match any instances")
718
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
719
  if not (opts.force_multi or not multi_on
720
          or _ConfirmOperation(inames, "startup")):
721
    return 1
722
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
723
  for name in inames:
724
    op = opcodes.OpStartupInstance(instance_name=name,
725
                                   force=opts.force)
726
    # do not add these parameters to the opcode unless they're defined
727
    if opts.hvparams:
728
      op.hvparams = opts.hvparams
729
    if opts.beparams:
730
      op.beparams = opts.beparams
731
    jex.QueueJob(name, op)
732
  jex.WaitOrShow(not opts.submit_only)
733
  return 0
734

    
735

    
736
def RebootInstance(opts, args):
737
  """Reboot instance(s).
738

    
739
  Depending on the parameters given, this will reboot one or more
740
  instances.
741

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

    
750
  """
751
  cl = GetClient()
752
  if opts.multi_mode is None:
753
    opts.multi_mode = _SHUTDOWN_INSTANCES
754
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
755
  if not inames:
756
    raise errors.OpPrereqError("Selection filter does not match any instances")
757
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
758
  if not (opts.force_multi or not multi_on
759
          or _ConfirmOperation(inames, "reboot")):
760
    return 1
761
  jex = JobExecutor(verbose=multi_on, cl=cl)
762
  for name in inames:
763
    op = opcodes.OpRebootInstance(instance_name=name,
764
                                  reboot_type=opts.reboot_type,
765
                                  ignore_secondaries=opts.ignore_secondaries)
766
    jex.QueueJob(name, op)
767
  jex.WaitOrShow(not opts.submit_only)
768
  return 0
769

    
770

    
771
def ShutdownInstance(opts, args):
772
  """Shutdown an instance.
773

    
774
  @param opts: the command line options selected by the user
775
  @type args: list
776
  @param args: the instance or node names based on which we
777
      create the final selection (in conjunction with the
778
      opts argument)
779
  @rtype: int
780
  @return: the desired exit code
781

    
782
  """
783
  cl = GetClient()
784
  if opts.multi_mode is None:
785
    opts.multi_mode = _SHUTDOWN_INSTANCES
786
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
787
  if not inames:
788
    raise errors.OpPrereqError("Selection filter does not match any instances")
789
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
790
  if not (opts.force_multi or not multi_on
791
          or _ConfirmOperation(inames, "shutdown")):
792
    return 1
793

    
794
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
795
  for name in inames:
796
    op = opcodes.OpShutdownInstance(instance_name=name)
797
    jex.QueueJob(name, op)
798
  jex.WaitOrShow(not opts.submit_only)
799
  return 0
800

    
801

    
802
def ReplaceDisks(opts, args):
803
  """Replace the disks of an instance
804

    
805
  @param opts: the command line options selected by the user
806
  @type args: list
807
  @param args: should contain only one element, the instance name
808
  @rtype: int
809
  @return: the desired exit code
810

    
811
  """
812
  instance_name = args[0]
813
  new_2ndary = opts.new_secondary
814
  iallocator = opts.iallocator
815
  if opts.disks is None:
816
    disks = []
817
  else:
818
    try:
819
      disks = [int(i) for i in opts.disks.split(",")]
820
    except ValueError, err:
821
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
822
  cnt = [opts.on_primary, opts.on_secondary,
823
         new_2ndary is not None, iallocator is not None].count(True)
824
  if cnt != 1:
825
    raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
826
                               " options must be passed")
827
  elif opts.on_primary:
828
    mode = constants.REPLACE_DISK_PRI
829
  elif opts.on_secondary:
830
    mode = constants.REPLACE_DISK_SEC
831
  elif new_2ndary is not None or iallocator is not None:
832
    # replace secondary
833
    mode = constants.REPLACE_DISK_CHG
834

    
835
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
836
                              remote_node=new_2ndary, mode=mode,
837
                              iallocator=iallocator)
838
  SubmitOrSend(op, opts)
839
  return 0
840

    
841

    
842
def FailoverInstance(opts, args):
843
  """Failover an instance.
844

    
845
  The failover is done by shutting it down on its present node and
846
  starting it on the secondary.
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
    usertext = ("Failover will happen to image %s."
863
                " This requires a shutdown of the instance. Continue?" %
864
                (instance_name,))
865
    if not AskUser(usertext):
866
      return 1
867

    
868
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
869
                                  ignore_consistency=opts.ignore_consistency)
870
  SubmitOrSend(op, opts, cl=cl)
871
  return 0
872

    
873

    
874
def MigrateInstance(opts, args):
875
  """Migrate an instance.
876

    
877
  The migrate is done without shutdown.
878

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

    
885
  """
886
  cl = GetClient()
887
  instance_name = args[0]
888
  force = opts.force
889

    
890
  if not force:
891
    _EnsureInstancesExist(cl, [instance_name])
892

    
893
    if opts.cleanup:
894
      usertext = ("Instance %s will be recovered from a failed migration."
895
                  " Note that the migration procedure (including cleanup)" %
896
                  (instance_name,))
897
    else:
898
      usertext = ("Instance %s will be migrated. Note that migration" %
899
                  (instance_name,))
900
    usertext += (" is **experimental** in this version."
901
                " This might impact the instance if anything goes wrong."
902
                " Continue?")
903
    if not AskUser(usertext):
904
      return 1
905

    
906
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
907
                                 cleanup=opts.cleanup)
908
  SubmitOpCode(op, cl=cl)
909
  return 0
910

    
911

    
912
def ConnectToInstanceConsole(opts, args):
913
  """Connect to the console of an instance.
914

    
915
  @param opts: the command line options selected by the user
916
  @type args: list
917
  @param args: should contain only one element, the instance name
918
  @rtype: int
919
  @return: the desired exit code
920

    
921
  """
922
  instance_name = args[0]
923

    
924
  op = opcodes.OpConnectConsole(instance_name=instance_name)
925
  cmd = SubmitOpCode(op)
926

    
927
  if opts.show_command:
928
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
929
  else:
930
    try:
931
      os.execvp(cmd[0], cmd)
932
    finally:
933
      ToStderr("Can't run console command %s with arguments:\n'%s'",
934
               cmd[0], " ".join(cmd))
935
      os._exit(1)
936

    
937

    
938
def _FormatLogicalID(dev_type, logical_id):
939
  """Formats the logical_id of a disk.
940

    
941
  """
942
  if dev_type == constants.LD_DRBD8:
943
    node_a, node_b, port, minor_a, minor_b, key = logical_id
944
    data = [
945
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
946
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
947
      ("port", port),
948
      ("auth key", key),
949
      ]
950
  elif dev_type == constants.LD_LV:
951
    vg_name, lv_name = logical_id
952
    data = ["%s/%s" % (vg_name, lv_name)]
953
  else:
954
    data = [str(logical_id)]
955

    
956
  return data
957

    
958

    
959
def _FormatBlockDevInfo(idx, top_level, dev, static):
960
  """Show block device information.
961

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

    
965
  @type idx: int
966
  @param idx: the index of the current disk
967
  @type top_level: boolean
968
  @param top_level: if this a top-level disk?
969
  @type dev: dict
970
  @param dev: dictionary with disk information
971
  @type static: boolean
972
  @param static: wheter the device information doesn't contain
973
      runtime information but only static data
974
  @return: a list of either strings, tuples or lists
975
      (which should be formatted at a higher indent level)
976

    
977
  """
978
  def helper(dtype, status):
979
    """Format one line for physical device status.
980

    
981
    @type dtype: str
982
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
983
    @type status: tuple
984
    @param status: a tuple as returned from L{backend.FindBlockDevice}
985
    @return: the string representing the status
986

    
987
    """
988
    if not status:
989
      return "not active"
990
    txt = ""
991
    (path, major, minor, syncp, estt, degr, ldisk) = status
992
    if major is None:
993
      major_string = "N/A"
994
    else:
995
      major_string = str(major)
996

    
997
    if minor is None:
998
      minor_string = "N/A"
999
    else:
1000
      minor_string = str(minor)
1001

    
1002
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1003
    if dtype in (constants.LD_DRBD8, ):
1004
      if syncp is not None:
1005
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1006
        if estt:
1007
          sync_text += " ETA %ds" % estt
1008
        else:
1009
          sync_text += " ETA unknown"
1010
      else:
1011
        sync_text = "in sync"
1012
      if degr:
1013
        degr_text = "*DEGRADED*"
1014
      else:
1015
        degr_text = "ok"
1016
      if ldisk:
1017
        ldisk_text = " *MISSING DISK*"
1018
      else:
1019
        ldisk_text = ""
1020
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1021
    elif dtype == constants.LD_LV:
1022
      if ldisk:
1023
        ldisk_text = " *FAILED* (failed drive?)"
1024
      else:
1025
        ldisk_text = ""
1026
      txt += ldisk_text
1027
    return txt
1028

    
1029
  # the header
1030
  if top_level:
1031
    if dev["iv_name"] is not None:
1032
      txt = dev["iv_name"]
1033
    else:
1034
      txt = "disk %d" % idx
1035
  else:
1036
    txt = "child %d" % idx
1037
  d1 = ["- %s: %s" % (txt, dev["dev_type"])]
1038
  data = []
1039
  if top_level:
1040
    data.append(("access mode", dev["mode"]))
1041
  if dev["logical_id"] is not None:
1042
    try:
1043
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1044
    except ValueError:
1045
      l_id = [str(dev["logical_id"])]
1046
    if len(l_id) == 1:
1047
      data.append(("logical_id", l_id[0]))
1048
    else:
1049
      data.extend(l_id)
1050
  elif dev["physical_id"] is not None:
1051
    data.append("physical_id:")
1052
    data.append([dev["physical_id"]])
1053
  if not static:
1054
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1055
  if dev["sstatus"] and not static:
1056
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1057

    
1058
  if dev["children"]:
1059
    data.append("child devices:")
1060
    for c_idx, child in enumerate(dev["children"]):
1061
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1062
  d1.append(data)
1063
  return d1
1064

    
1065

    
1066
def _FormatList(buf, data, indent_level):
1067
  """Formats a list of data at a given indent level.
1068

    
1069
  If the element of the list is:
1070
    - a string, it is simply formatted as is
1071
    - a tuple, it will be split into key, value and the all the
1072
      values in a list will be aligned all at the same start column
1073
    - a list, will be recursively formatted
1074

    
1075
  @type buf: StringIO
1076
  @param buf: the buffer into which we write the output
1077
  @param data: the list to format
1078
  @type indent_level: int
1079
  @param indent_level: the indent level to format at
1080

    
1081
  """
1082
  max_tlen = max([len(elem[0]) for elem in data
1083
                 if isinstance(elem, tuple)] or [0])
1084
  for elem in data:
1085
    if isinstance(elem, basestring):
1086
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1087
    elif isinstance(elem, tuple):
1088
      key, value = elem
1089
      spacer = "%*s" % (max_tlen - len(key), "")
1090
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1091
    elif isinstance(elem, list):
1092
      _FormatList(buf, elem, indent_level+1)
1093

    
1094
def ShowInstanceConfig(opts, args):
1095
  """Compute instance run-time status.
1096

    
1097
  @param opts: the command line options selected by the user
1098
  @type args: list
1099
  @param args: either an empty list, and then we query all
1100
      instances, or should contain a list of instance names
1101
  @rtype: int
1102
  @return: the desired exit code
1103

    
1104
  """
1105
  if not args and not opts.show_all:
1106
    ToStderr("No instance selected."
1107
             " Please pass in --all if you want to query all instances.\n"
1108
             "Note that this can take a long time on a big cluster.")
1109
    return 1
1110
  elif args and opts.show_all:
1111
    ToStderr("Cannot use --all if you specify instance names.")
1112
    return 1
1113

    
1114
  retcode = 0
1115
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1116
  result = SubmitOpCode(op)
1117
  if not result:
1118
    ToStdout("No instances.")
1119
    return 1
1120

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

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

    
1160
    for key in instance["hv_actual"]:
1161
      if key in instance["hv_instance"]:
1162
        val = instance["hv_instance"][key]
1163
      else:
1164
        val = "default (%s)" % instance["hv_actual"][key]
1165
      buf.write("    - %s: %s\n" % (key, val))
1166
    buf.write("  Hardware:\n")
1167
    buf.write("    - VCPUs: %d\n" %
1168
              instance["be_actual"][constants.BE_VCPUS])
1169
    buf.write("    - memory: %dMiB\n" %
1170
              instance["be_actual"][constants.BE_MEMORY])
1171
    buf.write("    - NICs:\n")
1172
    for idx, (mac, ip, mode, link) in enumerate(instance["nics"]):
1173
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1174
                (idx, mac, ip, mode, link))
1175
    buf.write("  Disks:\n")
1176

    
1177
    for idx, device in enumerate(instance["disks"]):
1178
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1179

    
1180
  ToStdout(buf.getvalue().rstrip('\n'))
1181
  return retcode
1182

    
1183

    
1184
def SetInstanceParams(opts, args):
1185
  """Modifies an instance.
1186

    
1187
  All parameters take effect only at the next restart of the instance.
1188

    
1189
  @param opts: the command line options selected by the user
1190
  @type args: list
1191
  @param args: should contain only one element, the instance name
1192
  @rtype: int
1193
  @return: the desired exit code
1194

    
1195
  """
1196
  if not (opts.nics or opts.disks or
1197
          opts.hypervisor or opts.beparams):
1198
    ToStderr("Please give at least one of the parameters.")
1199
    return 1
1200

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

    
1206
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1207
                      allowed_values=[constants.VALUE_DEFAULT])
1208

    
1209
  for param in opts.hypervisor:
1210
    if isinstance(opts.hypervisor[param], basestring):
1211
      if opts.hypervisor[param].lower() == "default":
1212
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1213

    
1214
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1215
                      allowed_values=[constants.VALUE_DEFAULT])
1216

    
1217
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1218
    try:
1219
      nic_op = int(nic_op)
1220
      opts.nics[idx] = (nic_op, nic_dict)
1221
    except ValueError:
1222
      pass
1223

    
1224
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1225
    try:
1226
      disk_op = int(disk_op)
1227
      opts.disks[idx] = (disk_op, disk_dict)
1228
    except ValueError:
1229
      pass
1230
    if disk_op == constants.DDM_ADD:
1231
      if 'size' not in disk_dict:
1232
        raise errors.OpPrereqError("Missing required parameter 'size'")
1233
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1234

    
1235
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1236
                                   nics=opts.nics,
1237
                                   disks=opts.disks,
1238
                                   hvparams=opts.hypervisor,
1239
                                   beparams=opts.beparams,
1240
                                   force=opts.force)
1241

    
1242
  # even if here we process the result, we allow submit only
1243
  result = SubmitOrSend(op, opts)
1244

    
1245
  if result:
1246
    ToStdout("Modified instance %s", args[0])
1247
    for param, data in result:
1248
      ToStdout(" - %-5s -> %s", param, data)
1249
    ToStdout("Please don't forget that these parameters take effect"
1250
             " only at the next start of the instance.")
1251
  return 0
1252

    
1253

    
1254
# options used in more than one cmd
1255
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1256
                       metavar="<node>")
1257

    
1258
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1259
                    metavar="<os>")
1260

    
1261
# multi-instance selection options
1262
m_force_multi = make_option("--force-multiple", dest="force_multi",
1263
                            help="Do not ask for confirmation when more than"
1264
                            " one instance is affected",
1265
                            action="store_true", default=False)
1266

    
1267
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1268
                             help="Filter by nodes (primary only)",
1269
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1270

    
1271
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1272
                             help="Filter by nodes (secondary only)",
1273
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1274

    
1275
m_node_opt = make_option("--node", dest="multi_mode",
1276
                         help="Filter by nodes (primary and secondary)",
1277
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1278

    
1279
m_clust_opt = make_option("--all", dest="multi_mode",
1280
                          help="Select all instances in the cluster",
1281
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1282

    
1283
m_inst_opt = make_option("--instance", dest="multi_mode",
1284
                         help="Filter by instance name [default]",
1285
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1286

    
1287

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

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

    
1505
  'reboot': (RebootInstance, ARGS_ANY,
1506
              [DEBUG_OPT, m_force_multi,
1507
               make_option("-t", "--type", dest="reboot_type",
1508
                           help="Type of reboot: soft/hard/full",
1509
                           default=constants.INSTANCE_REBOOT_HARD,
1510
                           type="string", metavar="<REBOOT>"),
1511
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1512
                           default=False, action="store_true",
1513
                           help="Ignore errors from secondaries"),
1514
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1515
               m_clust_opt, m_inst_opt,
1516
               SUBMIT_OPT,
1517
               ],
1518
            "<instance>", "Reboots an instance"),
1519
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
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}))