Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ 456798ab

History | View | Annotate | Download (52.8 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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
"""Instance related commands"""
22

    
23
# pylint: disable-msg=W0401,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0614: Unused import %s from wildcard import (since we need cli)
26
# C0103: Invalid name gnt-instance
27

    
28
import itertools
29
import simplejson
30
import logging
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import opcodes
35
from ganeti import constants
36
from ganeti import compat
37
from ganeti import utils
38
from ganeti import errors
39
from ganeti import netutils
40
from ganeti import ssh
41
from ganeti import objects
42

    
43

    
44
_SHUTDOWN_CLUSTER = "cluster"
45
_SHUTDOWN_NODES_BOTH = "nodes"
46
_SHUTDOWN_NODES_PRI = "nodes-pri"
47
_SHUTDOWN_NODES_SEC = "nodes-sec"
48
_SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
49
_SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
50
_SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
51
_SHUTDOWN_INSTANCES = "instances"
52
_SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
53

    
54
_SHUTDOWN_NODES_TAGS_MODES = (
55
    _SHUTDOWN_NODES_BOTH_BY_TAGS,
56
    _SHUTDOWN_NODES_PRI_BY_TAGS,
57
    _SHUTDOWN_NODES_SEC_BY_TAGS)
58

    
59

    
60
#: default list of options for L{ListInstances}
61
_LIST_DEF_FIELDS = [
62
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
63
  ]
64

    
65

    
66
def _ExpandMultiNames(mode, names, client=None):
67
  """Expand the given names using the passed mode.
68

69
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
70
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72
  instances having those nodes as either primary or secondary will be
73
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
74
  returned.
75

76
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
77
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
78
      L{_SHUTDOWN_INSTANCES}
79
  @param names: a list of names; for cluster, it must be empty,
80
      and for node and instance it must be a list of valid item
81
      names (short names are valid as usual, e.g. node1 instead of
82
      node1.example.com)
83
  @rtype: list
84
  @return: the list of names after the expansion
85
  @raise errors.ProgrammerError: for unknown selection type
86
  @raise errors.OpPrereqError: for invalid input parameters
87

88
  """
89
  # pylint: disable-msg=W0142
90

    
91
  if client is None:
92
    client = GetClient()
93
  if mode == _SHUTDOWN_CLUSTER:
94
    if names:
95
      raise errors.OpPrereqError("Cluster filter mode takes no arguments",
96
                                 errors.ECODE_INVAL)
97
    idata = client.QueryInstances([], ["name"], False)
98
    inames = [row[0] for row in idata]
99

    
100
  elif mode in (_SHUTDOWN_NODES_BOTH,
101
                _SHUTDOWN_NODES_PRI,
102
                _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
103
    if mode in _SHUTDOWN_NODES_TAGS_MODES:
104
      if not names:
105
        raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106
      ndata = client.QueryNodes([], ["name", "pinst_list",
107
                                     "sinst_list", "tags"], False)
108
      ndata = [row for row in ndata if set(row[3]).intersection(names)]
109
    else:
110
      if not names:
111
        raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112
      ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
113
                              False)
114

    
115
    ipri = [row[1] for row in ndata]
116
    pri_names = list(itertools.chain(*ipri))
117
    isec = [row[2] for row in ndata]
118
    sec_names = list(itertools.chain(*isec))
119
    if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120
      inames = pri_names + sec_names
121
    elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
122
      inames = pri_names
123
    elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
124
      inames = sec_names
125
    else:
126
      raise errors.ProgrammerError("Unhandled shutdown type")
127
  elif mode == _SHUTDOWN_INSTANCES:
128
    if not names:
129
      raise errors.OpPrereqError("No instance names passed",
130
                                 errors.ECODE_INVAL)
131
    idata = client.QueryInstances(names, ["name"], False)
132
    inames = [row[0] for row in idata]
133
  elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
134
    if not names:
135
      raise errors.OpPrereqError("No instance tags passed",
136
                                 errors.ECODE_INVAL)
137
    idata = client.QueryInstances([], ["name", "tags"], False)
138
    inames = [row[0] for row in idata if set(row[1]).intersection(names)]
139
  else:
140
    raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
141

    
142
  return inames
143

    
144

    
145
def _ConfirmOperation(inames, text, extra=""):
146
  """Ask the user to confirm an operation on a list of instances.
147

148
  This function is used to request confirmation for doing an operation
149
  on a given list of instances.
150

151
  @type inames: list
152
  @param inames: the list of names that we display when
153
      we ask for confirmation
154
  @type text: str
155
  @param text: the operation that the user should confirm
156
      (e.g. I{shutdown} or I{startup})
157
  @rtype: boolean
158
  @return: True or False depending on user's confirmation.
159

160
  """
161
  count = len(inames)
162
  msg = ("The %s will operate on %d instances.\n%s"
163
         "Do you want to continue?" % (text, count, extra))
164
  affected = ("\nAffected instances:\n" +
165
              "\n".join(["  %s" % name for name in inames]))
166

    
167
  choices = [('y', True, 'Yes, execute the %s' % text),
168
             ('n', False, 'No, abort the %s' % text)]
169

    
170
  if count > 20:
171
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
172
    ask = msg
173
  else:
174
    ask = msg + affected
175

    
176
  choice = AskUser(ask, choices)
177
  if choice == 'v':
178
    choices.pop(1)
179
    choice = AskUser(msg + affected, choices)
180
  return choice
181

    
182

    
183
def _EnsureInstancesExist(client, names):
184
  """Check for and ensure the given instance names exist.
185

186
  This function will raise an OpPrereqError in case they don't
187
  exist. Otherwise it will exit cleanly.
188

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

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

    
204

    
205
def GenericManyOps(operation, fn):
206
  """Generic multi-instance operations.
207

208
  The will return a wrapper that processes the options and arguments
209
  given, and uses the passed function to build the opcode needed for
210
  the specific operation. Thus all the generic loop/confirmation code
211
  is abstracted into this function.
212

213
  """
214
  def realfn(opts, args):
215
    if opts.multi_mode is None:
216
      opts.multi_mode = _SHUTDOWN_INSTANCES
217
    cl = GetClient()
218
    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
219
    if not inames:
220
      if opts.multi_mode == _SHUTDOWN_CLUSTER:
221
        ToStdout("Cluster is empty, no instances to shutdown")
222
        return 0
223
      raise errors.OpPrereqError("Selection filter does not match"
224
                                 " any instances", errors.ECODE_INVAL)
225
    multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
226
    if not (opts.force_multi or not multi_on
227
            or _ConfirmOperation(inames, operation)):
228
      return 1
229
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
230
    for name in inames:
231
      op = fn(name, opts)
232
      jex.QueueJob(name, op)
233
    results = jex.WaitOrShow(not opts.submit_only)
234
    rcode = compat.all(row[0] for row in results)
235
    return int(not rcode)
236
  return realfn
237

    
238

    
239
def ListInstances(opts, args):
240
  """List instances and their properties.
241

242
  @param opts: the command line options selected by the user
243
  @type args: list
244
  @param args: should be an empty list
245
  @rtype: int
246
  @return: the desired exit code
247

248
  """
249
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
250

    
251
  fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
252
                               "nic.modes", "nic.links", "nic.bridges",
253
                               "snodes"],
254
                              (lambda value: ",".join(str(item)
255
                                                      for item in value),
256
                               False))
257

    
258
  return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
259
                     opts.separator, not opts.no_headers,
260
                     format_override=fmtoverride, verbose=opts.verbose)
261

    
262

    
263
def ListInstanceFields(opts, args):
264
  """List instance fields.
265

266
  @param opts: the command line options selected by the user
267
  @type args: list
268
  @param args: fields to list, or empty for all
269
  @rtype: int
270
  @return: the desired exit code
271

272
  """
273
  return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
274
                           not opts.no_headers)
275

    
276

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

280
  This is just a wrapper over GenericInstanceCreate.
281

282
  """
283
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
284

    
285

    
286
def BatchCreate(opts, args):
287
  """Create instances using a definition file.
288

289
  This function reads a json file with instances defined
290
  in the form::
291

292
    {"instance-name":{
293
      "disk_size": [20480],
294
      "template": "drbd",
295
      "backend": {
296
        "memory": 512,
297
        "vcpus": 1 },
298
      "os": "debootstrap",
299
      "primary_node": "firstnode",
300
      "secondary_node": "secondnode",
301
      "iallocator": "dumb"}
302
    }
303

304
  Note that I{primary_node} and I{secondary_node} have precedence over
305
  I{iallocator}.
306

307
  @param opts: the command line options selected by the user
308
  @type args: list
309
  @param args: should contain one element, the json filename
310
  @rtype: int
311
  @return: the desired exit code
312

313
  """
314
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
315
                    "backend": {},
316
                    "iallocator": None,
317
                    "primary_node": None,
318
                    "secondary_node": None,
319
                    "nics": None,
320
                    "start": True,
321
                    "ip_check": True,
322
                    "name_check": True,
323
                    "hypervisor": None,
324
                    "hvparams": {},
325
                    "file_storage_dir": None,
326
                    "force_variant": False,
327
                    "file_driver": 'loop'}
