Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ 25bd815c

History | View | Annotate | Download (51.2 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
  else:
447
    os_name = opts.os
448

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

    
465
  jex = JobExecutor(verbose=multi_on, opts=opts)
466
  for instance_name in inames:
467
    op = opcodes.OpInstanceReinstall(instance_name=instance_name,
468
                                     os_type=os_name,
469
                                     force_variant=opts.force_variant,
470
                                     osparams=opts.osparams)
471
    jex.QueueJob(instance_name, op)
472

    
473
  jex.WaitOrShow(not opts.submit_only)
474
  return 0
475

    
476

    
477
def RemoveInstance(opts, args):
478
  """Remove an instance.
479

480
  @param opts: the command line options selected by the user
481
  @type args: list
482
  @param args: should contain only one element, the name of
483
      the instance to be removed
484
  @rtype: int
485
  @return: the desired exit code
486

487
  """
488
  instance_name = args[0]
489
  force = opts.force
490
  cl = GetClient()
491

    
492
  if not force:
493
    _EnsureInstancesExist(cl, [instance_name])
494

    
495
    usertext = ("This will remove the volumes of the instance %s"
496
                " (including mirrors), thus removing all the data"
497
                " of the instance. Continue?") % instance_name
498
    if not AskUser(usertext):
499
      return 1
500

    
501
  op = opcodes.OpInstanceRemove(instance_name=instance_name,
502
                                ignore_failures=opts.ignore_failures,
503
                                shutdown_timeout=opts.shutdown_timeout)
504
  SubmitOrSend(op, opts, cl=cl)
505
  return 0
506

    
507

    
508
def RenameInstance(opts, args):
509
  """Rename an instance.
510

511
  @param opts: the command line options selected by the user
512
  @type args: list
513
  @param args: should contain two elements, the old and the
514
      new instance names
515
  @rtype: int
516
  @return: the desired exit code
517

518
  """
519
  if not opts.name_check:
520
    if not AskUser("As you disabled the check of the DNS entry, please verify"
521
                   " that '%s' is a FQDN. Continue?" % args[1]):
522
      return 1
523

    
524
  op = opcodes.OpInstanceRename(instance_name=args[0],
525
                                new_name=args[1],
526
                                ip_check=opts.ip_check,
527
                                name_check=opts.name_check)
528
  result = SubmitOrSend(op, opts)
529

    
530
  if result:
531
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
532

    
533
  return 0
534

    
535

    
536
def ActivateDisks(opts, args):
537
  """Activate an instance's disks.
538

539
  This serves two purposes:
540
    - it allows (as long as the instance is not running)
541
      mounting the disks and modifying them from the node
542
    - it repairs inactive secondary drbds
543

544
  @param opts: the command line options selected by the user
545
  @type args: list
546
  @param args: should contain only one element, the instance name
547
  @rtype: int
548
  @return: the desired exit code
549

550
  """
551
  instance_name = args[0]
552
  op = opcodes.OpInstanceActivateDisks(instance_name=instance_name,
553
                                       ignore_size=opts.ignore_size)
554
  disks_info = SubmitOrSend(op, opts)
555
  for host, iname, nname in disks_info:
556
    ToStdout("%s:%s:%s", host, iname, nname)
557
  return 0
558

    
559

    
560
def DeactivateDisks(opts, args):
561
  """Deactivate an instance's disks.
562

563
  This function takes the instance name, looks for its primary node
564
  and the tries to shutdown its block devices on that node.
565

566
  @param opts: the command line options selected by the user
567
  @type args: list
568
  @param args: should contain only one element, the instance name
569
  @rtype: int
570
  @return: the desired exit code
571

572
  """
573
  instance_name = args[0]
574
  op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
575
                                         force=opts.force)
576
  SubmitOrSend(op, opts)
577
  return 0
578

    
579

    
580
def RecreateDisks(opts, args):
581
  """Recreate an instance's disks.
582

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

589
  """
590
  instance_name = args[0]
591
  if opts.disks:
592
    try:
593
      opts.disks = [int(v) for v in opts.disks.split(",")]
