Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 77921a95

History | View | Annotate | Download (52.9 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os
28
import itertools
29
import simplejson
30
from optparse import make_option
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import cli
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import utils
38
from ganeti import errors
39

    
40

    
41
_SHUTDOWN_CLUSTER = "cluster"
42
_SHUTDOWN_NODES_BOTH = "nodes"
43
_SHUTDOWN_NODES_PRI = "nodes-pri"
44
_SHUTDOWN_NODES_SEC = "nodes-sec"
45
_SHUTDOWN_INSTANCES = "instances"
46

    
47

    
48
_VALUE_TRUE = "true"
49

    
50
#: default list of options for L{ListInstances}
51
_LIST_DEF_FIELDS = [
52
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
53
  ]
54

    
55

    
56
def _ExpandMultiNames(mode, names, client=None):
57
  """Expand the given names using the passed mode.
58

    
59
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
60
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
61
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
62
  instances having those nodes as either primary or secondary will be
63
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
64
  returned.
65

    
66
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
67
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
68
      L{_SHUTDOWN_INSTANCES}
69
  @param names: a list of names; for cluster, it must be empty,
70
      and for node and instance it must be a list of valid item
71
      names (short names are valid as usual, e.g. node1 instead of
72
      node1.example.com)
73
  @rtype: list
74
  @return: the list of names after the expansion
75
  @raise errors.ProgrammerError: for unknown selection type
76
  @raise errors.OpPrereqError: for invalid input parameters
77

    
78
  """
79
  if client is None:
80
    client = GetClient()
81
  if mode == _SHUTDOWN_CLUSTER:
82
    if names:
83
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
84
    idata = client.QueryInstances([], ["name"], False)
85
    inames = [row[0] for row in idata]
86

    
87
  elif mode in (_SHUTDOWN_NODES_BOTH,
88
                _SHUTDOWN_NODES_PRI,
89
                _SHUTDOWN_NODES_SEC):
90
    if not names:
91
      raise errors.OpPrereqError("No node names passed")
92
    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
93
                              False)
94
    ipri = [row[1] for row in ndata]
95
    pri_names = list(itertools.chain(*ipri))
96
    isec = [row[2] for row in ndata]
97
    sec_names = list(itertools.chain(*isec))
98
    if mode == _SHUTDOWN_NODES_BOTH:
99
      inames = pri_names + sec_names
100
    elif mode == _SHUTDOWN_NODES_PRI:
101
      inames = pri_names
102
    elif mode == _SHUTDOWN_NODES_SEC:
103
      inames = sec_names
104
    else:
105
      raise errors.ProgrammerError("Unhandled shutdown type")
106

    
107
  elif mode == _SHUTDOWN_INSTANCES:
108
    if not names:
109
      raise errors.OpPrereqError("No instance names passed")
110
    idata = client.QueryInstances(names, ["name"], False)
111
    inames = [row[0] for row in idata]
112

    
113
  else:
114
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
115

    
116
  return inames
117

    
118

    
119
def _ConfirmOperation(inames, text):
120
  """Ask the user to confirm an operation on a list of instances.
121

    
122
  This function is used to request confirmation for doing an operation
123
  on a given list of instances.
124

    
125
  @type inames: list
126
  @param inames: the list of names that we display when
127
      we ask for confirmation
128
  @type text: str
129
  @param text: the operation that the user should confirm
130
      (e.g. I{shutdown} or I{startup})
131
  @rtype: boolean
132
  @return: True or False depending on user's confirmation.
133

    
134
  """
135
  count = len(inames)
136
  msg = ("The %s will operate on %d instances.\n"
137
         "Do you want to continue?" % (text, count))
138
  affected = ("\nAffected instances:\n" +
139
              "\n".join(["  %s" % name for name in inames]))
140

    
141
  choices = [('y', True, 'Yes, execute the %s' % text),
142
             ('n', False, 'No, abort the %s' % text)]
143

    
144
  if count > 20:
145
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
146
    ask = msg
147
  else:
148
    ask = msg + affected
149

    
150
  choice = AskUser(ask, choices)
151
  if choice == 'v':
152
    choices.pop(1)
153
    choice = AskUser(msg + affected, choices)
154
  return choice
155

    
156

    
157
def _TransformPath(user_input):
158
  """Transform a user path into a canonical value.
159

    
160
  This function transforms the a path passed as textual information
161
  into the constants that the LU code expects.
162

    
163
  """