328

    
329
  def _PopulateWithDefaults(spec):
330
    """Returns a new hash combined with default values."""
331
    mydict = _DEFAULT_SPECS.copy()
332
    mydict.update(spec)
333
    return mydict
334

    
335
  def _Validate(spec):
336
    """Validate the instance specs."""
337
    # Validate fields required under any circumstances
338
    for required_field in ('os', 'template'):
339
      if required_field not in spec:
340
        raise errors.OpPrereqError('Required field "%s" is missing.' %
341
                                   required_field, errors.ECODE_INVAL)
342
    # Validate special fields
343
    if spec['primary_node'] is not None:
344
      if (spec['template'] in constants.DTS_NET_MIRROR and
345
          spec['secondary_node'] is None):
346
        raise errors.OpPrereqError('Template requires secondary node, but'
347
                                   ' there was no secondary provided.',
348
                                   errors.ECODE_INVAL)
349
    elif spec['iallocator'] is None:
350
      raise errors.OpPrereqError('You have to provide at least a primary_node'
351
                                 ' or an iallocator.',
352
                                 errors.ECODE_INVAL)
353

    
354
    if (spec['hvparams'] and
355
        not isinstance(spec['hvparams'], dict)):
356
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
357
                                 errors.ECODE_INVAL)
358

    
359
  json_filename = args[0]
360
  try:
361
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
362
  except Exception, err: # pylint: disable-msg=W0703
363
    ToStderr("Can't parse the instance definition file: %s" % str(err))
364
    return 1
365

    
366
  if not isinstance(instance_data, dict):
367
    ToStderr("The instance definition file is not in dict format.")
368
    return 1
369

    
370
  jex = JobExecutor(opts=opts)
371

    
372
  # Iterate over the instances and do:
373
  #  * Populate the specs with default value
374
  #  * Validate the instance specs
375
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
376
  for name in i_names:
377
    specs = instance_data[name]
378
    specs = _PopulateWithDefaults(specs)
379
    _Validate(specs)
380

    
381
    hypervisor = specs['hypervisor']
382
    hvparams = specs['hvparams']
383

    
384
    disks = []
385
    for elem in specs['disk_size']:
386
      try:
387
        size = utils.ParseUnit(elem)
388
      except (TypeError, ValueError), err:
389
        raise errors.OpPrereqError("Invalid disk size '%s' for"
390
                                   " instance %s: %s" %
391
                                   (elem, name, err), errors.ECODE_INVAL)
392
      disks.append({"size": size})
393

    
394
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
395
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
396

    
397
    tmp_nics = []
398
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
399
      if field in specs:
