Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 8b46606c

History | View | Annotate | Download (55.2 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
      "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
      if not isinstance(ndict, dict):
306
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
307
        raise errors.OpPrereqError(msg)
308
      nics[nidx] = ndict
309
  elif opts.no_nics:
310
    # no nics
311
    nics = []
312
  else:
313
    # default of one nic, all auto
314
    nics = [{}]
315

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

    
348
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
349
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
350

    
351
  op = opcodes.OpCreateInstance(instance_name=instance,
352
                                disks=disks,
353
                                disk_template=opts.disk_template,
354
                                nics=nics,
355
                                mode=constants.INSTANCE_CREATE,
356
                                os_type=opts.os, pnode=pnode,
357
                                snode=snode,
358
                                start=opts.start, ip_check=opts.ip_check,
359
                                wait_for_sync=opts.wait_for_sync,
360
                                hypervisor=hypervisor,
361
                                hvparams=hvparams,
362
                                beparams=opts.beparams,
363
                                iallocator=opts.iallocator,
364
                                file_storage_dir=opts.file_storage_dir,
365
                                file_driver=opts.file_driver,
366
                                )
367

    
368
  SubmitOrSend(op, opts)
369
  return 0
370

    
371

    
372
def BatchCreate(opts, args):
373
  """Create instances using a definition file.
374

    
375
  This function reads a json file with instances defined
376
  in the form::
377

    
378
    {"instance-name":{
379
      "disk_size": [20480],
380
      "template": "drbd",
381
      "backend": {
382
        "memory": 512,
383
        "vcpus": 1 },
384
      "os": "debootstrap",
385
      "primary_node": "firstnode",
386
      "secondary_node": "secondnode",
387
      "iallocator": "dumb"}
388
    }
389

    
390
  Note that I{primary_node} and I{secondary_node} have precedence over
391
  I{iallocator}.
392

    
393
  @param opts: the command line options selected by the user
394
  @type args: list
395
  @param args: should contain one element, the json filename
396
  @rtype: int
397
  @return: the desired exit code
398

    
399
  """
400
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
401
                    "backend": {},
402
                    "iallocator": None,
403
                    "primary_node": None,
404
                    "secondary_node": None,
405
                    "nics": None,
406
                    "start": True,
407
                    "ip_check": True,
408
                    "hypervisor": None,
409
                    "hvparams": {},
410
                    "file_storage_dir": None,
411
                    "file_driver": 'loop'}
412

    
413
  def _PopulateWithDefaults(spec):
414
    """Returns a new hash combined with default values."""
415
    mydict = _DEFAULT_SPECS.copy()
416
    mydict.update(spec)
417
    return mydict
418

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

    
436
    if (spec['hvparams'] and
437
        not isinstance(spec['hvparams'], dict)):
438
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
439

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

    
449
  jex = JobExecutor()
450

    
451
  # Iterate over the instances and do:
452
  #  * Populate the specs with default value
453
  #  * Validate the instance specs
454
  i_names = utils.NiceSort(instance_data.keys())
455
  for name in i_names:
456
    specs = instance_data[name]
457
    specs = _PopulateWithDefaults(specs)
458
    _Validate(specs)
459

    
460
    hypervisor = specs['hypervisor']
461
    hvparams = specs['hvparams']
462

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

    
473
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
474
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
475

    
476
    tmp_nics = []
477
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
478
      if field in specs:
479
        if not tmp_nics:
480
          tmp_nics.append({})
481
        tmp_nics[0][field] = specs[field]
482

    
483
    if specs['nics'] is not None and tmp_nics:
484
      raise errors.OpPrereqError("'nics' list incompatible with using"
485
                                 " individual nic fields as well")
486
    elif specs['nics'] is not None:
487
      tmp_nics = specs['nics']
488
    elif not tmp_nics:
489
      tmp_nics = [{}]
490

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

    
509
    jex.QueueJob(name, op)
510
  # we never want to wait, just show the submitted job IDs
511
  jex.WaitOrShow(False)
