Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ a52ba89d

History | View | Annotate | Download (59.8 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
import time
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
      "ctime": "CTime", "mtime": "MTime",
230
      }
231
  else:
232
    headers = None
233

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

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

    
272
  data = GenerateTable(separator=opts.separator, headers=headers,
273
                       fields=selected_fields, unitfields=unitfields,
274
                       numfields=numfields, data=output, units=opts.units)
275

    
276
  for line in data:
277
    ToStdout(line)
278

    
279
  return 0
280

    
281

    
282
def AddInstance(opts, args):
283
  """Add an instance to the cluster.
284

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

    
291
  """
292
  instance = args[0]
293

    
294
  (pnode, snode) = SplitNodeOption(opts.node)
295

    
296
  hypervisor = None
297
  hvparams = {}
298
  if opts.hypervisor:
299
    hypervisor, hvparams = opts.hypervisor
300

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

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

    
352
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
353
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
354

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

    
372
  SubmitOrSend(op, opts)
373
  return 0
374

    
375

    
376
def BatchCreate(opts, args):
377
  """Create instances using a definition file.
378

    
379
  This function reads a json file with instances defined
380
  in the form::
381

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

    
394
  Note that I{primary_node} and I{secondary_node} have precedence over
395
  I{iallocator}.
396

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

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

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

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

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

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

    
453
  jex = JobExecutor()
454

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

    
464
    hypervisor = specs['hypervisor']
465
    hvparams = specs['hvparams']
466

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

    
477
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
478
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
479

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

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

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

    
513
    jex.QueueJob(name, op)
514
  # we never want to wait, just show the submitted job IDs
515
  jex.WaitOrShow(False)
516

    
517
  return 0
518

    
519

    
520
def ReinstallInstance(opts, args):
521
  """Reinstall an instance.
522

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

    
530
  """
531
  # first, compute the desired name list
532
  if opts.multi_mode is None:
533
    opts.multi_mode = _SHUTDOWN_INSTANCES
534

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

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

    
544
    if not result:
545
      ToStdout("Can't get the OS list")
546
      return 1
547

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

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

    
560
    if selected == 'exit':
561
      ToStderr("User aborted reinstall, exiting")
562
      return 1
563

    
564
    os_name = selected
565
  else:
566
    os_name = opts.os
567

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

    
583
  jex = JobExecutor(verbose=multi_on)
584
  for instance_name in inames:
585
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
586
                                     os_type=os_name)
587
    jex.QueueJob(instance_name, op)
588

    
589
  jex.WaitOrShow(not opts.submit_only)
590
  return 0
591

    
592

    
593
def RemoveInstance(opts, args):
594
  """Remove an instance.
595

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

    
603
  """
604
  instance_name = args[0]
605
  force = opts.force
606
  cl = GetClient()
607

    
608
  if not force:
609
    _EnsureInstancesExist(cl, [instance_name])
610

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

    
617
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
618
                                ignore_failures=opts.ignore_failures)
619
  SubmitOrSend(op, opts, cl=cl)
620
  return 0
621

    
622

    
623
def RenameInstance(opts, args):
624
  """Rename an instance.
625

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

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

    
640

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

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

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

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

    
664

    
665
def DeactivateDisks(opts, args):
666
  """Deactivate an instance's disks.
667

    
668
  This function takes the instance name, looks for its primary node
669
  and the tries to shutdown its block devices on that node.
670

    
671
  @param opts: the command line options selected by the user
672
  @type args: list
673
  @param args: should contain only one element, the instance name
674
  @rtype: int
675
  @return: the desired exit code
676

    
677
  """
678
  instance_name = args[0]
679
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
680
  SubmitOrSend(op, opts)
681
  return 0
682

    
683

    
684
def RecreateDisks(opts, args):
685
  """Recreate an instance's disks.
686

    
687
  @param opts: the command line options selected by the user
688
  @type args: list
689
  @param args: should contain only one element, the instance name
690
  @rtype: int
691
  @return: the desired exit code
692

    
693
  """
694
  instance_name = args[0]
695
  if opts.disks:
696
    try:
697
      opts.disks = [int(v) for v in opts.disks.split(",")]
698
    except (ValueError, TypeError), err:
699
      ToStderr("Invalid disks value: %s" % str(err))
700
      return 1
701
  else:
702
    opts.disks = []
703

    
704
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
705
                                       disks=opts.disks)
706
  SubmitOrSend(op, opts)
707
  return 0
708

    
709

    
710
def GrowDisk(opts, args):
711
  """Grow an instance's disks.
712

    
713
  @param opts: the command line options selected by the user
714
  @type args: list
715
  @param args: should contain two elements, the instance name
716
      whose disks we grow and the disk name, e.g. I{sda}
717
  @rtype: int
718
  @return: the desired exit code
719

    
720
  """
721
  instance = args[0]
722
  disk = args[1]
723
  try:
724
    disk = int(disk)
725
  except ValueError, err:
726
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
727
  amount = utils.ParseUnit(args[2])
728
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
729
                          wait_for_sync=opts.wait_for_sync)
730
  SubmitOrSend(op, opts)
