Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ 87e87959

History | View | Annotate | Download (52.5 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
                     force_filter=opts.force_filter)
224

    
225

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

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

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

    
239

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

243
  This is just a wrapper over GenericInstanceCreate.
244

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

    
248

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

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

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

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

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

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

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

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

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

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

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

    
333
  jex = JobExecutor(opts=opts)
334

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

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

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

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

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

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

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

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

    
400
  return 0
401

    
402

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

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

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

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

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

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

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

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

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

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

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

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

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

    
486

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

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

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

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

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

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

    
517

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

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

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

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

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

    
543
  return 0
544

    
545

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

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

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

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

    
569

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

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

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

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

    
589

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

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

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

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

    
615

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

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

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

    
641

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

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

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

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

    
663

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

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

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

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

    
680

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

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

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

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

    
696

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

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

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

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

    
742

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

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

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

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

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

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

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

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

    
783

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

787
  The migrate is done without shutdown.
788

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

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

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

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

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

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

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

    
838

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

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

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

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

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

    
866

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

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

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

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

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

    
889
  del cl
890

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

    
894

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

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

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

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

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

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

    
941
  return constants.EXIT_SUCCESS
942

    
943

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

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

    
964
  return data
965

    
966

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

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

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

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

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

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

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

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

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

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

    
1081

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

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

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

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

    
1110

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

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

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

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

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

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

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

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

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

    
1210

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1293

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1487

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