164
  if user_input:
165
    if user_input.lower() == "default":
166
      result_path = constants.VALUE_DEFAULT
167
    elif user_input.lower() == "none":
168
      result_path = constants.VALUE_NONE
169
    else:
170
      if not os.path.isabs(user_input):
171
        raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
172
                                   user_input)
173
      result_path = user_input
174
  else:
175
    result_path = constants.VALUE_DEFAULT
176

    
177
  return result_path
178

    
179

    
180
def _EnsureInstancesExist(client, names):
181
  """Check for and ensure the given instance names exist.
182

    
183
  This function will raise an OpPrereqError in case they don't
184
  exist. Otherwise it will exit cleanly.
185

    
186
  @type client: L{luxi.Client}
187
  @param client: the client to use for the query
188
  @type names: list
189
  @param names: the list of instance names to query
190
  @raise errors.OpPrereqError: in case any instance is missing
191

    
192
  """
193
  # TODO: change LUQueryInstances to that it actually returns None
194
  # instead of raising an exception, or devise a better mechanism
195
  result = client.QueryInstances(names, ["name"], False)
196
  for orig_name, row in zip(names, result):
197
    if row[0] is None:
198
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
199

    
200

    
201
def ListInstances(opts, args):
202
  """List instances and their properties.
203

    
204
  @param opts: the command line options selected by the user
205
  @type args: list
206
  @param args: should be an empty list
207
  @rtype: int
208
  @return: the desired exit code
209

    
210
  """
211
  if opts.output is None:
212
    selected_fields = _LIST_DEF_FIELDS
213
  elif opts.output.startswith("+"):
214
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
215
  else:
216
    selected_fields = opts.output.split(",")
217

    
218
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
219

    
220
  if not opts.no_headers:
221
    headers = {
222
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
223
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
224
      "oper_state": "Running",
225
      "oper_ram": "Memory", "disk_template": "Disk_template",
226
      "ip": "IP_address", "mac": "MAC_address",
227
      "bridge": "Bridge",
228
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
229
      "disk_usage": "DiskUsage",
230
      "status": "Status", "tags": "Tags",
231
      "network_port": "Network_port",
232
      "hv/kernel_path": "Kernel_path",
233
      "hv/initrd_path": "Initrd_path",
234
      "hv/boot_order": "HVM_boot_order",
235
      "hv/acpi": "HVM_ACPI",
236
      "hv/pae": "HVM_PAE",
237
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
238
      "hv/nic_type": "HVM_NIC_type",
239
      "hv/disk_type": "HVM_Disk_type",
240
      "hv/vnc_bind_address": "VNC_bind_address",
241
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
242
      "hvparams": "Hypervisor_parameters",
243
      "be/memory": "Configured_memory",
244
      "be/vcpus": "VCPUs",
245
      "be/auto_balance": "Auto_balance",
246
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
247
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
248
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
249
      }
250
  else:
251
    headers = None
252

    
253
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
254
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
255
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
256

    
257
  list_type_fields = ("tags", "disk.sizes",
258
                      "nic.macs", "nic.ips", "nic.bridges")
259
  # change raw values to nicer strings
260
  for row in output:
261
    for idx, field in enumerate(selected_fields):
262
      val = row[idx]
263
      if field == "snodes":
264
        val = ",".join(val) or "-"
265
      elif field == "admin_state":
266
        if val:
267
          val = "yes"
268
        else:
269
          val = "no"
270
      elif field == "oper_state":
271
        if val is None:
272
          val = "(node down)"
273
        elif val: # True
274
          val = "running"
275
        else:
276
          val = "stopped"
277
      elif field == "oper_ram":
278
        if val is None:
279
          val = "(node down)"
280
      elif field == "sda_size" or field == "sdb_size":
281
        if val is None:
282
          val = "N/A"
283
      elif field in list_type_fields:
284
        val = ",".join(str(item) for item in val)
285
      elif val is None:
286
        val = "-"
287
      row[idx] = str(val)
288

    
289
  data = GenerateTable(separator=opts.separator, headers=headers,
290
                       fields=selected_fields, unitfields=unitfields,
291
                       numfields=numfields, data=output, units=opts.units)
292

    
293
  for line in data:
294
    ToStdout(line)
295

    
296
  return 0
297

    
298

    
299
def AddInstance(opts, args):
300
  """Add an instance to the cluster.
301

    
302
  @param opts: the command line options selected by the user
303
  @type args: list
304
  @param args: should contain only one element, the new instance name
305
  @rtype: int
306
  @return: the desired exit code
307

    
308
  """
309
  instance = args[0]
310

    
311
  (pnode, snode) = SplitNodeOption(opts.node)
312

    
313
  hypervisor = None
314
  hvparams = {}
315
  if opts.hypervisor:
316
    hypervisor, hvparams = opts.hypervisor
317

    
318
  if opts.nics:
319
    try:
320
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
321
    except ValueError, err:
322
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
323
    nics = [{}] * nic_max
324
    for nidx, ndict in opts.nics:
325
      nidx = int(nidx)
326
      nics[nidx] = ndict
327
  elif opts.no_nics:
328
    # no nics
329
    nics = []
330
  else:
331
    # default of one nic, all auto
332
    nics = [{}]
333

    
334
  if opts.disk_template == constants.DT_DISKLESS:
335
    if opts.disks or opts.sd_size is not None:
336
      raise errors.OpPrereqError("Diskless instance but disk"
337
                                 " information passed")
338
    disks = []
339
  else:
340
    if not opts.disks and not opts.sd_size:
341
      raise errors.OpPrereqError("No disk information specified")
342
    if opts.disks and opts.sd_size is not None:
343
      raise errors.OpPrereqError("Please use either the '--disk' or"
344
                                 " '-s' option")
345
    if opts.sd_size is not None:
346
      opts.disks = [(0, {"size": opts.sd_size})]
347
    try:
348
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
349
    except ValueError, err:
350
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
351
    disks = [{}] * disk_max
352
    for didx, ddict in opts.disks:
353
      didx = int(didx)
354
      if "size" not in ddict:
355
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
356
      try:
357
        ddict["size"] = utils.ParseUnit(ddict["size"])
358
      except ValueError, err:
359
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
360
                                   (didx, err))
361
      disks[didx] = ddict
362

    
363
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
364
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
365

    
366
  op = opcodes.OpCreateInstance(instance_name=instance,
367
                                disks=disks,
368
                                disk_template=opts.disk_template,
369
                                nics=nics,
370
                                mode=constants.INSTANCE_CREATE,
371
                                os_type=opts.os, pnode=pnode,
372
                                snode=snode,
373
                                start=opts.start, ip_check=opts.ip_check,
374
                                wait_for_sync=opts.wait_for_sync,
375
                                hypervisor=hypervisor,
376
                                hvparams=hvparams,
377
                                beparams=opts.beparams,
378
                                iallocator=opts.iallocator,
379
                                file_storage_dir=opts.file_storage_dir,
380
                                file_driver=opts.file_driver,
381
                                )
382

    
383
  SubmitOrSend(op, opts)
384
  return 0
385

    
386

    
387
def BatchCreate(opts, args):
388
  """Create instances using a definition file.
389

    
390
  This function reads a json file with instances defined
391
  in the form::
392

    
393
    {"instance-name":{
394
      "disk_size": [20480],
395
      "template": "drbd",
396
      "backend": {
397
        "memory": 512,
398
        "vcpus": 1 },
399
      "os": "debootstrap",
400
      "primary_node": "firstnode",
401
      "secondary_node": "secondnode",
402
      "iallocator": "dumb"}
403
    }
404

    
405
  Note that I{primary_node} and I{secondary_node} have precedence over
406
  I{iallocator}.
407

    
408
  @param opts: the command line options selected by the user
409
  @type args: list
410
  @param args: should contain one element, the json filename
411
  @rtype: int
412
  @return: the desired exit code
413

    
414
  """
415
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
416
                    "backend": {},
417
                    "iallocator": None,
418
                    "primary_node": None,
419
                    "secondary_node": None,
420
                    "ip": 'none',
421
                    "mac": 'auto',
422
                    "bridge": None,
423
                    "start": True,
424
                    "ip_check": True,
425
                    "hypervisor": None,
426
                    "hvparams": {},
427
                    "file_storage_dir": None,
428
                    "file_driver": 'loop'}
429

    
430
  def _PopulateWithDefaults(spec):
431
    """Returns a new hash combined with default values."""