400
        if not tmp_nics:
401
          tmp_nics.append({})
402
        tmp_nics[0][field] = specs[field]
403

    
404
    if specs['nics'] is not None and tmp_nics:
405
      raise errors.OpPrereqError("'nics' list incompatible with using"
406
                                 " individual nic fields as well",
407
                                 errors.ECODE_INVAL)
408
    elif specs['nics'] is not None:
409
      tmp_nics = specs['nics']
410
    elif not tmp_nics:
411
      tmp_nics = [{}]
412

    
413
    op = opcodes.OpInstanceCreate(instance_name=name,
414
                                  disks=disks,
415
                                  disk_template=specs['template'],
416
                                  mode=constants.INSTANCE_CREATE,
417
                                  os_type=specs['os'],
418
                                  force_variant=specs["force_variant"],
419
                                  pnode=specs['primary_node'],
420
                                  snode=specs['secondary_node'],
421
                                  nics=tmp_nics,
422
                                  start=specs['start'],
423
                                  ip_check=specs['ip_check'],
424
                                  name_check=specs['name_check'],
425
                                  wait_for_sync=True,
426
                                  iallocator=specs['iallocator'],
427
                                  hypervisor=hypervisor,
428
                                  hvparams=hvparams,
429
                                  beparams=specs['backend'],
430
                                  file_storage_dir=specs['file_storage_dir'],
431
                                  file_driver=specs['file_driver'])
432

    
433
    jex.QueueJob(name, op)
434
  # we never want to wait, just show the submitted job IDs
435
  jex.WaitOrShow(False)
436

    
437
  return 0
438

    
439

    
440
def ReinstallInstance(opts, args):
441
  """Reinstall an instance.
442

443
  @param opts: the command line options selected by the user
444
  @type args: list
445
  @param args: should contain only one element, the name of the
446
      instance to be reinstalled
447
  @rtype: int
448
  @return: the desired exit code
449

450
  """
451
  # first, compute the desired name list
452
  if opts.multi_mode is None:
453
    opts.multi_mode = _SHUTDOWN_INSTANCES
454

    
455
  inames = _ExpandMultiNames(opts.multi_mode, args)
456
  if not inames:
457
    raise errors.OpPrereqError("Selection filter does not match any instances",
458
                               errors.ECODE_INVAL)
459

    
460
  # second, if requested, ask for an OS
461
  if opts.select_os is True:
462
    op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
463
    result = SubmitOpCode(op, opts=opts)
464

    
465
    if not result:
466
      ToStdout("Can't get the OS list")
467
      return 1
468

    
469
    ToStdout("Available OS templates:")
470
    number = 0
471
    choices = []
472
    for (name, variants) in result:
473
      for entry in CalculateOSNames(name, variants):
474
        ToStdout("%3s: %s", number, entry)
475
        choices.append(("%s" % number, entry, entry))
476
        number += 1
477

    
478
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
479
    selected = AskUser("Enter OS template number (or x to abort):",
480
                       choices)
481

    
482
    if selected == 'exit':
483
      ToStderr("User aborted reinstall, exiting")
484
      return 1
485

    
486
    os_name = selected
487
  else:
488
    os_name = opts.os
489

    
490
  # third, get confirmation: multi-reinstall requires --force-multi,
491
  # single-reinstall either --force or --force-multi (--force-multi is
492
  # a stronger --force)
493
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
494
  if multi_on:
495
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
496
    if not (opts.force_multi or
497
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
498
      return 1
499
  else:
500
    if not (opts.force or opts.force_multi):
501
      usertext = ("This will reinstall the instance %s and remove"
502
                  " all data. Continue?") % inames[0]
503
      if not AskUser(usertext):
504
        return 1
505

    
506
  jex = JobExecutor(verbose=multi_on, opts=opts)
507
  for instance_name in inames:
508
    op = opcodes.OpInstanceReinstall(instance_name=instance_name,
509
                                     os_type=os_name,
510
                                     force_variant=opts.force_variant,
511
                                     osparams=opts.osparams)
512
    jex.QueueJob(instance_name, op)
513

    
514
  jex.WaitOrShow(not opts.submit_only)
515
  return 0
516

    
517

    
518
def RemoveInstance(opts, args):
519
  """Remove an instance.
520

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

528
  """
529
  instance_name = args[0]
530
  force = opts.force
531
  cl = GetClient()
532

    
533
  if not force:
534
    _EnsureInstancesExist(cl, [instance_name])
535

    
536
    usertext = ("This will remove the volumes of the instance %s"
537
                " (including mirrors), thus removing all the data"
538
                " of the instance. Continue?") % instance_name
539
    if not AskUser(usertext):
540
      return 1
541

    
542
  op = opcodes.OpInstanceRemove(instance_name=instance_name,
543
                                ignore_failures=opts.ignore_failures,
544
                                shutdown_timeout=opts.shutdown_timeout)
545
  SubmitOrSend(op, opts, cl=cl)
546
  return 0
547

    
548

    
549
def RenameInstance(opts, args):
550
  """Rename an instance.
551

552
  @param opts: the command line options selected by the user
553
  @type args: list
554
  @param args: should contain two elements, the old and the
555
      new instance names
556
  @rtype: int
557
  @return: the desired exit code
558

559
  """
560
  if not opts.name_check:
561
    if not AskUser("As you disabled the check of the DNS entry, please verify"
562
                   " that '%s' is a FQDN. Continue?" % args[1]):
563
      return 1
564

    
565
  op = opcodes.OpInstanceRename(instance_name=args[0],
566
                                new_name=args[1],
567
                                ip_check=opts.ip_check,
568
                                name_check=opts.name_check)
569
  result = SubmitOrSend(op, opts)
570

    
571
  if result:
572
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
573

    
574
  return 0
575

    
576

    
577
def ActivateDisks(opts, args):
578
  """Activate an instance's disks.
579

580
  This serves two purposes:
581
    - it allows (as long as the instance is not running)
582
      mounting the disks and modifying them from the node
583
    - it repairs inactive secondary drbds
584

585
  @param opts: the command line options selected by the user
586
  @type args: list
587
  @param args: should contain only one element, the instance name
588
  @rtype: int
589
  @return: the desired exit code
590

591
  """
592
  instance_name = args[0]
593
  op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
594
                                       ignore_size=opts.ignore_size)