512

    
513
  return 0
514

    
515

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

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

    
526
  """
527
  # first, compute the desired name list
528
  if opts.multi_mode is None:
529
    opts.multi_mode = _SHUTDOWN_INSTANCES
530

    
531
  inames = _ExpandMultiNames(opts.multi_mode, args)
532
  if not inames:
533
    raise errors.OpPrereqError("Selection filter does not match any instances")
534

    
535
  # second, if requested, ask for an OS
536
  if opts.select_os is True:
537
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
538
    result = SubmitOpCode(op)
539

    
540
    if not result:
541
      ToStdout("Can't get the OS list")
542
      return 1
543

    
544
    ToStdout("Available OS templates:")
545
    number = 0
546
    choices = []
547
    for entry in result:
548
      ToStdout("%3s: %s", number, entry[0])
549
      choices.append(("%s" % number, entry[0], entry[0]))
550
      number = number + 1
551

    
552
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
553
    selected = AskUser("Enter OS template number (or x to abort):",
554
                       choices)
555

    
556
    if selected == 'exit':
557
      ToStderr("User aborted reinstall, exiting")
558
      return 1
559

    
560
    os_name = selected
561
  else:
562
    os_name = opts.os
563

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

    
579
  jex = JobExecutor(verbose=multi_on)
580
  for instance_name in inames:
581
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
582
                                     os_type=os_name)
583
    jex.QueueJob(instance_name, op)
584

    
585
  jex.WaitOrShow(not opts.submit_only)
586
  return 0
587

    
588

    
589
def RemoveInstance(opts, args):
590
  """Remove an instance.
591

    
592
  @param opts: the command line options selected by the user
593
  @type args: list
594
  @param args: should contain only one element, the name of
595
      the instance to be removed
596
  @rtype: int
597
  @return: the desired exit code
598

    
599
  """
600
  instance_name = args[0]
601
  force = opts.force
602
  cl = GetClient()
603

    
604
  if not force:
605
    _EnsureInstancesExist(cl, [instance_name])
606

    
607
    usertext = ("This will remove the volumes of the instance %s"
608
                " (including mirrors), thus removing all the data"
609
                " of the instance. Continue?") % instance_name
610
    if not AskUser(usertext):
611
      return 1
612

    
613
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
614
                                ignore_failures=opts.ignore_failures)
615
  SubmitOrSend(op, opts, cl=cl)
616
  return 0
617

    
618

    
619
def RenameInstance(opts, args):
620
  """Rename an instance.
621

    
622
  @param opts: the command line options selected by the user
623
  @type args: list
624
  @param args: should contain two elements, the old and the
625
      new instance names
626
  @rtype: int
627
  @return: the desired exit code
628

    
629
  """
630
  op = opcodes.OpRenameInstance(instance_name=args[0],
631
                                new_name=args[1],
632
                                ignore_ip=opts.ignore_ip)
633
  SubmitOrSend(op, opts)
634
  return 0
635

    
636

    
637
def ActivateDisks(opts, args):
638
  """Activate an instance's disks.
639

    
640
  This serves two purposes:
641
    - it allows (as long as the instance is not running)
642
      mounting the disks and modifying them from the node
643
    - it repairs inactive secondary drbds
644

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

    
651
  """
652
  instance_name = args[0]
653
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
654
  disks_info = SubmitOrSend(op, opts)
655
  for host, iname, nname in disks_info:
656
    ToStdout("%s:%s:%s", host, iname, nname)
657
  return 0
658

    
659

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

    
663
  This function takes the instance name, looks for its primary node
664
  and the tries to shutdown its block devices on that node.
665

    
666
  @param opts: the command line options selected by the user
667
  @type args: list
668
  @param args: should contain only one element, the instance name
669
  @rtype: int
670
  @return: the desired exit code
671

    
672
  """
673
  instance_name = args[0]
674
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
675
  SubmitOrSend(op, opts)
676
  return 0
