Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 98825740

History | View | Annotate | Download (55.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{ganeti.luxi.Client}
164
  @param client: the client to use for the query
165
  @type names: list
166
  @param names: the list of instance names to query
167
  @raise errors.OpPrereqError: in case any instance is missing
168

    
169
  """
170
  # TODO: change LUQueryInstances to that it actually returns None
171
  # instead of raising an exception, or devise a better mechanism
172
  result = client.QueryInstances(names, ["name"], False)
173
  for orig_name, row in zip(names, result):
174
    if row[0] is None:
175
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
176

    
177

    
178
def ListInstances(opts, args):
179
  """List instances and their properties.
180

    
181
  @param opts: the command line options selected by the user
182
  @type args: list
183
  @param args: should be an empty list
184
  @rtype: int
185
  @return: the desired exit code
186

    
187
  """
188
  if opts.output is None:
189
    selected_fields = _LIST_DEF_FIELDS
190
  elif opts.output.startswith("+"):
191
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
192
  else:
193
    selected_fields = opts.output.split(",")
194

    
195
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
196

    
197
  if not opts.no_headers:
198
    headers = {
199
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
200
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
201
      "oper_state": "Running",
202
      "oper_ram": "Memory", "disk_template": "Disk_template",
203
      "ip": "IP_address", "mac": "MAC_address",
204
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
205
      "bridge": "Bridge",
206
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
207
      "disk_usage": "DiskUsage",
208
      "status": "Status", "tags": "Tags",
209
      "network_port": "Network_port",
210
      "hv/kernel_path": "Kernel_path",
211
      "hv/initrd_path": "Initrd_path",
212
      "hv/boot_order": "HVM_boot_order",
213
      "hv/acpi": "HVM_ACPI",
214
      "hv/pae": "HVM_PAE",
215
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
216
      "hv/nic_type": "HVM_NIC_type",
217
      "hv/disk_type": "HVM_Disk_type",
218
      "hv/vnc_bind_address": "VNC_bind_address",
219
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
220
      "hvparams": "Hypervisor_parameters",
221
      "be/memory": "Configured_memory",
222
      "be/vcpus": "VCPUs",
223
      "vcpus": "VCPUs",
224
      "be/auto_balance": "Auto_balance",
225
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
226
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
227
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
228
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
229
      }
230
  else:
231
    headers = None
232

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

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

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

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

    
276
  return 0
277

    
278

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

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

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

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

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

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

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

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

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

    
369
  SubmitOrSend(op, opts)
370
  return 0
371

    
372

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

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

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

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

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

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

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

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

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

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

    
450
  jex = JobExecutor()
451

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

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

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

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

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

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

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

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

    
514
  return 0
515

    
516

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

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

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

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

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

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

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

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

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

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

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

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

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

    
589

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

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

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

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

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

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

    
619

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

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

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

    
637

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

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

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

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

    
660

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

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

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

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

    
679

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

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

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

    
703

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

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

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

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

    
742

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

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

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

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

    
777

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

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

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

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

    
808

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

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

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

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

    
848

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

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

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

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

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

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

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

    
880

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

    
884
  The migrate is done without shutdown.
885

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

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

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

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

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

    
918

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

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

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

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

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

    
944

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

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

    
963
  return data
964

    
965

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

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

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

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

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

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

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

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

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

    
1069
  if dev["children"]:
1070
    data.append("child devices:")
1071
    for c_idx, child in enumerate(dev["children"]):
1072
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1073
  d1.append(data)
1074
  return d1
1075

    
1076

    
1077
def _FormatList(buf, data, indent_level):
1078
  """Formats a list of data at a given indent level.
1079

    
1080
  If the element of the list is:
1081
    - a string, it is simply formatted as is
1082
    - a tuple, it will be split into key, value and the all the
1083
      values in a list will be aligned all at the same start column
1084
    - a list, will be recursively formatted
1085

    
1086
  @type buf: StringIO
1087
  @param buf: the buffer into which we write the output
1088
  @param data: the list to format
1089
  @type indent_level: int
1090
  @param indent_level: the indent level to format at
1091

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

    
1105

    
1106
def ShowInstanceConfig(opts, args):
1107
  """Compute instance run-time status.
1108

    
1109
  @param opts: the command line options selected by the user
1110
  @type args: list
1111
  @param args: either an empty list, and then we query all
1112
      instances, or should contain a list of instance names
1113
  @rtype: int
1114
  @return: the desired exit code
1115

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

    
1126
  retcode = 0
1127
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1128
  result = SubmitOpCode(op)
1129
  if not result:
1130
    ToStdout("No instances.")
1131
    return 1
1132

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

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

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

    
1189
    for idx, device in enumerate(instance["disks"]):
1190
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1191

    
1192
  ToStdout(buf.getvalue().rstrip('\n'))
1193
  return retcode
1194

    
1195

    
1196
def SetInstanceParams(opts, args):
1197
  """Modifies an instance.
1198

    
1199
  All parameters take effect only at the next restart of the instance.
1200

    
1201
  @param opts: the command line options selected by the user
1202
  @type args: list
1203
  @param args: should contain only one element, the instance name
1204
  @rtype: int
1205
  @return: the desired exit code
1206

    
1207
  """
1208
  if not (opts.nics or opts.disks or
1209
          opts.hypervisor or opts.beparams):
1210
    ToStderr("Please give at least one of the parameters.")
1211
    return 1
1212

    
1213
  for param in opts.beparams:
1214
    if isinstance(opts.beparams[param], basestring):
1215
      if opts.beparams[param].lower() == "default":
1216
        opts.beparams[param] = constants.VALUE_DEFAULT
1217

    
1218
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1219
                      allowed_values=[constants.VALUE_DEFAULT])
1220

    
1221
  for param in opts.hypervisor:
1222
    if isinstance(opts.hypervisor[param], basestring):
1223
      if opts.hypervisor[param].lower() == "default":
1224
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1225

    
1226
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1227
                      allowed_values=[constants.VALUE_DEFAULT])
1228

    
1229
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1230
    try:
1231
      nic_op = int(nic_op)
1232
      opts.nics[idx] = (nic_op, nic_dict)
1233
    except ValueError:
1234
      pass
1235

    
1236
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1237
    try:
1238
      disk_op = int(disk_op)
1239
      opts.disks[idx] = (disk_op, disk_dict)
1240
    except ValueError:
1241
      pass
1242
    if disk_op == constants.DDM_ADD:
1243
      if 'size' not in disk_dict:
1244
        raise errors.OpPrereqError("Missing required parameter 'size'")
1245
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1246

    
1247
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1248
                                   nics=opts.nics,
1249
                                   disks=opts.disks,
1250
                                   hvparams=opts.hypervisor,
1251
                                   beparams=opts.beparams,
1252
                                   force=opts.force)
1253

    
1254
  # even if here we process the result, we allow submit only
1255
  result = SubmitOrSend(op, opts)
1256

    
1257
  if result:
1258
    ToStdout("Modified instance %s", args[0])
1259
    for param, data in result:
1260
      ToStdout(" - %-5s -> %s", param, data)
1261
    ToStdout("Please don't forget that these parameters take effect"
1262
             " only at the next start of the instance.")
1263
  return 0
1264

    
1265

    
1266
# options used in more than one cmd
1267
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1268
                       metavar="<node>")
1269

    
1270
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1271
                    metavar="<os>")
1272

    
1273
# multi-instance selection options
1274
m_force_multi = make_option("--force-multiple", dest="force_multi",
1275
                            help="Do not ask for confirmation when more than"
1276
                            " one instance is affected",
1277
                            action="store_true", default=False)
1278

    
1279
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1280
                             help="Filter by nodes (primary only)",
1281
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1282

    
1283
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1284
                             help="Filter by nodes (secondary only)",
1285
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1286

    
1287
m_node_opt = make_option("--node", dest="multi_mode",
1288
                         help="Filter by nodes (primary and secondary)",
1289
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1290

    
1291
m_clust_opt = make_option("--all", dest="multi_mode",
1292
                          help="Select all instances in the cluster",
1293
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1294

    
1295
m_inst_opt = make_option("--instance", dest="multi_mode",
1296
                         help="Filter by instance name [default]",
1297
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1298

    
1299

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

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

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

    
1553
#: dictionary with aliases for commands
1554
aliases = {
1555
  'activate_block_devs': 'activate-disks',
1556
  'replace_disks': 'replace-disks',
1557
  'start': 'startup',
1558
  'stop': 'shutdown',
1559
  }
1560

    
1561
if __name__ == '__main__':
1562
  sys.exit(GenericMain(commands, aliases=aliases,
1563
                       override={"tag_type": constants.TAG_INSTANCE}))