Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ 016acd85

History | View | Annotate | Download (50.5 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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 os
29
import itertools
30
import simplejson
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

    
41

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

    
52
_SHUTDOWN_NODES_TAGS_MODES = (
53
    _SHUTDOWN_NODES_BOTH_BY_TAGS,
54
    _SHUTDOWN_NODES_PRI_BY_TAGS,
55
    _SHUTDOWN_NODES_SEC_BY_TAGS)
56

    
57

    
58
_VALUE_TRUE = "true"
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 LUQueryInstances 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
      raise errors.OpPrereqError("Selection filter does not match"
221
                                 " any instances", errors.ECODE_INVAL)
222
    multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
223
    if not (opts.force_multi or not multi_on
224
            or _ConfirmOperation(inames, operation)):
225
      return 1
226
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
227
    for name in inames:
228
      op = fn(name, opts)
229
      jex.QueueJob(name, op)
230
    results = jex.WaitOrShow(not opts.submit_only)
231
    rcode = compat.all(row[0] for row in results)
232
    return int(not rcode)
233
  return realfn
234

    
235

    
236
def ListInstances(opts, args):
237
  """List instances and their properties.
238

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

245
  """
246
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
247

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

    
255
  return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
256
                     opts.separator, not opts.no_headers,
257
                     format_override=fmtoverride)
258

    
259

    
260
def ListInstanceFields(opts, args):
261
  """List instance fields.
262

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

269
  """
270
  return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
271
                           not opts.no_headers)
272

    
273

    
274
def AddInstance(opts, args):
275
  """Add an instance to the cluster.
276

277
  This is just a wrapper over GenericInstanceCreate.
278

279
  """
280
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
281

    
282

    
283
def BatchCreate(opts, args):
284
  """Create instances using a definition file.
285

286
  This function reads a json file with instances defined
287
  in the form::
288

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

301
  Note that I{primary_node} and I{secondary_node} have precedence over
302
  I{iallocator}.
303

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

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

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

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

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

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

    
363
  if not isinstance(instance_data, dict):
364
    ToStderr("The instance definition file is not in dict format.")
365
    return 1
366

    
367
  jex = JobExecutor(opts=opts)
368

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

    
378
    hypervisor = specs['hypervisor']
379
    hvparams = specs['hvparams']
380

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

    
391
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
392
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
393

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

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

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

    
430
    jex.QueueJob(name, op)
431
  # we never want to wait, just show the submitted job IDs
432
  jex.WaitOrShow(False)
433

    
434
  return 0
435

    
436

    
437
def ReinstallInstance(opts, args):
438
  """Reinstall an instance.
439

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

447
  """
448
  # first, compute the desired name list
449
  if opts.multi_mode is None:
450
    opts.multi_mode = _SHUTDOWN_INSTANCES
451

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

    
457
  # second, if requested, ask for an OS
458
  if opts.select_os is True:
459
    op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
460
    result = SubmitOpCode(op, opts=opts)
461

    
462
    if not result:
463
      ToStdout("Can't get the OS list")
464
      return 1
465

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

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

    
479
    if selected == 'exit':
480
      ToStderr("User aborted reinstall, exiting")
481
      return 1
482

    
483
    os_name = selected
484
  else:
485
    os_name = opts.os
486

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

    
503
  jex = JobExecutor(verbose=multi_on, opts=opts)
504
  for instance_name in inames:
505
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
506
                                     os_type=os_name,
507
                                     force_variant=opts.force_variant,
508
                                     osparams=opts.osparams)
509
    jex.QueueJob(instance_name, op)
510

    
511
  jex.WaitOrShow(not opts.submit_only)
512
  return 0
513

    
514

    
515
def RemoveInstance(opts, args):
516
  """Remove an instance.
517

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

525
  """
526
  instance_name = args[0]
527
  force = opts.force
528
  cl = GetClient()
529

    
530
  if not force:
531
    _EnsureInstancesExist(cl, [instance_name])
532

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

    
539
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
540
                                ignore_failures=opts.ignore_failures,
541
                                shutdown_timeout=opts.shutdown_timeout)
542
  SubmitOrSend(op, opts, cl=cl)
543
  return 0
544

    
545

    
546
def RenameInstance(opts, args):
547
  """Rename an instance.
548

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

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

    
562
  op = opcodes.OpRenameInstance(instance_name=args[0],
563
                                new_name=args[1],
564
                                ip_check=opts.ip_check,
565
                                name_check=opts.name_check)
566
  result = SubmitOrSend(op, opts)