595
  disks_info = SubmitOrSend(op, opts)
596
  for host, iname, nname in disks_info:
597
    ToStdout("%s:%s:%s", host, iname, nname)
598
  return 0
599

    
600

    
601
def DeactivateDisks(opts, args):
602
  """Deactivate an instance's disks.
603

604
  This function takes the instance name, looks for its primary node
605
  and the tries to shutdown its block devices on that node.
606

607
  @param opts: the command line options selected by the user
608
  @type args: list
609
  @param args: should contain only one element, the instance name
610
  @rtype: int
611
  @return: the desired exit code
612

613
  """
614
  instance_name = args[0]
615
  op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
616
                                         force=opts.force)
617
  SubmitOrSend(op, opts)
618
  return 0
619

    
620

    
621
def RecreateDisks(opts, args):
622
  """Recreate an instance's disks.
623

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

630
  """
631
  instance_name = args[0]
632
  if opts.disks:
633
    try:
634
      opts.disks = [int(v) for v in opts.disks.split(",")]
635
    except (ValueError, TypeError), err:
636
      ToStderr("Invalid disks value: %s" % str(err))
637
      return 1
638
  else:
639
    opts.disks = []
640

    
641
  if opts.node:
642
    pnode, snode = SplitNodeOption(opts.node)
643
    nodes = [pnode]
644
    if snode is not None:
645
      nodes.append(snode)
646
  else:
647
    nodes = []
648

    
649
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
650
                                       disks=opts.disks,
651
                                       nodes=nodes)
652
  SubmitOrSend(op, opts)
653
  return 0
654

    
655

    
656
def GrowDisk(opts, args):
657
  """Grow an instance's disks.
658

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

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

    
681

    
682
def _StartupInstance(name, opts):
683
  """Startup instances.
684

685
  This returns the opcode to start an instance, and its decorator will
686
  wrap this into a loop starting all desired instances.
687

688
  @param name: the name of the instance to act on
689
  @param opts: the command line options selected by the user
690
  @return: the opcode needed for the operation
691

692
  """
693
  op = opcodes.OpInstanceStartup(instance_name=name,
694
                                 force=opts.force,
695
                                 ignore_offline_nodes=opts.ignore_offline)
696
  # do not add these parameters to the opcode unless they're defined
697
  if opts.hvparams:
698
    op.hvparams = opts.hvparams
699
  if opts.beparams:
700
    op.beparams = opts.beparams
701
  return op
702

    
703

    
704
def _RebootInstance(name, opts):
705
  """Reboot instance(s).
706

707
  This returns the opcode to reboot an instance, and its decorator
708
  will wrap this into a loop rebooting all desired instances.
709

710
  @param name: the name of the instance to act on
711
  @param opts: the command line options selected by the user
712
  @return: the opcode needed for the operation
713

714
  """
715
  return opcodes.OpInstanceReboot(instance_name=name,
716
                                  reboot_type=opts.reboot_type,
717
                                  ignore_secondaries=opts.ignore_secondaries,
718
                                  shutdown_timeout=opts.shutdown_timeout)
719

    
720

    
721
def _ShutdownInstance(name, opts):
722
  """Shutdown an instance.
723

724
  This returns the opcode to shutdown an instance, and its decorator
725
  will wrap this into a loop shutting down all desired instances.
726

727
  @param name: the name of the instance to act on
728
  @param opts: the command line options selected by the user
729
  @return: the opcode needed for the operation
730

731
  """
732
  return opcodes.OpInstanceShutdown(instance_name=name,
733
                                    timeout=opts.timeout,
734
                                    ignore_offline_nodes=opts.ignore_offline)
735

    
736

    
737
def ReplaceDisks(opts, args):
738
  """Replace the disks of an instance
739

740
  @param opts: the command line options selected by the user
741
  @type args: list
742
  @param args: should contain only one element, the instance name
743
  @rtype: int
744
  @return: the desired exit code
745

746
  """
747
  new_2ndary = opts.dst_node
748
  iallocator = opts.iallocator
749
  if opts.disks is None:
750
    disks = []
751
  else:
752
    try:
753
      disks = [int(i) for i in opts.disks.split(",")]
754
    except (TypeError, ValueError), err:
755
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
756
                                 errors.ECODE_INVAL)
757
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
758
         new_2ndary is not None, iallocator is not None].count(True)
759
  if cnt != 1:
760
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
761
                               " options must be passed", errors.ECODE_INVAL)
762
  elif opts.on_primary:
763
    mode = constants.REPLACE_DISK_PRI
764
  elif opts.on_secondary:
765
    mode = constants.REPLACE_DISK_SEC
766
  elif opts.auto:
767
    mode = constants.REPLACE_DISK_AUTO
768
    if disks:
769
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
770
                                 " mode", errors.ECODE_INVAL)
771
  elif new_2ndary is not None or iallocator is not None:
772
    # replace secondary
773
    mode = constants.REPLACE_DISK_CHG
774

    
775
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
776
                                      remote_node=new_2ndary, mode=mode,
777
                                      iallocator=iallocator,
778
                                      early_release=opts.early_release)
779
  SubmitOrSend(op, opts)
780
  return 0
781

    
782

    
783
def FailoverInstance(opts, args):
784
  """Failover an instance.
785

786
  The failover is done by shutting it down on its present node and
787
  starting it on the secondary.
788

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

