Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ e9c487be

History | View | Annotate | Download (52.4 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Instance related commands"""
22

    
23
# pylint: disable-msg=W0401,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0614: Unused import %s from wildcard import (since we need cli)
26
# C0103: Invalid name gnt-instance
27

    
28
import itertools
29
import simplejson
30
import logging
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import opcodes
35
from ganeti import constants
36
from ganeti import compat
37
from ganeti import utils
38
from ganeti import errors
39
from ganeti import netutils
40
from ganeti import ssh
41
from ganeti import objects
42

    
43

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

    
54
_SHUTDOWN_NODES_TAGS_MODES = (
55
    _SHUTDOWN_NODES_BOTH_BY_TAGS,
56
    _SHUTDOWN_NODES_PRI_BY_TAGS,
57
    _SHUTDOWN_NODES_SEC_BY_TAGS)
58

    
59

    
60
#: default list of options for L{ListInstances}
61
_LIST_DEF_FIELDS = [
62
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
63
  ]
64

    
65

    
66
def _ExpandMultiNames(mode, names, client=None):
67
  """Expand the given names using the passed mode.
68

69
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
70
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
71
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
72
  instances having those nodes as either primary or secondary will be
73
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
74
  returned.
75

76
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
77
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
78
      L{_SHUTDOWN_INSTANCES}
79
  @param names: a list of names; for cluster, it must be empty,
80
      and for node and instance it must be a list of valid item
81
      names (short names are valid as usual, e.g. node1 instead of
82
      node1.example.com)
83
  @rtype: list
84
  @return: the list of names after the expansion
85
  @raise errors.ProgrammerError: for unknown selection type
86
  @raise errors.OpPrereqError: for invalid input parameters
87

88
  """
89
  # pylint: disable-msg=W0142
90

    
91
  if client is None:
92
    client = GetClient()
93
  if mode == _SHUTDOWN_CLUSTER:
94
    if names:
95
      raise errors.OpPrereqError("Cluster filter mode takes no arguments",
96
                                 errors.ECODE_INVAL)
97
    idata = client.QueryInstances([], ["name"], False)
98
    inames = [row[0] for row in idata]
99

    
100
  elif mode in (_SHUTDOWN_NODES_BOTH,
101
                _SHUTDOWN_NODES_PRI,
102
                _SHUTDOWN_NODES_SEC) + _SHUTDOWN_NODES_TAGS_MODES:
103
    if mode in _SHUTDOWN_NODES_TAGS_MODES:
104
      if not names:
105
        raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL)
106
      ndata = client.QueryNodes([], ["name", "pinst_list",
107
                                     "sinst_list", "tags"], False)
108
      ndata = [row for row in ndata if set(row[3]).intersection(names)]
109
    else:
110
      if not names:
111
        raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL)
112
      ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
113
                              False)
114

    
115
    ipri = [row[1] for row in ndata]
116
    pri_names = list(itertools.chain(*ipri))
117
    isec = [row[2] for row in ndata]
118
    sec_names = list(itertools.chain(*isec))
119
    if mode in (_SHUTDOWN_NODES_BOTH, _SHUTDOWN_NODES_BOTH_BY_TAGS):
120
      inames = pri_names + sec_names
121
    elif mode in (_SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_PRI_BY_TAGS):
122
      inames = pri_names
123
    elif mode in (_SHUTDOWN_NODES_SEC, _SHUTDOWN_NODES_SEC_BY_TAGS):
124
      inames = sec_names
125
    else:
126
      raise errors.ProgrammerError("Unhandled shutdown type")
127
  elif mode == _SHUTDOWN_INSTANCES:
128
    if not names:
129
      raise errors.OpPrereqError("No instance names passed",
130
                                 errors.ECODE_INVAL)
131
    idata = client.QueryInstances(names, ["name"], False)
132
    inames = [row[0] for row in idata]
133
  elif mode == _SHUTDOWN_INSTANCES_BY_TAGS:
134
    if not names:
135
      raise errors.OpPrereqError("No instance tags passed",
136
                                 errors.ECODE_INVAL)
137
    idata = client.QueryInstances([], ["name", "tags"], False)
138
    inames = [row[0] for row in idata if set(row[1]).intersection(names)]
139
  else:
140
    raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL)
141

    
142
  return inames
143

    
144

    
145
def _EnsureInstancesExist(client, names):
146
  """Check for and ensure the given instance names exist.
147

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

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

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

    
166

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

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

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

    
200

    
201
def ListInstances(opts, args):
202
  """List instances and their properties.
203

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

210
  """
211
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
212

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

    
220
  return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units,
221
                     opts.separator, not opts.no_headers,
222
                     format_override=fmtoverride, verbose=opts.verbose)