567

    
568
  if result:
569
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
570

    
571
  return 0
572

    
573

    
574
def ActivateDisks(opts, args):
575
  """Activate an instance's disks.
576

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

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

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

    
597

    
598
def DeactivateDisks(opts, args):
599
  """Deactivate an instance's disks.
600

601
  This function takes the instance name, looks for its primary node
602
  and the tries to shutdown its block devices on that node.
603

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

610
  """
611
  instance_name = args[0]
612
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
613
  SubmitOrSend(op, opts)
614
  return 0
615

    
616

    
617
def RecreateDisks(opts, args):
618
  """Recreate an instance's disks.
619

620
  @param opts: the command line options selected by the user
621
  @type args: list
622
  @param args: should contain only one element, the instance name
623
  @rtype: int
624
  @return: the desired exit code
625

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

    
637
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
638
                                       disks=opts.disks)
639
  SubmitOrSend(op, opts)
640
  return 0
641

    
642

    
643
def GrowDisk(opts, args):
644
  """Grow an instance's disks.
645

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

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

    
667

    
668
def _StartupInstance(name, opts):
669
  """Startup instances.
670

671
  This returns the opcode to start an instance, and its decorator will
672
  wrap this into a loop starting all desired instances.
673

674
  @param name: the name of the instance to act on
675
  @param opts: the command line options selected by the user
676
  @return: the opcode needed for the operation
677

678
  """
679
  op = opcodes.OpStartupInstance(instance_name=name,
680
                                 force=opts.force,
681
                                 ignore_offline_nodes=opts.ignore_offline)
682
  # do not add these parameters to the opcode unless they're defined
683
  if opts.hvparams:
684
    op.hvparams = opts.hvparams
685
  if opts.beparams:
686
    op.beparams = opts.beparams
687
  return op
688

    
689

    
690
def _RebootInstance(name, opts):
691
  """Reboot instance(s).
692

693
  This returns the opcode to reboot an instance, and its decorator
694
  will wrap this into a loop rebooting all desired instances.
695

696
  @param name: the name of the instance to act on
697
  @param opts: the command line options selected by the user
698
  @return: the opcode needed for the operation
699

700
  """
701
  return opcodes.OpRebootInstance(instance_name=name,
702
                                  reboot_type=opts.reboot_type,
703
                                  ignore_secondaries=opts.ignore_secondaries,
704
                                  shutdown_timeout=opts.shutdown_timeout)
705

    
706

    
707
def _ShutdownInstance(name, opts):
708
  """Shutdown an instance.
709

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

713
  @param name: the name of the instance to act on
714
  @param opts: the command line options selected by the user
715
  @return: the opcode needed for the operation
716

717
  """
718
  return opcodes.OpShutdownInstance(instance_name=name,
719
                                    timeout=opts.timeout,
720
                                    ignore_offline_nodes=opts.ignore_offline)
721

    
722

    
723
def ReplaceDisks(opts, args):
724
  """Replace the disks of an instance
725

726
  @param opts: the command line options selected by the user
727
  @type args: list
728
  @param args: should contain only one element, the instance name
729
  @rtype: int
730
  @return: the desired exit code
731

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

    
761
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
762
                              remote_node=new_2ndary, mode=mode,
763
                              iallocator=iallocator,
764
                              early_release=opts.early_release)
765
  SubmitOrSend(op, opts)
766
  return 0
767

    
768

    
769
def FailoverInstance(opts, args):
770
  """Failover an instance.
771

772
  The failover is done by shutting it down on its present node and
773
  starting it on the secondary.
774

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

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

    
786
  if not force:
787
    _EnsureInstancesExist(cl, [instance_name])
788

    
789
    usertext = ("Failover will happen to image %s."
790
                " This requires a shutdown of the instance. Continue?" %
791
                (instance_name,))
792
    if not AskUser(usertext):
793
      return 1
794

    
795
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
796
                                  ignore_consistency=opts.ignore_consistency,
797
                                  shutdown_timeout=opts.shutdown_timeout)
798
  SubmitOrSend(op, opts, cl=cl)
799
  return 0
800

    
801

    
802
def MigrateInstance(opts, args):
803
  """Migrate an instance.
804

805
  The migrate is done without shutdown.
806

807
  @param opts: the command line options selected by the user
808
  @type args: list
809
  @param args: should contain only one element, the instance name
810
  @rtype: int
811
  @return: the desired exit code
812