677

    
678

    
679
def GrowDisk(opts, args):
680
  """Grow an instance's disks.
681

    
682
  @param opts: the command line options selected by the user
683
  @type args: list
684
  @param args: should contain two elements, the instance name
685
      whose disks we grow and the disk name, e.g. I{sda}
686
  @rtype: int
687
  @return: the desired exit code
688

    
689
  """
690
  instance = args[0]
691
  disk = args[1]
692
  try:
693
    disk = int(disk)
694
  except ValueError, err:
695
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
696
  amount = utils.ParseUnit(args[2])
697
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
698
                          wait_for_sync=opts.wait_for_sync)
699
  SubmitOrSend(op, opts)
700
  return 0
701

    
702

    
703
def StartupInstance(opts, args):
704
  """Startup instances.
705

    
706
  Depending on the options given, this will start one or more
707
  instances.
708

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

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

    
741

    
742
def RebootInstance(opts, args):
743
  """Reboot instance(s).
744

    
745
  Depending on the parameters given, this will reboot one or more
746
  instances.
747

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

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

    
776

    
777
def ShutdownInstance(opts, args):
778
  """Shutdown an instance.
779

    
780
  @param opts: the command line options selected by the user
781
  @type args: list
782
  @param args: the instance or node names based on which we
783
      create the final selection (in conjunction with the
784
      opts argument)
785
  @rtype: int
786
  @return: the desired exit code
787

    
788
  """
789
  cl = GetClient()
790
  if opts.multi_mode is None:
791
    opts.multi_mode = _SHUTDOWN_INSTANCES
792
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
793
  if not inames:
794
    raise errors.OpPrereqError("Selection filter does not match any instances")
795
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
796
  if not (opts.force_multi or not multi_on
797
          or _ConfirmOperation(inames, "shutdown")):
798
    return 1
799

    
800
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
801
  for name in inames:
802
    op = opcodes.OpShutdownInstance(instance_name=name)
803
    jex.QueueJob(name, op)
804
  jex.WaitOrShow(not opts.submit_only)
805
  return 0
806

    
807

    
808
def ReplaceDisks(opts, args):
809
  """Replace the disks of an instance
810

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

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

    
841
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
842
                              remote_node=new_2ndary, mode=mode,
843
                              iallocator=iallocator)
844
  SubmitOrSend(op, opts)
845
  return 0
846

    
847

    
848
def FailoverInstance(opts, args):
849
  """Failover an instance.
850

    
851
  The failover is done by shutting it down on its present node and
852
  starting it on the secondary.
853

    
854
  @param opts: the command line options selected by the user
855
  @type args: list
856
  @param args: should contain only one element, the instance name
857
  @rtype: int
858
  @return: the desired exit code
859

    
860
  """
861
  cl = GetClient()
862
  instance_name = args[0]
863
  force = opts.force
864

    
865
  if not force:
866
    _EnsureInstancesExist(cl, [instance_name])
867

    
868
    usertext = ("Failover will happen to image %s."
869
                " This requires a shutdown of the instance. Continue?" %
870
                (instance_name,))
871
    if not AskUser(usertext):
872
      return 1
873

    
874
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
875
                                  ignore_consistency=opts.ignore_consistency)
876
  SubmitOrSend(op, opts, cl=cl)
877
  return 0
878

    
879

    
880
def MigrateInstance(opts, args):
881
  """Migrate an instance.
882

    
883
  The migrate is done without shutdown.
884

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

    
891
  """
892
  cl = GetClient()
893
  instance_name = args[0]
894
  force = opts.force
895

    
896
  if not force:
897
    _EnsureInstancesExist(cl, [instance_name])
898

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

    
912
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
913
                                 cleanup=opts.cleanup)
914
  SubmitOpCode(op, cl=cl)
915
  return 0
916

    
917

    
918
def ConnectToInstanceConsole(opts, args):
919
  """Connect to the console of an instance.
920

    
921
  @param opts: the command line options selected by the user
922
  @type args: list
923
  @param args: should contain only one element, the instance name
924
  @rtype: int