223

    
224

    
225
def ListInstanceFields(opts, args):
226
  """List instance fields.
227

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

234
  """
235
  return GenericListFields(constants.QR_INSTANCE, args, opts.separator,
236
                           not opts.no_headers)
237

    
238

    
239
def AddInstance(opts, args):
240
  """Add an instance to the cluster.
241

242
  This is just a wrapper over GenericInstanceCreate.
243

244
  """
245
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
246

    
247

    
248
def BatchCreate(opts, args):
249
  """Create instances using a definition file.
250

251
  This function reads a json file with instances defined
252
  in the form::
253

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

266
  Note that I{primary_node} and I{secondary_node} have precedence over
267
  I{iallocator}.
268

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

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

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

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

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

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

    
328
  if not isinstance(instance_data, dict):
329
    ToStderr("The instance definition file is not in dict format.")
330
    return 1
331

    
332
  jex = JobExecutor(opts=opts)
333

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

    
343
    hypervisor = specs['hypervisor']
344
    hvparams = specs['hvparams']
345

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

    
356
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
357
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
358

    
359
    tmp_nics = []
360
    for field in constants.INIC_PARAMS:
361
      if field in specs:
362
        if not tmp_nics:
363
          tmp_nics.append({})
364
        tmp_nics[0][field] = specs[field]
365

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

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

    
395
    jex.QueueJob(name, op)
396
  # we never want to wait, just show the submitted job IDs
397
  jex.WaitOrShow(False)
398

    
399
  return 0
400

    
401

    
402
def ReinstallInstance(opts, args):
403
  """Reinstall an instance.
404

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

412
  """
413
  # first, compute the desired name list
414
  if opts.multi_mode is None:
415
    opts.multi_mode = _SHUTDOWN_INSTANCES
416

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

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

    
427
    if not result:
428
      ToStdout("Can't get the OS list")
429
      return 1
430

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

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

    
444
    if selected == 'exit':
445
      ToStderr("User aborted reinstall, exiting")
446
      return 1
447

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

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

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

    
482
  jex.WaitOrShow(not opts.submit_only)
483
  return 0
484

    
485

    
486
def RemoveInstance(opts, args):
487
  """Remove an instance.
488

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

496
  """
497
  instance_name = args[0]
498
  force = opts.force
499
  cl = GetClient()
500

    
501
  if not force:
502
    _EnsureInstancesExist(cl, [instance_name])
503

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

    
510
  op = opcodes.OpInstanceRemove(instance_name=instance_name,
511
                                ignore_failures=opts.ignore_failures,
512
                                shutdown_timeout=opts.shutdown_timeout)
513
  SubmitOrSend(op, opts, cl=cl)
514
  return 0
515

    
516

    
517
def RenameInstance(opts, args):
518
  """Rename an instance.
519

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

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

    
533
  op = opcodes.OpInstanceRename(instance_name=args[0],
534
                                new_name=args[1],
535
                                ip_check=opts.ip_check,
536
                                name_check=opts.name_check)
537
  result = SubmitOrSend(op, opts)
538

    
539
  if result:
540
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
541

    
542
  return 0
543

    
544

    
545
def ActivateDisks(opts, args):
546
  """Activate an instance's disks.
547

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

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

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

    
568

    
569
def DeactivateDisks(opts, args):
570
  """Deactivate an instance's disks.
571

572
  This function takes the instance name, looks for its primary node
573
  and the tries to shutdown its block devices on that node.
574

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

581
  """
582
  instance_name = args[0]
583
  op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
584
                                         force=opts.force)
585
  SubmitOrSend(op, opts)
586
  return 0
587

    
588

    
589
def RecreateDisks(opts, args):
590
  """Recreate an instance's disks.
591

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

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

    
609
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
610
                                       disks=opts.disks)
611
  SubmitOrSend(op, opts)
612
  return 0
