Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ c9c41373

History | View | Annotate | Download (52.3 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
_VALUE_TRUE = "true"
61

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

    
67

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

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

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

90
  """
91
  # pylint: disable-msg=W0142
92

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

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

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

    
144
  return inames
145

    
146

    
147
def _ConfirmOperation(inames, text, extra=""):
148
  """Ask the user to confirm an operation on a list of instances.
149

150
  This function is used to request confirmation for doing an operation
151
  on a given list of instances.
152

153
  @type inames: list
154
  @param inames: the list of names that we display when
155
      we ask for confirmation
156
  @type text: str
157
  @param text: the operation that the user should confirm
158
      (e.g. I{shutdown} or I{startup})
159
  @rtype: boolean
160
  @return: True or False depending on user's confirmation.
161

162
  """
163
  count = len(inames)
164
  msg = ("The %s will operate on %d instances.\n%s"
165
         "Do you want to continue?" % (text, count, extra))
166
  affected = ("\nAffected instances:\n" +
167
              "\n".join(["  %s" % name for name in inames]))
168

    
169
  choices = [('y', True, 'Yes, execute the %s' % text),
170
             ('n', False, 'No, abort the %s' % text)]
171

    
172
  if count > 20:
173
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
174
    ask = msg
175
  else:
176
    ask = msg + affected
177

    
178
  choice = AskUser(ask, choices)
179
  if choice == 'v':
180
    choices.pop(1)
181
    choice = AskUser(msg + affected, choices)
182
  return choice
183

    
184

    
185
def _EnsureInstancesExist(client, names):
186
  """Check for and ensure the given instance names exist.
187

188
  This function will raise an OpPrereqError in case they don't
189
  exist. Otherwise it will exit cleanly.
190

191
  @type client: L{ganeti.luxi.Client}
192
  @param client: the client to use for the query
193
  @type names: list
194
  @param names: the list of instance names to query
195
  @raise errors.OpPrereqError: in case any instance is missing
196

197
  """
198
  # TODO: change LUInstanceQuery to that it actually returns None
199
  # instead of raising an exception, or devise a better mechanism
200
  result = client.QueryInstances(names, ["name"], False)
201
  for orig_name, row in zip(names, result):
202
    if row[0] is None:
203
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name,
204
                                 errors.ECODE_NOENT)
205

    
206

    
207
def GenericManyOps(operation, fn):
208
  """Generic multi-instance operations.
209

210
  The will return a wrapper that processes the options and arguments
211
  given, and uses the passed function to build the opcode needed for
212
  the specific operation. Thus all the generic loop/confirmation code
213
  is abstracted into this function.
214

215
  """
216
  def realfn(opts, args):
217
    if opts.multi_mode is None:
218
      opts.multi_mode = _SHUTDOWN_INSTANCES
219
    cl = GetClient()
220
    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
221
    if not inames:
222
      raise errors.OpPrereqError("Selection filter does not match"
223
                                 " any instances", errors.ECODE_INVAL)
224
    multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
225
    if not (opts.force_multi or not multi_on
226
            or _ConfirmOperation(inames, operation)):
227
      return 1
228
    jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts)
229
    for name in inames:
230
      op = fn(name, opts)
231
      jex.QueueJob(name, op)
232
    results = jex.WaitOrShow(not opts.submit_only)
233
    rcode = compat.all(row[0] for row in results)
234
    return int(not rcode)
235
  return realfn
236

    
237

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

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

247
  """
248
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
249

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

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

    
261

    
262
def ListInstanceFields(opts, args):
263
  """List instance fields.
264

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

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

    
275

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

279
  This is just a wrapper over GenericInstanceCreate.
280

281
  """
282
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
283

    
284

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

288
  This function reads a json file with instances defined
289
  in the form::
290

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

303
  Note that I{primary_node} and I{secondary_node} have precedence over
304
  I{iallocator}.
305

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

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

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

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

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

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

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

    
369
  jex = JobExecutor(opts=opts)
370

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

    
380
    hypervisor = specs['hypervisor']
381
    hvparams = specs['hvparams']
382

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

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

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

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

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

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

    
436
  return 0
437

    
438

    
439
def ReinstallInstance(opts, args):
440
  """Reinstall an instance.
441

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

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

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

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

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

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

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

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

    
485
    os_name = selected
486
  else:
487
    os_name = opts.os
488

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

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

    
513
  jex.WaitOrShow(not opts.submit_only)
514
  return 0
515

    
516

    
517
def RemoveInstance(opts, args):
518
  """Remove an instance.
519

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

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

    
532
  if not force:
533
    _EnsureInstancesExist(cl, [instance_name])
534

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

    
541
  op = opcodes.OpInstanceRemove(instance_name=instance_name,
542
                                ignore_failures=opts.ignore_failures,
543
                                shutdown_timeout=opts.shutdown_timeout)
544
  SubmitOrSend(op, opts, cl=cl)
545
  return 0
546

    
547

    
548
def RenameInstance(opts, args):
549
  """Rename an instance.
550

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

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

    
564
  op = opcodes.OpInstanceRename(instance_name=args[0],
565
                                new_name=args[1],
566
                                ip_check=opts.ip_check,
567
                                name_check=opts.name_check)
568
  result = SubmitOrSend(op, opts)
569

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

    
573
  return 0
574

    
575

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

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

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

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

    
599

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

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

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

612
  """
613
  instance_name = args[0]
614
  op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name,
615
                                         force=opts.force)