731
  return 0
732

    
733

    
734
def StartupInstance(opts, args):
735
  """Startup instances.
736

    
737
  Depending on the options given, this will start one or more
738
  instances.
739

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

    
748
  """
749
  cl = GetClient()
750
  if opts.multi_mode is None:
751
    opts.multi_mode = _SHUTDOWN_INSTANCES
752
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
753
  if not inames:
754
    raise errors.OpPrereqError("Selection filter does not match any instances")
755
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
756
  if not (opts.force_multi or not multi_on
757
          or _ConfirmOperation(inames, "startup")):
758
    return 1
759
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
760
  for name in inames:
761
    op = opcodes.OpStartupInstance(instance_name=name,
762
                                   force=opts.force)
763
    # do not add these parameters to the opcode unless they're defined
764
    if opts.hvparams:
765
      op.hvparams = opts.hvparams
766
    if opts.beparams:
767
      op.beparams = opts.beparams
768
    jex.QueueJob(name, op)
769
  jex.WaitOrShow(not opts.submit_only)
770
  return 0
771

    
772

    
773
def RebootInstance(opts, args):
774
  """Reboot instance(s).
775

    
776
  Depending on the parameters given, this will reboot one or more
777
  instances.
778

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

    
787
  """
788
  cl = GetClient()
789
  if opts.multi_mode is None:
790
    opts.multi_mode = _SHUTDOWN_INSTANCES
791
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
792
  if not inames:
793
    raise errors.OpPrereqError("Selection filter does not match any instances")
794
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
795
  if not (opts.force_multi or not multi_on
796
          or _ConfirmOperation(inames, "reboot")):
797
    return 1
798
  jex = JobExecutor(verbose=multi_on, cl=cl)
799
  for name in inames:
800
    op = opcodes.OpRebootInstance(instance_name=name,
801
                                  reboot_type=opts.reboot_type,
802
                                  ignore_secondaries=opts.ignore_secondaries)
803
    jex.QueueJob(name, op)
804
  jex.WaitOrShow(not opts.submit_only)
805
  return 0
806

    
807

    
808
def ShutdownInstance(opts, args):
809
  """Shutdown an instance.
810

    
811
  @param opts: the command line options selected by the user
812
  @type args: list
813
  @param args: the instance or node names based on which we
814
      create the final selection (in conjunction with the
815
      opts argument)
816
  @rtype: int
817
  @return: the desired exit code
818

    
819
  """
820
  cl = GetClient()
821
  if opts.multi_mode is None:
822
    opts.multi_mode = _SHUTDOWN_INSTANCES
823
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
824
  if not inames:
825
    raise errors.OpPrereqError("Selection filter does not match any instances")
826
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
827
  if not (opts.force_multi or not multi_on
828
          or _ConfirmOperation(inames, "shutdown")):
829
    return 1
830

    
831
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
832
  for name in inames:
833
    op = opcodes.OpShutdownInstance(instance_name=name)
834
    jex.QueueJob(name, op)
835
  jex.WaitOrShow(not opts.submit_only)
836
  return 0
837

    
838

    
839
def ReplaceDisks(opts, args):
840
  """Replace the disks of an instance
841

    
842
  @param opts: the command line options selected by the user
843
  @type args: list
844
  @param args: should contain only one element, the instance name
845
  @rtype: int
846
  @return: the desired exit code
847

    
848
  """
849
  instance_name = args[0]
850
  new_2ndary = opts.new_secondary
851
  iallocator = opts.iallocator
852
  if opts.disks is None:
853
    disks = []
854
  else:
855
    try:
856
      disks = [int(i) for i in opts.disks.split(",")]
857
    except ValueError, err:
858
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
859
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
860
         new_2ndary is not None, iallocator is not None].count(True)
861
  if cnt != 1:
862
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
863
                               " options must be passed")
864
  elif opts.on_primary:
865
    mode = constants.REPLACE_DISK_PRI
866
  elif opts.on_secondary:
867
    mode = constants.REPLACE_DISK_SEC
868
  elif opts.auto:
869
    mode = constants.REPLACE_DISK_AUTO
870
    if disks:
871
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
872
                                 " mode")
873
  elif new_2ndary is not None or iallocator is not None:
874
    # replace secondary
875
    mode = constants.REPLACE_DISK_CHG
876

    
877
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
878
                              remote_node=new_2ndary, mode=mode,
879
                              iallocator=iallocator)
880
  SubmitOrSend(op, opts)
881
  return 0
882

    
883

    
884
def FailoverInstance(opts, args):
885
  """Failover an instance.
886

    
887
  The failover is done by shutting it down on its present node and
888
  starting it on the secondary.
889

    
890
  @param opts: the command line options selected by the user
891
  @type args: list
892
  @param args: should contain only one element, the instance name
893
  @rtype: int
894
  @return: the desired exit code
895

    
896
  """
897
  cl = GetClient()
898
  instance_name = args[0]
899
  force = opts.force
900

    
901
  if not force:
902
    _EnsureInstancesExist(cl, [instance_name])