613

    
614

    
615
def GrowDisk(opts, args):
616
  """Grow an instance's disks.
617

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

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

    
640

    
641
def _StartupInstance(name, opts):
642
  """Startup instances.
643

644
  This returns the opcode to start an instance, and its decorator will
645
  wrap this into a loop starting all desired instances.
646

647
  @param name: the name of the instance to act on
648
  @param opts: the command line options selected by the user
649
  @return: the opcode needed for the operation
650

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

    
662

    
663
def _RebootInstance(name, opts):
664
  """Reboot instance(s).
665

666
  This returns the opcode to reboot an instance, and its decorator
667
  will wrap this into a loop rebooting all desired instances.
668

669
  @param name: the name of the instance to act on
670
  @param opts: the command line options selected by the user
671
  @return: the opcode needed for the operation
672

673
  """
674
  return opcodes.OpInstanceReboot(instance_name=name,
675
                                  reboot_type=opts.reboot_type,
676
                                  ignore_secondaries=opts.ignore_secondaries,
677
                                  shutdown_timeout=opts.shutdown_timeout)
678

    
679

    
680
def _ShutdownInstance(name, opts):
681
  """Shutdown an instance.
682

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

686
  @param name: the name of the instance to act on
687
  @param opts: the command line options selected by the user
688
  @return: the opcode needed for the operation
689

690
  """
691
  return opcodes.OpInstanceShutdown(instance_name=name,
692
                                    timeout=opts.timeout,
693
                                    ignore_offline_nodes=opts.ignore_offline)
694

    
695

    
696
def ReplaceDisks(opts, args):
697
  """Replace the disks of an instance
698

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

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

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

    
741

    
742
def FailoverInstance(opts, args):
743
  """Failover an instance.
744

745
  The failover is done by shutting it down on its present node and
746
  starting it on the secondary.
747

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

754
  """
755
  cl = GetClient()
756
  instance_name = args[0]
757
  force = opts.force
758
  iallocator = opts.iallocator
759
  target_node = opts.dst_node
760

    
761
  if iallocator and target_node:
762
    raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
763
                               " node (-n) but not both", errors.ECODE_INVAL)
764

    
765
  if not force:
766
    _EnsureInstancesExist(cl, [instance_name])
767

    
768
    usertext = ("Failover will happen to image %s."
769
                " This requires a shutdown of the instance. Continue?" %
770
                (instance_name,))
771
    if not AskUser(usertext):
772
      return 1
773

    
774
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
775
                                  ignore_consistency=opts.ignore_consistency,
776
                                  shutdown_timeout=opts.shutdown_timeout,
777
                                  iallocator=iallocator,
778
                                  target_node=target_node)
779
  SubmitOrSend(op, opts, cl=cl)
780
  return 0
781

    
782

    
783
def MigrateInstance(opts, args):
784
  """Migrate an instance.
785

786
  The migrate is done without shutdown.
787

788
  @param opts: the command line options selected by the user
789
  @type args: list
790
  @param args: should contain only one element, the instance name
791
  @rtype: int
792
  @return: the desired exit code
793

794
  """
795
  cl = GetClient()
796
  instance_name = args[0]
797
  force = opts.force
798
  iallocator = opts.iallocator
799
  target_node = opts.dst_node
800

    
801
  if iallocator and target_node:
802
    raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
803
                               " node (-n) but not both", errors.ECODE_INVAL)
804

    
805
  if not force:
806
    _EnsureInstancesExist(cl, [instance_name])
807

    
808
    if opts.cleanup:
809
      usertext = ("Instance %s will be recovered from a failed migration."
810
                  " Note that the migration procedure (including cleanup)" %
811
                  (instance_name,))
812
    else:
813
      usertext = ("Instance %s will be migrated. Note that migration" %
814
                  (instance_name,))
815
    usertext += (" might impact the instance if anything goes wrong"
816
                 " (e.g. due to bugs in the hypervisor). Continue?")
817
    if not AskUser(usertext):
818
      return 1
819

    
820
  # this should be removed once --non-live is deprecated
821
  if not opts.live and opts.migration_mode is not None:
822
    raise errors.OpPrereqError("Only one of the --non-live and "
823
                               "--migration-mode options can be passed",
824
                               errors.ECODE_INVAL)
825
  if not opts.live: # --non-live passed
826
    mode = constants.HT_MIGRATION_NONLIVE
827
  else:
828
    mode = opts.migration_mode
829

    
830
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
831
                                 cleanup=opts.cleanup, iallocator=iallocator,
832
                                 target_node=target_node,
833
                                 allow_failover=opts.allow_failover)
834
  SubmitOpCode(op, cl=cl, opts=opts)
835
  return 0
836

    
837

    
838
def MoveInstance(opts, args):
839
  """Move an instance.
