Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ 6bc3ed14

History | View | Annotate | Download (55.1 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=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
_EXPAND_CLUSTER = "cluster"
45
_EXPAND_NODES_BOTH = "nodes"
46
_EXPAND_NODES_PRI = "nodes-pri"
47
_EXPAND_NODES_SEC = "nodes-sec"
48
_EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags"
49
_EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
50
_EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
51
_EXPAND_INSTANCES = "instances"
52
_EXPAND_INSTANCES_BY_TAGS = "instances-by-tags"
53

    
54
_EXPAND_NODES_TAGS_MODES = frozenset([
55
  _EXPAND_NODES_BOTH_BY_TAGS,
56
  _EXPAND_NODES_PRI_BY_TAGS,
57
  _EXPAND_NODES_SEC_BY_TAGS,
58
  ])
59

    
60

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

    
66

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

70
  For _EXPAND_CLUSTER, all instances will be returned. For
71
  _EXPAND_NODES_PRI/SEC, all instances having those nodes as
72
  primary/secondary will be returned. For _EXPAND_NODES_BOTH, all
73
  instances having those nodes as either primary or secondary will be
74
  returned. For _EXPAND_INSTANCES, the given instances will be
75
  returned.
76

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

89
  """
90
  # pylint: disable=W0142
91

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

    
101
  elif (mode in _EXPAND_NODES_TAGS_MODES or
102
        mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)):
103
    if mode in _EXPAND_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 (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS):
120
      inames = pri_names + sec_names
121
    elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS):
122
      inames = pri_names
123
    elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS):
124
      inames = sec_names
125
    else:
126
      raise errors.ProgrammerError("Unhandled shutdown type")
127
  elif mode == _EXPAND_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 == _EXPAND_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 = _EXPAND_INSTANCES
179
    cl = GetClient()
180
    inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
181
    if not inames:
182
      if opts.multi_mode == _EXPAND_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 != _EXPAND_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", "snodes.group", "snodes.group.uuid"],
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=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=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 = _EXPAND_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 != _EXPAND_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
  if opts.node:
611
    pnode, snode = SplitNodeOption(opts.node)
612
    nodes = [pnode]
613
    if snode is not None:
614
      nodes.append(snode)
615
  else:
616
    nodes = []
617

    
618
  op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name,
619
                                       disks=opts.disks,
620
                                       nodes=nodes)
621
  SubmitOrSend(op, opts)
622
  return 0
623

    
624

    
625
def GrowDisk(opts, args):
626
  """Grow an instance's disks.
627

628
  @param opts: the command line options selected by the user
629
  @type args: list
630
  @param args: should contain two elements, the instance name
631
      whose disks we grow and the disk name, e.g. I{sda}
632
  @rtype: int
633
  @return: the desired exit code
634

635
  """
636
  instance = args[0]
637
  disk = args[1]
638
  try:
639
    disk = int(disk)
640
  except (TypeError, ValueError), err:
641
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
642
                               errors.ECODE_INVAL)
643
  amount = utils.ParseUnit(args[2])
644
  op = opcodes.OpInstanceGrowDisk(instance_name=instance,
645
                                  disk=disk, amount=amount,
646
                                  wait_for_sync=opts.wait_for_sync)
647
  SubmitOrSend(op, opts)
648
  return 0
649

    
650

    
651
def _StartupInstance(name, opts):
652
  """Startup instances.
653

654
  This returns the opcode to start an instance, and its decorator will
655
  wrap this into a loop starting all desired instances.
656

657
  @param name: the name of the instance to act on
658
  @param opts: the command line options selected by the user
659
  @return: the opcode needed for the operation
660

661
  """
662
  op = opcodes.OpInstanceStartup(instance_name=name,
663
                                 force=opts.force,
664
                                 ignore_offline_nodes=opts.ignore_offline,
665
                                 no_remember=opts.no_remember,
666
                                 startup_paused=opts.startup_paused)
667
  # do not add these parameters to the opcode unless they're defined
668
  if opts.hvparams:
669
    op.hvparams = opts.hvparams
670
  if opts.beparams:
671
    op.beparams = opts.beparams
672
  return op
673

    
674

    
675
def _RebootInstance(name, opts):
676
  """Reboot instance(s).
677

678
  This returns the opcode to reboot an instance, and its decorator
679
  will wrap this into a loop rebooting all desired instances.
680

681
  @param name: the name of the instance to act on
682
  @param opts: the command line options selected by the user
683
  @return: the opcode needed for the operation
684

685
  """
686
  return opcodes.OpInstanceReboot(instance_name=name,
687
                                  reboot_type=opts.reboot_type,
688
                                  ignore_secondaries=opts.ignore_secondaries,
689
                                  shutdown_timeout=opts.shutdown_timeout)
690

    
691

    
692
def _ShutdownInstance(name, opts):
693
  """Shutdown an instance.
694

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

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

702
  """
703
  return opcodes.OpInstanceShutdown(instance_name=name,
704
                                    timeout=opts.timeout,
705
                                    ignore_offline_nodes=opts.ignore_offline,
706
                                    no_remember=opts.no_remember)
707

    
708

    
709
def ReplaceDisks(opts, args):
710
  """Replace the disks of an instance
711

712
  @param opts: the command line options selected by the user
713
  @type args: list
714
  @param args: should contain only one element, the instance name
715
  @rtype: int
716
  @return: the desired exit code
717

718
  """
719
  new_2ndary = opts.dst_node
720
  iallocator = opts.iallocator
721
  if opts.disks is None:
722
    disks = []
723
  else:
724
    try:
725
      disks = [int(i) for i in opts.disks.split(",")]
726
    except (TypeError, ValueError), err:
727
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
728
                                 errors.ECODE_INVAL)
729
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
730
         new_2ndary is not None, iallocator is not None].count(True)
731
  if cnt != 1:
732
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I"
733
                               " options must be passed", errors.ECODE_INVAL)
734
  elif opts.on_primary:
735
    mode = constants.REPLACE_DISK_PRI
736
  elif opts.on_secondary:
737
    mode = constants.REPLACE_DISK_SEC
738
  elif opts.auto:
739
    mode = constants.REPLACE_DISK_AUTO
740
    if disks:
741
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
742
                                 " mode", errors.ECODE_INVAL)
743
  elif new_2ndary is not None or iallocator is not None:
744
    # replace secondary
745
    mode = constants.REPLACE_DISK_CHG
746

    
747
  op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks,
748
                                      remote_node=new_2ndary, mode=mode,
749
                                      iallocator=iallocator,
750
                                      early_release=opts.early_release)
751
  SubmitOrSend(op, opts)
752
  return 0
753

    
754

    
755
def FailoverInstance(opts, args):
756
  """Failover an instance.
757

758
  The failover is done by shutting it down on its present node and
759
  starting it on the secondary.
760

761
  @param opts: the command line options selected by the user
762
  @type args: list
763
  @param args: should contain only one element, the instance name
764
  @rtype: int
765
  @return: the desired exit code
766

767
  """
768
  cl = GetClient()
769
  instance_name = args[0]
770
  force = opts.force
771
  iallocator = opts.iallocator
772
  target_node = opts.dst_node
773

    
774
  if iallocator and target_node:
775
    raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
776
                               " node (-n) but not both", errors.ECODE_INVAL)
777

    
778
  if not force:
779
    _EnsureInstancesExist(cl, [instance_name])
780

    
781
    usertext = ("Failover will happen to image %s."
782
                " This requires a shutdown of the instance. Continue?" %
783
                (instance_name,))
784
    if not AskUser(usertext):
785
      return 1
786

    
787
  op = opcodes.OpInstanceFailover(instance_name=instance_name,
788
                                  ignore_consistency=opts.ignore_consistency,
789
                                  shutdown_timeout=opts.shutdown_timeout,
790
                                  iallocator=iallocator,
791
                                  target_node=target_node)
792
  SubmitOrSend(op, opts, cl=cl)
793
  return 0
794

    
795

    
796
def MigrateInstance(opts, args):
797
  """Migrate an instance.
798

799
  The migrate is done without shutdown.
800

801
  @param opts: the command line options selected by the user
802
  @type args: list
803
  @param args: should contain only one element, the instance name
804
  @rtype: int
805
  @return: the desired exit code
806

807
  """
808
  cl = GetClient()
809
  instance_name = args[0]
810
  force = opts.force
811
  iallocator = opts.iallocator
812
  target_node = opts.dst_node
813

    
814
  if iallocator and target_node:
815
    raise errors.OpPrereqError("Specify either an iallocator (-I), or a target"
816
                               " node (-n) but not both", errors.ECODE_INVAL)
817

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

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

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

    
843
  op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode,
844
                                 cleanup=opts.cleanup, iallocator=iallocator,
845
                                 target_node=target_node,
846
                                 allow_failover=opts.allow_failover)
847
  SubmitOpCode(op, cl=cl, opts=opts)
848
  return 0
849

    
850

    
851
def MoveInstance(opts, args):
852
  """Move an instance.
853

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

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

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

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

    
879

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

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

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

    
892
  cl = GetClient()
893
  try:
894
    cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
895
    ((console_data, oper_state), ) = \
896
      cl.QueryInstances([instance_name], ["console", "oper_state"], False)
897
  finally:
898
    # Ensure client connection is closed while external commands are run
899
    cl.Close()
900

    
901
  del cl
902

    
903
  if not console_data:
904
    if oper_state:
905
      # Instance is running
906
      raise errors.OpExecError("Console information for instance %s is"
907
                               " unavailable" % instance_name)
908
    else:
909
      raise errors.OpExecError("Instance %s is not running, can't get console" %
910
                               instance_name)
911

    
912
  return _DoConsole(objects.InstanceConsole.FromDict(console_data),
913
                    opts.show_command, cluster_name)
914

    
915

    
916
def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout,
917
               _runcmd_fn=utils.RunCmd):