594
    except (ValueError, TypeError), err:
595
      ToStderr("Invalid disks value: %s" % str(err))
596
      return 1
597
  else:
598
    opts.disks = []
599

    
600
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
601
                                       disks=opts.disks)
602
  SubmitOrSend(op, opts)
603
  return 0
604

    
605

    
606
def GrowDisk(opts, args):
607
  """Grow an instance's disks.
608

609
  @param opts: the command line options selected by the user
610
  @type args: list
611
  @param args: should contain two elements, the instance name
612
      whose disks we grow and the disk name, e.g. I{sda}
613
  @rtype: int
614
  @return: the desired exit code
615

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

    
631

    
632
def _StartupInstance(name, opts):
633
  """Startup instances.
634

635
  This returns the opcode to start an instance, and its decorator will
636
  wrap this into a loop starting all desired instances.
637

638
  @param name: the name of the instance to act on
639
  @param opts: the command line options selected by the user
640
  @return: the opcode needed for the operation
641

642
  """
643
  op = opcodes.OpInstanceStartup(instance_name=name,
644
                                 force=opts.force,
645
                                 ignore_offline_nodes=opts.ignore_offline)
646
  # do not add these parameters to the opcode unless they're defined
647
  if opts.hvparams:
648
    op.hvparams = opts.hvparams
649
  if opts.beparams:
650
    op.beparams = opts.beparams
651
  return op
652

    
653

    
654
def _RebootInstance(name, opts):
655
  """Reboot instance(s).
656

657
  This returns the opcode to reboot an instance, and its decorator
658
  will wrap this into a loop rebooting all desired instances.
659

660
  @param name: the name of the instance to act on
661
  @param opts: the command line options selected by the user
662
  @return: the opcode needed for the operation
663

664
  """
665
  return opcodes.OpInstanceReboot(instance_name=name,
666
                                  reboot_type=opts.reboot_type,
667
                                  ignore_secondaries=opts.ignore_secondaries,
668
                                  shutdown_timeout=opts.shutdown_timeout)
669

    
670

    
671
def _ShutdownInstance(name, opts):
672
  """Shutdown an instance.
673

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

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

681
  """
682
  return opcodes.OpInstanceShutdown(instance_name=name,
683
                                    timeout=opts.timeout,
684
                                    ignore_offline_nodes=opts.ignore_offline)
685

    
686

    
687
def ReplaceDisks(opts, args):
688
  """Replace the disks of an instance
689

690
  @param opts: the command line options selected by the user
691
  @type args: list
692
  @param args: should contain only one element, the instance name
693
  @rtype: int
694
  @return: the desired exit code
695

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

    
725
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
726
                                      remote_node=new_2ndary, mode=mode,
727
                                      iallocator=iallocator,
728
                                      early_release=opts.early_release)
729
  SubmitOrSend(op, opts)
730
  return 0
731

    
732

    
733
def FailoverInstance(opts, args):
734
  """Failover an instance.
735

736
  The failover is done by shutting it down on its present node and
737
  starting it on the secondary.
738

739
  @param opts: the command line options selected by the user
740
  @type args: list
741
  @param args: should contain only one element, the instance name
742
  @rtype: int
743
  @return: the desired exit code
744

745
  """
746
  cl = GetClient()
747
  instance_name = args[0]
748
  force = opts.force
749

    
750
  if not force:
751
    _EnsureInstancesExist(cl, [instance_name])
752

    
753
    usertext = ("Failover will happen to image %s."
754
                " This requires a shutdown of the instance. Continue?" %
755
                (instance_name,))
756
    if not AskUser(usertext):
757
      return 1
758

    
759
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
760
                                  ignore_consistency=opts.ignore_consistency,
761
                                  shutdown_timeout=opts.shutdown_timeout)
762
  SubmitOrSend(op, opts, cl=cl)
763
  return 0
764

    
765

    
766
def MigrateInstance(opts, args):
767
  """Migrate an instance.
768

769
  The migrate is done without shutdown.
770

771
  @param opts: the command line options selected by the user
772
  @type args: list
773
  @param args: should contain only one element, the instance name
774
  @rtype: int
775
  @return: the desired exit code
776

777
  """