840

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

847
  """
848
  cl = GetClient()
849
  instance_name = args[0]
850
  force = opts.force
851

    
852
  if not force:
853
    usertext = ("Instance %s will be moved."
854
                " This requires a shutdown of the instance. Continue?" %
855
                (instance_name,))
856
    if not AskUser(usertext):
857
      return 1
858

    
859
  op = opcodes.OpInstanceMove(instance_name=instance_name,
860
                              target_node=opts.node,
861
                              shutdown_timeout=opts.shutdown_timeout)
862
  SubmitOrSend(op, opts, cl=cl)
863
  return 0
864

    
865

    
866
def ConnectToInstanceConsole(opts, args):
867
  """Connect to the console of an instance.
868

869
  @param opts: the command line options selected by the user
870
  @type args: list
871
  @param args: should contain only one element, the instance name
872
  @rtype: int
873
  @return: the desired exit code
874

875
  """
876
  instance_name = args[0]
877

    
878
  op = opcodes.OpInstanceConsole(instance_name=instance_name)
879

    
880
  cl = GetClient()
881
  try:
882
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
883
    console_data = SubmitOpCode(op, opts=opts, cl=cl)
884
  finally:
885
    # Ensure client connection is closed while external commands are run
886
    cl.Close()
887

    
888
  del cl
889

    
890
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
891
                    opts.show_command, cluster_name)
892

    
893

    
894
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
895
               _runcmd_fn=utils.RunCmd):
896
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
897

898
  @type console: L{objects.InstanceConsole}
899
  @param console: Console object
900
  @type show_command: bool
901
  @param show_command: Whether to just display commands
902
  @type cluster_name: string
903
  @param cluster_name: Cluster name as retrieved from master daemon
904

905
  """
906
  assert console.Validate()
907

    
908
  if console.kind == constants.CONS_MESSAGE:
909
    feedback_fn(console.message)
910
  elif console.kind == constants.CONS_VNC:
911
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
912
                " URL <vnc://%s:%s/>",
913
                console.instance, console.host, console.port,
914
                console.display, console.host, console.port)
915
  elif console.kind == constants.CONS_SSH:
916
    # Convert to string if not already one
917
    if isinstance(console.command, basestring):
918
      cmd = console.command
919
    else:
920
      cmd = utils.ShellQuoteArgs(console.command)
921

    
922
    srun = ssh.SshRunner(cluster_name=cluster_name)
923
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
924
                            batch=True, quiet=False, tty=True)
925

    
926
    if show_command:
927
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
928
    else:
929
      result = _runcmd_fn(ssh_cmd, interactive=True)
930
      if result.failed:
931
        logging.error("Console command \"%s\" failed with reason '%s' and"
932
                      " output %r", result.cmd, result.fail_reason,
933
                      result.output)
934
        raise errors.OpExecError("Connection to console of instance %s failed,"
935
                                 " please check cluster configuration" %
936
                                 console.instance)
937
  else:
938
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
939

    
940
  return constants.EXIT_SUCCESS
941

    
942

    
943
def _FormatLogicalID(dev_type, logical_id, roman):
944
  """Formats the logical_id of a disk.
945

946
  """
947
  if dev_type == constants.LD_DRBD8:
948
    node_a, node_b, port, minor_a, minor_b, key = logical_id
949
    data = [
950
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
951
                                                            convert=roman))),
952
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
953
                                                            convert=roman))),
954
      ("port", compat.TryToRoman(port, convert=roman)),
955
      ("auth key", key),
956
      ]
957
  elif dev_type == constants.LD_LV:
958
    vg_name, lv_name = logical_id
959
    data = ["%s/%s" % (vg_name, lv_name)]
960
  else:
961
    data = [str(logical_id)]
962

    
963
  return data
