Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ bb851c63

History | View | Annotate | Download (52.9 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
                              ignore_consistency=opts.ignore_consistency)
888
  SubmitOrSend(op, opts, cl=cl)
889
  return 0
890

    
891

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

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

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

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

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

    
914
  del cl
915

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

    
919

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

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

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

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

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

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

    
966
  return constants.EXIT_SUCCESS
967

    
968

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

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

    
989
  return data
990

    
991

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

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

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

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

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

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

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

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

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

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

    
1106

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

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

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

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

    
1135

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

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

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

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

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

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

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

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

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

    
1236

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1320

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1513

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