Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ f0b1bafe

History | View | Annotate | Download (52.5 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
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
642
                                       disks=opts.disks)
643
  SubmitOrSend(op, opts)
644
  return 0
645

    
646

    
647
def GrowDisk(opts, args):
648
  """Grow an instance's disks.
649

650
  @param opts: the command line options selected by the user
651
  @type args: list
652
  @param args: should contain two elements, the instance name
653
      whose disks we grow and the disk name, e.g. I{sda}
654
  @rtype: int
655
  @return: the desired exit code
656

657
  """
658
  instance = args[0]
659
  disk = args[1]
660
  try:
661
    disk = int(disk)
662
  except (TypeError, ValueError), err:
663
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
664
                               errors.ECODE_INVAL)
665
  amount = utils.ParseUnit(args[2])
666
  op = opcodes.OpInstanceGrowDisk(instance_name=instance,
667
                                  disk=disk, amount=amount,
668
                                  wait_for_sync=opts.wait_for_sync)
669
  SubmitOrSend(op, opts)
670
  return 0
671

    
672

    
673
def _StartupInstance(name, opts):
674
  """Startup instances.
675

676
  This returns the opcode to start an instance, and its decorator will
677
  wrap this into a loop starting all desired instances.
678

679
  @param name: the name of the instance to act on
680
  @param opts: the command line options selected by the user
681
  @return: the opcode needed for the operation
682

683
  """
684
  op = opcodes.OpInstanceStartup(instance_name=name,
685
                                 force=opts.force,
686
                                 ignore_offline_nodes=opts.ignore_offline)
687
  # do not add these parameters to the opcode unless they're defined
688
  if opts.hvparams:
689
    op.hvparams = opts.hvparams
690
  if opts.beparams:
691
    op.beparams = opts.beparams
692
  return op
693

    
694

    
695
def _RebootInstance(name, opts):
696
  """Reboot instance(s).
697

698
  This returns the opcode to reboot an instance, and its decorator
699
  will wrap this into a loop rebooting all desired instances.
700

701
  @param name: the name of the instance to act on
702
  @param opts: the command line options selected by the user
703
  @return: the opcode needed for the operation
704

705
  """
706
  return opcodes.OpInstanceReboot(instance_name=name,
707
                                  reboot_type=opts.reboot_type,
708
                                  ignore_secondaries=opts.ignore_secondaries,
709
                                  shutdown_timeout=opts.shutdown_timeout)
710

    
711

    
712
def _ShutdownInstance(name, opts):
713
  """Shutdown an instance.
714

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

718
  @param name: the name of the instance to act on
719
  @param opts: the command line options selected by the user
720
  @return: the opcode needed for the operation
721

722
  """
723
  return opcodes.OpInstanceShutdown(instance_name=name,
724
                                    timeout=opts.timeout,
725
                                    ignore_offline_nodes=opts.ignore_offline)
726

    
727

    
728
def ReplaceDisks(opts, args):
729
  """Replace the disks of an instance
730

731
  @param opts: the command line options selected by the user
732
  @type args: list
733
  @param args: should contain only one element, the instance name
734
  @rtype: int
735
  @return: the desired exit code
736

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

    
766
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
767
                                      remote_node=new_2ndary, mode=mode,
768
                                      iallocator=iallocator,
769
                                      early_release=opts.early_release)
770
  SubmitOrSend(op, opts)
771
  return 0
772

    
773

    
774
def FailoverInstance(opts, args):
775
  """Failover an instance.
776

777
  The failover is done by shutting it down on its present node and
778
  starting it on the secondary.
779

780
  @param opts: the command line options selected by the user
781
  @type args: list
782
  @param args: should contain only one element, the instance name
783
  @rtype: int
784
  @return: the desired exit code
785

786
  """
787
  cl = GetClient()
788
  instance_name = args[0]
789
  force = opts.force
790

    
791
  if not force:
792
    _EnsureInstancesExist(cl, [instance_name])
793

    
794
    usertext = ("Failover will happen to image %s."
795
                " This requires a shutdown of the instance. Continue?" %
796
                (instance_name,))
797
    if not AskUser(usertext):
798
      return 1
799

    
800
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
801
                                  ignore_consistency=opts.ignore_consistency,
802
                                  shutdown_timeout=opts.shutdown_timeout)
803
  SubmitOrSend(op, opts, cl=cl)
804
  return 0
805

    
806

    
807
def MigrateInstance(opts, args):
808
  """Migrate an instance.
809

810
  The migrate is done without shutdown.
811

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