925
  @return: the desired exit code
926

    
927
  """
928
  instance_name = args[0]
929

    
930
  op = opcodes.OpConnectConsole(instance_name=instance_name)
931
  cmd = SubmitOpCode(op)
932

    
933
  if opts.show_command:
934
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
935
  else:
936
    try:
937
      os.execvp(cmd[0], cmd)
938
    finally:
939
      ToStderr("Can't run console command %s with arguments:\n'%s'",
940
               cmd[0], " ".join(cmd))
941
      os._exit(1)
942

    
943

    
944
def _FormatLogicalID(dev_type, logical_id):
945
  """Formats the logical_id of a disk.
946

    
947
  """
948
  if dev_type == constants.LD_DRBD8:
949
    node_a, node_b, port, minor_a, minor_b, key = logical_id
950
    data = [
951
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
952
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
953
      ("port", port),
954
      ("auth key", key),
955
      ]
956
  elif dev_type == constants.LD_LV:
957
    vg_name, lv_name = logical_id
958
    data = ["%s/%s" % (vg_name, lv_name)]
959
  else:
960
    data = [str(logical_id)]
961

    
962
  return data
963

    
964

    
965
def _FormatBlockDevInfo(idx, top_level, dev, static):
966
  """Show block device information.
967

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

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

    
983
  """
984
  def helper(dtype, status):
985
    """Format one line for physical device status.
986

    
987
    @type dtype: str
988
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
989
    @type status: tuple
990
    @param status: a tuple as returned from L{backend.FindBlockDevice}
991
    @return: the string representing the status
992

    
993
    """
994
    if not status:
995
      return "not active"
996
    txt = ""
997
    (path, major, minor, syncp, estt, degr, ldisk) = status
998
    if major is None:
999
      major_string = "N/A"
1000
    else:
1001
      major_string = str(major)
1002

    
1003
    if minor is None:
1004
      minor_string = "N/A"
1005
    else:
1006
      minor_string = str(minor)
1007

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

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

    
1064
  if dev["children"]:
1065
    data.append("child devices:")
1066
    for c_idx, child in enumerate(dev["children"]):
1067
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1068
  d1.append(data)
1069
  return d1
1070

    
1071

    
1072
def _FormatList(buf, data, indent_level):
1073
  """Formats a list of data at a given indent level.
1074

    
1075
  If the element of the list is:
1076
    - a string, it is simply formatted as is
1077
    - a tuple, it will be split into key, value and the all the
1078
      values in a list will be aligned all at the same start column
1079
    - a list, will be recursively formatted
1080

    
1081
  @type buf: StringIO
1082
  @param buf: the buffer into which we write the output
1083
  @param data: the list to format
1084
  @type indent_level: int
1085
  @param indent_level: the indent level to format at
1086

    
1087
  """
1088
  max_tlen = max([len(elem[0]) for elem in data
1089
                 if isinstance(elem, tuple)] or [0])
1090
  for elem in data:
1091
    if isinstance(elem, basestring):
1092
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1093
    elif isinstance(elem, tuple):
1094
      key, value = elem
1095
      spacer = "%*s" % (max_tlen - len(key), "")
1096
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1097
    elif isinstance(elem, list):
1098
      _FormatList(buf, elem, indent_level+1)
1099

    
1100
def ShowInstanceConfig(opts, args):
1101
  """Compute instance run-time status.
1102

    
1103
  @param opts: the command line options selected by the user
1104
  @type args: list
1105
  @param args: either an empty list, and then we query all
1106
      instances, or should contain a list of instance names
1107
  @rtype: int
1108
  @return: the desired exit code
1109

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

    
1120
  retcode = 0
1121
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1122
  result = SubmitOpCode(op)
1123
  if not result:
1124
    ToStdout("No instances.")
1125
    return 1
1126

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

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

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

    
1183
    for idx, device in enumerate(instance["disks"]):
1184
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1185

    
1186
  ToStdout(buf.getvalue().rstrip('\n'))