964

    
965

    
966
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
967
  """Show block device information.
968

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

972
  @type idx: int
973
  @param idx: the index of the current disk
974
  @type top_level: boolean
975
  @param top_level: if this a top-level disk?
976
  @type dev: dict
977
  @param dev: dictionary with disk information
978
  @type static: boolean
979
  @param static: wheter the device information doesn't contain
980
      runtime information but only static data
981
  @type roman: boolean
982
  @param roman: whether to try to use roman integers
983
  @return: a list of either strings, tuples or lists
984
      (which should be formatted at a higher indent level)
985

986
  """
987
  def helper(dtype, status):
988
    """Format one line for physical device status.
989

990
    @type dtype: str
991
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
992
    @type status: tuple
993
    @param status: a tuple as returned from L{backend.FindBlockDevice}
994
    @return: the string representing the status
995

996
    """
997
    if not status:
998
      return "not active"
999
    txt = ""
1000
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1001
    if major is None:
1002
      major_string = "N/A"
1003
    else:
1004
      major_string = str(compat.TryToRoman(major, convert=roman))
1005

    
1006
    if minor is None:
1007
      minor_string = "N/A"
1008
    else:
1009
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1010

    
1011
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1012
    if dtype in (constants.LD_DRBD8, ):
1013
      if syncp is not None:
1014
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1015
        if estt:
1016
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1017
        else:
1018
          sync_text += " ETA unknown"
1019
      else:
1020
        sync_text = "in sync"
1021
      if degr:
1022
        degr_text = "*DEGRADED*"
1023
      else:
1024
        degr_text = "ok"
1025
      if ldisk_status == constants.LDS_FAULTY:
1026
        ldisk_text = " *MISSING DISK*"
1027
      elif ldisk_status == constants.LDS_UNKNOWN:
1028
        ldisk_text = " *UNCERTAIN STATE*"
1029
      else:
1030
        ldisk_text = ""
1031
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1032
    elif dtype == constants.LD_LV:
1033
      if ldisk_status == constants.LDS_FAULTY:
1034
        ldisk_text = " *FAILED* (failed drive?)"
1035
      else:
1036
        ldisk_text = ""
1037
      txt += ldisk_text
1038
    return txt
1039

    
1040
  # the header
1041
  if top_level:
1042
    if dev["iv_name"] is not None:
1043
      txt = dev["iv_name"]
1044
    else:
1045
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1046
  else:
1047
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1048
  if isinstance(dev["size"], int):
1049
    nice_size = utils.FormatUnit(dev["size"], "h")
1050
  else:
1051
    nice_size = dev["size"]
1052
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1053
  data = []
1054
  if top_level:
1055
    data.append(("access mode", dev["mode"]))
1056
  if dev["logical_id"] is not None:
1057
    try:
1058
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1059
    except ValueError:
1060
      l_id = [str(dev["logical_id"])]
1061
    if len(l_id) == 1:
1062
      data.append(("logical_id", l_id[0]))
1063
    else:
1064
      data.extend(l_id)
1065
  elif dev["physical_id"] is not None:
1066
    data.append("physical_id:")
1067
    data.append([dev["physical_id"]])
1068
  if not static:
1069
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1070
  if dev["sstatus"] and not static:
1071
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1072

    
1073
  if dev["children"]:
1074
    data.append("child devices:")
1075
    for c_idx, child in enumerate(dev["children"]):
1076
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1077
  d1.append(data)
1078
  return d1
1079

    
1080

    
1081
def _FormatList(buf, data, indent_level):
1082
  """Formats a list of data at a given indent level.
1083

1084
  If the element of the list is:
1085
    - a string, it is simply formatted as is
1086
    - a tuple, it will be split into key, value and the all the
1087
      values in a list will be aligned all at the same start column
1088
    - a list, will be recursively formatted
1089

1090
  @type buf: StringIO
1091
  @param buf: the buffer into which we write the output
1092
  @param data: the list to format
1093
  @type indent_level: int
1094
  @param indent_level: the indent level to format at
1095

1096
  """
1097
  max_tlen = max([len(elem[0]) for elem in data
1098
                 if isinstance(elem, tuple)] or [0])
1099
  for elem in data:
1100
    if isinstance(elem, basestring):
1101
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1102
    elif isinstance(elem, tuple):
1103
      key, value = elem
1104
      spacer = "%*s" % (max_tlen - len(key), "")