818
  """
819
  cl = GetClient()
820
  instance_name = args[0]
821
  force = opts.force
822

    
823
  if not force:
824
    _EnsureInstancesExist(cl, [instance_name])
825

    
826
    if opts.cleanup:
827
      usertext = ("Instance %s will be recovered from a failed migration."
828
                  " Note that the migration procedure (including cleanup)" %
829
                  (instance_name,))
830
    else:
831
      usertext = ("Instance %s will be migrated. Note that migration" %
832
                  (instance_name,))
833
    usertext += (" might impact the instance if anything goes wrong"
834
                 " (e.g. due to bugs in the hypervisor). Continue?")
835
    if not AskUser(usertext):
836
      return 1
837

    
838
  # this should be removed once --non-live is deprecated
839
  if not opts.live and opts.migration_mode is not None:
840
    raise errors.OpPrereqError("Only one of the --non-live and "
841
                               "--migration-mode options can be passed",
842
                               errors.ECODE_INVAL)
843
  if not opts.live: # --non-live passed
844
    mode = constants.HT_MIGRATION_NONLIVE
845
  else:
846
    mode = opts.migration_mode
847

    
848
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
849
                                 cleanup=opts.cleanup)
850
  SubmitOpCode(op, cl=cl, opts=opts)
851
  return 0
852

    
853

    
854
def MoveInstance(opts, args):
855
  """Move an instance.
856

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

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

    
868
  if not force:
869
    usertext = ("Instance %s will be moved."
870
                " This requires a shutdown of the instance. Continue?" %
871
                (instance_name,))
872
    if not AskUser(usertext):
873
      return 1
874

    
875
  op = opcodes.OpInstanceMove(instance_name=instance_name,
876
                              target_node=opts.node,
877
                              shutdown_timeout=opts.shutdown_timeout)
878
  SubmitOrSend(op, opts, cl=cl)
879
  return 0
880

    
881

    
882
def ConnectToInstanceConsole(opts, args):
883
  """Connect to the console of an instance.
884

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

891
  """
892
  instance_name = args[0]
893

    
894
  op = opcodes.OpInstanceConsole(instance_name=instance_name)
895

    
896
  cl = GetClient()
897
  try:
898
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
899
    console_data = SubmitOpCode(op, opts=opts, cl=cl)
900
  finally:
901
    # Ensure client connection is closed while external commands are run
902
    cl.Close()
903

    
904
  del cl
905

    
906
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
907
                    opts.show_command, cluster_name)
908

    
909

    
910
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
911
               _runcmd_fn=utils.RunCmd):
912
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
913

914
  @type console: L{objects.InstanceConsole}
915
  @param console: Console object
916
  @type show_command: bool
917
  @param show_command: Whether to just display commands
918
  @type cluster_name: string
919
  @param cluster_name: Cluster name as retrieved from master daemon
920

921
  """
922
  assert console.Validate()
923

    
924
  if console.kind == constants.CONS_MESSAGE:
925
    feedback_fn(console.message)
926
  elif console.kind == constants.CONS_VNC:
927
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
928
                " URL <vnc://%s:%s/>",
929
                console.instance, console.host, console.port,
930
                console.display, console.host, console.port)
931
  elif console.kind == constants.CONS_SSH:
932
    # Convert to string if not already one
933
    if isinstance(console.command, basestring):
934
      cmd = console.command
935
    else:
936
      cmd = utils.ShellQuoteArgs(console.command)
937

    
938
    srun = ssh.SshRunner(cluster_name=cluster_name)
939
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
940
                            batch=True, quiet=False, tty=True)
941

    
942
    if show_command:
943
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
944
    else:
945
      result = _runcmd_fn(ssh_cmd, interactive=True)
946
      if result.failed:
947
        logging.error("Console command \"%s\" failed with reason '%s' and"
948
                      " output %r", result.cmd, result.fail_reason,
949
                      result.output)
950
        raise errors.OpExecError("Connection to console of instance %s failed,"
951
                                 " please check cluster configuration" %
952
                                 console.instance)
953
  else:
954
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
955

    
956
  return constants.EXIT_SUCCESS
957

    
958

    
959
def _FormatLogicalID(dev_type, logical_id, roman):
960
  """Formats the logical_id of a disk.
961

962
  """
963
  if dev_type == constants.LD_DRBD8:
964
    node_a, node_b, port, minor_a, minor_b, key = logical_id
965
    data = [
966
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
967
                                                            convert=roman))),
968
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
969
                                                            convert=roman))),
970
      ("port", compat.TryToRoman(port, convert=roman)),
971
      ("auth key", key),
972
      ]
973
  elif dev_type == constants.LD_LV:
974
    vg_name, lv_name = logical_id
975
    data = ["%s/%s" % (vg_name, lv_name)]
976
  else:
977
    data = [str(logical_id)]
978

    
979
  return data
980

    
981

    
982
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
983
  """Show block device information.
984

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

988
  @type idx: int
989
  @param idx: the index of the current disk
990
  @type top_level: boolean