903

    
904
    usertext = ("Failover will happen to image %s."
905
                " This requires a shutdown of the instance. Continue?" %
906
                (instance_name,))
907
    if not AskUser(usertext):
908
      return 1
909

    
910
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
911
                                  ignore_consistency=opts.ignore_consistency)
912
  SubmitOrSend(op, opts, cl=cl)
913
  return 0
914

    
915

    
916
def MigrateInstance(opts, args):
917
  """Migrate an instance.
918

    
919
  The migrate is done without shutdown.
920

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

    
927
  """
928
  cl = GetClient()
929
  instance_name = args[0]
930
  force = opts.force
931

    
932
  if not force:
933
    _EnsureInstancesExist(cl, [instance_name])
934

    
935
    if opts.cleanup:
936
      usertext = ("Instance %s will be recovered from a failed migration."
937
                  " Note that the migration procedure (including cleanup)" %
938
                  (instance_name,))
939
    else:
940
      usertext = ("Instance %s will be migrated. Note that migration" %
941
                  (instance_name,))
942
    usertext += (" is **experimental** in this version."
943
                " This might impact the instance if anything goes wrong."
944
                " Continue?")
945
    if not AskUser(usertext):
946
      return 1
947

    
948
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
949
                                 cleanup=opts.cleanup)
950
  SubmitOpCode(op, cl=cl)
951
  return 0
952

    
953

    
954
def MoveInstance(opts, args):
955
  """Move an instance.
956

    
957
  @param opts: the command line options selected by the user
958
  @type args: list
959
  @param args: should contain only one element, the instance name
960
  @rtype: int
961
  @return: the desired exit code
962

    
963
  """
964
  cl = GetClient()
965
  instance_name = args[0]
966
  force = opts.force
967

    
968
  if not force:
969
    usertext = ("Instance %s will be moved."
970
                " This requires a shutdown of the instance. Continue?" %
971
                (instance_name,))
972
    if not AskUser(usertext):
973
      return 1
974

    
975
  op = opcodes.OpMoveInstance(instance_name=instance_name,
976
                              target_node=opts.target_node)
977
  SubmitOrSend(op, opts, cl=cl)
978
  return 0
979

    
980

    
981
def ConnectToInstanceConsole(opts, args):
982
  """Connect to the console of an instance.
983

    
984
  @param opts: the command line options selected by the user
985
  @type args: list
986
  @param args: should contain only one element, the instance name
987
  @rtype: int
988
  @return: the desired exit code
989

    
990
  """
991
  instance_name = args[0]
992

    
993
  op = opcodes.OpConnectConsole(instance_name=instance_name)
994
  cmd = SubmitOpCode(op)
995

    
996
  if opts.show_command:
997
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
998
  else:
999
    try:
1000
      os.execvp(cmd[0], cmd)
1001
    finally:
1002
      ToStderr("Can't run console command %s with arguments:\n'%s'",
1003
               cmd[0], " ".join(cmd))
1004
      os._exit(1)
1005

    
1006

    
1007
def _FormatLogicalID(dev_type, logical_id):
1008
  """Formats the logical_id of a disk.
1009

    
1010
  """
1011
  if dev_type == constants.LD_DRBD8:
1012
    node_a, node_b, port, minor_a, minor_b, key = logical_id
1013
    data = [
1014
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
1015
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
1016
      ("port", port),
1017
      ("auth key", key),
1018
      ]
1019
  elif dev_type == constants.LD_LV:
1020
    vg_name, lv_name = logical_id
1021
    data = ["%s/%s" % (vg_name, lv_name)]
1022
  else:
1023
    data = [str(logical_id)]
1024

    
1025
  return data
1026

    
1027

    
1028
def _FormatBlockDevInfo(idx, top_level, dev, static):
1029
  """Show block device information.
1030

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

    
1034
  @type idx: int
1035
  @param idx: the index of the current disk
1036
  @type top_level: boolean
1037
  @param top_level: if this a top-level disk?
1038
  @type dev: dict
1039
  @param dev: dictionary with disk information
1040
  @type static: boolean
1041
  @param static: wheter the device information doesn't contain
1042
      runtime information but only static data
1043
  @return: a list of either strings, tuples or lists
1044
      (which should be formatted at a higher indent level)
1045

    
1046
  """
1047
  def helper(dtype, status):
1048
    """Format one line for physical device status.
1049

    
1050
    @type dtype: str
1051
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1052
    @type status: tuple
1053
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1054
    @return: the string representing the status
1055

    
1056
    """
1057
    if not status:
1058
      return "not active"
1059
    txt = ""
1060
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1061
    if major is None:
1062
      major_string = "N/A"
1063
    else:
1064
      major_string = str(major)
1065

    
1066
    if minor is None:
1067
      minor_string = "N/A"
1068
    else:
1069
      minor_string = str(minor)
1070

    
1071
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1072
    if dtype in (constants.LD_DRBD8, ):
1073
      if syncp is not None:
1074
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1075
        if estt:
1076
          sync_text += " ETA %ds" % estt
1077
        else:
1078
          sync_text += " ETA unknown"
1079
      else:
1080
        sync_text = "in sync"
1081
      if degr:
1082
        degr_text = "*DEGRADED*"
1083
      else:
1084
        degr_text = "ok"
1085
      if ldisk_status == constants.LDS_FAULTY:
1086
        ldisk_text = " *MISSING DISK*"
1087
      elif ldisk_status == constants.LDS_UNKNOWN:
1088
        ldisk_text = " *UNCERTAIN STATE*"
1089
      else:
1090
        ldisk_text = ""
1091
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1092
    elif dtype == constants.LD_LV:
1093
      if ldisk_status == constants.LDS_FAULTY:
1094
        ldisk_text = " *FAILED* (failed drive?)"
1095
      else:
1096
        ldisk_text = ""
1097
      txt += ldisk_text
1098
    return txt
1099

    
1100
  # the header
1101
  if top_level:
1102
    if dev["iv_name"] is not None:
1103
      txt = dev["iv_name"]
1104
    else:
1105
      txt = "disk %d" % idx
1106
  else:
1107
    txt = "child %d" % idx
1108
  if isinstance(dev["size"], int):
1109
    nice_size = utils.FormatUnit(dev["size"], "h")
1110
  else:
1111
    nice_size = dev["size"]
1112
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1113
  data = []
1114
  if top_level:
1115
    data.append(("access mode", dev["mode"]))
1116
  if dev["logical_id"] is not None:
1117
    try:
1118
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1119
    except ValueError:
1120
      l_id = [str(dev["logical_id"])]
1121
    if len(l_id) == 1:
1122
      data.append(("logical_id", l_id[0]))
1123
    else:
1124
      data.extend(l_id)
1125
  elif dev["physical_id"] is not None:
1126
    data.append("physical_id:")
1127
    data.append([dev["physical_id"]])
1128
  if not static:
1129
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1130
  if dev["sstatus"] and not static:
1131
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1132

    
1133
  if dev["children"]:
1134
    data.append("child devices:")
1135
    for c_idx, child in enumerate(dev["children"]):
1136
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1137
  d1.append(data)
1138
  return d1
1139

    
1140

    
1141
def _FormatList(buf, data, indent_level):
1142
  """Formats a list of data at a given indent level.
1143

    
1144
  If the element of the list is:
1145
    - a string, it is simply formatted as is
1146
    - a tuple, it will be split into key, value and the all the
1147
      values in a list will be aligned all at the same start column
1148
    - a list, will be recursively formatted
1149

    
1150
  @type buf: StringIO
1151
  @param buf: the buffer into which we write the output
1152
  @param data: the list to format
1153
  @type indent_level: int
1154
  @param indent_level: the indent level to format at
1155

    
1156
  """
1157
  max_tlen = max([len(elem[0]) for elem in data
1158
                 if isinstance(elem, tuple)] or [0])
1159
  for elem in data:
1160
    if isinstance(elem, basestring):
1161
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1162
    elif isinstance(elem, tuple):
1163
      key, value = elem
1164
      spacer = "%*s" % (max_tlen - len(key), "")
1165
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1166
    elif isinstance(elem, list):
1167
      _FormatList(buf, elem, indent_level+1)
1168

    
1169

    
1170
def ShowInstanceConfig(opts, args):
1171
  """Compute instance run-time status.
1172

    
1173
  @param opts: the command line options selected by the user
1174
  @type args: list
1175
  @param args: either an empty list, and then we query all
1176
      instances, or should contain a list of instance names
1177
  @rtype: int
1178
  @return: the desired exit code
1179

    
1180
  """
1181
  if not args and not opts.show_all:
1182
    ToStderr("No instance selected."
1183
             " Please pass in --all if you want to query all instances.\n"
1184
             "Note that this can take a long time on a big cluster.")
1185
    return 1
1186
  elif args and opts.show_all:
1187
    ToStderr("Cannot use --all if you specify instance names.")
1188
    return 1
1189

    
1190
  retcode = 0
1191
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1192
  result = SubmitOpCode(op)
1193
  if not result:
1194
    ToStdout("No instances.")
1195
    return 1
1196

    
1197
  buf = StringIO()
1198
  retcode = 0
1199
  for instance_name in result:
1200
    instance = result[instance_name]
1201
    buf.write("Instance name: %s\n" % instance["name"])
1202
    buf.write("Serial number: %s\n" % instance["serial_no"])
1203
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1204
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1205
    buf.write("State: configured to be %s" % instance["config_state"])
1206
    if not opts.static:
1207
      buf.write(", actual state is %s" % instance["run_state"])
1208
    buf.write("\n")
1209
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1210
    ##          instance["auto_balance"])
1211
    buf.write("  Nodes:\n")
1212
    buf.write("    - primary: %s\n" % instance["pnode"])
1213
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1214
    buf.write("  Operating system: %s\n" % instance["os"])
1215
    if instance.has_key("network_port"):
1216
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1217
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1218

    
1219
    # custom VNC console information
1220
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1221
                                                 None)
1222
    if vnc_bind_address:
1223
      port = instance["network_port"]
1224
      display = int(port) - constants.VNC_BASE_PORT
1225
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1226
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1227
                                                   port,