432
    mydict = _DEFAULT_SPECS.copy()
433
    mydict.update(spec)
434
    return mydict
435

    
436
  def _Validate(spec):
437
    """Validate the instance specs."""
438
    # Validate fields required under any circumstances
439
    for required_field in ('os', 'template'):
440
      if required_field not in spec:
441
        raise errors.OpPrereqError('Required field "%s" is missing.' %
442
                                   required_field)
443
    # Validate special fields
444
    if spec['primary_node'] is not None:
445
      if (spec['template'] in constants.DTS_NET_MIRROR and
446
          spec['secondary_node'] is None):
447
        raise errors.OpPrereqError('Template requires secondary node, but'
448
                                   ' there was no secondary provided.')
449
    elif spec['iallocator'] is None:
450
      raise errors.OpPrereqError('You have to provide at least a primary_node'
451
                                 ' or an iallocator.')
452

    
453
    if (spec['hvparams'] and
454
        not isinstance(spec['hvparams'], dict)):
455
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
456

    
457
  json_filename = args[0]
458
  try:
459
    fd = open(json_filename, 'r')
460
    instance_data = simplejson.load(fd)
461
    fd.close()
462
  except Exception, err:
463
    ToStderr("Can't parse the instance definition file: %s" % str(err))
464
    return 1
465

    
466
  # Iterate over the instances and do:
467
  #  * Populate the specs with default value
468
  #  * Validate the instance specs
469
  i_names = utils.NiceSort(instance_data.keys())
470
  for name in i_names:
471
    specs = instance_data[name]
472
    specs = _PopulateWithDefaults(specs)
473
    _Validate(specs)
474

    
475
    hypervisor = specs['hypervisor']
476
    hvparams = specs['hvparams']
477

    
478
    disks = []
479
    for elem in specs['disk_size']:
480
      try:
481
        size = utils.ParseUnit(elem)
482
      except ValueError, err:
483
        raise errors.OpPrereqError("Invalid disk size '%s' for"
484
                                   " instance %s: %s" %
485
                                   (elem, name, err))
486
      disks.append({"size": size})
487

    
488
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
489

    
490
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
491
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
492

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

    
511
    ToStdout("%s: %s", name, cli.SendJob([op]))
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
  instance_name = args[0]
528

    
529
  if opts.select_os is True:
530
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
531
    result = SubmitOpCode(op)
532

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

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

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

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

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

    
557
  if not opts.force:
558
    usertext = ("This will reinstall the instance %s and remove"
559
                " all data. Continue?") % instance_name
560
    if not AskUser(usertext):
561
      return 1
562

    
563
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
564
                                   os_type=os_name)
565
  SubmitOrSend(op, opts)
566

    
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
    jex.QueueJob(name, op)
714
  jex.WaitOrShow(not opts.submit_only)
715
  return 0
716

    
717

    
718
def RebootInstance(opts, args):
719
  """Reboot instance(s).
720

    
721
  Depending on the parameters given, this will reboot one or more
722
  instances.
723

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

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

    
752

    
753
def ShutdownInstance(opts, args):
754
  """Shutdown an instance.
755

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

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

    
776
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
777
  for name in inames:
778
    op = opcodes.OpShutdownInstance(instance_name=name)
779
    jex.QueueJob(name, op)
780
  jex.WaitOrShow(not opts.submit_only)
781
  return 0
782

    
783

    
784
def ReplaceDisks(opts, args):
785
  """Replace the disks of an instance
786

    
787
  @param opts: the command line options selected by the user
788
  @type args: list
789
  @param args: should contain only one element, the instance name
790
  @rtype: int
791
  @return: the desired exit code
792

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

    
817
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
818
                              remote_node=new_2ndary, mode=mode,
819
                              iallocator=iallocator)
820
  SubmitOrSend(op, opts)
821
  return 0
822

    
823

    
824
def FailoverInstance(opts, args):
825
  """Failover an instance.
826

    
827
  The failover is done by shutting it down on its present node and
828
  starting it on the secondary.
829

    
830
  @param opts: the command line options selected by the user
831
  @type args: list
832
  @param args: should contain only one element, the instance name
833
  @rtype: int
834
  @return: the desired exit code