991
  @param top_level: if this a top-level disk?
992
  @type dev: dict
993
  @param dev: dictionary with disk information
994
  @type static: boolean
995
  @param static: wheter the device information doesn't contain
996
      runtime information but only static data
997
  @type roman: boolean
998
  @param roman: whether to try to use roman integers
999
  @return: a list of either strings, tuples or lists
1000
      (which should be formatted at a higher indent level)
1001

1002
  """
1003
  def helper(dtype, status):
1004
    """Format one line for physical device status.
1005

1006
    @type dtype: str
1007
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1008
    @type status: tuple
1009
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1010
    @return: the string representing the status
1011

1012
    """
1013
    if not status:
1014
      return "not active"
1015
    txt = ""
1016
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1017
    if major is None:
1018
      major_string = "N/A"
1019
    else:
1020
      major_string = str(compat.TryToRoman(major, convert=roman))
1021

    
1022
    if minor is None:
1023
      minor_string = "N/A"
1024
    else:
1025
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1026

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

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

    
1089
  if dev["children"]:
1090
    data.append("child devices:")
1091
    for c_idx, child in enumerate(dev["children"]):
1092
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1093
  d1.append(data)
1094
  return d1
1095

    
1096

    
1097
def _FormatList(buf, data, indent_level):
1098
  """Formats a list of data at a given indent level.
1099

1100
  If the element of the list is:
1101
    - a string, it is simply formatted as is
1102
    - a tuple, it will be split into key, value and the all the
1103
      values in a list will be aligned all at the same start column
1104
    - a list, will be recursively formatted
1105

1106
  @type buf: StringIO
1107
  @param buf: the buffer into which we write the output
1108
  @param data: the list to format
1109
  @type indent_level: int
1110
  @param indent_level: the indent level to format at
1111

1112
  """
1113
  max_tlen = max([len(elem[0]) for elem in data
1114
                 if isinstance(elem, tuple)] or [0])
1115
  for elem in data:
1116
    if isinstance(elem, basestring):
1117
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1118
    elif isinstance(elem, tuple):
1119
      key, value = elem
1120
      spacer = "%*s" % (max_tlen - len(key), "")
1121
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1122
    elif isinstance(elem, list):
1123
      _FormatList(buf, elem, indent_level+1)
1124

    
1125

    
1126
def ShowInstanceConfig(opts, args):
1127
  """Compute instance run-time status.
1128

1129
  @param opts: the command line options selected by the user
1130
  @type args: list
1131
  @param args: either an empty list, and then we query all
1132
      instances, or should contain a list of instance names
1133
  @rtype: int
1134
  @return: the desired exit code
1135

1136
  """
1137
  if not args and not opts.show_all:
1138
    ToStderr("No instance selected."
1139
             " Please pass in --all if you want to query all instances.\n"
1140
             "Note that this can take a long time on a big cluster.")
1141
    return 1
1142
  elif args and opts.show_all:
1143
    ToStderr("Cannot use --all if you specify instance names.")
1144
    return 1
1145

    
1146
  retcode = 0
1147
  op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1148
  result = SubmitOpCode(op, opts=opts)
1149
  if not result:
1150
    ToStdout("No instances.")
1151
    return 1
1152

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

    
1182
    # custom VNC console information
1183
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1184
                                                 None)
1185
    if vnc_bind_address:
1186
      port = instance["network_port"]
1187
      display = int(port) - constants.VNC_BASE_PORT
1188
      if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1189
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1190
                                                   port,
1191
                                                   display)
1192
      elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1193
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1194
                             (vnc_bind_address, port,
1195
                              instance["pnode"], display))
1196
      else:
1197
        # vnc bind address is a file
1198
        vnc_console_port = "%s:%s" % (instance["pnode"],
1199
                                      vnc_bind_address)
1200
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1201

    
1202
    FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1203
                        level=2)
1204
    buf.write("  Hardware:\n")
1205
    buf.write("    - VCPUs: %s\n" %
1206
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1207
                                convert=opts.roman_integers))
1208
    buf.write("    - memory: %sMiB\n" %
1209
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1210
                                convert=opts.roman_integers))
1211
    buf.write("    - NICs:\n")
1212
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1213
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1214
                (idx, mac, ip, mode, link))
1215
    buf.write("  Disk template: %s\n" % instance["disk_template"])
1216
    buf.write("  Disks:\n")
1217

    
1218
    for idx, device in enumerate(instance["disks"]):
1219
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1220
                  opts.roman_integers), 2)
1221

    
1222
  ToStdout(buf.getvalue().rstrip('\n'))
1223
  return retcode
1224

    
1225

    
1226
def SetInstanceParams(opts, args):
1227
  """Modifies an instance.
1228

1229
  All parameters take effect only at the next restart of the instance.
1230