795
  """
796
  cl = GetClient()
797
  instance_name = args[0]
798
  force = opts.force
799

    
800
  if not force:
801
    _EnsureInstancesExist(cl, [instance_name])
802

    
803
    usertext = ("Failover will happen to image %s."
804
                " This requires a shutdown of the instance. Continue?" %
805
                (instance_name,))
806
    if not AskUser(usertext):
807
      return 1
808

    
809
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
810
                                  ignore_consistency=opts.ignore_consistency,
811
                                  shutdown_timeout=opts.shutdown_timeout)
812
  SubmitOrSend(op, opts, cl=cl)
813
  return 0
814

    
815

    
816
def MigrateInstance(opts, args):
817
  """Migrate an instance.
818

819
  The migrate is done without shutdown.
820

821
  @param opts: the command line options selected by the user
822
  @type args: list
823
  @param args: should contain only one element, the instance name
824
  @rtype: int
825
  @return: the desired exit code
826

827
  """
828
  cl = GetClient()
829
  instance_name = args[0]
830
  force = opts.force
831

    
832
  if not force:
833
    _EnsureInstancesExist(cl, [instance_name])
834

    
835
    if opts.cleanup:
836
      usertext = ("Instance %s will be recovered from a failed migration."
837
                  " Note that the migration procedure (including cleanup)" %
838
                  (instance_name,))
839
    else:
840
      usertext = ("Instance %s will be migrated. Note that migration" %
841
                  (instance_name,))
842
    usertext += (" might impact the instance if anything goes wrong"
843
                 " (e.g. due to bugs in the hypervisor). Continue?")
844
    if not AskUser(usertext):
845
      return 1
846

    
847
  # this should be removed once --non-live is deprecated
848
  if not opts.live and opts.migration_mode is not None:
849
    raise errors.OpPrereqError("Only one of the --non-live and "
850
                               "--migration-mode options can be passed",
851
                               errors.ECODE_INVAL)
852
  if not opts.live: # --non-live passed
853
    mode = constants.HT_MIGRATION_NONLIVE
854
  else:
855
    mode = opts.migration_mode
856

    
857
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
858
                                 cleanup=opts.cleanup)
859
  SubmitOpCode(op, cl=cl, opts=opts)
860
  return 0
861

    
862

    
863
def MoveInstance(opts, args):
864
  """Move an instance.
865

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

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

    
877
  if not force:
878
    usertext = ("Instance %s will be moved."
879
                " This requires a shutdown of the instance. Continue?" %
880
                (instance_name,))
881
    if not AskUser(usertext):
882
      return 1
883

    
884
  op = opcodes.OpInstanceMove(instance_name=instance_name,
885
                              target_node=opts.node,
886
                              shutdown_timeout=opts.shutdown_timeout)
887
  SubmitOrSend(op, opts, cl=cl)
888
  return 0
889

    
890

    
891
def ConnectToInstanceConsole(opts, args):
892
  """Connect to the console of an instance.
893

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

900
  """
901
  instance_name = args[0]
902

    
903
  op = opcodes.OpInstanceConsole(instance_name=instance_name)
904

    
905
  cl = GetClient()
906
  try:
907
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
908
    console_data = SubmitOpCode(op, opts=opts, cl=cl)
909
  finally:
910
    # Ensure client connection is closed while external commands are run
911
    cl.Close()
912

    
913
  del cl
914

    
915
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
916
                    opts.show_command, cluster_name)
917

    
918

    
919
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
920
               _runcmd_fn=utils.RunCmd):
921
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
922

923
  @type console: L{objects.InstanceConsole}
924
  @param console: Console object
925
  @type show_command: bool
926
  @param show_command: Whether to just display commands
927
  @type cluster_name: string
928
  @param cluster_name: Cluster name as retrieved from master daemon
929

930
  """
931
  assert console.Validate()
932

    
933
  if console.kind == constants.CONS_MESSAGE:
934
    feedback_fn(console.message)
935
  elif console.kind == constants.CONS_VNC:
936
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
937
                " URL <vnc://%s:%s/>",
938
                console.instance, console.host, console.port,
939
                console.display, console.host, console.port)
940
  elif console.kind == constants.CONS_SSH:
941
    # Convert to string if not already one
942
    if isinstance(console.command, basestring):
943
      cmd = console.command
944
    else:
945
      cmd = utils.ShellQuoteArgs(console.command)
946

    
947
    srun = ssh.SshRunner(cluster_name=cluster_name)
948
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
949
                            batch=True, quiet=False, tty=True)
950

    
951
    if show_command:
952
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
953
    else:
954
      result = _runcmd_fn(ssh_cmd, interactive=True)
955
      if result.failed:
956
        logging.error("Console command \"%s\" failed with reason '%s' and"
957
                      " output %r", result.cmd, result.fail_reason,
958
                      result.output)
959
        raise errors.OpExecError("Connection to console of instance %s failed,"
960
                                 " please check cluster configuration" %
961
                                 console.instance)
962
  else:
963
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
964

    
965
  return constants.EXIT_SUCCESS
966

    
967

    
968
def _FormatLogicalID(dev_type, logical_id, roman):
969
  """Formats the logical_id of a disk.
970

971
  """
972
  if dev_type == constants.LD_DRBD8:
973
    node_a, node_b, port, minor_a, minor_b, key = logical_id
974
    data = [
975
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
976
                                                            convert=roman))),
977
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
978
                                                            convert=roman))),
979
      ("port", compat.TryToRoman(port, convert=roman)),
980
      ("auth key", key),
981
      ]
982
  elif dev_type == constants.LD_LV:
983
    vg_name, lv_name = logical_id
984
    data = ["%s/%s" % (vg_name, lv_name)]
985
  else:
986
    data = [str(logical_id)]
987

    
988
  return data
989

    
990

    
991
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
992
  """Show block device information.
993

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

997
  @type idx: int
998
  @param idx: the index of the current disk
999
  @type top_level: boolean
1000
  @param top_level: if this a top-level disk?
1001
  @type dev: dict
