Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ d8d838cb

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
                                   use_locking=not opts.static)
1149
  result = SubmitOpCode(op, opts=opts)
1150
  if not result:
1151
    ToStdout("No instances.")
1152
    return 1
1153

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

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

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

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

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

    
1226

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1309

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1502

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