616
  SubmitOrSend(op, opts)
617
  return 0
618

    
619

    
620
def RecreateDisks(opts, args):
621
  """Recreate an instance's disks.
622

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

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

    
640
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
641
                                       disks=opts.disks)
642
  SubmitOrSend(op, opts)
643
  return 0
644

    
645

    
646
def GrowDisk(opts, args):
647
  """Grow an instance's disks.
648

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

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

    
671

    
672
def _StartupInstance(name, opts):
673
  """Startup instances.
674

675
  This returns the opcode to start an instance, and its decorator will
676
  wrap this into a loop starting all desired instances.
677

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

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

    
693

    
694
def _RebootInstance(name, opts):
695
  """Reboot instance(s).
696

697
  This returns the opcode to reboot an instance, and its decorator
698
  will wrap this into a loop rebooting all desired instances.
699

700
  @param name: the name of the instance to act on
701
  @param opts: the command line options selected by the user
702
  @return: the opcode needed for the operation
703

704
  """
705
  return opcodes.OpInstanceReboot(instance_name=name,
706
                                  reboot_type=opts.reboot_type,
707
                                  ignore_secondaries=opts.ignore_secondaries,
708
                                  shutdown_timeout=opts.shutdown_timeout)
709

    
710

    
711
def _ShutdownInstance(name, opts):
712
  """Shutdown an instance.
713

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

717
  @param name: the name of the instance to act on
718
  @param opts: the command line options selected by the user
719
  @return: the opcode needed for the operation
720

721
  """
722
  return opcodes.OpInstanceShutdown(instance_name=name,
723
                                    timeout=opts.timeout,
724
                                    ignore_offline_nodes=opts.ignore_offline)
725

    
726

    
727
def ReplaceDisks(opts, args):
728
  """Replace the disks of an instance
729

730
  @param opts: the command line options selected by the user
731
  @type args: list
732
  @param args: should contain only one element, the instance name
733
  @rtype: int
734
  @return: the desired exit code
735

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

    
765
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
766
                                      remote_node=new_2ndary, mode=mode,
767
                                      iallocator=iallocator,
768
                                      early_release=opts.early_release)
769
  SubmitOrSend(op, opts)
770
  return 0
771

    
772

    
773
def FailoverInstance(opts, args):
774
  """Failover an instance.
775

776
  The failover is done by shutting it down on its present node and
777
  starting it on the secondary.
778

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

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

    
790
  if not force:
791
    _EnsureInstancesExist(cl, [instance_name])
792

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

    
799
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
800
                                  ignore_consistency=opts.ignore_consistency,
801
                                  shutdown_timeout=opts.shutdown_timeout)
802
  SubmitOrSend(op, opts, cl=cl)
803
  return 0
804

    
805

    
806
def MigrateInstance(opts, args):
807
  """Migrate an instance.
808

809
  The migrate is done without shutdown.
810

811
  @param opts: the command line options selected by the user
812
  @type args: list
813
  @param args: should contain only one element, the instance name
814
  @rtype: int
815
  @return: the desired exit code
816

817
  """
818
  cl = GetClient()
819
  instance_name = args[0]
820
  force = opts.force
821

    
822
  if not force:
823
    _EnsureInstancesExist(cl, [instance_name])
824

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

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

    
847
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
848
                                 cleanup=opts.cleanup)
849
  SubmitOpCode(op, cl=cl, opts=opts)
850
  return 0
851

    
852

    
853
def MoveInstance(opts, args):
854
  """Move an instance.
855

856
  @param opts: the command line options selected by the user
857
  @type args: list
858
  @param args: should contain only one element, the instance name
859
  @rtype: int
860
  @return: the desired exit code
861