1228
                                                   display)
1229
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1230
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1231
                             (vnc_bind_address, port,
1232
                              instance["pnode"], display))
1233
      else:
1234
        # vnc bind address is a file
1235
        vnc_console_port = "%s:%s" % (instance["pnode"],
1236
                                      vnc_bind_address)
1237
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1238

    
1239
    for key in instance["hv_actual"]:
1240
      if key in instance["hv_instance"]:
1241
        val = instance["hv_instance"][key]
1242
      else:
1243
        val = "default (%s)" % instance["hv_actual"][key]
1244
      buf.write("    - %s: %s\n" % (key, val))
1245
    buf.write("  Hardware:\n")
1246
    buf.write("    - VCPUs: %d\n" %
1247
              instance["be_actual"][constants.BE_VCPUS])
1248
    buf.write("    - memory: %dMiB\n" %
1249
              instance["be_actual"][constants.BE_MEMORY])
1250
    buf.write("    - NICs:\n")
1251
    for idx, (mac, ip, mode, link) in enumerate(instance["nics"]):
1252
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1253
                (idx, mac, ip, mode, link))
1254
    buf.write("  Disks:\n")
1255

    
1256
    for idx, device in enumerate(instance["disks"]):
1257
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1258

    
1259
  ToStdout(buf.getvalue().rstrip('\n'))
1260
  return retcode
1261

    
1262

    
1263
def SetInstanceParams(opts, args):
1264
  """Modifies an instance.
1265

    
1266
  All parameters take effect only at the next restart of the instance.
1267

    
1268
  @param opts: the command line options selected by the user
1269
  @type args: list
1270
  @param args: should contain only one element, the instance name
1271
  @rtype: int
1272
  @return: the desired exit code
1273

    
1274
  """
1275
  if not (opts.nics or opts.disks or
1276
          opts.hypervisor or opts.beparams):
1277
    ToStderr("Please give at least one of the parameters.")
1278
    return 1
1279

    
1280
  for param in opts.beparams:
1281
    if isinstance(opts.beparams[param], basestring):
1282
      if opts.beparams[param].lower() == "default":
1283
        opts.beparams[param] = constants.VALUE_DEFAULT
1284

    
1285
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1286
                      allowed_values=[constants.VALUE_DEFAULT])
1287

    
1288
  for param in opts.hypervisor:
1289
    if isinstance(opts.hypervisor[param], basestring):
1290
      if opts.hypervisor[param].lower() == "default":
1291
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1292

    
1293
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1294
                      allowed_values=[constants.VALUE_DEFAULT])
1295

    
1296
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1297
    try:
1298
      nic_op = int(nic_op)
1299
      opts.nics[idx] = (nic_op, nic_dict)
1300
    except ValueError:
1301
      pass
1302

    
1303
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1304
    try:
1305
      disk_op = int(disk_op)
1306
      opts.disks[idx] = (disk_op, disk_dict)
1307
    except ValueError:
1308
      pass
1309
    if disk_op == constants.DDM_ADD:
1310
      if 'size' not in disk_dict:
1311
        raise errors.OpPrereqError("Missing required parameter 'size'")
1312
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1313

    
1314
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1315
                                   nics=opts.nics,
1316
                                   disks=opts.disks,
1317
                                   hvparams=opts.hypervisor,
1318
                                   beparams=opts.beparams,
1319
                                   force=opts.force)
1320

    
1321
  # even if here we process the result, we allow submit only
1322
  result = SubmitOrSend(op, opts)
1323

    
1324
  if result:
1325
    ToStdout("Modified instance %s", args[0])
1326
    for param, data in result:
1327
      ToStdout(" - %-5s -> %s", param, data)
1328
    ToStdout("Please don't forget that these parameters take effect"
1329
             " only at the next start of the instance.")
1330
  return 0
1331

    
1332

    
1333
# options used in more than one cmd
1334
node_opt = cli_option("-n", "--node", dest="node", help="Target node",
1335
                      metavar="<node>",
1336
                      completion_suggest=OPT_COMPL_ONE_NODE)
1337

    
1338
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1339
                    metavar="<os>",
1340
                    completion_suggest=OPT_COMPL_ONE_OS)
1341

    
1342
# multi-instance selection options
1343
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1344
                           help="Do not ask for confirmation when more than"
1345
                           " one instance is affected",
1346
                           action="store_true", default=False)
1347

    
1348
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1349
                            help="Filter by nodes (primary only)",
1350
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1351

    
1352
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1353
                            help="Filter by nodes (secondary only)",
