Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ b6e243ab

History | View | Annotate | Download (54.4 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
      "bridge": "Bridge",
205
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
206
      "disk_usage": "DiskUsage",
207
      "status": "Status", "tags": "Tags",
208
      "network_port": "Network_port",
209
      "hv/kernel_path": "Kernel_path",
210
      "hv/initrd_path": "Initrd_path",
211
      "hv/boot_order": "HVM_boot_order",
212
      "hv/acpi": "HVM_ACPI",
213
      "hv/pae": "HVM_PAE",
214
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
215
      "hv/nic_type": "HVM_NIC_type",
216
      "hv/disk_type": "HVM_Disk_type",
217
      "hv/vnc_bind_address": "VNC_bind_address",
218
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
219
      "hvparams": "Hypervisor_parameters",
220
      "be/memory": "Configured_memory",
221
      "be/vcpus": "VCPUs",
222
      "be/auto_balance": "Auto_balance",
223
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
224
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
225
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
226
      }
227
  else:
228
    headers = None
229

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

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

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

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

    
273
  return 0
274

    
275

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

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

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

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

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

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

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

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

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

    
360
  SubmitOrSend(op, opts)
361
  return 0
362

    
363

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

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

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

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

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

    
391
  """
392
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
393
                    "backend": {},
394
                    "iallocator": None,
395
                    "primary_node": None,
396
                    "secondary_node": None,
397
                    "ip": 'none',
398
                    "mac": 'auto',
399
                    "bridge": 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
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
468

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

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

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

    
494
  return 0
495

    
496

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

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

    
507
  """
508
  # first, compute the desired name list
509
  if opts.multi_mode is None:
510
    opts.multi_mode = _SHUTDOWN_INSTANCES
511

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

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

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

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

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

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

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

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

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

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

    
569

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

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

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

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

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

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

    
599

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

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

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

    
617

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

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

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

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

    
640

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

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

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

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

    
659

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

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

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

    
683

    
684
def StartupInstance(opts, args):
685
  """Startup instances.
686

    
687
  Depending on the options given, this will start one or more
688
  instances.
689

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

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

    
722

    
723
def RebootInstance(opts, args):
724
  """Reboot instance(s).
725

    
726
  Depending on the parameters given, this will reboot one or more
727
  instances.
728

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

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

    
757

    
758
def ShutdownInstance(opts, args):
759
  """Shutdown an instance.
760

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

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

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

    
788

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

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

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

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

    
828

    
829
def FailoverInstance(opts, args):
830
  """Failover an instance.
831

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

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

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

    
846
  if not force:
847
    _EnsureInstancesExist(cl, [instance_name])
848

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

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

    
860

    
861
def MigrateInstance(opts, args):
862
  """Migrate an instance.
863

    
864
  The migrate is done without shutdown.
865

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

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

    
877
  if not force:
878
    _EnsureInstancesExist(cl, [instance_name])
879

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

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

    
898

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

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

    
908
  """
909
  instance_name = args[0]
910

    
911
  op = opcodes.OpConnectConsole(instance_name=instance_name)
912
  cmd = SubmitOpCode(op)
913

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

    
924

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

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

    
943
  return data
944

    
945

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

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

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

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

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

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

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

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

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

    
1045
  if dev["children"]:
1046
    data.append("child devices:")
1047
    for c_idx, child in enumerate(dev["children"]):
1048
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1049
  d1.append(data)
1050
  return d1
1051

    
1052

    
1053
def _FormatList(buf, data, indent_level):
1054
  """Formats a list of data at a given indent level.
1055

    
1056
  If the element of the list is:
1057
    - a string, it is simply formatted as is
1058
    - a tuple, it will be split into key, value and the all the
1059
      values in a list will be aligned all at the same start column
1060
    - a list, will be recursively formatted
1061

    
1062
  @type buf: StringIO
1063
  @param buf: the buffer into which we write the output
1064
  @param data: the list to format
1065
  @type indent_level: int
1066
  @param indent_level: the indent level to format at
1067

    
1068
  """
1069
  max_tlen = max([len(elem[0]) for elem in data
1070
                 if isinstance(elem, tuple)] or [0])
1071
  for elem in data:
1072
    if isinstance(elem, basestring):
1073
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1074
    elif isinstance(elem, tuple):
1075
      key, value = elem
1076
      spacer = "%*s" % (max_tlen - len(key), "")
1077
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1078
    elif isinstance(elem, list):
1079
      _FormatList(buf, elem, indent_level+1)