813
  """
814
  cl = GetClient()
815
  instance_name = args[0]
816
  force = opts.force
817

    
818
  if not force:
819
    _EnsureInstancesExist(cl, [instance_name])
820

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

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

    
843
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
844
                                 cleanup=opts.cleanup)
845
  SubmitOpCode(op, cl=cl, opts=opts)
846
  return 0
847

    
848

    
849
def MoveInstance(opts, args):
850
  """Move an instance.
851

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

858
  """
859
  cl = GetClient()
860
  instance_name = args[0]
861
  force = opts.force
862

    
863
  if not force:
864
    usertext = ("Instance %s will be moved."
865
                " This requires a shutdown of the instance. Continue?" %
866
                (instance_name,))
867
    if not AskUser(usertext):
868
      return 1
869

    
870
  op = opcodes.OpMoveInstance(instance_name=instance_name,
871
                              target_node=opts.node,
872
                              shutdown_timeout=opts.shutdown_timeout)
873
  SubmitOrSend(op, opts, cl=cl)
874
  return 0
875

    
876

    
877
def ConnectToInstanceConsole(opts, args):
878
  """Connect to the console of an instance.
879

880
  @param opts: the command line options selected by the user
881
  @type args: list
882
  @param args: should contain only one element, the instance name
883
  @rtype: int
884
  @return: the desired exit code
885

886
  """
887
  instance_name = args[0]
888

    
889
  op = opcodes.OpConnectConsole(instance_name=instance_name)
890
  cmd = SubmitOpCode(op, opts=opts)
891

    
892
  if opts.show_command:
893
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
894
  else:
895
    try:
896
      os.execvp(cmd[0], cmd)
897
    finally:
898
      ToStderr("Can't run console command %s with arguments:\n'%s'",
899
               cmd[0], " ".join(cmd))
900
      os._exit(1) # pylint: disable-msg=W0212
901

    
902

    
903
def _FormatLogicalID(dev_type, logical_id, roman):
904
  """Formats the logical_id of a disk.
905

906
  """
907
  if dev_type == constants.LD_DRBD8:
908
    node_a, node_b, port, minor_a, minor_b, key = logical_id
909
    data = [
910
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
911
                                                            convert=roman))),
912
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
913
                                                            convert=roman))),
914
      ("port", compat.TryToRoman(port, convert=roman)),
915
      ("auth key", key),
916
      ]
917
  elif dev_type == constants.LD_LV:
918
    vg_name, lv_name = logical_id
919
    data = ["%s/%s" % (vg_name, lv_name)]
920
  else:
921
    data = [str(logical_id)]
922

    
923
  return data
924

    
925

    
926
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
927
  """Show block device information.
928

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

932
  @type idx: int
933
  @param idx: the index of the current disk
934
  @type top_level: boolean
935
  @param top_level: if this a top-level disk?
936
  @type dev: dict
937
  @param dev: dictionary with disk information
938
  @type static: boolean
939
  @param static: wheter the device information doesn't contain
940
      runtime information but only static data
941
  @type roman: boolean
942
  @param roman: whether to try to use roman integers
943
  @return: a list of either strings, tuples or lists
944
      (which should be formatted at a higher indent level)
945

946
  """
947
  def helper(dtype, status):
948
    """Format one line for physical device status.
949

950
    @type dtype: str
951
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
952
    @type status: tuple
953
    @param status: a tuple as returned from L{backend.FindBlockDevice}
954
    @return: the string representing the status
955