918
  """Acts based on the result of L{opcodes.OpInstanceConsole}.
919

920
  @type console: L{objects.InstanceConsole}
921
  @param console: Console object
922
  @type show_command: bool
923
  @param show_command: Whether to just display commands
924
  @type cluster_name: string
925
  @param cluster_name: Cluster name as retrieved from master daemon
926

927
  """
928
  assert console.Validate()
929

    
930
  if console.kind == constants.CONS_MESSAGE:
931
    feedback_fn(console.message)
932
  elif console.kind == constants.CONS_VNC:
933
    feedback_fn("Instance %s has VNC listening on %s:%s (display %s),"
934
                " URL <vnc://%s:%s/>",
935
                console.instance, console.host, console.port,
936
                console.display, console.host, console.port)
937
  elif console.kind == constants.CONS_SPICE:
938
    feedback_fn("Instance %s has SPICE listening on %s:%s", console.instance,
939
                console.host, console.port)
940
  elif console.kind == constants.CONS_SSH:
941
    # Convert to string if not already one
942
    if isinstance(console.command, basestring):
943
      cmd = console.command
944
    else:
945
      cmd = utils.ShellQuoteArgs(console.command)
946

    
947
    srun = ssh.SshRunner(cluster_name=cluster_name)
948
    ssh_cmd = srun.BuildCmd(console.host, console.user, cmd,
949
                            batch=True, quiet=False, tty=True)