862
  """
863
  cl = GetClient()
864
  instance_name = args[0]
865
  force = opts.force
866

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

    
874
  op = opcodes.OpInstanceMove(instance_name=instance_name,
875
                              target_node=opts.node,
876
                              shutdown_timeout=opts.shutdown_timeout)
877
  SubmitOrSend(op, opts, cl=cl)
878
  return 0
879

    
880

    
881
def ConnectToInstanceConsole(opts, args):
882
  """Connect to the console of an instance.
883

884
  @param opts: the command line options selected by the user
885
  @type args: list
886
  @param args: should contain only one element, the instance name
887
  @rtype: int
888
  @return: the desired exit code
889

890
  """
891
  instance_name = args[0]
892

    
893
  op = opcodes.OpInstanceConsole(instance_name=instance_name)
894

    
895
  cl = GetClient()
896
  try:
897
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
898
    console_data = SubmitOpCode(op, opts=opts, cl=cl)
899
  finally:
900
    # Ensure client connection is closed while external commands are run
901
    cl.Close()
902

    
903
  del cl
904

    
905
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
906
                    opts.show_command, cluster_name)
907

    
908

    
909
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
910
               _runcmd_fn=utils.RunCmd):
911
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
912

913
  @type console: L{objects.InstanceConsole}
914
  @param console: Console object
915
  @type show_command: bool
916
  @param show_command: Whether to just display commands
917
  @type cluster_name: string
918
  @param cluster_name: Cluster name as retrieved from master daemon
919

920
  """
921
  assert console.Validate()
922

    
923
  if console.kind == constants.CONS_MESSAGE:
924
    feedback_fn(console.message)
925
  elif console.kind == constants.CONS_VNC:
926
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
927
                " URL <vnc://%s:%s/>",
928
                console.instance, console.host, console.port,
929
                console.display, console.host, console.port)
930
  elif console.kind == constants.CONS_SSH:
931
    # Convert to string if not already one
932
    if isinstance(console.command, basestring):
933
      cmd = console.command
934
    else:
935
      cmd = utils.ShellQuoteArgs(console.command)
936

    
937
    srun = ssh.SshRunner(cluster_name=cluster_name)
938
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
939
                            batch=True, quiet=False, tty=True)
940

    
941
    if show_command:
942
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
943
    else:
944
      result = _runcmd_fn(ssh_cmd, interactive=True)
945
      if result.failed:
946
        logging.error("Console command \"%s\" failed with reason '%s' and"
947
                      " output %r", result.cmd, result.fail_reason,
948
                      result.output)
949
        raise errors.OpExecError("Connection to console of instance %s failed,"
950
                                 " please check cluster configuration" %
951
                                 console.instance)
952
  else:
953
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
954

    
955
  return constants.EXIT_SUCCESS
956

    
957

    
958
def _FormatLogicalID(dev_type, logical_id, roman):
959
  """Formats the logical_id of a disk.
960

961
  """
962
  if dev_type == constants.LD_DRBD8:
963
    node_a, node_b, port, minor_a, minor_b, key = logical_id
964
    data = [
965
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
966
                                                            convert=roman))),
967
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
968
                                                            convert=roman))),
969
      ("port", compat.TryToRoman(port, convert=roman)),
970
      ("auth key", key),
971
      ]
972
  elif dev_type == constants.LD_LV:
973
    vg_name, lv_name = logical_id
974
    data = ["%s/%s" % (vg_name, lv_name)]
975
  else:
976
    data = [str(logical_id)]
977

    
978
  return data
979

    
980

    
981
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
982
  """Show block device information.
983

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

987
  @type idx: int
988
  @param idx: the index of the current disk
989
  @type top_level: boolean
990
  @param top_level: if this a top-level disk?
991
  @type dev: dict
992
  @param dev: dictionary with disk information
993
  @type static: boolean
994
  @param static: wheter the device information doesn't contain
995
      runtime information but only static data
996
  @type roman: boolean
997
  @param roman: whether to try to use roman integers
998
  @return: a list of either strings, tuples or lists
999
      (which should be formatted at a higher indent level)
1000

1001
  """
1002
  def helper(dtype, status):
1003
    """Format one line for physical device status.
1004

1005
    @type dtype: str
1006
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1007
    @type status: tuple
1008
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1009
    @return: the string representing the status
1010

1011
    """
1012
    if not status:
1013
      return "not active"
1014
    txt = ""
1015
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1016
    if major is None:
1017
      major_string = "N/A"
1018
    else:
1019
      major_string = str(compat.TryToRoman(major, convert=roman))
1020

    
1021
    if minor is None:
1022
      minor_string = "N/A"
1023
    else:
1024
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1025

    
1026
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1027
    if dtype in (constants.LD_DRBD8, ):