1002
  @param dev: dictionary with disk information
1003
  @type static: boolean
1004
  @param static: wheter the device information doesn't contain
1005
      runtime information but only static data
1006
  @type roman: boolean
1007
  @param roman: whether to try to use roman integers
1008
  @return: a list of either strings, tuples or lists
1009
      (which should be formatted at a higher indent level)
1010

1011
  """
1012
  def helper(dtype, status):
1013
    """Format one line for physical device status.
1014

1015
    @type dtype: str
1016
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1017
    @type status: tuple
1018
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1019
    @return: the string representing the status
1020

1021
    """
1022
    if not status:
1023
      return "not active"
1024
    txt = ""
1025
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1026
    if major is None:
1027
      major_string = "N/A"
1028
    else:
1029
      major_string = str(compat.TryToRoman(major, convert=roman))
1030

    
1031
    if minor is None:
1032
      minor_string = "N/A"
1033
    else:
1034
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1035

    
1036
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1037
    if dtype in (constants.LD_DRBD8, ):
1038
      if syncp is not None:
1039
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1040
        if estt:
1041
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1042
        else:
1043
          sync_text += " ETA unknown"
1044
      else:
1045
        sync_text = "in sync"
1046
      if degr:
1047
        degr_text = "*DEGRADED*"
1048
      else:
1049
        degr_text = "ok"
1050
      if ldisk_status == constants.LDS_FAULTY:
1051
        ldisk_text = " *MISSING DISK*"
1052
      elif ldisk_status == constants.LDS_UNKNOWN:
1053
        ldisk_text = " *UNCERTAIN STATE*"
1054
      else:
1055
        ldisk_text = ""
1056
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1057
    elif dtype == constants.LD_LV:
1058
      if ldisk_status == constants.LDS_FAULTY:
1059
        ldisk_text = " *FAILED* (failed drive?)"
1060
      else:
1061
        ldisk_text = ""
1062
      txt += ldisk_text
1063
    return txt
1064

    
1065
  # the header
1066
  if top_level:
1067
    if dev["iv_name"] is not None:
1068
      txt = dev["iv_name"]
1069
    else:
1070
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1071
  else:
1072
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1073
  if isinstance(dev["size"], int):
1074
    nice_size = utils.FormatUnit(dev["size"], "h")
1075
  else:
1076
    nice_size = dev["size"]
1077
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1078
  data = []
1079
  if top_level:
1080
    data.append(("access mode", dev["mode"]))
1081
  if dev["logical_id"] is not None:
1082
    try:
1083
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1084
    except ValueError:
1085
      l_id = [str(dev["logical_id"])]
1086
    if len(l_id) == 1:
1087
      data.append(("logical_id", l_id[0]))
1088
    else:
1089
      data.extend(l_id)
1090
  elif dev["physical_id"] is not None:
1091
    data.append("physical_id:")
1092
    data.append([dev["physical_id"]])
1093
  if not static:
1094
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1095
  if dev["sstatus"] and not static:
1096
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1097

    
1098
  if dev["children"]:
1099
    data.append("child devices:")
1100
    for c_idx, child in enumerate(dev["children"]):
1101
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1102
  d1.append(data)
1103
  return d1
1104

    
1105

    
1106
def _FormatList(buf, data, indent_level):
1107
  """Formats a list of data at a given indent level.
1108

1109
  If the element of the list is:
1110
    - a string, it is simply formatted as is
1111
    - a tuple, it will be split into key, value and the all the
1112
      values in a list will be aligned all at the same start column
1113
    - a list, will be recursively formatted
1114

1115
  @type buf: StringIO
1116
  @param buf: the buffer into which we write the output
1117
  @param data: the list to format
1118
  @type indent_level: int
1119
  @param indent_level: the indent level to format at
1120

1121
  """
1122
  max_tlen = max([len(elem[0]) for elem in data
1123
                 if isinstance(elem, tuple)] or [0])
1124
  for elem in data:
1125
    if isinstance(elem, basestring):
1126
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1127
    elif isinstance(elem, tuple):
1128
      key, value = elem
1129
      spacer = "%*s" % (max_tlen - len(key), "")
1130
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1131
    elif isinstance(elem, list):
1132
      _FormatList(buf, elem, indent_level+1)
1133

    
1134

    
1135
def ShowInstanceConfig(opts, args):
1136
  """Compute instance run-time status.
1137

1138
  @param opts: the command line options selected by the user
1139
  @type args: list
1140
  @param args: either an empty list, and then we query all
1141
      instances, or should contain a list of instance names
1142
  @rtype: int
1143
  @return: the desired exit code
1144