950

    
951
    if show_command:
952
      feedback_fn(utils.ShellQuoteArgs(ssh_cmd))
953
    else:
954
      result = _runcmd_fn(ssh_cmd, interactive=True)
955
      if result.failed:
956
        logging.error("Console command \"%s\" failed with reason '%s' and"
957
                      " output %r", result.cmd, result.fail_reason,
958
                      result.output)
959
        raise errors.OpExecError("Connection to console of instance %s failed,"
960
                                 " please check cluster configuration" %
961
                                 console.instance)
962
  else:
963
    raise errors.GenericError("Unknown console type '%s'" % console.kind)
964

    
965
  return constants.EXIT_SUCCESS
966

    
967

    
968
def _FormatLogicalID(dev_type, logical_id, roman):
969
  """Formats the logical_id of a disk.
970

971
  """
972
  if dev_type == constants.LD_DRBD8:
973
    node_a, node_b, port, minor_a, minor_b, key = logical_id
974
    data = [
975
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
976
                                                            convert=roman))),
977
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
978
                                                            convert=roman))),
979
      ("port", compat.TryToRoman(port, convert=roman)),
980
      ("auth key", key),
981
      ]
982
  elif dev_type == constants.LD_LV:
983
    vg_name, lv_name = logical_id
984
    data = ["%s/%s" % (vg_name, lv_name)]