1028
      if syncp is not None:
1029
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1030
        if estt:
1031
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1032
        else:
1033
          sync_text += " ETA unknown"
1034
      else:
1035
        sync_text = "in sync"
1036
      if degr:
1037
        degr_text = "*DEGRADED*"
1038
      else:
1039
        degr_text = "ok"
1040
      if ldisk_status == constants.LDS_FAULTY:
1041
        ldisk_text = " *MISSING DISK*"
1042
      elif ldisk_status == constants.LDS_UNKNOWN:
1043
        ldisk_text = " *UNCERTAIN STATE*"
1044
      else:
1045
        ldisk_text = ""
1046
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1047
    elif dtype == constants.LD_LV:
1048
      if ldisk_status == constants.LDS_FAULTY:
1049
        ldisk_text = " *FAILED* (failed drive?)"
1050
      else:
1051
        ldisk_text = ""
1052
      txt += ldisk_text
1053
    return txt
1054

    
1055
  # the header
1056
  if top_level:
1057
    if dev["iv_name"] is not None:
1058
      txt = dev["iv_name"]
1059
    else:
1060
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1061
  else:
1062
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1063
  if isinstance(dev["size"], int):
1064
    nice_size = utils.FormatUnit(dev["size"], "h")
1065
  else:
1066
    nice_size = dev["size"]
1067
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1068
  data = []
1069
  if top_level:
1070
    data.append(("access mode", dev["mode"]))
1071
  if dev["logical_id"] is not None:
1072
    try:
1073
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1074
    except ValueError:
1075
      l_id = [str(dev["logical_id"])]
1076
    if len(l_id) == 1:
1077
      data.append(("logical_id", l_id[0]))
1078
    else:
1079
      data.extend(l_id)
1080
  elif dev["physical_id"] is not None:
1081
    data.append("physical_id:")
1082
    data.append([dev["physical_id"]])
1083
  if not static:
1084
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1085
  if dev["sstatus"] and not static:
1086
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1087

    
1088
  if dev["children"]:
1089
    data.append("child devices:")
1090
    for c_idx, child in enumerate(dev["children"]):
1091
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1092
  d1.append(data)
1093
  return d1
1094

    
1095

    
1096
def _FormatList(buf, data, indent_level):
1097
  """Formats a list of data at a given indent level.
1098

1099
  If the element of the list is:
1100
    - a string, it is simply formatted as is
1101
    - a tuple, it will be split into key, value and the all the
1102
      values in a list will be aligned all at the same start column
1103
    - a list, will be recursively formatted
1104

1105
  @type buf: StringIO
1106
  @param buf: the buffer into which we write the output
1107
  @param data: the list to format
1108
  @type indent_level: int
1109
  @param indent_level: the indent level to format at
1110

1111
  """
1112
  max_tlen = max([len(elem[0]) for elem in data
1113
                 if isinstance(elem, tuple)] or [0])
1114
  for elem in data:
1115
    if isinstance(elem, basestring):
1116
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1117
    elif isinstance(elem, tuple):
1118
      key, value = elem
1119
      spacer = "%*s" % (max_tlen - len(key), "")
1120
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1121
    elif isinstance(elem, list):
1122
      _FormatList(buf, elem, indent_level+1)
1123

    
1124

    
1125
def ShowInstanceConfig(opts, args):
1126
  """Compute instance run-time status.
1127

1128
  @param opts: the command line options selected by the user
1129
  @type args: list
1130
  @param args: either an empty list, and then we query all
1131
      instances, or should contain a list of instance names
1132
  @rtype: int
1133
  @return: the desired exit code
1134

1135
  """
1136
  if not args and not opts.show_all:
1137
    ToStderr("No instance selected."
1138
             " Please pass in --all if you want to query all instances.\n"
1139
             "Note that this can take a long time on a big cluster.")
1140
    return 1
1141
  elif args and opts.show_all:
1142
    ToStderr("Cannot use --all if you specify instance names.")
1143
    return 1
1144

    
1145
  retcode = 0
1146
  op = opcodes.OpInstanceQueryData(instances=args, static=opts.static)
1147
  result = SubmitOpCode(op, opts=opts)
1148
  if not result:
1149
    ToStdout("No instances.")
1150
    return 1
1151

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

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

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

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

    
1221
  ToStdout(buf.getvalue().rstrip('\n'))
1222
  return retcode
1223

    
1224

    
1225
def SetInstanceParams(opts, args):
1226
  """Modifies an instance.
1227

1228
  All parameters take effect only at the next restart of the instance.
1229

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

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

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

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

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

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

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

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

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

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

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

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

    
1307

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1500

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