1145
  """
1146
  if not args and not opts.show_all:
1147
    ToStderr("No instance selected."
1148
             " Please pass in --all if you want to query all instances.\n"
1149
             "Note that this can take a long time on a big cluster.")
1150
    return 1
1151
  elif args and opts.show_all:
1152
    ToStderr("Cannot use --all if you specify instance names.")
1153
    return 1
1154

    
1155
  retcode = 0
1156
  op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1157
                                   use_locking=not opts.static)
1158
  result = SubmitOpCode(op, opts=opts)
1159
  if not result:
1160
    ToStdout("No instances.")
1161
    return 1
1162

    
1163
  buf = StringIO()
1164
  retcode = 0
1165
  for instance_name in result:
1166
    instance = result[instance_name]
1167
    buf.write("Instance name: %s\n" % instance["name"])
1168
    buf.write("UUID: %s\n" % instance["uuid"])
1169
    buf.write("Serial number: %s\n" %
1170
              compat.TryToRoman(instance["serial_no"],
1171
                                convert=opts.roman_integers))
1172
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1173
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1174
    buf.write("State: configured to be %s" % instance["config_state"])
1175
    if not opts.static:
1176
      buf.write(", actual state is %s" % instance["run_state"])
1177
    buf.write("\n")
1178
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1179
    ##          instance["auto_balance"])
1180
    buf.write("  Nodes:\n")
1181
    buf.write("    - primary: %s\n" % instance["pnode"])
1182
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1183
    buf.write("  Operating system: %s\n" % instance["os"])
1184
    FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1185
                        level=2)
1186
    if instance.has_key("network_port"):
1187
      buf.write("  Allocated network port: %s\n" %
1188
                compat.TryToRoman(instance["network_port"],
1189
                                  convert=opts.roman_integers))
1190
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1191

    
1192
    # custom VNC console information
1193
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1194
                                                 None)
1195
    if vnc_bind_address:
1196
      port = instance["network_port"]
1197
      display = int(port) - constants.VNC_BASE_PORT
1198
      if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1199
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1200
                                                   port,
1201
                                                   display)
1202
      elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1203
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1204
                             (vnc_bind_address, port,
1205
                              instance["pnode"], display))
1206
      else:
1207
        # vnc bind address is a file
1208
        vnc_console_port = "%s:%s" % (instance["pnode"],
1209
                                      vnc_bind_address)
1210
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1211

    
1212
    FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1213
                        level=2)
1214
    buf.write("  Hardware:\n")
1215
    buf.write("    - VCPUs: %s\n" %
1216
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1217
                                convert=opts.roman_integers))
1218
    buf.write("    - memory: %sMiB\n" %
1219
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1220
                                convert=opts.roman_integers))
1221
    buf.write("    - NICs:\n")
1222
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1223
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1224
                (idx, mac, ip, mode, link))
1225
    buf.write("  Disk template: %s\n" % instance["disk_template"])
1226
    buf.write("  Disks:\n")
1227

    
1228
    for idx, device in enumerate(instance["disks"]):
1229
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1230
                  opts.roman_integers), 2)
1231

    
1232
  ToStdout(buf.getvalue().rstrip('\n'))
1233
  return retcode
1234

    
1235

    
1236
def SetInstanceParams(opts, args):
1237
  """Modifies an instance.
1238

1239
  All parameters take effect only at the next restart of the instance.
1240

1241
  @param opts: the command line options selected by the user
1242
  @type args: list
1243
  @param args: should contain only one element, the instance name
1244
  @rtype: int
1245
  @return: the desired exit code
1246