1231
  @param opts: the command line options selected by the user
1232
  @type args: list
1233
  @param args: should contain only one element, the instance name
1234
  @rtype: int
1235
  @return: the desired exit code
1236

1237
  """
1238
  if not (opts.nics or opts.disks or opts.disk_template or
1239
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1240
    ToStderr("Please give at least one of the parameters.")
1241
    return 1
1242

    
1243
  for param in opts.beparams:
1244
    if isinstance(opts.beparams[param], basestring):
1245
      if opts.beparams[param].lower() == "default":
1246
        opts.beparams[param] = constants.VALUE_DEFAULT
1247

    
1248
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1249
                      allowed_values=[constants.VALUE_DEFAULT])
1250

    
1251
  for param in opts.hvparams:
1252
    if isinstance(opts.hvparams[param], basestring):
1253
      if opts.hvparams[param].lower() == "default":
1254
        opts.hvparams[param] = constants.VALUE_DEFAULT
1255

    
1256
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1257
                      allowed_values=[constants.VALUE_DEFAULT])
1258

    
1259
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1260
    try:
1261
      nic_op = int(nic_op)
1262
      opts.nics[idx] = (nic_op, nic_dict)
1263
    except (TypeError, ValueError):
1264
      pass
1265

    
1266
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1267
    try:
1268
      disk_op = int(disk_op)
1269
      opts.disks[idx] = (disk_op, disk_dict)
1270
    except (TypeError, ValueError):
1271
      pass
1272
    if disk_op == constants.DDM_ADD:
1273
      if 'size' not in disk_dict:
1274
        raise errors.OpPrereqError("Missing required parameter 'size'",
1275
                                   errors.ECODE_INVAL)
1276
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1277

    
1278
  if (opts.disk_template and
1279
      opts.disk_template in constants.DTS_NET_MIRROR and
1280
      not opts.node):
1281
    ToStderr("Changing the disk template to a mirrored one requires"
1282
             " specifying a secondary node")
1283
    return 1
1284

    
1285
  op = opcodes.OpInstanceSetParams(instance_name=args[0],
1286
                                   nics=opts.nics,
1287
                                   disks=opts.disks,
1288
                                   disk_template=opts.disk_template,
1289
                                   remote_node=opts.node,
1290
                                   hvparams=opts.hvparams,
1291
                                   beparams=opts.beparams,
1292
                                   os_name=opts.os,
1293
                                   osparams=opts.osparams,
1294
                                   force_variant=opts.force_variant,
1295
                                   force=opts.force)
1296

    
1297
  # even if here we process the result, we allow submit only
1298
  result = SubmitOrSend(op, opts)
1299

    
1300
  if result:
1301
    ToStdout("Modified instance %s", args[0])
1302
    for param, data in result:
1303
      ToStdout(" - %-5s -> %s", param, data)
1304
    ToStdout("Please don't forget that most parameters take effect"
1305
             " only at the next start of the instance.")
1306
  return 0
1307

    
1308

    
1309
# multi-instance selection options
1310
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1311
                           help="Do not ask for confirmation when more than"
1312
                           " one instance is affected",
1313
                           action="store_true", default=False)
1314

    
1315
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1316
                            help="Filter by nodes (primary only)",
1317
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1318

    
1319
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1320
                            help="Filter by nodes (secondary only)",
1321
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1322

    
1323
m_node_opt = cli_option("--node", dest="multi_mode",
1324
                        help="Filter by nodes (primary and secondary)",
1325
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1326

    
1327
m_clust_opt = cli_option("--all", dest="multi_mode",
1328
                         help="Select all instances in the cluster",
1329
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1330

    
1331
m_inst_opt = cli_option("--instance", dest="multi_mode",
1332
                        help="Filter by instance name [default]",
1333
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1334

    
1335
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1336
                             help="Filter by node tag",
1337
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1338
                             action="store_const")
1339

    
1340
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1341
                                 help="Filter by primary node tag",
1342
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1343
                                 action="store_const")
1344

    
1345
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1346
                                 help="Filter by secondary node tag",
1347
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1348
                                 action="store_const")
1349

    
1350
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1351
                             help="Filter by instance tag",
1352
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1353
                             action="store_const")
1354

    
1355
# this is defined separately due to readability only
1356
add_opts = [
1357
  NOSTART_OPT,
1358
  OS_OPT,
1359
  FORCE_VARIANT_OPT,
1360
  NO_INSTALL_OPT,
1361
  ]
1362

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

    
1495
#: dictionary with aliases for commands
1496
aliases = {
1497
  'start': 'startup',
1498
  'stop': 'shutdown',
1499
  }
1500

    
1501

    
1502
def Main():
1503
  return GenericMain(commands, aliases=aliases,
1504
                     override={"tag_type": constants.TAG_INSTANCE})