956
    """
957
    if not status:
958
      return "not active"
959
    txt = ""
960
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
961
    if major is None:
962
      major_string = "N/A"
963
    else:
964
      major_string = str(compat.TryToRoman(major, convert=roman))
965

    
966
    if minor is None:
967
      minor_string = "N/A"
968
    else:
969
      minor_string = str(compat.TryToRoman(minor, convert=roman))
970

    
971
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
972
    if dtype in (constants.LD_DRBD8, ):
973
      if syncp is not None:
974
        sync_text = "*RECOVERING* %5.2f%%," % syncp
975
        if estt:
976
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
977
        else:
978
          sync_text += " ETA unknown"
979
      else:
980
        sync_text = "in sync"
981
      if degr:
982
        degr_text = "*DEGRADED*"
983
      else:
984
        degr_text = "ok"
985
      if ldisk_status == constants.LDS_FAULTY:
986
        ldisk_text = " *MISSING DISK*"
987
      elif ldisk_status == constants.LDS_UNKNOWN:
988
        ldisk_text = " *UNCERTAIN STATE*"
989
      else:
990
        ldisk_text = ""
991
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
992
    elif dtype == constants.LD_LV:
993
      if ldisk_status == constants.LDS_FAULTY:
994
        ldisk_text = " *FAILED* (failed drive?)"
995
      else:
996
        ldisk_text = ""
997
      txt += ldisk_text
998
    return txt
999

    
1000
  # the header
1001
  if top_level:
1002
    if dev["iv_name"] is not None:
1003
      txt = dev["iv_name"]
1004
    else:
1005
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1006
  else:
1007
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1008
  if isinstance(dev["size"], int):
1009
    nice_size = utils.FormatUnit(dev["size"], "h")
1010
  else:
1011
    nice_size = dev["size"]
1012
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1013
  data = []
1014
  if top_level:
1015
    data.append(("access mode", dev["mode"]))
1016
  if dev["logical_id"] is not None:
1017
    try:
1018
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1019
    except ValueError:
1020
      l_id = [str(dev["logical_id"])]
1021
    if len(l_id) == 1:
1022
      data.append(("logical_id", l_id[0]))
1023
    else:
1024
      data.extend(l_id)
1025
  elif dev["physical_id"] is not None:
1026
    data.append("physical_id:")
1027
    data.append([dev["physical_id"]])
1028
  if not static:
1029
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1030
  if dev["sstatus"] and not static:
1031
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1032

    
1033
  if dev["children"]:
1034
    data.append("child devices:")
1035
    for c_idx, child in enumerate(dev["children"]):
1036
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1037
  d1.append(data)
1038
  return d1
1039

    
1040

    
1041
def _FormatList(buf, data, indent_level):
1042
  """Formats a list of data at a given indent level.
1043

1044
  If the element of the list is:
1045
    - a string, it is simply formatted as is
1046
    - a tuple, it will be split into key, value and the all the
1047
      values in a list will be aligned all at the same start column
1048
    - a list, will be recursively formatted
1049

1050
  @type buf: StringIO
1051
  @param buf: the buffer into which we write the output
1052
  @param data: the list to format
1053
  @type indent_level: int
1054
  @param indent_level: the indent level to format at
1055

1056
  """
1057
  max_tlen = max([len(elem[0]) for elem in data
1058
                 if isinstance(elem, tuple)] or [0])
1059
  for elem in data:
1060
    if isinstance(elem, basestring):
1061
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1062
    elif isinstance(elem, tuple):
1063
      key, value = elem
1064
      spacer = "%*s" % (max_tlen - len(key), "")
1065
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1066
    elif isinstance(elem, list):
1067
      _FormatList(buf, elem, indent_level+1)
1068

    
1069

    
1070
def _FormatParameterDict(buf, per_inst, actual):
1071
  """Formats a parameter dictionary.
1072

1073
  @type buf: L{StringIO}
1074
  @param buf: the buffer into which to write
1075
  @type per_inst: dict
1076
  @param per_inst: the instance's own parameters
1077
  @type actual: dict
1078
  @param actual: the current parameter set (including defaults)
1079

1080
  """
1081
  for key in sorted(actual):
1082
    val = per_inst.get(key, "default (%s)" % actual[key])
1083
    buf.write("    - %s: %s\n" % (key, val))
1084

    
1085
def ShowInstanceConfig(opts, args):
1086
  """Compute instance run-time status.
1087

1088
  @param opts: the command line options selected by the user
1089
  @type args: list
1090
  @param args: either an empty list, and then we query all
1091
      instances, or should contain a list of instance names
1092
  @rtype: int
1093
  @return: the desired exit code
1094

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

    
1105
  retcode = 0
1106
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1107
  result = SubmitOpCode(op, opts=opts)
1108
  if not result:
1109
    ToStdout("No instances.")
1110
    return 1
1111

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

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

    
1160
    _FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"])
1161
    buf.write("  Hardware:\n")
1162
    buf.write("    - VCPUs: %s\n" %
1163
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1164
                                convert=opts.roman_integers))
1165
    buf.write("    - memory: %sMiB\n" %
1166
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1167
                                convert=opts.roman_integers))
1168
    buf.write("    - NICs:\n")
1169
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1170
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1171
                (idx, mac, ip, mode, link))
1172
    buf.write("  Disks:\n")
1173

    
1174
    for idx, device in enumerate(instance["disks"]):
1175
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1176
                  opts.roman_integers), 2)
1177

    
1178
  ToStdout(buf.getvalue().rstrip('\n'))
1179
  return retcode