1187
  return retcode
1188

    
1189

    
1190
def SetInstanceParams(opts, args):
1191
  """Modifies an instance.
1192

    
1193
  All parameters take effect only at the next restart of the instance.
1194

    
1195
  @param opts: the command line options selected by the user
1196
  @type args: list
1197
  @param args: should contain only one element, the instance name
1198
  @rtype: int
1199
  @return: the desired exit code
1200

    
1201
  """
1202
  if not (opts.nics or opts.disks or
1203
          opts.hypervisor or opts.beparams):
1204
    ToStderr("Please give at least one of the parameters.")
1205
    return 1
1206

    
1207
  for param in opts.beparams:
1208
    if isinstance(opts.beparams[param], basestring):
1209
      if opts.beparams[param].lower() == "default":
1210
        opts.beparams[param] = constants.VALUE_DEFAULT
1211

    
1212
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1213
                      allowed_values=[constants.VALUE_DEFAULT])
1214

    
1215
  for param in opts.hypervisor:
1216
    if isinstance(opts.hypervisor[param], basestring):
1217
      if opts.hypervisor[param].lower() == "default":
1218
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1219

    
1220
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1221
                      allowed_values=[constants.VALUE_DEFAULT])
1222

    
1223
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1224
    try:
1225
      nic_op = int(nic_op)
1226
      opts.nics[idx] = (nic_op, nic_dict)
1227
    except ValueError:
1228
      pass
1229

    
1230
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1231
    try:
1232
      disk_op = int(disk_op)
1233
      opts.disks[idx] = (disk_op, disk_dict)
1234
    except ValueError:
1235
      pass
1236
    if disk_op == constants.DDM_ADD:
1237
      if 'size' not in disk_dict:
1238
        raise errors.OpPrereqError("Missing required parameter 'size'")
1239
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1240

    
1241
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1242
                                   nics=opts.nics,
1243
                                   disks=opts.disks,
1244
                                   hvparams=opts.hypervisor,
1245
                                   beparams=opts.beparams,
1246
                                   force=opts.force)
1247

    
1248
  # even if here we process the result, we allow submit only
1249
  result = SubmitOrSend(op, opts)
1250

    
1251
  if result:
1252
    ToStdout("Modified instance %s", args[0])
1253
    for param, data in result:
1254
      ToStdout(" - %-5s -> %s", param, data)
1255
    ToStdout("Please don't forget that these parameters take effect"
1256
             " only at the next start of the instance.")
1257
  return 0
1258

    
1259

    
1260
# options used in more than one cmd
1261
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1262
                       metavar="<node>")
1263

    
1264
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1265
                    metavar="<os>")
1266

    
1267
# multi-instance selection options
1268
m_force_multi = make_option("--force-multiple", dest="force_multi",
1269
                            help="Do not ask for confirmation when more than"
1270
                            " one instance is affected",
1271
                            action="store_true", default=False)
1272

    
1273
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1274
                             help="Filter by nodes (primary only)",
1275
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1276

    
1277
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1278
                             help="Filter by nodes (secondary only)",
1279
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1280

    
1281
m_node_opt = make_option("--node", dest="multi_mode",
1282
                         help="Filter by nodes (primary and secondary)",
1283
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1284

    
1285
m_clust_opt = make_option("--all", dest="multi_mode",
1286
                          help="Select all instances in the cluster",
1287
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1288

    
1289
m_inst_opt = make_option("--instance", dest="multi_mode",
1290
                         help="Filter by instance name [default]",
1291
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1292

    
1293

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

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

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

    
1547
#: dictionary with aliases for commands
1548
aliases = {
1549
  'activate_block_devs': 'activate-disks',
1550
  'replace_disks': 'replace-disks',
1551
  'start': 'startup',
1552
  'stop': 'shutdown',
1553
  }
1554

    
1555
if __name__ == '__main__':
1556
  sys.exit(GenericMain(commands, aliases=aliases,
1557
                       override={"tag_type": constants.TAG_INSTANCE}))