1105
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1106
    elif isinstance(elem, list):
1107
      _FormatList(buf, elem, indent_level+1)
1108

    
1109

    
1110
def ShowInstanceConfig(opts, args):
1111
  """Compute instance run-time status.
1112

1113
  @param opts: the command line options selected by the user
1114
  @type args: list
1115
  @param args: either an empty list, and then we query all
1116
      instances, or should contain a list of instance names
1117
  @rtype: int
1118
  @return: the desired exit code
1119

1120
  """
1121
  if not args and not opts.show_all:
1122
    ToStderr("No instance selected."
1123
             " Please pass in --all if you want to query all instances.\n"
1124
             "Note that this can take a long time on a big cluster.")
1125
    return 1
1126
  elif args and opts.show_all:
1127
    ToStderr("Cannot use --all if you specify instance names.")
1128
    return 1
1129

    
1130
  retcode = 0
1131
  op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1132
  result = SubmitOpCode(op, opts=opts)
1133
  if not result:
1134
    ToStdout("No instances.")
1135
    return 1
1136

    
1137
  buf = StringIO()
1138
  retcode = 0
1139
  for instance_name in result:
1140
    instance = result[instance_name]
1141
    buf.write("Instance name: %s\n" % instance["name"])
1142
    buf.write("UUID: %s\n" % instance["uuid"])
1143
    buf.write("Serial number: %s\n" %
1144
              compat.TryToRoman(instance["serial_no"],
1145
                                convert=opts.roman_integers))
1146
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1147
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1148
    buf.write("State: configured to be %s" % instance["config_state"])
1149
    if not opts.static:
1150
      buf.write(", actual state is %s" % instance["run_state"])
1151
    buf.write("\n")
1152
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1153
    ##          instance["auto_balance"])
1154
    buf.write("  Nodes:\n")
1155
    buf.write("    - primary: %s\n" % instance["pnode"])
1156
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1157
    buf.write("  Operating system: %s\n" % instance["os"])
1158
    FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1159
                        level=2)
1160
    if instance.has_key("network_port"):
1161
      buf.write("  Allocated network port: %s\n" %
1162
                compat.TryToRoman(instance["network_port"],
1163
                                  convert=opts.roman_integers))
1164
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1165

    
1166
    # custom VNC console information
1167
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1168
                                                 None)
1169
    if vnc_bind_address:
1170
      port = instance["network_port"]
1171
      display = int(port) - constants.VNC_BASE_PORT
1172
      if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1173
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1174
                                                   port,
1175
                                                   display)
1176
      elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1177
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1178
                             (vnc_bind_address, port,
1179
                              instance["pnode"], display))
1180
      else:
1181
        # vnc bind address is a file
1182
        vnc_console_port = "%s:%s" % (instance["pnode"],
1183
                                      vnc_bind_address)
1184
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1185

    
1186
    FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1187
                        level=2)
1188
    buf.write("  Hardware:\n")
1189
    buf.write("    - VCPUs: %s\n" %
1190
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1191
                                convert=opts.roman_integers))
1192
    buf.write("    - memory: %sMiB\n" %
1193
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1194
                                convert=opts.roman_integers))
1195
    buf.write("    - NICs:\n")
1196
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1197
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1198
                (idx, mac, ip, mode, link))
1199
    buf.write("  Disk template: %s\n" % instance["disk_template"])
1200
    buf.write("  Disks:\n")
1201

    
1202
    for idx, device in enumerate(instance["disks"]):
1203
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1204
                  opts.roman_integers), 2)
1205

    
1206
  ToStdout(buf.getvalue().rstrip('\n'))
1207
  return retcode
1208

    
1209

    
1210
def SetInstanceParams(opts, args):
1211
  """Modifies an instance.
1212

1213
  All parameters take effect only at the next restart of the instance.
1214

1215
  @param opts: the command line options selected by the user
1216
  @type args: list
1217
  @param args: should contain only one element, the instance name
1218
  @rtype: int
1219
  @return: the desired exit code
1220

1221
  """
1222
  if not (opts.nics or opts.disks or opts.disk_template or
1223
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1224
    ToStderr("Please give at least one of the parameters.")
1225
    return 1
1226

    
1227
  for param in opts.beparams:
1228
    if isinstance(opts.beparams[param], basestring):
1229
      if opts.beparams[param].lower() == "default":
1230
        opts.beparams[param] = constants.VALUE_DEFAULT
1231

    
1232
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1233
                      allowed_values=[constants.VALUE_DEFAULT])
