Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ f86426f5

History | View | Annotate | Download (51.4 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 _EnsureInstancesExist(client, names):
146
  """Check for and ensure the given instance names exist.
147

148
  This function will raise an OpPrereqError in case they don't
149
  exist. Otherwise it will exit cleanly.
150

151
  @type client: L{ganeti.luxi.Client}
152
  @param client: the client to use for the query
153
  @type names: list
154
  @param names: the list of instance names to query
155
  @raise errors.OpPrereqError: in case any instance is missing
156

157
  """
158
  # TODO: change LUInstanceQuery to that it actually returns None
159
  # instead of raising an exception, or devise a better mechanism
160
  result = client.QueryInstances(names, ["name"], False)
161
  for orig_name, row in zip(names, result):
162
    if row[0] is None:
163
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
164
                                 errors.ECODE_NOENT)
165

    
166

    
167
def GenericManyOps(operation, fn):
168
  """Generic multi-instance operations.
169

170
  The will return a wrapper that processes the options and arguments
171
  given, and uses the passed function to build the opcode needed for
172
  the specific operation. Thus all the generic loop/confirmation code
173
  is abstracted into this function.
174

175
  """
176
  def realfn(opts, args):
177
    if opts.multi_mode is None:
178
      opts.multi_mode = _SHUTDOWN_INSTANCES
179
    cl = GetClient()
180
    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
181
    if not inames:
182
      raise errors.OpPrereqError("Selection filter does not match"
183
                                 " any instances", errors.ECODE_INVAL)
184
    multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
185
    if not (opts.force_multi or not multi_on
186
            or ConfirmOperation(inames, "instances", operation)):
187
      return 1
188
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
189
    for name in inames:
190
      op = fn(name, opts)
191
      jex.QueueJob(name, op)
192
    results = jex.WaitOrShow(not opts.submit_only)
193
    rcode = compat.all(row[0] for row in results)
194
    return int(not rcode)
195
  return realfn
196

    
197

    
198
def ListInstances(opts, args):
199
  """List instances and their properties.
200

201
  @param opts: the command line options selected by the user
202
  @type args: list
203
  @param args: should be an empty list
204
  @rtype: int
205
  @return: the desired exit code
206

207
  """
208
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
209

    
210
  fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips",
211
                               "nic.modes", "nic.links", "nic.bridges",
212
                               "snodes"],
213
                              (lambda value: ",".join(str(item)
214
                                                      for item in value),
215
                               False))
216

    
217
  return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
218
                     opts.separator, not opts.no_headers,
219
                     format_override=fmtoverride)
220

    
221

    
222
def ListInstanceFields(opts, args):
223
  """List instance fields.
224

225
  @param opts: the command line options selected by the user
226
  @type args: list
227
  @param args: fields to list, or empty for all
228
  @rtype: int
229
  @return: the desired exit code
230

231
  """
232
  return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
233
                           not opts.no_headers)
234

    
235

    
236
def AddInstance(opts, args):
237
  """Add an instance to the cluster.
238

239
  This is just a wrapper over GenericInstanceCreate.
240

241
  """
242
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
243

    
244

    
245
def BatchCreate(opts, args):
246
  """Create instances using a definition file.
247

248
  This function reads a json file with instances defined
249
  in the form::
250

251
    {"instance-name":{
252
      "disk_size": [20480],
253
      "template": "drbd",
254
      "backend": {
255
        "memory": 512,
256
        "vcpus": 1 },
257
      "os": "debootstrap",
258
      "primary_node": "firstnode",
259
      "secondary_node": "secondnode",
260
      "iallocator": "dumb"}
261
    }
262

263
  Note that I{primary_node} and I{secondary_node} have precedence over
264
  I{iallocator}.
265

266
  @param opts: the command line options selected by the user
267
  @type args: list
268
  @param args: should contain one element, the json filename
269
  @rtype: int
270
  @return: the desired exit code
271

272
  """
273
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
274
                    "backend": {},
275
                    "iallocator": None,
276
                    "primary_node": None,
277
                    "secondary_node": None,
278
                    "nics": None,
279
                    "start": True,
280
                    "ip_check": True,
281
                    "name_check": True,
282
                    "hypervisor": None,
283
                    "hvparams": {},
284
                    "file_storage_dir": None,
285
                    "force_variant": False,
286
                    "file_driver": 'loop'}
287

    
288
  def _PopulateWithDefaults(spec):
289
    """Returns a new hash combined with default values."""
290
    mydict = _DEFAULT_SPECS.copy()
291
    mydict.update(spec)
292
    return mydict
293

    
294
  def _Validate(spec):
295
    """Validate the instance specs."""
296
    # Validate fields required under any circumstances
297
    for required_field in ('os', 'template'):
298
      if required_field not in spec:
299
        raise errors.OpPrereqError('Required field "%s" is missing.' %
300
                                   required_field, errors.ECODE_INVAL)