985
  else:
986
    data = [str(logical_id)]
987

    
988
  return data
989

    
990

    
991
def _FormatBlockDevInfo(idx, top_level, dev, roman):
992
  """Show block device information.
993

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

997
  @type idx: int
998
  @param idx: the index of the current disk
999
  @type top_level: boolean
1000
  @param top_level: if this a top-level disk?
1001
  @type dev: dict
1002
  @param dev: dictionary with disk information
1003
  @type roman: boolean
1004
  @param roman: whether to try to use roman integers
1005
  @return: a list of either strings, tuples or lists
1006
      (which should be formatted at a higher indent level)
1007

1008
  """
1009
  def helper(dtype, status):
1010
    """Format one line for physical device status.
1011

1012
    @type dtype: str
1013
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1014
    @type status: tuple
1015
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1016
    @return: the string representing the status
1017

1018
    """
1019
    if not status:
1020
      return "not active"
1021
    txt = ""
1022
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1023
    if major is None:
1024
      major_string = "N/A"
1025
    else:
1026
      major_string = str(compat.TryToRoman(major, convert=roman))
1027

    
1028
    if minor is None:
1029
      minor_string = "N/A"
1030
    else:
1031
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1032

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

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

    
1091
  if dev["pstatus"]:
1092
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1093

    
1094
  if dev["sstatus"]:
1095
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1096

    
1097
  if dev["children"]:
1098
    data.append("child devices:")
1099
    for c_idx, child in enumerate(dev["children"]):
1100
      data.append(_FormatBlockDevInfo(c_idx, False, child, roman))
1101
  d1.append(data)
1102
  return d1
1103

    
1104

    
1105
def _FormatList(buf, data, indent_level):
1106
  """Formats a list of data at a given indent level.
1107

1108
  If the element of the list is:
1109
    - a string, it is simply formatted as is
1110
    - a tuple, it will be split into key, value and the all the
1111
      values in a list will be aligned all at the same start column
1112
    - a list, will be recursively formatted
1113

1114
  @type buf: StringIO
1115
  @param buf: the buffer into which we write the output
1116
  @param data: the list to format
1117
  @type indent_level: int
1118
  @param indent_level: the indent level to format at
1119

1120
  """
1121
  max_tlen = max([len(elem[0]) for elem in data
1122
                 if isinstance(elem, tuple)] or [0])
1123
  for elem in data:
1124
    if isinstance(elem, basestring):
1125
      buf.write("%*s%s\n" % (2 * indent_level, "", elem))
1126
    elif isinstance(elem, tuple):
1127
      key, value = elem
1128
      spacer = "%*s" % (max_tlen - len(key), "")
1129
      buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value))
1130
    elif isinstance(elem, list):
1131
      _FormatList(buf, elem, indent_level + 1)
1132

    
1133

    
1134
def ShowInstanceConfig(opts, args):
1135
  """Compute instance run-time status.
1136

1137
  @param opts: the command line options selected by the user
1138
  @type args: list
1139
  @param args: either an empty list, and then we query all
1140
      instances, or should contain a list of instance names
1141
  @rtype: int
1142
  @return: the desired exit code
1143

1144
  """
1145
  if not args and not opts.show_all:
1146
    ToStderr("No instance selected."
1147
             " Please pass in --all if you want to query all instances.\n"
1148
             "Note that this can take a long time on a big cluster.")