1080

    
1081
def ShowInstanceConfig(opts, args):
1082
  """Compute instance run-time status.
1083

    
1084
  @param opts: the command line options selected by the user
1085
  @type args: list
1086
  @param args: either an empty list, and then we query all
1087
      instances, or should contain a list of instance names
1088
  @rtype: int
1089
  @return: the desired exit code
1090

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

    
1101
  retcode = 0
1102
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1103
  result = SubmitOpCode(op)
1104
  if not result:
1105
    ToStdout("No instances.")
1106
    return 1
1107

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

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

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

    
1164
    for idx, device in enumerate(instance["disks"]):
1165
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1166

    
1167
  ToStdout(buf.getvalue().rstrip('\n'))
1168
  return retcode
1169

    
1170

    
1171
def SetInstanceParams(opts, args):
1172
  """Modifies an instance.
1173

    
1174
  All parameters take effect only at the next restart of the instance.
1175

    
1176
  @param opts: the command line options selected by the user
1177
  @type args: list
1178
  @param args: should contain only one element, the instance name
1179
  @rtype: int
1180
  @return: the desired exit code
1181

    
1182
  """
1183
  if not (opts.nics or opts.disks or
1184
          opts.hypervisor or opts.beparams):
1185
    ToStderr("Please give at least one of the parameters.")
1186
    return 1
1187

    
1188
  for param in opts.beparams:
1189
    if isinstance(opts.beparams[param], basestring):
1190
      if opts.beparams[param].lower() == "default":
1191
        opts.beparams[param] = constants.VALUE_DEFAULT
1192

    
1193
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1194
                      allowed_values=[constants.VALUE_DEFAULT])
1195

    
1196
  for param in opts.hypervisor:
1197
    if isinstance(opts.hypervisor[param], basestring):
1198
      if opts.hypervisor[param].lower() == "default":
1199
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1200

    
1201
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1202
                      allowed_values=[constants.VALUE_DEFAULT])
1203

    
1204
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1205
    try:
1206
      nic_op = int(nic_op)
1207
      opts.nics[idx] = (nic_op, nic_dict)
1208
    except ValueError:
1209
      pass
1210

    
1211
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1212
    try:
1213
      disk_op = int(disk_op)
1214
      opts.disks[idx] = (disk_op, disk_dict)
1215
    except ValueError:
1216
      pass
1217
    if disk_op == constants.DDM_ADD:
1218
      if 'size' not in disk_dict:
1219
        raise errors.OpPrereqError("Missing required parameter 'size'")
1220
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1221

    
1222
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1223
                                   nics=opts.nics,
1224
                                   disks=opts.disks,
1225
                                   hvparams=opts.hypervisor,
1226
                                   beparams=opts.beparams,
1227
                                   force=opts.force)
1228

    
1229
  # even if here we process the result, we allow submit only
1230
  result = SubmitOrSend(op, opts)
1231

    
1232
  if result:
1233
    ToStdout("Modified instance %s", args[0])
1234
    for param, data in result:
1235
      ToStdout(" - %-5s -> %s", param, data)
1236
    ToStdout("Please don't forget that these parameters take effect"
1237
             " only at the next start of the instance.")
1238
  return 0
1239

    
1240

    
1241
# options used in more than one cmd
1242
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1243
                       metavar="<node>")
1244

    
1245
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1246
                    metavar="<os>")
1247

    
1248
# multi-instance selection options
1249
m_force_multi = make_option("--force-multiple", dest="force_multi",
1250
                            help="Do not ask for confirmation when more than"
1251
                            " one instance is affected",
1252
                            action="store_true", default=False)
1253

    
1254
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1255
                             help="Filter by nodes (primary only)",
1256
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1257

    
1258
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1259
                             help="Filter by nodes (secondary only)",
1260
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1261

    
1262
m_node_opt = make_option("--node", dest="multi_mode",
1263
                         help="Filter by nodes (primary and secondary)",
1264
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1265

    
1266
m_clust_opt = make_option("--all", dest="multi_mode",
1267
                          help="Select all instances in the cluster",
1268
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1269

    
1270
m_inst_opt = make_option("--instance", dest="multi_mode",
1271
                         help="Filter by instance name [default]",
1272
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1273

    
1274

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

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

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

    
1528
#: dictionary with aliases for commands
1529
aliases = {
1530
  'activate_block_devs': 'activate-disks',
1531
  'replace_disks': 'replace-disks',
1532
  'start': 'startup',
1533
  'stop': 'shutdown',
1534
  }
1535

    
1536
if __name__ == '__main__':
1537
  sys.exit(GenericMain(commands, aliases=aliases,
1538
                       override={"tag_type": constants.TAG_INSTANCE}))