301
    # Validate special fields
302
    if spec['primary_node'] is not None:
303
      if (spec['template'] in constants.DTS_NET_MIRROR and
304
          spec['secondary_node'] is None):
305
        raise errors.OpPrereqError('Template requires secondary node, but'
306
                                   ' there was no secondary provided.',
307
                                   errors.ECODE_INVAL)
308
    elif spec['iallocator'] is None:
309
      raise errors.OpPrereqError('You have to provide at least a primary_node'
310
                                 ' or an iallocator.',
311
                                 errors.ECODE_INVAL)
312

    
313
    if (spec['hvparams'] and
314
        not isinstance(spec['hvparams'], dict)):
315
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
316
                                 errors.ECODE_INVAL)
317

    
318
  json_filename = args[0]
319
  try:
320
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
321
  except Exception, err: # pylint: disable-msg=W0703
322
    ToStderr("Can't parse the instance definition file: %s" % str(err))
323
    return 1
324

    
325
  if not isinstance(instance_data, dict):
326
    ToStderr("The instance definition file is not in dict format.")
327
    return 1
328

    
329
  jex = JobExecutor(opts=opts)
330

    
331
  # Iterate over the instances and do:
332
  #  * Populate the specs with default value
333
  #  * Validate the instance specs
334
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
335
  for name in i_names:
336
    specs = instance_data[name]
337
    specs = _PopulateWithDefaults(specs)
338
    _Validate(specs)
339

    
340
    hypervisor = specs['hypervisor']
341
    hvparams = specs['hvparams']
342

    
343
    disks = []
344
    for elem in specs['disk_size']:
345
      try:
346
        size = utils.ParseUnit(elem)
347
      except (TypeError, ValueError), err:
348
        raise errors.OpPrereqError("Invalid disk size '%s' for"
349
                                   " instance %s: %s" %
350
                                   (elem, name, err), errors.ECODE_INVAL)
351
      disks.append({"size": size})
352

    
353
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
354
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
355

    
356
    tmp_nics = []
357
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
358
      if field in specs:
359
        if not tmp_nics:
360
          tmp_nics.append({})
361
        tmp_nics[0][field] = specs[field]
362

    
363
    if specs['nics'] is not None and tmp_nics:
364
      raise errors.OpPrereqError("'nics' list incompatible with using"
365
                                 " individual nic fields as well",
366
                                 errors.ECODE_INVAL)
367
    elif specs['nics'] is not None:
368
      tmp_nics = specs['nics']
369
    elif not tmp_nics:
370
      tmp_nics = [{}]
371

    
372
    op = opcodes.OpInstanceCreate(instance_name=name,
373
                                  disks=disks,
374
                                  disk_template=specs['template'],
375
                                  mode=constants.INSTANCE_CREATE,
376
                                  os_type=specs['os'],
377
                                  force_variant=specs["force_variant"],
378
                                  pnode=specs['primary_node'],
379
                                  snode=specs['secondary_node'],
380
                                  nics=tmp_nics,
381
                                  start=specs['start'],
382
                                  ip_check=specs['ip_check'],
383
                                  name_check=specs['name_check'],
384
                                  wait_for_sync=True,
385
                                  iallocator=specs['iallocator'],
386
                                  hypervisor=hypervisor,
387
                                  hvparams=hvparams,
388
                                  beparams=specs['backend'],
389
                                  file_storage_dir=specs['file_storage_dir'],
390
                                  file_driver=specs['file_driver'])
391

    
392
    jex.QueueJob(name, op)
393
  # we never want to wait, just show the submitted job IDs
394
  jex.WaitOrShow(False)
395

    
396
  return 0
397

    
398

    
399
def ReinstallInstance(opts, args):
400
  """Reinstall an instance.
401

402
  @param opts: the command line options selected by the user
403
  @type args: list
404
  @param args: should contain only one element, the name of the
405
      instance to be reinstalled
406
  @rtype: int
407
  @return: the desired exit code
408

409
  """
410
  # first, compute the desired name list
411
  if opts.multi_mode is None:
412
    opts.multi_mode = _SHUTDOWN_INSTANCES
413

    
414
  inames = _ExpandMultiNames(opts.multi_mode, args)
415
  if not inames:
416
    raise errors.OpPrereqError("Selection filter does not match any instances",
417
                               errors.ECODE_INVAL)
418

    
419
  # second, if requested, ask for an OS
420
  if opts.select_os is True:
421
    op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[])
422
    result = SubmitOpCode(op, opts=opts)
423

    
424
    if not result:
425
      ToStdout("Can't get the OS list")
426
      return 1
427

    
428
    ToStdout("Available OS templates:")
429
    number = 0
430
    choices = []
431
    for (name, variants) in result:
432
      for entry in CalculateOSNames(name, variants):