1149
    return 1
1150
  elif args and opts.show_all:
1151
    ToStderr("Cannot use --all if you specify instance names.")
1152
    return 1
1153

    
1154
  retcode = 0
1155
  op = opcodes.OpInstanceQueryData(instances=args, static=opts.static,
1156
                                   use_locking=not opts.static)
1157
  result = SubmitOpCode(op, opts=opts)
1158
  if not result:
1159
    ToStdout("No instances.")
1160
    return 1
1161

    
1162
  buf = StringIO()
1163
  retcode = 0
1164
  for instance_name in result:
1165
    instance = result[instance_name]
1166
    buf.write("Instance name: %s\n" % instance["name"])
1167
    buf.write("UUID: %s\n" % instance["uuid"])
1168
    buf.write("Serial number: %s\n" %
1169
              compat.TryToRoman(instance["serial_no"],
1170
                                convert=opts.roman_integers))
1171
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1172
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1173
    buf.write("State: configured to be %s" % instance["config_state"])
1174
    if instance["run_state"]:
1175
      buf.write(", actual state is %s" % instance["run_state"])
1176
    buf.write("\n")
1177
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1178
    ##          instance["auto_balance"])
1179
    buf.write("  Nodes:\n")
1180
    buf.write("    - primary: %s\n" % instance["pnode"])
1181
    buf.write("      group: %s (UUID %s)\n" %
1182
              (instance["pnode_group_name"], instance["pnode_group_uuid"]))
1183
    buf.write("    - secondaries: %s\n" %
1184
              utils.CommaJoin("%s (group %s, group UUID %s)" %
1185
                                (name, group_name, group_uuid)
1186
                              for (name, group_name, group_uuid) in
1187
                                zip(instance["snodes"],
1188
                                    instance["snodes_group_names"],
1189
                                    instance["snodes_group_uuids"])))
1190
    buf.write("  Operating system: %s\n" % instance["os"])
1191
    FormatParameterDict(buf, instance["os_instance"], instance["os_actual"],
1192
                        level=2)
1193
    if "network_port" in instance:
1194
      buf.write("  Allocated network port: %s\n" %
1195
                compat.TryToRoman(instance["network_port"],
1196
                                  convert=opts.roman_integers))
1197
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1198

    
1199
    # custom VNC console information
1200
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1201
                                                 None)
1202
    if vnc_bind_address:
1203
      port = instance["network_port"]
1204
      display = int(port) - constants.VNC_BASE_PORT
1205
      if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1206
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1207
                                                   port,
1208
                                                   display)
1209
      elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1210
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1211
                             (vnc_bind_address, port,
1212
                              instance["pnode"], display))
1213
      else:
1214
        # vnc bind address is a file
1215
        vnc_console_port = "%s:%s" % (instance["pnode"],
1216
                                      vnc_bind_address)
1217
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1218

    
1219
    FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"],
1220
                        level=2)
1221
    buf.write("  Hardware:\n")
1222
    buf.write("    - VCPUs: %s\n" %
1223
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1224
                                convert=opts.roman_integers))
1225
    buf.write("    - memory: %sMiB\n" %
1226
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1227
                                convert=opts.roman_integers))
1228
    buf.write("    - NICs:\n")
1229
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1230
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1231
                (idx, mac, ip, mode, link))
1232
    buf.write("  Disk template: %s\n" % instance["disk_template"])
1233
    buf.write("  Disks:\n")
1234

    
1235
    for idx, device in enumerate(instance["disks"]):
1236
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device,
1237
                  opts.roman_integers), 2)
1238

    
1239
  ToStdout(buf.getvalue().rstrip("\n"))
1240
  return retcode
1241

    
1242

    
1243
def SetInstanceParams(opts, args):
1244
  """Modifies an instance.
1245

1246
  All parameters take effect only at the next restart of the instance.
1247

1248
  @param opts: the command line options selected by the user
1249
  @type args: list
1250
  @param args: should contain only one element, the instance name
1251
  @rtype: int
1252
  @return: the desired exit code
1253

1254
  """