778
  cl = GetClient()
779
  instance_name = args[0]
780
  force = opts.force
781

    
782
  if not force:
783
    _EnsureInstancesExist(cl, [instance_name])
784

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

    
797
  # this should be removed once --non-live is deprecated
798
  if not opts.live and opts.migration_mode is not None:
799
    raise errors.OpPrereqError("Only one of the --non-live and "
800
                               "--migration-mode options can be passed",
801
                               errors.ECODE_INVAL)
802
  if not opts.live: # --non-live passed
803
    mode = constants.HT_MIGRATION_NONLIVE
804
  else:
805
    mode = opts.migration_mode
806

    
807
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
808
                                 cleanup=opts.cleanup)
809
  SubmitOpCode(op, cl=cl, opts=opts)
810
  return 0
811

    
812

    
813
def MoveInstance(opts, args):
814
  """Move an instance.
815

816
  @param opts: the command line options selected by the user
817
  @type args: list
818
  @param args: should contain only one element, the instance name
819
  @rtype: int
820
  @return: the desired exit code
821

822
  """
823
  cl = GetClient()
824
  instance_name = args[0]
825
  force = opts.force
826

    
827
  if not force:
828
    usertext = ("Instance %s will be moved."
829
                " This requires a shutdown of the instance. Continue?" %
830
                (instance_name,))
831
    if not AskUser(usertext):
832
      return 1
833

    
834
  op = opcodes.OpInstanceMove(instance_name=instance_name,
835
                              target_node=opts.node,
836
                              shutdown_timeout=opts.shutdown_timeout)
837
  SubmitOrSend(op, opts, cl=cl)
838
  return 0
839

    
840

    
841
def ConnectToInstanceConsole(opts, args):
842
  """Connect to the console of an instance.
843

844
  @param opts: the command line options selected by the user
845
  @type args: list
846
  @param args: should contain only one element, the instance name
847
  @rtype: int
848
  @return: the desired exit code
849

850
  """
851
  instance_name = args[0]
852

    
853
  op = opcodes.OpInstanceConsole(instance_name=instance_name)
854

    
855
  cl = GetClient()
856
  try:
857
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
858
    console_data = SubmitOpCode(op, opts=opts, cl=cl)
859
  finally:
860
    # Ensure client connection is closed while external commands are run
861
    cl.Close()
862

    
863
  del cl
864

    
865
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
866
                    opts.show_command, cluster_name)
867

    
868

    
869
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
870
               _runcmd_fn=utils.RunCmd):
871
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
872

873
  @type console: L{objects.InstanceConsole}
874
  @param console: Console object
875
  @type show_command: bool
876
  @param show_command: Whether to just display commands
877
  @type cluster_name: string
878
  @param cluster_name: Cluster name as retrieved from master daemon
879

880
  """
881
  assert console.Validate()
882

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

    
897
    srun = ssh.SshRunner(cluster_name=cluster_name)
898
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
899
                            batch=True, quiet=False, tty=True)
900

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

    
915
  return constants.EXIT_SUCCESS
916

    
917

    
918
def _FormatLogicalID(dev_type, logical_id, roman):
919
  """Formats the logical_id of a disk.
920

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

    
938
  return data
939

    
940

    
941
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
942
  """Show block device information.
943

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

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

961
  """
962
  def helper(dtype, status):
963
    """Format one line for physical device status.
964

965
    @type dtype: str
966
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
967
    @type status: tuple
968
    @param status: a tuple as returned from L{backend.FindBlockDevice}
969
    @return: the string representing the status
970

971
    """
972
    if not status:
973
      return "not active"
974
    txt = ""
975
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
976
    if major is None:
977
      major_string = "N/A"
978
    else:
979
      major_string = str(compat.TryToRoman(major, convert=roman))
980

    
981
    if minor is None:
982
      minor_string = "N/A"
983
    else:
984
      minor_string = str(compat.TryToRoman(minor, convert=roman))
985

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

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

    
1048
  if dev["children"]:
1049
    data.append("child devices:")
1050
    for c_idx, child in enumerate(dev["children"]):
1051
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1052
  d1.append(data)
1053
  return d1
1054

    
1055

    
1056
def _FormatList(buf, data, indent_level):
1057
  """Formats a list of data at a given indent level.
1058

1059
  If the element of the list is:
1060
    - a string, it is simply formatted as is
1061
    - a tuple, it will be split into key, value and the all the
1062
      values in a list will be aligned all at the same start column
1063
    - a list, will be recursively formatted
1064

1065
  @type buf: StringIO
1066
  @param buf: the buffer into which we write the output
1067
  @param data: the list to format
1068
  @type indent_level: int
1069
  @param indent_level: the indent level to format at
1070

1071
  """
1072
  max_tlen = max([len(elem[0]) for elem in data
1073
                 if isinstance(elem, tuple)] or [0])
1074
  for elem in data:
1075
    if isinstance(elem, basestring):
1076
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1077
    elif isinstance(elem, tuple):
1078
      key, value = elem
1079
      spacer = "%*s" % (max_tlen - len(key), "")
1080
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1081
    elif isinstance(elem, list):
1082
      _FormatList(buf, elem, indent_level+1)
1083

    
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.OpInstanceQueryData(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
                        level=2)
1135
    if instance.has_key("network_port"):
1136
      buf.write("  Allocated network port: %s\n" %
1137
                compat.TryToRoman(instance["network_port"],
1138
                                  convert=opts.roman_integers))
1139
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1140

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

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

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

    
1181
  ToStdout(buf.getvalue().rstrip('\n'))
1182
  return retcode
1183

    
1184

    
1185
def SetInstanceParams(opts, args):
1186
  """Modifies an instance.
1187

1188
  All parameters take effect only at the next restart of the instance.
1189

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

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

    
1202
  for param in opts.beparams:
1203
    if isinstance(opts.beparams[param], basestring):
1204
      if opts.beparams[param].lower() == "default":
1205
        opts.beparams[param] = constants.VALUE_DEFAULT
1206

    
1207
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1208
                      allowed_values=[constants.VALUE_DEFAULT])
1209

    
1210
  for param in opts.hvparams:
1211
    if isinstance(opts.hvparams[param], basestring):
1212
      if opts.hvparams[param].lower() == "default":
1213
        opts.hvparams[param] = constants.VALUE_DEFAULT
1214

    
1215
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1216
                      allowed_values=[constants.VALUE_DEFAULT])
1217

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

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

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

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

    
1256
  # even if here we process the result, we allow submit only
1257
  result = SubmitOrSend(op, opts)
1258

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

    
1267

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

    
1274
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1275
                            help="Filter by nodes (primary only)",
1276
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1277

    
1278
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1279
                            help="Filter by nodes (secondary only)",
1280
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1281

    
1282
m_node_opt = cli_option("--node", dest="multi_mode",
1283
                        help="Filter by nodes (primary and secondary)",
1284
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1285

    
1286
m_clust_opt = cli_option("--all", dest="multi_mode",
1287
                         help="Select all instances in the cluster",
1288
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1289

    
1290
m_inst_opt = cli_option("--instance", dest="multi_mode",
1291
                        help="Filter by instance name [default]",
1292
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1293

    
1294
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1295
                             help="Filter by node tag",
1296
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1297
                             action="store_const")
1298

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

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

    
1309
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1310
                             help="Filter by instance tag",
1311
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1312
                             action="store_const")
1313

    
1314
# this is defined separately due to readability only
1315
add_opts = [
1316
  NOSTART_OPT,
1317
  OS_OPT,
1318
  FORCE_VARIANT_OPT,
1319
  NO_INSTALL_OPT,
1320
  ]
1321

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

    
1454
#: dictionary with aliases for commands
1455
aliases = {
1456
  'start': 'startup',
1457
  'stop': 'shutdown',
1458
  }
1459

    
1460

    
1461
def Main():
1462
  return GenericMain(commands, aliases=aliases,
1463
                     override={"tag_type": constants.TAG_INSTANCE})