433
        ToStdout("%3s: %s", number, entry)
434
        choices.append(("%s" % number, entry, entry))
435
        number += 1
436

    
437
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
438
    selected = AskUser("Enter OS template number (or x to abort):",
439
                       choices)
440

    
441
    if selected == 'exit':
442
      ToStderr("User aborted reinstall, exiting")
443
      return 1
444

    
445
    os_name = selected
446
    os_msg = "change the OS to '%s'" % selected
447
  else:
448
    os_name = opts.os
449
    if opts.os is not None:
450
      os_msg = "change the OS to '%s'" % os_name
451
    else:
452
      os_msg = "keep the same OS"
453

    
454
  # third, get confirmation: multi-reinstall requires --force-multi,
455
  # single-reinstall either --force or --force-multi (--force-multi is
456
  # a stronger --force)
457
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
458
  if multi_on:
459
    warn_msg = ("Note: this will remove *all* data for the"
460
                " below instances! It will %s.\n" % os_msg)
461
    if not (opts.force_multi or
462
            ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)):
463
      return 1
464
  else:
465
    if not (opts.force or opts.force_multi):
466
      usertext = ("This will reinstall the instance '%s' (and %s) which"
467
                  " removes all data. Continue?") % (inames[0], os_msg)
468
      if not AskUser(usertext):
469
        return 1
470

    
471
  jex = JobExecutor(verbose=multi_on, opts=opts)
472
  for instance_name in inames:
473
    op = opcodes.OpInstanceReinstall(instance_name=instance_name,
474
                                     os_type=os_name,
475
                                     force_variant=opts.force_variant,
476
                                     osparams=opts.osparams)
477
    jex.QueueJob(instance_name, op)
478

    
479
  jex.WaitOrShow(not opts.submit_only)
480
  return 0
481

    
482

    
483
def RemoveInstance(opts, args):
484
  """Remove an instance.
485

486
  @param opts: the command line options selected by the user
487
  @type args: list
488
  @param args: should contain only one element, the name of
489
      the instance to be removed
490
  @rtype: int
491
  @return: the desired exit code
492

493
  """
494
  instance_name = args[0]
495
  force = opts.force
496
  cl = GetClient()
497

    
498
  if not force:
499
    _EnsureInstancesExist(cl, [instance_name])
500

    
501
    usertext = ("This will remove the volumes of the instance %s"
502
                " (including mirrors), thus removing all the data"
503
                " of the instance. Continue?") % instance_name
504
    if not AskUser(usertext):
505
      return 1
506

    
507
  op = opcodes.OpInstanceRemove(instance_name=instance_name,
508
                                ignore_failures=opts.ignore_failures,
509
                                shutdown_timeout=opts.shutdown_timeout)
510
  SubmitOrSend(op, opts, cl=cl)
511
  return 0
512

    
513

    
514
def RenameInstance(opts, args):
515
  """Rename an instance.
516

517
  @param opts: the command line options selected by the user
518
  @type args: list
519
  @param args: should contain two elements, the old and the
520
      new instance names
521
  @rtype: int
522
  @return: the desired exit code
523

524
  """
525
  if not opts.name_check:
526
    if not AskUser("As you disabled the check of the DNS entry, please verify"
527
                   " that '%s' is a FQDN. Continue?" % args[1]):
528
      return 1
529

    
530
  op = opcodes.OpInstanceRename(instance_name=args[0],
531
                                new_name=args[1],
532
                                ip_check=opts.ip_check,
533
                                name_check=opts.name_check)
534
  result = SubmitOrSend(op, opts)
535

    
536
  if result:
537
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
538

    
539
  return 0
540

    
541

    
542
def ActivateDisks(opts, args):
543
  """Activate an instance's disks.
544

545
  This serves two purposes:
546
    - it allows (as long as the instance is not running)
547
      mounting the disks and modifying them from the node
548
    - it repairs inactive secondary drbds
549

550
  @param opts: the command line options selected by the user
551
  @type args: list
552
  @param args: should contain only one element, the instance name
553
  @rtype: int
554
  @return: the desired exit code
555

556
  """
557
  instance_name = args[0]
558
  op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
559
                                       ignore_size=opts.ignore_size)
560
  disks_info = SubmitOrSend(op, opts)
561
  for host, iname, nname in disks_info:
562
    ToStdout("%s:%s:%s", host, iname, nname)
563
  return 0
564

    
565

    
566
def DeactivateDisks(opts, args):
567
  """Deactivate an instance's disks.
568

569
  This function takes the instance name, looks for its primary node
570
  and the tries to shutdown its block devices on that node.
571

572
  @param opts: the command line options selected by the user
573
  @type args: list
574
  @param args: should contain only one element, the instance name
575
  @rtype: int
576
  @return: the desired exit code
577

578
  """
579
  instance_name = args[0]
580
  op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
581
                                         force=opts.force)