1255
  if not (opts.nics or opts.disks or opts.disk_template or
1256
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1257
    ToStderr("Please give at least one of the parameters.")
1258
    return 1
1259

    
1260
  for param in opts.beparams:
1261
    if isinstance(opts.beparams[param], basestring):
1262
      if opts.beparams[param].lower() == "default":
1263
        opts.beparams[param] = constants.VALUE_DEFAULT
1264

    
1265
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1266
                      allowed_values=[constants.VALUE_DEFAULT])
1267

    
1268
  for param in opts.hvparams:
1269
    if isinstance(opts.hvparams[param], basestring):
1270
      if opts.hvparams[param].lower() == "default":
1271
        opts.hvparams[param] = constants.VALUE_DEFAULT
1272

    
1273
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1274
                      allowed_values=[constants.VALUE_DEFAULT])
1275

    
1276
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1277
    try:
1278
      nic_op = int(nic_op)
1279
      opts.nics[idx] = (nic_op, nic_dict)
1280
    except (TypeError, ValueError):
1281
      pass
1282

    
1283
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1284
    try:
1285
      disk_op = int(disk_op)
1286
      opts.disks[idx] = (disk_op, disk_dict)
1287
    except (TypeError, ValueError):
1288
      pass
1289
    if disk_op == constants.DDM_ADD:
1290
      if "size" not in disk_dict:
1291
        raise errors.OpPrereqError("Missing required parameter 'size'",
1292
                                   errors.ECODE_INVAL)
1293
      disk_dict["size"] = utils.ParseUnit(disk_dict["size"])
1294

    
1295
  if (opts.disk_template and
1296
      opts.disk_template in constants.DTS_INT_MIRROR and
1297
      not opts.node):
1298
    ToStderr("Changing the disk template to a mirrored one requires"
1299
             " specifying a secondary node")
1300
    return 1
1301

    
1302
  op = opcodes.OpInstanceSetParams(instance_name=args[0],
1303
                                   nics=opts.nics,
1304
                                   disks=opts.disks,
1305
                                   disk_template=opts.disk_template,
1306
                                   remote_node=opts.node,
1307
                                   hvparams=opts.hvparams,
1308
                                   beparams=opts.beparams,
1309
                                   os_name=opts.os,
1310
                                   osparams=opts.osparams,
1311
                                   force_variant=opts.force_variant,
1312
                                   force=opts.force,
1313
                                   wait_for_sync=opts.wait_for_sync)
1314

    
1315
  # even if here we process the result, we allow submit only
1316
  result = SubmitOrSend(op, opts)
1317

    
1318
  if result:
1319
    ToStdout("Modified instance %s", args[0])
1320
    for param, data in result:
1321
      ToStdout(" - %-5s -> %s", param, data)
1322
    ToStdout("Please don't forget that most parameters take effect"
1323
             " only at the next start of the instance.")
1324
  return 0
1325

    
1326

    
1327
def ChangeGroup(opts, args):
1328
  """Moves an instance to another group.
1329

1330
  """
1331
  (instance_name, ) = args
1332

    
1333
  cl = GetClient()
1334

    
1335
  op = opcodes.OpInstanceChangeGroup(instance_name=instance_name,
1336
                                     iallocator=opts.iallocator,
1337
                                     target_groups=opts.to,
1338
                                     early_release=opts.early_release)
1339
  result = SubmitOpCode(op, cl=cl, opts=opts)
1340

    
1341
  # Keep track of submitted jobs
1342
  jex = JobExecutor(cl=cl, opts=opts)
1343

    
1344
  for (status, job_id) in result[constants.JOB_IDS_KEY]:
1345
    jex.AddJobId(None, status, job_id)
1346

    
1347
  results = jex.GetResults()
1348
  bad_cnt = len([row for row in results if not row[0]])
1349
  if bad_cnt == 0:
1350
    ToStdout("Instance '%s' changed group successfully.", instance_name)
1351
    rcode = constants.EXIT_SUCCESS
1352
  else:
1353
    ToStdout("There were %s errors while changing group of instance '%s'.",
1354
             bad_cnt, instance_name)
1355
    rcode = constants.EXIT_FAILURE
1356

    
1357
  return rcode
1358

    
1359

    
1360
# multi-instance selection options
1361
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1362
                           help="Do not ask for confirmation when more than"
1363
                           " one instance is affected",
1364
                           action="store_true", default=False)
1365

    
1366
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1367
                            help="Filter by nodes (primary only)",
1368
                            const=_EXPAND_NODES_PRI, action="store_const")
1369

    
1370
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1371
                            help="Filter by nodes (secondary only)",
1372
                            const=_EXPAND_NODES_SEC, action="store_const")
1373

    
1374
m_node_opt = cli_option("--node", dest="multi_mode",
1375
                        help="Filter by nodes (primary and secondary)",
1376
                        const=_EXPAND_NODES_BOTH, action="store_const")
1377

    
1378
m_clust_opt = cli_option("--all", dest="multi_mode",
1379
                         help="Select all instances in the cluster",
1380
                         const=_EXPAND_CLUSTER, action="store_const")
1381

    
1382
m_inst_opt = cli_option("--instance", dest="multi_mode",
1383
                        help="Filter by instance name [default]",
1384
                        const=_EXPAND_INSTANCES, action="store_const")
1385

    
1386
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1387
                             help="Filter by node tag",
1388
                             const=_EXPAND_NODES_BOTH_BY_TAGS,
1389
                             action="store_const")
1390

    
1391
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1392
                                 help="Filter by primary node tag",
1393
                                 const=_EXPAND_NODES_PRI_BY_TAGS,
1394
                                 action="store_const")
1395

    
1396
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1397
                                 help="Filter by secondary node tag",
1398
                                 const=_EXPAND_NODES_SEC_BY_TAGS,
1399
                                 action="store_const")
1400

    
1401
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1402
                             help="Filter by instance tag",
1403
                             const=_EXPAND_INSTANCES_BY_TAGS,
1404
                             action="store_const")
1405

    
1406
# this is defined separately due to readability only
1407
add_opts = [
1408
  NOSTART_OPT,
1409
  OS_OPT,
1410
  FORCE_VARIANT_OPT,
1411
  NO_INSTALL_OPT,
1412
  ]