1247
  """
1248
  if not (opts.nics or opts.disks or opts.disk_template or
1249
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1250
    ToStderr("Please give at least one of the parameters.")
1251
    return 1
1252

    
1253
  for param in opts.beparams:
1254
    if isinstance(opts.beparams[param], basestring):
1255
      if opts.beparams[param].lower() == "default":
1256
        opts.beparams[param] = constants.VALUE_DEFAULT
1257

    
1258
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1259
                      allowed_values=[constants.VALUE_DEFAULT])
1260

    
1261
  for param in opts.hvparams:
1262
    if isinstance(opts.hvparams[param], basestring):
1263
      if opts.hvparams[param].lower() == "default":
1264
        opts.hvparams[param] = constants.VALUE_DEFAULT
1265

    
1266
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1267
                      allowed_values=[constants.VALUE_DEFAULT])
1268

    
1269
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1270
    try:
1271
      nic_op = int(nic_op)
1272
      opts.nics[idx] = (nic_op, nic_dict)
1273
    except (TypeError, ValueError):
1274
      pass
1275

    
1276
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1277
    try:
1278
      disk_op = int(disk_op)
1279
      opts.disks[idx] = (disk_op, disk_dict)
1280
    except (TypeError, ValueError):
1281
      pass
1282
    if disk_op == constants.DDM_ADD:
1283
      if 'size' not in disk_dict:
1284
        raise errors.OpPrereqError("Missing required parameter 'size'",
1285
                                   errors.ECODE_INVAL)
1286
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1287

    
1288
  if (opts.disk_template and
1289
      opts.disk_template in constants.DTS_NET_MIRROR and
1290
      not opts.node):
1291
    ToStderr("Changing the disk template to a mirrored one requires"
1292
             " specifying a secondary node")
1293
    return 1
1294

    
1295
  op = opcodes.OpInstanceSetParams(instance_name=args[0],
1296
                                   nics=opts.nics,
1297
                                   disks=opts.disks,
1298
                                   disk_template=opts.disk_template,
1299
                                   remote_node=opts.node,
1300
                                   hvparams=opts.hvparams,
1301
                                   beparams=opts.beparams,
1302
                                   os_name=opts.os,
1303
                                   osparams=opts.osparams,
1304
                                   force_variant=opts.force_variant,
1305
                                   force=opts.force,
1306
                                   wait_for_sync=opts.wait_for_sync)
1307

    
1308
  # even if here we process the result, we allow submit only
1309
  result = SubmitOrSend(op, opts)
1310

    
1311
  if result:
1312
    ToStdout("Modified instance %s", args[0])
1313
    for param, data in result:
1314
      ToStdout(" - %-5s -> %s", param, data)
1315
    ToStdout("Please don't forget that most parameters take effect"
1316
             " only at the next start of the instance.")
1317
  return 0
1318

    
1319

    
1320
# multi-instance selection options
1321
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1322
                           help="Do not ask for confirmation when more than"
1323
                           " one instance is affected",
1324
                           action="store_true", default=False)
1325

    
1326
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1327
                            help="Filter by nodes (primary only)",
1328
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1329

    
1330
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1331
                            help="Filter by nodes (secondary only)",
1332
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1333

    
1334
m_node_opt = cli_option("--node", dest="multi_mode",
1335
                        help="Filter by nodes (primary and secondary)",
1336
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1337

    
1338
m_clust_opt = cli_option("--all", dest="multi_mode",
1339
                         help="Select all instances in the cluster",
1340
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1341

    
1342
m_inst_opt = cli_option("--instance", dest="multi_mode",
1343
                        help="Filter by instance name [default]",
1344
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1345

    
1346
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1347
                             help="Filter by node tag",
1348
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1349
                             action="store_const")
1350

    
1351
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1352
                                 help="Filter by primary node tag",
1353
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1354
                                 action="store_const")
1355

    
1356
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1357
                                 help="Filter by secondary node tag",
1358
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1359
                                 action="store_const")
1360

    
1361
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1362
                             help="Filter by instance tag",
1363
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1364
                             action="store_const")
1365

    
1366
# this is defined separately due to readability only
1367
add_opts = [
1368
  NOSTART_OPT,
1369
  OS_OPT,
1370
  FORCE_VARIANT_OPT,
1371
  NO_INSTALL_OPT,
1372
  ]
1373

    
1374
commands = {
1375
  'add': (
1376
    AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1377
    "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1378
    "Creates and adds a new instance to the cluster"),
1379
  'batch-create': (
1380
    BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1381
    "<instances.json>",
1382
    "Create a bunch of instances based on specs in the file."),
1383
  'console': (
1384
    ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1385
    [SHOWCMD_OPT, PRIORITY_OPT],
1386
    "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1387
  'failover': (
1388
    FailoverInstance, ARGS_ONE_INSTANCE,
1389
    [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1390
     DRY_RUN_OPT, PRIORITY_OPT],
1391
    "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1392
    " using the remote mirror (only for instances of type drbd)"),
1393
  'migrate': (
1394
    MigrateInstance, ARGS_ONE_INSTANCE,
1395
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1396
     PRIORITY_OPT],
1397
    "[-f] <instance>", "Migrate instance to its secondary node"
1398
    " (only for instances of type drbd)"),
1399
  'move': (
1400
    MoveInstance, ARGS_ONE_INSTANCE,
1401
    [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1402
     DRY_RUN_OPT, PRIORITY_OPT],
1403
    "[-f] <instance>", "Move instance to an arbitrary node"
1404
    " (only for instances of type file and lv)"),
1405
  'info': (
1406
    ShowInstanceConfig, ARGS_MANY_INSTANCES,
1407
    [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1408
    "[-s] {--all | <instance>...}",
1409
    "Show information on the specified instance(s)"),
1410
  'list': (
1411
    ListInstances, ARGS_MANY_INSTANCES,
1412
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT],
1413
    "[<instance>...]",
1414
    "Lists the instances and their status. The available fields can be shown"
1415
    " using the \"list-fields\" command (see the man page for details)."
1416
    " The default field list is (in order): %s." %
1417
    utils.CommaJoin(_LIST_DEF_FIELDS),
1418
    ),
1419
  "list-fields": (
1420
    ListInstanceFields, [ArgUnknown()],
1421
    [NOHDR_OPT, SEP_OPT],
1422
    "[fields...]",
1423
    "Lists all available fields for instances"),
1424
  'reinstall': (
1425
    ReinstallInstance, [ArgInstance()],
1426
    [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1427
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1428
     m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1429
     SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1430
    "[-f] <instance>", "Reinstall a stopped instance"),
1431
  'remove': (
1432
    RemoveInstance, ARGS_ONE_INSTANCE,
1433
    [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1434
     DRY_RUN_OPT, PRIORITY_OPT],
1435
    "[-f] <instance>", "Shuts down the instance and removes it"),
1436
  'rename': (
1437
    RenameInstance,
1438
    [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1439
    [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1440
    "<instance> <new_name>", "Rename the instance"),
1441
  'replace-disks': (
1442
    ReplaceDisks, ARGS_ONE_INSTANCE,
1443
    [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1444
     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1445
     DRY_RUN_OPT, PRIORITY_OPT],
1446
    "[-s|-p|-n NODE|-I NAME] <instance>",
1447
    "Replaces all disks for the instance"),
1448
  'modify': (
1449
    SetInstanceParams, ARGS_ONE_INSTANCE,
1450
    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1451
     DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1452
     OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT],
1453
    "<instance>", "Alters the parameters of an instance"),
1454
  'shutdown': (
1455
    GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1456
    [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1457
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1458
     m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1459
     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1460
    "<instance>", "Stops an instance"),
1461
  'startup': (
1462
    GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1463
    [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1464
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1465
     m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1466
     BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1467
    "<instance>", "Starts an instance"),
1468
  'reboot': (
1469
    GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1470
    [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1471
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1472
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1473
     m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1474
    "<instance>", "Reboots an instance"),
1475
  'activate-disks': (
1476
    ActivateDisks, ARGS_ONE_INSTANCE,
1477
    [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1478
    "<instance>", "Activate an instance's disks"),
1479
  'deactivate-disks': (
1480
    DeactivateDisks, ARGS_ONE_INSTANCE,
1481
    [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1482
    "[-f] <instance>", "Deactivate an instance's disks"),
1483
  'recreate-disks': (
1484
    RecreateDisks, ARGS_ONE_INSTANCE,
1485
    [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1486
    "<instance>", "Recreate an instance's disks"),
1487
  'grow-disk': (
1488
    GrowDisk,
1489
    [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1490
     ArgUnknown(min=1, max=1)],
1491
    [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1492
    "<instance> <disk> <size>", "Grow an instance's disk"),
1493
  'list-tags': (
1494
    ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1495
    "<instance_name>", "List the tags of the given instance"),
1496
  'add-tags': (
1497
    AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1498
    [TAG_SRC_OPT, PRIORITY_OPT],
1499
    "<instance_name> tag...", "Add tags to the given instance"),
1500
  'remove-tags': (
1501
    RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1502
    [TAG_SRC_OPT, PRIORITY_OPT],
1503
    "<instance_name> tag...", "Remove tags from given instance"),
1504
  }
1505

    
1506
#: dictionary with aliases for commands
1507
aliases = {
1508
  'start': 'startup',
1509
  'stop': 'shutdown',
1510
  }
1511

    
1512

    
1513
def Main():
1514
  return GenericMain(commands, aliases=aliases,
1515
                     override={"tag_type": constants.TAG_INSTANCE})