582
  SubmitOrSend(op, opts)
583
  return 0
584

    
585

    
586
def RecreateDisks(opts, args):
587
  """Recreate an instance's disks.
588

589
  @param opts: the command line options selected by the user
590
  @type args: list
591
  @param args: should contain only one element, the instance name
592
  @rtype: int
593
  @return: the desired exit code
594

595
  """
596
  instance_name = args[0]
597
  if opts.disks:
598
    try:
599
      opts.disks = [int(v) for v in opts.disks.split(",")]
600
    except (ValueError, TypeError), err:
601
      ToStderr("Invalid disks value: %s" % str(err))
602
      return 1
603
  else:
604
    opts.disks = []
605

    
606
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
607
                                       disks=opts.disks)
608
  SubmitOrSend(op, opts)
609
  return 0
610

    
611

    
612
def GrowDisk(opts, args):
613
  """Grow an instance's disks.
614

615
  @param opts: the command line options selected by the user
616
  @type args: list
617
  @param args: should contain two elements, the instance name
618
      whose disks we grow and the disk name, e.g. I{sda}
619
  @rtype: int
620
  @return: the desired exit code
621

622
  """
623
  instance = args[0]
624
  disk = args[1]
625
  try:
626
    disk = int(disk)
627
  except (TypeError, ValueError), err:
628
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
629
                               errors.ECODE_INVAL)
630
  amount = utils.ParseUnit(args[2])
631
  op = opcodes.OpInstanceGrowDisk(instance_name=instance,
632
                                  disk=disk, amount=amount,
633
                                  wait_for_sync=opts.wait_for_sync)
634
  SubmitOrSend(op, opts)
635
  return 0
636

    
637

    
638
def _StartupInstance(name, opts):
639
  """Startup instances.
640

641
  This returns the opcode to start an instance, and its decorator will
642
  wrap this into a loop starting all desired instances.
643

644
  @param name: the name of the instance to act on
645
  @param opts: the command line options selected by the user
646
  @return: the opcode needed for the operation
647

648
  """
649
  op = opcodes.OpInstanceStartup(instance_name=name,
650
                                 force=opts.force,
651
                                 ignore_offline_nodes=opts.ignore_offline)
652
  # do not add these parameters to the opcode unless they're defined
653
  if opts.hvparams:
654
    op.hvparams = opts.hvparams
655
  if opts.beparams:
656
    op.beparams = opts.beparams
657
  return op
658

    
659

    
660
def _RebootInstance(name, opts):
661
  """Reboot instance(s).
662

663
  This returns the opcode to reboot an instance, and its decorator
664
  will wrap this into a loop rebooting all desired instances.
665

666
  @param name: the name of the instance to act on
667
  @param opts: the command line options selected by the user
668
  @return: the opcode needed for the operation
669

670
  """
671
  return opcodes.OpInstanceReboot(instance_name=name,
672
                                  reboot_type=opts.reboot_type,
673
                                  ignore_secondaries=opts.ignore_secondaries,
674
                                  shutdown_timeout=opts.shutdown_timeout)
675

    
676

    
677
def _ShutdownInstance(name, opts):
678
  """Shutdown an instance.
679

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

683
  @param name: the name of the instance to act on
684
  @param opts: the command line options selected by the user
685
  @return: the opcode needed for the operation
686

687
  """
688
  return opcodes.OpInstanceShutdown(instance_name=name,
689
                                    timeout=opts.timeout,
690
                                    ignore_offline_nodes=opts.ignore_offline)
691

    
692

    
693
def ReplaceDisks(opts, args):
694
  """Replace the disks of an instance
695

696
  @param opts: the command line options selected by the user
697
  @type args: list
698
  @param args: should contain only one element, the instance name
699
  @rtype: int
700
  @return: the desired exit code
701

702
  """
703
  new_2ndary = opts.dst_node
704
  iallocator = opts.iallocator
705
  if opts.disks is None:
706
    disks = []
707
  else:
708
    try:
709
      disks = [int(i) for i in opts.disks.split(",")]
710
    except (TypeError, ValueError), err:
711
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
712
                                 errors.ECODE_INVAL)
713
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
714
         new_2ndary is not None, iallocator is not None].count(True)
715
  if cnt != 1:
716
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
717
                               " options must be passed", errors.ECODE_INVAL)
718
  elif opts.on_primary:
719
    mode = constants.REPLACE_DISK_PRI
720
  elif opts.on_secondary:
721
    mode = constants.REPLACE_DISK_SEC
722
  elif opts.auto:
723
    mode = constants.REPLACE_DISK_AUTO
724
    if disks:
725
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
726
                                 " mode", errors.ECODE_INVAL)
727
  elif new_2ndary is not None or iallocator is not None:
728
    # replace secondary
729
    mode = constants.REPLACE_DISK_CHG
730

    
731
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
732
                                      remote_node=new_2ndary, mode=mode,