1413

    
1414
commands = {
1415
  "add": (
1416
    AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1417
    "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1418
    "Creates and adds a new instance to the cluster"),
1419
  "batch-create": (
1420
    BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1421
    "<instances.json>",
1422
    "Create a bunch of instances based on specs in the file."),
1423
  "console": (
1424
    ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1425
    [SHOWCMD_OPT, PRIORITY_OPT],
1426
    "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1427
  "failover": (
1428
    FailoverInstance, ARGS_ONE_INSTANCE,
1429
    [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1430
     DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT],
1431
    "[-f] <instance>", "Stops the instance, changes its primary node and"
1432
    " (if it was originally running) starts it on the new node"
1433
    " (the secondary for mirrored instances or any node"
1434
    " for shared storage)."),
1435
  "migrate": (
1436
    MigrateInstance, ARGS_ONE_INSTANCE,
1437
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1438
     PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT],
1439
    "[-f] <instance>", "Migrate instance to its secondary node"
1440
    " (only for mirrored instances)"),
1441
  "move": (
1442
    MoveInstance, ARGS_ONE_INSTANCE,
1443
    [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1444
     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT],
1445
    "[-f] <instance>", "Move instance to an arbitrary node"
1446
    " (only for instances of type file and lv)"),
1447
  "info": (
1448
    ShowInstanceConfig, ARGS_MANY_INSTANCES,
1449
    [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1450
    "[-s] {--all | <instance>...}",
1451
    "Show information on the specified instance(s)"),
1452
  "list": (
1453
    ListInstances, ARGS_MANY_INSTANCES,
1454
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT,
1455
     FORCE_FILTER_OPT],
1456
    "[<instance>...]",
1457
    "Lists the instances and their status. The available fields can be shown"
1458
    " using the \"list-fields\" command (see the man page for details)."
1459
    " The default field list is (in order): %s." %
1460
    utils.CommaJoin(_LIST_DEF_FIELDS),
1461
    ),
1462
  "list-fields": (
1463
    ListInstanceFields, [ArgUnknown()],
1464
    [NOHDR_OPT, SEP_OPT],
1465
    "[fields...]",
1466
    "Lists all available fields for instances"),
1467
  "reinstall": (
1468
    ReinstallInstance, [ArgInstance()],
1469
    [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1470
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1471
     m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1472
     SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1473
    "[-f] <instance>", "Reinstall a stopped instance"),
1474
  "remove": (
1475
    RemoveInstance, ARGS_ONE_INSTANCE,
1476
    [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1477
     DRY_RUN_OPT, PRIORITY_OPT],
1478
    "[-f] <instance>", "Shuts down the instance and removes it"),
1479
  "rename": (
1480
    RenameInstance,
1481
    [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1482
    [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1483
    "<instance> <new_name>", "Rename the instance"),
1484
  "replace-disks": (
1485
    ReplaceDisks, ARGS_ONE_INSTANCE,
1486
    [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1487
     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1488
     DRY_RUN_OPT, PRIORITY_OPT],
1489
    "[-s|-p|-n NODE|-I NAME] <instance>",
1490
    "Replaces all disks for the instance"),
1491
  "modify": (
1492
    SetInstanceParams, ARGS_ONE_INSTANCE,
1493
    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1494
     DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1495
     OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT],
1496
    "<instance>", "Alters the parameters of an instance"),
1497
  "shutdown": (
1498
    GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1499
    [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1500
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1501
     m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1502
     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT],
1503
    "<instance>", "Stops an instance"),
1504
  "startup": (
1505
    GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1506
    [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1507
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1508
     m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1509
     BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT,
1510
     NO_REMEMBER_OPT, STARTUP_PAUSED_OPT],
1511
    "<instance>", "Starts an instance"),
1512
  "reboot": (
1513
    GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1514
    [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1515
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1516
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1517
     m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1518
    "<instance>", "Reboots an instance"),
1519
  "activate-disks": (
1520
    ActivateDisks, ARGS_ONE_INSTANCE,
1521
    [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1522
    "<instance>", "Activate an instance's disks"),
1523
  "deactivate-disks": (
1524
    DeactivateDisks, ARGS_ONE_INSTANCE,
1525
    [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1526
    "[-f] <instance>", "Deactivate an instance's disks"),
1527
  "recreate-disks": (
1528
    RecreateDisks, ARGS_ONE_INSTANCE,
1529
    [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1530
    "<instance>", "Recreate an instance's disks"),
1531
  "grow-disk": (
1532
    GrowDisk,
1533
    [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1534
     ArgUnknown(min=1, max=1)],
1535
    [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1536
    "<instance> <disk> <size>", "Grow an instance's disk"),
1537
  "change-group": (
1538
    ChangeGroup, ARGS_ONE_INSTANCE,
1539
    [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT],
1540
    "[-I <iallocator>] [--to <group>]", "Change group of instance"),
1541
  "list-tags": (
1542
    ListTags, ARGS_ONE_INSTANCE, [],
1543
    "<instance_name>", "List the tags of the given instance"),
1544
  "add-tags": (
1545
    AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1546
    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1547
    "<instance_name> tag...", "Add tags to the given instance"),
1548
  "remove-tags": (
1549
    RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1550
    [TAG_SRC_OPT, PRIORITY_OPT, SUBMIT_OPT],
1551
    "<instance_name> tag...", "Remove tags from given instance"),
1552
  }
1553

    
1554
#: dictionary with aliases for commands
1555
aliases = {
1556
  "start": "startup",
1557
  "stop": "shutdown",
1558
  }
1559

    
1560

    
1561
def Main():
1562
  return GenericMain(commands, aliases=aliases,
1563
                     override={"tag_type": constants.TAG_INSTANCE})