1234

    
1235
  for param in opts.hvparams:
1236
    if isinstance(opts.hvparams[param], basestring):
1237
      if opts.hvparams[param].lower() == "default":
1238
        opts.hvparams[param] = constants.VALUE_DEFAULT
1239

    
1240
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1241
                      allowed_values=[constants.VALUE_DEFAULT])
1242

    
1243
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1244
    try:
1245
      nic_op = int(nic_op)
1246
      opts.nics[idx] = (nic_op, nic_dict)
1247
    except (TypeError, ValueError):
1248
      pass
1249

    
1250
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1251
    try:
1252
      disk_op = int(disk_op)
1253
      opts.disks[idx] = (disk_op, disk_dict)
1254
    except (TypeError, ValueError):
1255
      pass
1256
    if disk_op == constants.DDM_ADD:
1257
      if 'size' not in disk_dict:
1258
        raise errors.OpPrereqError("Missing required parameter 'size'",
1259
                                   errors.ECODE_INVAL)
1260
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1261

    
1262
  if (opts.disk_template and
1263
      opts.disk_template in constants.DTS_INT_MIRROR and
1264
      not opts.node):
1265
    ToStderr("Changing the disk template to a mirrored one requires"
1266
             " specifying a secondary node")
1267
    return 1
1268

    
1269
  op = opcodes.OpInstanceSetParams(instance_name=args[0],
1270
                                   nics=opts.nics,
1271
                                   disks=opts.disks,
1272
                                   disk_template=opts.disk_template,
1273
                                   remote_node=opts.node,
1274
                                   hvparams=opts.hvparams,
1275
                                   beparams=opts.beparams,
1276
                                   os_name=opts.os,
1277
                                   osparams=opts.osparams,
1278
                                   force_variant=opts.force_variant,
1279
                                   force=opts.force)
1280

    
1281
  # even if here we process the result, we allow submit only
1282
  result = SubmitOrSend(op, opts)
1283

    
1284
  if result:
1285
    ToStdout("Modified instance %s", args[0])
1286
    for param, data in result:
1287
      ToStdout(" - %-5s -> %s", param, data)
1288
    ToStdout("Please don't forget that most parameters take effect"
1289
             " only at the next start of the instance.")
1290
  return 0
1291

    
1292

    
1293
# multi-instance selection options
1294
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1295
                           help="Do not ask for confirmation when more than"
1296
                           " one instance is affected",
1297
                           action="store_true", default=False)
1298

    
1299
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1300
                            help="Filter by nodes (primary only)",
1301
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1302

    
1303
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1304
                            help="Filter by nodes (secondary only)",
1305
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1306

    
1307
m_node_opt = cli_option("--node", dest="multi_mode",
1308
                        help="Filter by nodes (primary and secondary)",
1309
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1310

    
1311
m_clust_opt = cli_option("--all", dest="multi_mode",
1312
                         help="Select all instances in the cluster",
1313
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1314

    
1315
m_inst_opt = cli_option("--instance", dest="multi_mode",
1316
                        help="Filter by instance name [default]",
1317
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1318

    
1319
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1320
                             help="Filter by node tag",
1321
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1322
                             action="store_const")
1323

    
1324
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1325
                                 help="Filter by primary node tag",
1326
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1327
                                 action="store_const")
1328

    
1329
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1330
                                 help="Filter by secondary node tag",
1331
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1332
                                 action="store_const")
1333

    
1334
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1335
                             help="Filter by instance tag",
1336
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1337
                             action="store_const")
1338

    
1339
# this is defined separately due to readability only
1340
add_opts = [
1341
  NOSTART_OPT,
1342
  OS_OPT,
1343
  FORCE_VARIANT_OPT,
1344
  NO_INSTALL_OPT,
1345
  ]
1346

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

    
1479
#: dictionary with aliases for commands
1480
aliases = {
1481
  'start': 'startup',
1482
  'stop': 'shutdown',
1483
  }
1484

    
1485

    
1486
def Main():
1487
  return GenericMain(commands, aliases=aliases,
1488
                     override={"tag_type": constants.TAG_INSTANCE})