733
                                      iallocator=iallocator,
734
                                      early_release=opts.early_release)
735
  SubmitOrSend(op, opts)
736
  return 0
737

    
738

    
739
def FailoverInstance(opts, args):
740
  """Failover an instance.
741

742
  The failover is done by shutting it down on its present node and
743
  starting it on the secondary.
744

745
  @param opts: the command line options selected by the user
746
  @type args: list
747
  @param args: should contain only one element, the instance name
748
  @rtype: int
749
  @return: the desired exit code
750

751
  """
752
  cl = GetClient()
753
  instance_name = args[0]
754
  force = opts.force
755

    
756
  if not force:
757
    _EnsureInstancesExist(cl, [instance_name])
758

    
759
    usertext = ("Failover will happen to image %s."
760
                " This requires a shutdown of the instance. Continue?" %
761
                (instance_name,))
762
    if not AskUser(usertext):
763
      return 1
764

    
765
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
766
                                  ignore_consistency=opts.ignore_consistency,
767
                                  shutdown_timeout=opts.shutdown_timeout)
768
  SubmitOrSend(op, opts, cl=cl)
769
  return 0
770

    
771

    
772
def MigrateInstance(opts, args):
773
  """Migrate an instance.
774

775
  The migrate is done without shutdown.
776

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

783
  """
784
  cl = GetClient()
785
  instance_name = args[0]
786
  force = opts.force
787

    
788
  if not force:
789
    _EnsureInstancesExist(cl, [instance_name])
790

    
791
    if opts.cleanup:
792
      usertext = ("Instance %s will be recovered from a failed migration."
793
                  " Note that the migration procedure (including cleanup)" %
794
                  (instance_name,))
795
    else:
796
      usertext = ("Instance %s will be migrated. Note that migration" %
797
                  (instance_name,))
798
    usertext += (" might impact the instance if anything goes wrong"
799
                 " (e.g. due to bugs in the hypervisor). Continue?")
800
    if not AskUser(usertext):
801
      return 1
802

    
803
  # this should be removed once --non-live is deprecated
804
  if not opts.live and opts.migration_mode is not None:
805
    raise errors.OpPrereqError("Only one of the --non-live and "
806
                               "--migration-mode options can be passed",
807
                               errors.ECODE_INVAL)
808
  if not opts.live: # --non-live passed
809
    mode = constants.HT_MIGRATION_NONLIVE
810
  else:
811
    mode = opts.migration_mode
812

    
813
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
814
                                 cleanup=opts.cleanup)
815
  SubmitOpCode(op, cl=cl, opts=opts)
816
  return 0
817

    
818

    
819
def MoveInstance(opts, args):
820
  """Move an instance.
821

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

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

    
833
  if not force:
834
    usertext = ("Instance %s will be moved."
835
                " This requires a shutdown of the instance. Continue?" %
836
                (instance_name,))
837
    if not AskUser(usertext):
838
      return 1
839

    
840
  op = opcodes.OpInstanceMove(instance_name=instance_name,
841
                              target_node=opts.node,
842
                              shutdown_timeout=opts.shutdown_timeout)
843
  SubmitOrSend(op, opts, cl=cl)
844
  return 0
845

    
846

    
847
def ConnectToInstanceConsole(opts, args):
848
  """Connect to the console of an instance.
849

850
  @param opts: the command line options selected by the user
851
  @type args: list
852
  @param args: should contain only one element, the instance name
853
  @rtype: int
854
  @return: the desired exit code
855

856
  """
857
  instance_name = args[0]
858

    
859
  op = opcodes.OpInstanceConsole(instance_name=instance_name)
860

    
861
  cl = GetClient()
862
  try:
863
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
864
    console_data = SubmitOpCode(op, opts=opts, cl=cl)
865
  finally:
866
    # Ensure client connection is closed while external commands are run
867
    cl.Close()
868

    
869
  del cl
870

    
871
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
872
                    opts.show_command, cluster_name)
873

    
874

    
875
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
876
               _runcmd_fn=utils.RunCmd):
877
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
878

879
  @type console: L{objects.InstanceConsole}
880
  @param console: Console object
881
  @type show_command: bool
882
  @param show_command: Whether to just display commands
883
  @type cluster_name: string
884
  @param cluster_name: Cluster name as retrieved from master daemon
885

886
  """
887
  assert console.Validate()
888

    
889
  if console.kind == constants.CONS_MESSAGE:
890
    feedback_fn(console.message)
891
  elif console.kind == constants.CONS_VNC:
892
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
893
                " URL <vnc://%s:%s/>",
894
                console.instance, console.host, console.port,
895
                console.display, console.host, console.port)
896
  elif console.kind == constants.CONS_SSH:
897
    # Convert to string if not already one
898
    if isinstance(console.command, basestring):
899
      cmd = console.command
900
    else:
901
      cmd = utils.ShellQuoteArgs(console.command)
902

    
903
    srun = ssh.SshRunner(cluster_name=cluster_name)
904
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
905
                            batch=True, quiet=False, tty=True)
906

    
907
    if show_command:
908
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
909
    else:
910
      result = _runcmd_fn(ssh_cmd, interactive=True)
911
      if result.failed:
912
        logging.error("Console command \"%s\" failed with reason '%s' and"
913
                      " output %r", result.cmd, result.fail_reason,
914
                      result.output)
915
        raise errors.OpExecError("Connection to console of instance %s failed,"
916
                                 " please check cluster configuration" %
917
                                 console.instance)
918
  else:
919
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
920

    
921
  return constants.EXIT_SUCCESS
922

    
923

    
924
def _FormatLogicalID(dev_type, logical_id, roman):
925
  """Formats the logical_id of a disk.
926

927
  """
928
  if dev_type == constants.LD_DRBD8:
929
    node_a, node_b, port, minor_a, minor_b, key = logical_id
930
    data = [
931
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
932
                                                            convert=roman))),
933
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
934
                                                            convert=roman))),
935
      ("port", compat.TryToRoman(port, convert=roman)),
936
      ("auth key", key),
937
      ]
938
  elif dev_type == constants.LD_LV:
939
    vg_name, lv_name = logical_id
940
    data = ["%s/%s" % (vg_name, lv_name)]
941
  else:
942
    data = [str(logical_id)]
943

    
944
  return data
945

    
946

    
947
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
948
  """Show block device information.
949

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

953
  @type idx: int
954
  @param idx: the index of the current disk
955
  @type top_level: boolean
956
  @param top_level: if this a top-level disk?
957
  @type dev: dict
958
  @param dev: dictionary with disk information
959
  @type static: boolean
960
  @param static: wheter the device information doesn't contain
961
      runtime information but only static data
962
  @type roman: boolean
963
  @param roman: whether to try to use roman integers
964
  @return: a list of either strings, tuples or lists
965
      (which should be formatted at a higher indent level)
966

967
  """
968
  def helper(dtype, status):
969
    """Format one line for physical device status.
970

971
    @type dtype: str
972
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
973
    @type status: tuple
974
    @param status: a tuple as returned from L{backend.FindBlockDevice}
975
    @return: the string representing the status
976

977
    """
978
    if not status:
979
      return "not active"
980
    txt = ""
981
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
982
    if major is None:
983
      major_string = "N/A"
984
    else:
985
      major_string = str(compat.TryToRoman(major, convert=roman))
986

    
987
    if minor is None:
988
      minor_string = "N/A"
989
    else:
990
      minor_string = str(compat.TryToRoman(minor, convert=roman))
991

    
992
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
993
    if dtype in (constants.LD_DRBD8, ):
994
      if syncp is not None:
995
        sync_text = "*RECOVERING* %5.2f%%," % syncp
996
        if estt:
997
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
998
        else:
999
          sync_text += " ETA unknown"
1000
      else:
1001
        sync_text = "in sync"
1002
      if degr:
1003
        degr_text = "*DEGRADED*"
1004
      else:
1005
        degr_text = "ok"
1006
      if ldisk_status == constants.LDS_FAULTY:
1007
        ldisk_text = " *MISSING DISK*"
1008
      elif ldisk_status == constants.LDS_UNKNOWN:
1009
        ldisk_text = " *UNCERTAIN STATE*"
1010
      else:
1011
        ldisk_text = ""
1012
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1013
    elif dtype == constants.LD_LV:
1014
      if ldisk_status == constants.LDS_FAULTY:
1015
        ldisk_text = " *FAILED* (failed drive?)"
1016
      else:
1017
        ldisk_text = ""
1018
      txt += ldisk_text
1019
    return txt
1020

    
1021
  # the header
1022
  if top_level:
1023
    if dev["iv_name"] is not None:
1024
      txt = dev["iv_name"]
1025
    else:
1026
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1027
  else:
1028
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1029
  if isinstance(dev["size"], int):
1030
    nice_size = utils.FormatUnit(dev["size"], "h")
1031
  else:
1032
    nice_size = dev["size"]
1033
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1034
  data = []
1035
  if top_level:
1036
    data.append(("access mode", dev["mode"]))
1037
  if dev["logical_id"] is not None:
1038
    try:
1039
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1040
    except ValueError:
1041
      l_id = [str(dev["logical_id"])]
1042
    if len(l_id) == 1:
1043
      data.append(("logical_id", l_id[0]))
1044
    else:
1045
      data.extend(l_id)
1046
  elif dev["physical_id"] is not None:
1047
    data.append("physical_id:")
1048
    data.append([dev["physical_id"]])
1049
  if not static:
1050
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1051
  if dev["sstatus"] and not static:
1052
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1053

    
1054
  if dev["children"]:
1055
    data.append("child devices:")
1056
    for c_idx, child in enumerate(dev["children"]):
1057
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1058
  d1.append(data)
1059
  return d1
1060

    
1061

    
1062
def _FormatList(buf, data, indent_level):
1063
  """Formats a list of data at a given indent level.
1064

1065
  If the element of the list is:
1066
    - a string, it is simply formatted as is
1067
    - a tuple, it will be split into key, value and the all the
1068
      values in a list will be aligned all at the same start column
1069
    - a list, will be recursively formatted
1070

1071
  @type buf: StringIO
1072
  @param buf: the buffer into which we write the output
1073
  @param data: the list to format
1074
  @type indent_level: int
1075
  @param indent_level: the indent level to format at
1076

1077
  """
1078
  max_tlen = max([len(elem[0]) for elem in data
1079
                 if isinstance(elem, tuple)] or [0])
1080
  for elem in data:
1081
    if isinstance(elem, basestring):
1082
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1083
    elif isinstance(elem, tuple):
1084
      key, value = elem
1085
      spacer = "%*s" % (max_tlen - len(key), "")
1086
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1087
    elif isinstance(elem, list):
1088
      _FormatList(buf, elem, indent_level+1)
1089

    
1090

    
1091
def ShowInstanceConfig(opts, args):
1092
  """Compute instance run-time status.
1093

1094
  @param opts: the command line options selected by the user
1095
  @type args: list
1096
  @param args: either an empty list, and then we query all
1097
      instances, or should contain a list of instance names
1098
  @rtype: int
1099
  @return: the desired exit code
1100

1101
  """
1102
  if not args and not opts.show_all:
1103
    ToStderr("No instance selected."
1104
             " Please pass in --all if you want to query all instances.\n"
1105
             "Note that this can take a long time on a big cluster.")
1106
    return 1
1107
  elif args and opts.show_all:
1108
    ToStderr("Cannot use --all if you specify instance names.")
1109
    return 1
1110

    
1111
  retcode = 0
1112
  op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1113
  result = SubmitOpCode(op, opts=opts)
1114
  if not result:
1115
    ToStdout("No instances.")
1116
    return 1
1117

    
1118
  buf = StringIO()
1119
  retcode = 0
1120
  for instance_name in result:
1121
    instance = result[instance_name]
1122
    buf.write("Instance name: %s\n" % instance["name"])
1123
    buf.write("UUID: %s\n" % instance["uuid"])
1124
    buf.write("Serial number: %s\n" %
1125
              compat.TryToRoman(instance["serial_no"],
1126
                                convert=opts.roman_integers))
1127
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1128
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1129
    buf.write("State: configured to be %s" % instance["config_state"])
1130
    if not opts.static:
1131
      buf.write(", actual state is %s" % instance["run_state"])
1132
    buf.write("\n")
1133
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1134
    ##          instance["auto_balance"])
1135
    buf.write("  Nodes:\n")
1136
    buf.write("    - primary: %s\n" % instance["pnode"])
1137
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1138
    buf.write("  Operating system: %s\n" % instance["os"])
1139
    FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1140
                        level=2)
1141
    if instance.has_key("network_port"):
1142
      buf.write("  Allocated network port: %s\n" %
1143
                compat.TryToRoman(instance["network_port"],
1144
                                  convert=opts.roman_integers))
1145
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1146

    
1147
    # custom VNC console information
1148
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1149
                                                 None)
1150
    if vnc_bind_address:
1151
      port = instance["network_port"]
1152
      display = int(port) - constants.VNC_BASE_PORT
1153
      if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1154
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1155
                                                   port,
1156
                                                   display)
1157
      elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1158
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1159
                             (vnc_bind_address, port,
1160
                              instance["pnode"], display))
1161
      else:
1162
        # vnc bind address is a file
1163
        vnc_console_port = "%s:%s" % (instance["pnode"],
1164
                                      vnc_bind_address)
1165
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1166

    
1167
    FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1168
                        level=2)
1169
    buf.write("  Hardware:\n")
1170
    buf.write("    - VCPUs: %s\n" %
1171
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1172
                                convert=opts.roman_integers))
1173
    buf.write("    - memory: %sMiB\n" %
1174
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1175
                                convert=opts.roman_integers))
1176
    buf.write("    - NICs:\n")
1177
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1178
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1179
                (idx, mac, ip, mode, link))
1180
    buf.write("  Disk template: %s\n" % instance["disk_template"])
1181
    buf.write("  Disks:\n")
1182

    
1183
    for idx, device in enumerate(instance["disks"]):
1184
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1185
                  opts.roman_integers), 2)
1186

    
1187
  ToStdout(buf.getvalue().rstrip('\n'))
1188
  return retcode