1180

    
1181

    
1182
def SetInstanceParams(opts, args):
1183
  """Modifies an instance.
1184

1185
  All parameters take effect only at the next restart of the instance.
1186

1187
  @param opts: the command line options selected by the user
1188
  @type args: list
1189
  @param args: should contain only one element, the instance name
1190
  @rtype: int
1191
  @return: the desired exit code
1192

1193
  """
1194
  if not (opts.nics or opts.disks or opts.disk_template or
1195
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1196
    ToStderr("Please give at least one of the parameters.")
1197
    return 1
1198

    
1199
  for param in opts.beparams:
1200
    if isinstance(opts.beparams[param], basestring):
1201
      if opts.beparams[param].lower() == "default":
1202
        opts.beparams[param] = constants.VALUE_DEFAULT
1203

    
1204
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1205
                      allowed_values=[constants.VALUE_DEFAULT])
1206

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

    
1212
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1213
                      allowed_values=[constants.VALUE_DEFAULT])
1214

    
1215
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1216
    try:
1217
      nic_op = int(nic_op)
1218
      opts.nics[idx] = (nic_op, nic_dict)
1219
    except (TypeError, ValueError):
1220
      pass
1221

    
1222
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1223
    try:
1224
      disk_op = int(disk_op)
1225
      opts.disks[idx] = (disk_op, disk_dict)
1226
    except (TypeError, ValueError):
1227
      pass
1228
    if disk_op == constants.DDM_ADD:
1229
      if 'size' not in disk_dict:
1230
        raise errors.OpPrereqError("Missing required parameter 'size'",
1231
                                   errors.ECODE_INVAL)
1232
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1233

    
1234
  if (opts.disk_template and
1235
      opts.disk_template in constants.DTS_NET_MIRROR and
1236
      not opts.node):
1237
    ToStderr("Changing the disk template to a mirrored one requires"
1238
             " specifying a secondary node")
1239
    return 1
1240

    
1241
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1242
                                   nics=opts.nics,
1243
                                   disks=opts.disks,
1244
                                   disk_template=opts.disk_template,
1245
                                   remote_node=opts.node,
1246
                                   hvparams=opts.hvparams,
1247
                                   beparams=opts.beparams,
1248
                                   os_name=opts.os,
1249
                                   osparams=opts.osparams,
1250
                                   force_variant=opts.force_variant,
1251
                                   force=opts.force)
1252

    
1253
  # even if here we process the result, we allow submit only
1254
  result = SubmitOrSend(op, opts)
1255

    
1256
  if result:
1257
    ToStdout("Modified instance %s", args[0])
1258
    for param, data in result:
1259
      ToStdout(" - %-5s -> %s", param, data)
1260
    ToStdout("Please don't forget that most parameters take effect"
1261
             " only at the next start of the instance.")
1262
  return 0
1263

    
1264

    
1265
# multi-instance selection options
1266
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1267
                           help="Do not ask for confirmation when more than"
1268
                           " one instance is affected",
1269
                           action="store_true", default=False)
1270

    
1271
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1272
                            help="Filter by nodes (primary only)",
1273
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1274

    
1275
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1276
                            help="Filter by nodes (secondary only)",
1277
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1278

    
1279
m_node_opt = cli_option("--node", dest="multi_mode",
1280
                        help="Filter by nodes (primary and secondary)",
1281
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1282

    
1283
m_clust_opt = cli_option("--all", dest="multi_mode",
1284
                         help="Select all instances in the cluster",
1285
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1286

    
1287
m_inst_opt = cli_option("--instance", dest="multi_mode",
1288
                        help="Filter by instance name [default]",
1289
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1290

    
1291
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1292
                             help="Filter by node tag",
1293
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1294
                             action="store_const")
1295

    
1296
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1297
                                 help="Filter by primary node tag",
1298
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1299
                                 action="store_const")
1300

    
1301
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1302
                                 help="Filter by secondary node tag",
1303
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1304
                                 action="store_const")
1305

    
1306
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1307
                             help="Filter by instance tag",
1308
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1309
                             action="store_const")
1310

    
1311
# this is defined separately due to readability only
1312
add_opts = [
1313
  NOSTART_OPT,
1314
  OS_OPT,
1315
  FORCE_VARIANT_OPT,
1316
  NO_INSTALL_OPT,
1317
  ]
1318

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

    
1451
#: dictionary with aliases for commands
1452
aliases = {
1453
  'start': 'startup',
1454
  'stop': 'shutdown',
1455
  }
1456

    
1457

    
1458
def Main():
1459
  return GenericMain(commands, aliases=aliases,
1460
                     override={"tag_type": constants.TAG_INSTANCE})