1354
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1355

    
1356
m_node_opt = cli_option("--node", dest="multi_mode",
1357
                        help="Filter by nodes (primary and secondary)",
1358
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1359

    
1360
m_clust_opt = cli_option("--all", dest="multi_mode",
1361
                         help="Select all instances in the cluster",
1362
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1363

    
1364
m_inst_opt = cli_option("--instance", dest="multi_mode",
1365
                        help="Filter by instance name [default]",
1366
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1367

    
1368

    
1369
# this is defined separately due to readability only
1370
add_opts = [
1371
  DEBUG_OPT,
1372
  cli_option("-n", "--node", dest="node",
1373
             help="Target node and optional secondary node",
1374
             metavar="<pnode>[:<snode>]"),
1375
  os_opt,
1376
  cli_option("-B", "--backend", dest="beparams",
1377
             type="keyval", default={},
1378
             help="Backend parameters"),
1379
  cli_option("-t", "--disk-template", dest="disk_template",
1380
             help="Custom disk setup (diskless, file, plain or drbd)",
1381
             default=None, metavar="TEMPL",
1382
             choices=list(constants.DISK_TEMPLATES)),
1383
  cli_option("-s", "--os-size", dest="sd_size", help="Disk size for a"
1384
             " single-disk configuration, when not using the --disk option,"
1385
             " in MiB unless a suffix is used",
1386
             default=None, type="unit", metavar="<size>"),
1387
  cli_option("--disk", help="Disk information",
1388
             default=[], dest="disks",
1389
             action="append",
1390
             type="identkeyval"),
1391
  cli_option("--net", help="NIC information",
1392
             default=[], dest="nics",
1393
             action="append",
1394
             type="identkeyval"),
1395
  cli_option("--no-nics", default=False, action="store_true",
1396
             help="Do not create any network cards for the instance"),
1397
  cli_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1398
             action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1399
  cli_option("--no-start", dest="start", default=True,
1400
             action="store_false", help="Don't start the instance after"
1401
             " creation"),
1402
  cli_option("--no-ip-check", dest="ip_check", default=True,
1403
             action="store_false", help="Don't check that the instance's IP"
1404
             " is alive (only valid with --no-start)"),
1405
  cli_option("--file-storage-dir", dest="file_storage_dir",
1406
             help="Relative path under default cluster-wide file storage dir"
1407
             " to store file-based disks", default=None,
1408
             metavar="<DIR>"),
1409
  cli_option("--file-driver", dest="file_driver", help="Driver to use"
1410
             " for image files", default="loop", metavar="<DRIVER>",
1411
             choices=list(constants.FILE_DRIVER)),
1412
  cli_option("-I", "--iallocator", metavar="<NAME>",
1413
             help="Select nodes for the instance automatically using the"
1414
             " <NAME> iallocator plugin", default=None, type="string",
1415
             completion_suggest=OPT_COMPL_ONE_IALLOCATOR),
1416
  cli_option("-H", "--hypervisor", dest="hypervisor",
1417
             help="Hypervisor and hypervisor options, in the format"
1418
             " hypervisor:option=value,option=value,...", default=None,
1419
             type="identkeyval"),
1420
  SUBMIT_OPT,
1421
  ]
1422

    
1423
commands = {
1424
  'add': (AddInstance, [ArgHost(min=1, max=1)], add_opts,
1425
          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1426
          "Creates and adds a new instance to the cluster"),
1427
  'batch-create': (BatchCreate, [ArgFile(min=1, max=1)],
1428
                   [DEBUG_OPT],
1429
                   "<instances_file.json>",
1430
                   "Create a bunch of instances based on specs in the file."),
1431
  'console': (ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1432
              [DEBUG_OPT,
1433
               cli_option("--show-cmd", dest="show_command",
1434
                          action="store_true", default=False,
1435
                          help=("Show command instead of executing it"))],
1436
              "[--show-cmd] <instance>",
1437
              "Opens a console on the specified instance"),
1438
  'failover': (FailoverInstance, ARGS_ONE_INSTANCE,
1439
               [DEBUG_OPT, FORCE_OPT,
1440
                cli_option("--ignore-consistency", dest="ignore_consistency",
1441
                           action="store_true", default=False,
1442
                           help="Ignore the consistency of the disks on"
1443
                           " the secondary"),
1444
                SUBMIT_OPT,
1445
                ],
1446
               "[-f] <instance>",
1447
               "Stops the instance and starts it on the backup node, using"
1448
               " the remote mirror (only for instances of type drbd)"),
1449
  'migrate': (MigrateInstance, ARGS_ONE_INSTANCE,
1450
               [DEBUG_OPT, FORCE_OPT,
1451
                cli_option("--non-live", dest="live",
1452
                           default=True, action="store_false",
1453
                           help="Do a non-live migration (this usually means"
1454
                           " freeze the instance, save the state,"
1455
                           " transfer and only then resume running on the"
1456
                           " secondary node)"),
1457
                cli_option("--cleanup", dest="cleanup",
1458
                           default=False, action="store_true",
1459
                           help="Instead of performing the migration, try to"
1460
                           " recover from a failed cleanup. This is safe"
1461
                           " to run even if the instance is healthy, but it"
1462
                           " will create extra replication traffic and "
1463
                           " disrupt briefly the replication (like during the"
1464
                           " migration"),
1465
                ],
1466
               "[-f] <instance>",
1467
               "Migrate instance to its secondary node"
1468
               " (only for instances of type drbd)"),
1469
  'move': (MoveInstance, ARGS_ONE_INSTANCE,
1470
           [DEBUG_OPT, FORCE_OPT, SUBMIT_OPT,
1471
            cli_option("-n", "--new-node", dest="target_node",
1472
                       help="Destinattion node", metavar="NODE",
1473
                       default=None,
1474
                       completion_suggest=OPT_COMPL_ONE_NODE),
1475
            ],
1476
           "[-f] <instance>",
1477
           "Move instance to an arbitrary node"
1478
           " (only for instances of type file and lv)"),
1479
  'info': (ShowInstanceConfig, ARGS_MANY_INSTANCES,
1480
           [DEBUG_OPT,
1481
            cli_option("-s", "--static", dest="static",
1482
                       action="store_true", default=False,
1483
                       help="Only show configuration data, not runtime data"),
1484
            cli_option("--all", dest="show_all",
1485
                       default=False, action="store_true",
1486
                       help="Show info on all instances on the cluster."
1487
                       " This can take a long time to run, use wisely."),
1488
            ], "[-s] {--all | <instance>...}",
1489
           "Show information on the specified instance(s)"),
1490
  'list': (ListInstances, ARGS_MANY_INSTANCES,
1491
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1492
           "[<instance>...]",
1493
           "Lists the instances and their status. The available fields are"
1494
           " (see the man page for details): status, oper_state, oper_ram,"
1495
           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1496
           " ip, mac, mode, link, sda_size, sdb_size, vcpus, serial_no,"
1497
           " hypervisor."
1498
           " The default field"
1499
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1500
           ),
1501
  'reinstall': (ReinstallInstance, [ArgInstance(min=1)],
1502
                [DEBUG_OPT, FORCE_OPT, os_opt,
1503
                 m_force_multi,
1504
                 m_node_opt, m_pri_node_opt, m_sec_node_opt,
1505
                 m_clust_opt, m_inst_opt,
1506
                 cli_option("--select-os", dest="select_os",
1507
                            action="store_true", default=False,
1508
                            help="Interactive OS reinstall, lists available"
1509
                            " OS templates for selection"),
1510
                 SUBMIT_OPT,
1511
                 ],
1512
                "[-f] <instance>", "Reinstall a stopped instance"),
1513
  'remove': (RemoveInstance, ARGS_ONE_INSTANCE,
1514
             [DEBUG_OPT, FORCE_OPT,
1515
              cli_option("--ignore-failures", dest="ignore_failures",
1516
                         action="store_true", default=False,
1517
                         help=("Remove the instance from the cluster even"
1518
                               " if there are failures during the removal"
1519
                               " process (shutdown, disk removal, etc.)")),
1520
              SUBMIT_OPT,
1521
              ],
1522
             "[-f] <instance>", "Shuts down the instance and removes it"),
1523
  'rename': (RenameInstance,
1524
             [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1525
             [DEBUG_OPT,
1526
              cli_option("--no-ip-check", dest="ignore_ip",
1527
                         help="Do not check that the IP of the new name"
1528
                         " is alive",
1529
                         default=False, action="store_true"),
1530
              SUBMIT_OPT,
1531
              ],
1532
             "<instance> <new_name>", "Rename the instance"),
1533
  'replace-disks': (ReplaceDisks, ARGS_ONE_INSTANCE,
1534
                    [DEBUG_OPT,
1535
                     cli_option("-n", "--new-secondary", dest="new_secondary",
1536
                                help=("New secondary node (for secondary"
1537
                                      " node change)"), metavar="NODE",
1538
                                default=None,
1539
                                completion_suggest=OPT_COMPL_ONE_NODE),
1540
                     cli_option("-p", "--on-primary", dest="on_primary",
1541
                                default=False, action="store_true",
1542
                                help=("Replace the disk(s) on the primary"
1543
                                      " node (only for the drbd template)")),
1544
                     cli_option("-s", "--on-secondary", dest="on_secondary",
1545
                                default=False, action="store_true",
1546
                                help=("Replace the disk(s) on the secondary"
1547
                                      " node (only for the drbd template)")),
1548
                     cli_option("-a", "--auto", dest="auto",
1549
                                default=False, action="store_true",
1550
                                help=("Automatically replace faulty disks"
1551
                                      " (only for the drbd template)")),
1552
                     cli_option("--disks", dest="disks", default=None,
1553
                                help="Comma-separated list of disks"
1554
                                " indices to replace (e.g. 0,2) (optional,"
1555
                                " defaults to all disks)"),
1556
                     cli_option("-I", "--iallocator", metavar="<NAME>",
1557
                                help="Select new secondary for the instance"
1558
                                " automatically using the"
1559
                                " <NAME> iallocator plugin (enables"
1560
                                " secondary node replacement)",
1561
                                default=None, type="string",
1562
                                completion_suggest=OPT_COMPL_ONE_IALLOCATOR),
1563
                     SUBMIT_OPT,
1564
                     ],
1565
                    "[-s|-p|-n NODE|-I NAME] <instance>",
1566
                    "Replaces all disks for the instance"),
1567
  'modify': (SetInstanceParams, ARGS_ONE_INSTANCE,
1568
             [DEBUG_OPT, FORCE_OPT,
1569
              cli_option("-H", "--hypervisor", type="keyval",
1570
                         default={}, dest="hypervisor",
1571
                         help="Change hypervisor parameters"),
1572
              cli_option("-B", "--backend", type="keyval",
1573
                         default={}, dest="beparams",
1574
                         help="Change backend parameters"),
1575
              cli_option("--disk", help="Disk changes",
1576
                         default=[], dest="disks",
1577
                         action="append",
1578
                         type="identkeyval"),
1579
              cli_option("--net", help="NIC changes",
1580
                         default=[], dest="nics",
1581
                         action="append",
1582
                         type="identkeyval"),
1583
              SUBMIT_OPT,
1584
              ],
1585
             "<instance>", "Alters the parameters of an instance"),
1586
  'shutdown': (ShutdownInstance, [ArgInstance(min=1)],
1587
               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1588
                m_clust_opt, m_inst_opt, m_force_multi,
1589
                SUBMIT_OPT,
1590
                ],
1591
               "<instance>", "Stops an instance"),
1592
  'startup': (StartupInstance, [ArgInstance(min=1)],
1593
              [DEBUG_OPT, FORCE_OPT, m_force_multi,
1594
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1595
               m_clust_opt, m_inst_opt,
1596
               SUBMIT_OPT,
1597
               cli_option("-H", "--hypervisor", type="keyval",
1598
                          default={}, dest="hvparams",
1599
                          help="Temporary hypervisor parameters"),
1600
               cli_option("-B", "--backend", type="keyval",
1601
                          default={}, dest="beparams",
1602
                          help="Temporary backend parameters"),
1603
               ],
1604
              "<instance>", "Starts an instance"),
1605
  'reboot': (RebootInstance, [ArgInstance(min=1)],
1606
              [DEBUG_OPT, m_force_multi,
1607
               cli_option("-t", "--type", dest="reboot_type",
1608
                          help="Type of reboot: soft/hard/full",
1609
                          default=constants.INSTANCE_REBOOT_HARD,
1610
                          metavar="<REBOOT>",
1611
                          choices=list(constants.REBOOT_TYPES)),
1612
               cli_option("--ignore-secondaries", dest="ignore_secondaries",
1613
                          default=False, action="store_true",
1614
                          help="Ignore errors from secondaries"),
1615
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1616
               m_clust_opt, m_inst_opt,
1617
               SUBMIT_OPT,
1618
               ],
1619
            "<instance>", "Reboots an instance"),
1620
  'activate-disks': (ActivateDisks, ARGS_ONE_INSTANCE,
1621
                     [DEBUG_OPT, SUBMIT_OPT,
1622
                      cli_option("--ignore-size", dest="ignore_size",
1623
                                 default=False, action="store_true",
1624
                                 help="Ignore current recorded size"
1625
                                 " (useful for forcing activation when"
1626
                                 " the recorded size is wrong)"),
1627
                      ],
1628
                     "<instance>",
1629
                     "Activate an instance's disks"),
1630
  'deactivate-disks': (DeactivateDisks, ARGS_ONE_INSTANCE,
1631
                       [DEBUG_OPT, SUBMIT_OPT],
1632
                       "<instance>",
1633
                       "Deactivate an instance's disks"),
1634
  'recreate-disks': (RecreateDisks, ARGS_ONE_INSTANCE,
1635
                     [DEBUG_OPT, SUBMIT_OPT,
1636
                     cli_option("--disks", dest="disks", default=None,
1637
                                help="Comma-separated list of disks"
1638
                                " indices to replace (e.g. 0,2) (optional,"
1639
                                " defaults to all disks)"),
1640
                      ],
1641
                     "<instance>",
1642
                     "Recreate an instance's disks"),
1643
  'grow-disk': (GrowDisk,
1644
                [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1645
                 ArgUnknown(min=1, max=1)],
1646
                [DEBUG_OPT, SUBMIT_OPT,
1647
                 cli_option("--no-wait-for-sync",
1648
                            dest="wait_for_sync", default=True,
1649
                            action="store_false",
1650
                            help="Don't wait for sync (DANGEROUS!)"),
1651
                 ],
1652
                "<instance> <disk> <size>", "Grow an instance's disk"),
1653
  'list-tags': (ListTags, ARGS_ONE_INSTANCE, [DEBUG_OPT],
1654
                "<instance_name>", "List the tags of the given instance"),
1655
  'add-tags': (AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1656
               [DEBUG_OPT, TAG_SRC_OPT],
1657
               "<instance_name> tag...", "Add tags to the given instance"),
1658
  'remove-tags': (RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1659
                  [DEBUG_OPT, TAG_SRC_OPT],
1660
                  "<instance_name> tag...", "Remove tags from given instance"),
1661
  }
1662

    
1663
#: dictionary with aliases for commands
1664
aliases = {
1665
  'activate_block_devs': 'activate-disks',
1666
  'replace_disks': 'replace-disks',
1667
  'start': 'startup',
1668
  'stop': 'shutdown',
1669
  }
1670

    
1671

    
1672
if __name__ == '__main__':
1673
  sys.exit(GenericMain(commands, aliases=aliases,
1674
                       override={"tag_type": constants.TAG_INSTANCE}))