835

    
836
  """
837
  cl = GetClient()
838
  instance_name = args[0]
839
  force = opts.force
840

    
841
  if not force:
842
    _EnsureInstancesExist(cl, [instance_name])
843

    
844
    usertext = ("Failover will happen to image %s."
845
                " This requires a shutdown of the instance. Continue?" %
846
                (instance_name,))
847
    if not AskUser(usertext):
848
      return 1
849

    
850
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
851
                                  ignore_consistency=opts.ignore_consistency)
852
  SubmitOrSend(op, opts, cl=cl)
853
  return 0
854

    
855

    
856
def MigrateInstance(opts, args):
857
  """Migrate an instance.
858

    
859
  The migrate is done without shutdown.
860

    
861
  @param opts: the command line options selected by the user
862
  @type args: list
863
  @param args: should contain only one element, the instance name
864
  @rtype: int
865
  @return: the desired exit code
866

    
867
  """
868
  cl = GetClient()
869
  instance_name = args[0]
870
  force = opts.force
871

    
872
  if not force:
873
    _EnsureInstancesExist(cl, [instance_name])
874

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

    
888
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
889
                                 cleanup=opts.cleanup)
890
  SubmitOpCode(op, cl=cl)
891
  return 0
892

    
893

    
894
def ConnectToInstanceConsole(opts, args):
895
  """Connect to the console of an instance.
896

    
897
  @param opts: the command line options selected by the user
898
  @type args: list
899
  @param args: should contain only one element, the instance name
900
  @rtype: int
901
  @return: the desired exit code
902

    
903
  """
904
  instance_name = args[0]
905

    
906
  op = opcodes.OpConnectConsole(instance_name=instance_name)
907
  cmd = SubmitOpCode(op)
908

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

    
919

    
920
def _FormatLogicalID(dev_type, logical_id):
921
  """Formats the logical_id of a disk.
922

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

    
938
  return data
939

    
940

    
941
def _FormatBlockDevInfo(idx, top_level, dev, static):
942
  """Show block device information.
943

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

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

    
959
  """
960
  def helper(dtype, status):
961
    """Format one line for physical device status.
962

    
963
    @type dtype: str
964
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
965
    @type status: tuple
966
    @param status: a tuple as returned from L{backend.FindBlockDevice}
967
    @return: the string representing the status
968

    
969
    """
970
    if not status:
971
      return "not active"
972
    txt = ""
973
    (path, major, minor, syncp, estt, degr, ldisk) = status
974
    if major is None:
975
      major_string = "N/A"
976
    else:
977
      major_string = str(major)
978

    
979
    if minor is None:
980
      minor_string = "N/A"
981
    else:
982
      minor_string = str(minor)
983

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

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

    
1040
  if dev["children"]:
1041
    data.append("child devices:")
1042
    for c_idx, child in enumerate(dev["children"]):
1043
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1044
  d1.append(data)
1045
  return d1
1046

    
1047

    
1048
def _FormatList(buf, data, indent_level):
1049
  """Formats a list of data at a given indent level.
1050

    
1051
  If the element of the list is:
1052
    - a string, it is simply formatted as is
1053
    - a tuple, it will be split into key, value and the all the
1054
      values in a list will be aligned all at the same start column
1055
    - a list, will be recursively formatted
1056

    
1057
  @type buf: StringIO
1058
  @param buf: the buffer into which we write the output
1059
  @param data: the list to format
1060
  @type indent_level: int
1061
  @param indent_level: the indent level to format at
1062

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

    
1076
def ShowInstanceConfig(opts, args):
1077
  """Compute instance run-time status.
1078

    
1079
  @param opts: the command line options selected by the user
1080
  @type args: list
1081
  @param args: either an empty list, and then we query all
1082
      instances, or should contain a list of instance names
1083
  @rtype: int
1084
  @return: the desired exit code
1085

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

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

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

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

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

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

    
1156

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1226

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

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

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

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

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

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

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

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

    
1260

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

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

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

    
1501
#: dictionary with aliases for commands
1502
aliases = {
1503
  'activate_block_devs': 'activate-disks',
1504
  'replace_disks': 'replace-disks',
1505
  'start': 'startup',
1506
  'stop': 'shutdown',
1507
  }
1508

    
1509
if __name__ == '__main__':
1510
  sys.exit(GenericMain(commands, aliases=aliases,
1511
                       override={"tag_type": constants.TAG_INSTANCE}))