1189

    
1190

    
1191
def SetInstanceParams(opts, args):
1192
  """Modifies an instance.
1193

1194
  All parameters take effect only at the next restart of the instance.
1195

1196
  @param opts: the command line options selected by the user
1197
  @type args: list
1198
  @param args: should contain only one element, the instance name
1199
  @rtype: int
1200
  @return: the desired exit code
1201

1202
  """
1203
  if not (opts.nics or opts.disks or opts.disk_template or
1204
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1205
    ToStderr("Please give at least one of the parameters.")
1206
    return 1
1207

    
1208
  for param in opts.beparams:
1209
    if isinstance(opts.beparams[param], basestring):
1210
      if opts.beparams[param].lower() == "default":
1211
        opts.beparams[param] = constants.VALUE_DEFAULT
1212

    
1213
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1214
                      allowed_values=[constants.VALUE_DEFAULT])
1215

    
1216
  for param in opts.hvparams:
1217
    if isinstance(opts.hvparams[param], basestring):
1218
      if opts.hvparams[param].lower() == "default":
1219
        opts.hvparams[param] = constants.VALUE_DEFAULT
1220

    
1221
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1222
                      allowed_values=[constants.VALUE_DEFAULT])
1223

    
1224
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1225
    try:
1226
      nic_op = int(nic_op)
1227
      opts.nics[idx] = (nic_op, nic_dict)
1228
    except (TypeError, ValueError):
1229
      pass
1230

    
1231
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1232
    try:
1233
      disk_op = int(disk_op)
1234
      opts.disks[idx] = (disk_op, disk_dict)
1235
    except (TypeError, ValueError):
1236
      pass
1237
    if disk_op == constants.DDM_ADD:
1238
      if 'size' not in disk_dict:
1239
        raise errors.OpPrereqError("Missing required parameter 'size'",
1240
                                   errors.ECODE_INVAL)
1241
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1242

    
1243
  if (opts.disk_template and
1244
      opts.disk_template in constants.DTS_NET_MIRROR and
1245
      not opts.node):
1246
    ToStderr("Changing the disk template to a mirrored one requires"
1247
             " specifying a secondary node")
1248
    return 1
1249

    
1250
  op = opcodes.OpInstanceSetParams(instance_name=args[0],
1251
                                   nics=opts.nics,
1252
                                   disks=opts.disks,
1253
                                   disk_template=opts.disk_template,
1254
                                   remote_node=opts.node,
1255
                                   hvparams=opts.hvparams,
1256
                                   beparams=opts.beparams,
1257
                                   os_name=opts.os,
1258
                                   osparams=opts.osparams,
1259
                                   force_variant=opts.force_variant,
1260
                                   force=opts.force)
1261

    
1262
  # even if here we process the result, we allow submit only
1263
  result = SubmitOrSend(op, opts)
1264

    
1265
  if result:
1266
    ToStdout("Modified instance %s", args[0])
1267
    for param, data in result:
1268
      ToStdout(" - %-5s -> %s", param, data)
1269
    ToStdout("Please don't forget that most parameters take effect"
1270
             " only at the next start of the instance.")
1271
  return 0
1272

    
1273

    
1274
# multi-instance selection options
1275
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1276
                           help="Do not ask for confirmation when more than"
1277
                           " one instance is affected",
1278
                           action="store_true", default=False)
1279

    
1280
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1281
                            help="Filter by nodes (primary only)",
1282
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1283

    
1284
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1285
                            help="Filter by nodes (secondary only)",
1286
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1287

    
1288
m_node_opt = cli_option("--node", dest="multi_mode",
1289
                        help="Filter by nodes (primary and secondary)",
1290
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1291

    
1292
m_clust_opt = cli_option("--all", dest="multi_mode",
1293
                         help="Select all instances in the cluster",
1294
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1295

    
1296
m_inst_opt = cli_option("--instance", dest="multi_mode",
1297
                        help="Filter by instance name [default]",
1298
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1299

    
1300
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1301
                             help="Filter by node tag",
1302
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1303
                             action="store_const")
1304

    
1305
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1306
                                 help="Filter by primary node tag",
1307
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1308
                                 action="store_const")
1309

    
1310
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1311
                                 help="Filter by secondary node tag",
1312
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1313
                                 action="store_const")
1314

    
1315
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1316
                             help="Filter by instance tag",
1317
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1318
                             action="store_const")
1319

    
1320
# this is defined separately due to readability only
1321
add_opts = [
1322
  NOSTART_OPT,
1323
  OS_OPT,
1324
  FORCE_VARIANT_OPT,
1325
  NO_INSTALL_OPT,
1326
  ]
1327

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

    
1460
#: dictionary with aliases for commands
1461
aliases = {
1462
  'start': 'startup',
1463
  'stop': 'shutdown',
1464
  }
1465

    
1466

    
1467
def Main():
1468
  return GenericMain(commands, aliases=aliases,
1469
                     override={"tag_type": constants.TAG_INSTANCE})