Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ 65cb5c4d

History | View | Annotate | Download (53.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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
from cStringIO import StringIO
31

    
32
from ganeti.cli import *
33
from ganeti import opcodes
34
from ganeti import constants
35
from ganeti import compat
36
from ganeti import utils
37
from ganeti import errors
38
from ganeti import netutils
39

    
40

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

    
51
_SHUTDOWN_NODES_TAGS_MODES = (
52
    _SHUTDOWN_NODES_BOTH_BY_TAGS,
53
    _SHUTDOWN_NODES_PRI_BY_TAGS,
54
    _SHUTDOWN_NODES_SEC_BY_TAGS)
55

    
56

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

    
62

    
63
def _ExpandMultiNames(mode, names, client=None):
64
  """Expand the given names using the passed mode.
65

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

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

85
  """
86
  # pylint: disable-msg=W0142
87

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

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

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

    
139
  return inames
140

    
141

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

145
  This function is used to request confirmation for doing an operation
146
  on a given list of instances.
147

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

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

    
164
  choices = [('y', True, 'Yes, execute the %s' % text),
165
             ('n', False, 'No, abort the %s' % text)]
166

    
167
  if count > 20:
168
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
169
    ask = msg
170
  else:
171
    ask = msg + affected
172

    
173
  choice = AskUser(ask, choices)
174
  if choice == 'v':
175
    choices.pop(1)
176
    choice = AskUser(msg + affected, choices)
177
  return choice
178

    
179

    
180
def _EnsureInstancesExist(client, names):
181
  """Check for and ensure the given instance names exist.
182

183
  This function will raise an OpPrereqError in case they don't
184
  exist. Otherwise it will exit cleanly.
185

186
  @type client: L{ganeti.luxi.Client}
187
  @param client: the client to use for the query
188
  @type names: list
189
  @param names: the list of instance names to query
190
  @raise errors.OpPrereqError: in case any instance is missing
191

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

    
201

    
202
def GenericManyOps(operation, fn):
203
  """Generic multi-instance operations.
204

205
  The will return a wrapper that processes the options and arguments
206
  given, and uses the passed function to build the opcode needed for
207
  the specific operation. Thus all the generic loop/confirmation code
208
  is abstracted into this function.
209

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

    
232

    
233
def ListInstances(opts, args):
234
  """List instances and their properties.
235

236
  @param opts: the command line options selected by the user
237
  @type args: list
238
  @param args: should be an empty list
239
  @rtype: int
240
  @return: the desired exit code
241

242
  """
243
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
244

    
245
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
246

    
247
  if not opts.no_headers:
248
    headers = {
249
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
250
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
251
      "oper_state": "Running",
252
      "oper_ram": "Memory", "disk_template": "Disk_template",
253
      "oper_vcpus": "VCPUs",
254
      "ip": "IP_address", "mac": "MAC_address",
255
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
256
      "bridge": "Bridge",
257
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
258
      "disk_usage": "DiskUsage",
259
      "status": "Status", "tags": "Tags",
260
      "network_port": "Network_port",
261
      "hv/kernel_path": "Kernel_path",
262
      "hv/initrd_path": "Initrd_path",
263
      "hv/boot_order": "Boot_order",
264
      "hv/acpi": "ACPI",
265
      "hv/pae": "PAE",
266
      "hv/cdrom_image_path": "CDROM_image_path",
267
      "hv/nic_type": "NIC_type",
268
      "hv/disk_type": "Disk_type",
269
      "hv/vnc_bind_address": "VNC_bind_address",
270
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
271
      "hvparams": "Hypervisor_parameters",
272
      "be/memory": "Configured_memory",
273
      "be/vcpus": "VCPUs",
274
      "vcpus": "VCPUs",
275
      "be/auto_balance": "Auto_balance",
276
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
277
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
278
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
279
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
280
      "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
281
      }
282
  else:
283
    headers = None
284

    
285
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
286
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
287
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
288

    
289
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
290
                      "nic.modes", "nic.links", "nic.bridges")
291
  # change raw values to nicer strings
292
  for row in output:
293
    for idx, field in enumerate(selected_fields):
294
      val = row[idx]
295
      if field == "snodes":
296
        val = ",".join(val) or "-"
297
      elif field == "admin_state":
298
        if val:
299
          val = "yes"
300
        else:
301
          val = "no"
302
      elif field == "oper_state":
303
        if val is None:
304
          val = "(node down)"
305
        elif val: # True
306
          val = "running"
307
        else:
308
          val = "stopped"
309
      elif field == "oper_ram":
310
        if val is None:
311
          val = "(node down)"
312
      elif field == "oper_vcpus":
313
        if val is None:
314
          val = "(node down)"
315
      elif field == "sda_size" or field == "sdb_size":
316
        if val is None:
317
          val = "N/A"
318
      elif field == "ctime" or field == "mtime":
319
        val = utils.FormatTime(val)
320
      elif field in list_type_fields:
321
        val = ",".join(str(item) for item in val)
322
      elif val is None:
323
        val = "-"
324
      if opts.roman_integers and isinstance(val, int):
325
        val = compat.TryToRoman(val)
326
      row[idx] = str(val)
327

    
328
  data = GenerateTable(separator=opts.separator, headers=headers,
329
                       fields=selected_fields, unitfields=unitfields,
330
                       numfields=numfields, data=output, units=opts.units)
331

    
332
  for line in data:
333
    ToStdout(line)
334

    
335
  return 0
336

    
337

    
338
def AddInstance(opts, args):
339
  """Add an instance to the cluster.
340

341
  This is just a wrapper over GenericInstanceCreate.
342

343
  """
344
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
345

    
346

    
347
def BatchCreate(opts, args):
348
  """Create instances using a definition file.
349

350
  This function reads a json file with instances defined
351
  in the form::
352

353
    {"instance-name":{
354
      "disk_size": [20480],
355
      "template": "drbd",
356
      "backend": {
357
        "memory": 512,
358
        "vcpus": 1 },
359
      "os": "debootstrap",
360
      "primary_node": "firstnode",
361
      "secondary_node": "secondnode",
362
      "iallocator": "dumb"}
363
    }
364

365
  Note that I{primary_node} and I{secondary_node} have precedence over
366
  I{iallocator}.
367

368
  @param opts: the command line options selected by the user
369
  @type args: list
370
  @param args: should contain one element, the json filename
371
  @rtype: int
372
  @return: the desired exit code
373

374
  """
375
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
376
                    "backend": {},
377
                    "iallocator": None,
378
                    "primary_node": None,
379
                    "secondary_node": None,
380
                    "nics": None,
381
                    "start": True,
382
                    "ip_check": True,
383
                    "name_check": True,
384
                    "hypervisor": None,
385
                    "hvparams": {},
386
                    "file_storage_dir": None,
387
                    "force_variant": False,
388
                    "file_driver": 'loop'}
389

    
390
  def _PopulateWithDefaults(spec):
391
    """Returns a new hash combined with default values."""
392
    mydict = _DEFAULT_SPECS.copy()
393
    mydict.update(spec)
394
    return mydict
395

    
396
  def _Validate(spec):
397
    """Validate the instance specs."""
398
    # Validate fields required under any circumstances
399
    for required_field in ('os', 'template'):
400
      if required_field not in spec:
401
        raise errors.OpPrereqError('Required field "%s" is missing.' %
402
                                   required_field, errors.ECODE_INVAL)
403
    # Validate special fields
404
    if spec['primary_node'] is not None:
405
      if (spec['template'] in constants.DTS_NET_MIRROR and
406
          spec['secondary_node'] is None):
407
        raise errors.OpPrereqError('Template requires secondary node, but'
408
                                   ' there was no secondary provided.',
409
                                   errors.ECODE_INVAL)
410
    elif spec['iallocator'] is None:
411
      raise errors.OpPrereqError('You have to provide at least a primary_node'
412
                                 ' or an iallocator.',
413
                                 errors.ECODE_INVAL)
414

    
415
    if (spec['hvparams'] and
416
        not isinstance(spec['hvparams'], dict)):
417
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
418
                                 errors.ECODE_INVAL)
419

    
420
  json_filename = args[0]
421
  try:
422
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
423
  except Exception, err: # pylint: disable-msg=W0703
424
    ToStderr("Can't parse the instance definition file: %s" % str(err))
425
    return 1
426

    
427
  if not isinstance(instance_data, dict):
428
    ToStderr("The instance definition file is not in dict format.")
429
    return 1
430

    
431
  jex = JobExecutor(opts=opts)
432

    
433
  # Iterate over the instances and do:
434
  #  * Populate the specs with default value
435
  #  * Validate the instance specs
436
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
437
  for name in i_names:
438
    specs = instance_data[name]
439
    specs = _PopulateWithDefaults(specs)
440
    _Validate(specs)
441

    
442
    hypervisor = specs['hypervisor']
443
    hvparams = specs['hvparams']
444

    
445
    disks = []
446
    for elem in specs['disk_size']:
447
      try:
448
        size = utils.ParseUnit(elem)
449
      except (TypeError, ValueError), err:
450
        raise errors.OpPrereqError("Invalid disk size '%s' for"
451
                                   " instance %s: %s" %
452
                                   (elem, name, err), errors.ECODE_INVAL)
453
      disks.append({"size": size})
454

    
455
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
456
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
457

    
458
    tmp_nics = []
459
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
460
      if field in specs:
461
        if not tmp_nics:
462
          tmp_nics.append({})
463
        tmp_nics[0][field] = specs[field]
464

    
465
    if specs['nics'] is not None and tmp_nics:
466
      raise errors.OpPrereqError("'nics' list incompatible with using"
467
                                 " individual nic fields as well",
468
                                 errors.ECODE_INVAL)
469
    elif specs['nics'] is not None:
470
      tmp_nics = specs['nics']
471
    elif not tmp_nics:
472
      tmp_nics = [{}]
473

    
474
    op = opcodes.OpCreateInstance(instance_name=name,
475
                                  disks=disks,
476
                                  disk_template=specs['template'],
477
                                  mode=constants.INSTANCE_CREATE,
478
                                  os_type=specs['os'],
479
                                  force_variant=specs["force_variant"],
480
                                  pnode=specs['primary_node'],
481
                                  snode=specs['secondary_node'],
482
                                  nics=tmp_nics,
483
                                  start=specs['start'],
484
                                  ip_check=specs['ip_check'],
485
                                  name_check=specs['name_check'],
486
                                  wait_for_sync=True,
487
                                  iallocator=specs['iallocator'],
488
                                  hypervisor=hypervisor,
489
                                  hvparams=hvparams,
490
                                  beparams=specs['backend'],
491
                                  file_storage_dir=specs['file_storage_dir'],
492
                                  file_driver=specs['file_driver'])
493

    
494
    jex.QueueJob(name, op)
495
  # we never want to wait, just show the submitted job IDs
496
  jex.WaitOrShow(False)
497

    
498
  return 0
499

    
500

    
501
def ReinstallInstance(opts, args):
502
  """Reinstall an instance.
503

504
  @param opts: the command line options selected by the user
505
  @type args: list
506
  @param args: should contain only one element, the name of the
507
      instance to be reinstalled
508
  @rtype: int
509
  @return: the desired exit code
510

511
  """
512
  # first, compute the desired name list
513
  if opts.multi_mode is None:
514
    opts.multi_mode = _SHUTDOWN_INSTANCES
515

    
516
  inames = _ExpandMultiNames(opts.multi_mode, args)
517
  if not inames:
518
    raise errors.OpPrereqError("Selection filter does not match any instances",
519
                               errors.ECODE_INVAL)
520

    
521
  # second, if requested, ask for an OS
522
  if opts.select_os is True:
523
    op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
524
    result = SubmitOpCode(op, opts=opts)
525

    
526
    if not result:
527
      ToStdout("Can't get the OS list")
528
      return 1
529

    
530
    ToStdout("Available OS templates:")
531
    number = 0
532
    choices = []
533
    for (name, variants) in result:
534
      for entry in CalculateOSNames(name, variants):
535
        ToStdout("%3s: %s", number, entry)
536
        choices.append(("%s" % number, entry, entry))
537
        number += 1
538

    
539
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
540
    selected = AskUser("Enter OS template number (or x to abort):",
541
                       choices)
542

    
543
    if selected == 'exit':
544
      ToStderr("User aborted reinstall, exiting")
545
      return 1
546

    
547
    os_name = selected
548
  else:
549
    os_name = opts.os
550

    
551
  # third, get confirmation: multi-reinstall requires --force-multi,
552
  # single-reinstall either --force or --force-multi (--force-multi is
553
  # a stronger --force)
554
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
555
  if multi_on:
556
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
557
    if not (opts.force_multi or
558
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
559
      return 1
560
  else:
561
    if not (opts.force or opts.force_multi):
562
      usertext = ("This will reinstall the instance %s and remove"
563
                  " all data. Continue?") % inames[0]
564
      if not AskUser(usertext):
565
        return 1
566

    
567
  jex = JobExecutor(verbose=multi_on, opts=opts)
568
  for instance_name in inames:
569
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
570
                                     os_type=os_name,
571
                                     force_variant=opts.force_variant,
572
                                     osparams=opts.osparams)
573
    jex.QueueJob(instance_name, op)
574

    
575
  jex.WaitOrShow(not opts.submit_only)
576
  return 0
577

    
578

    
579
def RemoveInstance(opts, args):
580
  """Remove an instance.
581

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

589
  """
590
  instance_name = args[0]
591
  force = opts.force
592
  cl = GetClient()
593

    
594
  if not force:
595
    _EnsureInstancesExist(cl, [instance_name])
596

    
597
    usertext = ("This will remove the volumes of the instance %s"
598
                " (including mirrors), thus removing all the data"
599
                " of the instance. Continue?") % instance_name
600
    if not AskUser(usertext):
601
      return 1
602

    
603
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
604
                                ignore_failures=opts.ignore_failures,
605
                                shutdown_timeout=opts.shutdown_timeout)
606
  SubmitOrSend(op, opts, cl=cl)
607
  return 0
608

    
609

    
610
def RenameInstance(opts, args):
611
  """Rename an instance.
612

613
  @param opts: the command line options selected by the user
614
  @type args: list
615
  @param args: should contain two elements, the old and the
616
      new instance names
617
  @rtype: int
618
  @return: the desired exit code
619

620
  """
621
  if not opts.name_check:
622
    if not AskUser("As you disabled the check of the DNS entry, please verify"
623
                   " that '%s' is a FQDN. Continue?" % args[1]):
624
      return 1
625

    
626
  op = opcodes.OpRenameInstance(instance_name=args[0],
627
                                new_name=args[1],
628
                                ip_check=opts.ip_check,
629
                                name_check=opts.name_check)
630
  result = SubmitOrSend(op, opts)
631

    
632
  if result:
633
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
634

    
635
  return 0
636

    
637

    
638
def ActivateDisks(opts, args):
639
  """Activate an instance's disks.
640

641
  This serves two purposes:
642
    - it allows (as long as the instance is not running)
643
      mounting the disks and modifying them from the node
644
    - it repairs inactive secondary drbds
645

646
  @param opts: the command line options selected by the user
647
  @type args: list
648
  @param args: should contain only one element, the instance name
649
  @rtype: int
650
  @return: the desired exit code
651

652
  """
653
  instance_name = args[0]
654
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
655
                                       ignore_size=opts.ignore_size)
656
  disks_info = SubmitOrSend(op, opts)
657
  for host, iname, nname in disks_info:
658
    ToStdout("%s:%s:%s", host, iname, nname)
659
  return 0
660

    
661

    
662
def DeactivateDisks(opts, args):
663
  """Deactivate an instance's disks.
664

665
  This function takes the instance name, looks for its primary node
666
  and the tries to shutdown its block devices on that node.
667

668
  @param opts: the command line options selected by the user
669
  @type args: list
670
  @param args: should contain only one element, the instance name
671
  @rtype: int
672
  @return: the desired exit code
673

674
  """
675
  instance_name = args[0]
676
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
677
  SubmitOrSend(op, opts)
678
  return 0
679

    
680

    
681
def RecreateDisks(opts, args):
682
  """Recreate an instance's disks.
683

684
  @param opts: the command line options selected by the user
685
  @type args: list
686
  @param args: should contain only one element, the instance name
687
  @rtype: int
688
  @return: the desired exit code
689

690
  """
691
  instance_name = args[0]
692
  if opts.disks:
693
    try:
694
      opts.disks = [int(v) for v in opts.disks.split(",")]
695
    except (ValueError, TypeError), err:
696
      ToStderr("Invalid disks value: %s" % str(err))
697
      return 1
698
  else:
699
    opts.disks = []
700

    
701
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
702
                                       disks=opts.disks)
703
  SubmitOrSend(op, opts)
704
  return 0
705

    
706

    
707
def GrowDisk(opts, args):
708
  """Grow an instance's disks.
709

710
  @param opts: the command line options selected by the user
711
  @type args: list
712
  @param args: should contain two elements, the instance name
713
      whose disks we grow and the disk name, e.g. I{sda}
714
  @rtype: int
715
  @return: the desired exit code
716

717
  """
718
  instance = args[0]
719
  disk = args[1]
720
  try:
721
    disk = int(disk)
722
  except (TypeError, ValueError), err:
723
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
724
                               errors.ECODE_INVAL)
725
  amount = utils.ParseUnit(args[2])
726
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
727
                          wait_for_sync=opts.wait_for_sync)
728
  SubmitOrSend(op, opts)
729
  return 0
730

    
731

    
732
def _StartupInstance(name, opts):
733
  """Startup instances.
734

735
  This returns the opcode to start an instance, and its decorator will
736
  wrap this into a loop starting all desired instances.
737

738
  @param name: the name of the instance to act on
739
  @param opts: the command line options selected by the user
740
  @return: the opcode needed for the operation
741

742
  """
743
  op = opcodes.OpStartupInstance(instance_name=name,
744
                                 force=opts.force,
745
                                 ignore_offline_nodes=opts.ignore_offline)
746
  # do not add these parameters to the opcode unless they're defined
747
  if opts.hvparams:
748
    op.hvparams = opts.hvparams
749
  if opts.beparams:
750
    op.beparams = opts.beparams
751
  return op
752

    
753

    
754
def _RebootInstance(name, opts):
755
  """Reboot instance(s).
756

757
  This returns the opcode to reboot an instance, and its decorator
758
  will wrap this into a loop rebooting all desired instances.
759

760
  @param name: the name of the instance to act on
761
  @param opts: the command line options selected by the user
762
  @return: the opcode needed for the operation
763

764
  """
765
  return opcodes.OpRebootInstance(instance_name=name,
766
                                  reboot_type=opts.reboot_type,
767
                                  ignore_secondaries=opts.ignore_secondaries,
768
                                  shutdown_timeout=opts.shutdown_timeout)
769

    
770

    
771
def _ShutdownInstance(name, opts):
772
  """Shutdown an instance.
773

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

777
  @param name: the name of the instance to act on
778
  @param opts: the command line options selected by the user
779
  @return: the opcode needed for the operation
780

781
  """
782
  return opcodes.OpShutdownInstance(instance_name=name,
783
                                    timeout=opts.timeout,
784
                                    ignore_offline_nodes=opts.ignore_offline)
785

    
786

    
787
def ReplaceDisks(opts, args):
788
  """Replace the disks of an instance
789

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

796
  """
797
  new_2ndary = opts.dst_node
798
  iallocator = opts.iallocator
799
  if opts.disks is None:
800
    disks = []
801
  else:
802
    try:
803
      disks = [int(i) for i in opts.disks.split(",")]
804
    except (TypeError, ValueError), err:
805
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
806
                                 errors.ECODE_INVAL)
807
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
808
         new_2ndary is not None, iallocator is not None].count(True)
809
  if cnt != 1:
810
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
811
                               " options must be passed", errors.ECODE_INVAL)
812
  elif opts.on_primary:
813
    mode = constants.REPLACE_DISK_PRI
814
  elif opts.on_secondary:
815
    mode = constants.REPLACE_DISK_SEC
816
  elif opts.auto:
817
    mode = constants.REPLACE_DISK_AUTO
818
    if disks:
819
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
820
                                 " mode", errors.ECODE_INVAL)
821
  elif new_2ndary is not None or iallocator is not None:
822
    # replace secondary
823
    mode = constants.REPLACE_DISK_CHG
824

    
825
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
826
                              remote_node=new_2ndary, mode=mode,
827
                              iallocator=iallocator,
828
                              early_release=opts.early_release)
829
  SubmitOrSend(op, opts)
830
  return 0
831

    
832

    
833
def FailoverInstance(opts, args):
834
  """Failover an instance.
835

836
  The failover is done by shutting it down on its present node and
837
  starting it on the secondary.
838

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

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

    
850
  if not force:
851
    _EnsureInstancesExist(cl, [instance_name])
852

    
853
    usertext = ("Failover will happen to image %s."
854
                " This requires a shutdown of the instance. Continue?" %
855
                (instance_name,))
856
    if not AskUser(usertext):
857
      return 1
858

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

    
865

    
866
def MigrateInstance(opts, args):
867
  """Migrate an instance.
868

869
  The migrate is done without shutdown.
870

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

877
  """
878
  cl = GetClient()
879
  instance_name = args[0]
880
  force = opts.force
881

    
882
  if not force:
883
    _EnsureInstancesExist(cl, [instance_name])
884

    
885
    if opts.cleanup:
886
      usertext = ("Instance %s will be recovered from a failed migration."
887
                  " Note that the migration procedure (including cleanup)" %
888
                  (instance_name,))
889
    else:
890
      usertext = ("Instance %s will be migrated. Note that migration" %
891
                  (instance_name,))
892
    usertext += (" might impact the instance if anything goes wrong"
893
                 " (e.g. due to bugs in the hypervisor). Continue?")
894
    if not AskUser(usertext):
895
      return 1
896

    
897
  # this should be removed once --non-live is deprecated
898
  if not opts.live and opts.migration_mode is not None:
899
    raise errors.OpPrereqError("Only one of the --non-live and "
900
                               "--migration-mode options can be passed",
901
                               errors.ECODE_INVAL)
902
  if not opts.live: # --non-live passed
903
    mode = constants.HT_MIGRATION_NONLIVE
904
  else:
905
    mode = opts.migration_mode
906

    
907
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
908
                                 cleanup=opts.cleanup)
909
  SubmitOpCode(op, cl=cl, opts=opts)
910
  return 0
911

    
912

    
913
def MoveInstance(opts, args):
914
  """Move an instance.
915

916
  @param opts: the command line options selected by the user
917
  @type args: list
918
  @param args: should contain only one element, the instance name
919
  @rtype: int
920
  @return: the desired exit code
921

922
  """
923
  cl = GetClient()
924
  instance_name = args[0]
925
  force = opts.force
926

    
927
  if not force:
928
    usertext = ("Instance %s will be moved."
929
                " This requires a shutdown of the instance. Continue?" %
930
                (instance_name,))
931
    if not AskUser(usertext):
932
      return 1
933

    
934
  op = opcodes.OpMoveInstance(instance_name=instance_name,
935
                              target_node=opts.node,
936
                              shutdown_timeout=opts.shutdown_timeout)
937
  SubmitOrSend(op, opts, cl=cl)
938
  return 0
939

    
940

    
941
def ConnectToInstanceConsole(opts, args):
942
  """Connect to the console of an instance.
943

944
  @param opts: the command line options selected by the user
945
  @type args: list
946
  @param args: should contain only one element, the instance name
947
  @rtype: int
948
  @return: the desired exit code
949

950
  """
951
  instance_name = args[0]
952

    
953
  op = opcodes.OpConnectConsole(instance_name=instance_name)
954
  cmd = SubmitOpCode(op, opts=opts)
955

    
956
  if opts.show_command:
957
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
958
  else:
959
    result = utils.RunCmd(cmd, interactive=True)
960
    if result.failed:
961
      raise errors.OpExecError("Console command \"%s\" failed: %s" %
962
                               (utils.ShellQuoteArgs(cmd), result.fail_reason))
963

    
964
  return constants.EXIT_SUCCESS
965

    
966

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

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

    
987
  return data
988

    
989

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

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

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

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

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

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

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

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

    
1064
  # the header
1065
  if top_level:
1066
    if dev["iv_name"] is not None:
1067
      txt = dev["iv_name"]
1068
    else:
1069
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1070
  else:
1071
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1072
  if isinstance(dev["size"], int):
1073
    nice_size = utils.FormatUnit(dev["size"], "h")
1074
  else:
1075
    nice_size = dev["size"]
1076
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1077
  data = []
1078
  if top_level:
1079
    data.append(("access mode", dev["mode"]))
1080
  if dev["logical_id"] is not None:
1081
    try:
1082
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1083
    except ValueError:
1084
      l_id = [str(dev["logical_id"])]
1085
    if len(l_id) == 1:
1086
      data.append(("logical_id", l_id[0]))
1087
    else:
1088
      data.extend(l_id)
1089
  elif dev["physical_id"] is not None:
1090
    data.append("physical_id:")
1091
    data.append([dev["physical_id"]])
1092
  if not static:
1093
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1094
  if dev["sstatus"] and not static:
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, static, 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 _FormatParameterDict(buf, per_inst, actual):
1135
  """Formats a parameter dictionary.
1136

1137
  @type buf: L{StringIO}
1138
  @param buf: the buffer into which to write
1139
  @type per_inst: dict
1140
  @param per_inst: the instance's own parameters
1141
  @type actual: dict
1142
  @param actual: the current parameter set (including defaults)
1143

1144
  """
1145
  for key in sorted(actual):
1146
    val = per_inst.get(key, "default (%s)" % actual[key])
1147
    buf.write("    - %s: %s\n" % (key, val))
1148

    
1149

    
1150
def ShowInstanceConfig(opts, args):
1151
  """Compute instance run-time status.
1152

1153
  @param opts: the command line options selected by the user
1154
  @type args: list
1155
  @param args: either an empty list, and then we query all
1156
      instances, or should contain a list of instance names
1157
  @rtype: int
1158
  @return: the desired exit code
1159

1160
  """
1161
  if not args and not opts.show_all:
1162
    ToStderr("No instance selected."
1163
             " Please pass in --all if you want to query all instances.\n"
1164
             "Note that this can take a long time on a big cluster.")
1165
    return 1
1166
  elif args and opts.show_all:
1167
    ToStderr("Cannot use --all if you specify instance names.")
1168
    return 1
1169

    
1170
  retcode = 0
1171
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1172
  result = SubmitOpCode(op, opts=opts)
1173
  if not result:
1174
    ToStdout("No instances.")
1175
    return 1
1176

    
1177
  buf = StringIO()
1178
  retcode = 0
1179
  for instance_name in result:
1180
    instance = result[instance_name]
1181
    buf.write("Instance name: %s\n" % instance["name"])
1182
    buf.write("UUID: %s\n" % instance["uuid"])
1183
    buf.write("Serial number: %s\n" %
1184
              compat.TryToRoman(instance["serial_no"],
1185
                                convert=opts.roman_integers))
1186
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1187
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1188
    buf.write("State: configured to be %s" % instance["config_state"])
1189
    if not opts.static:
1190
      buf.write(", actual state is %s" % instance["run_state"])
1191
    buf.write("\n")
1192
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1193
    ##          instance["auto_balance"])
1194
    buf.write("  Nodes:\n")
1195
    buf.write("    - primary: %s\n" % instance["pnode"])
1196
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1197
    buf.write("  Operating system: %s\n" % instance["os"])
1198
    _FormatParameterDict(buf, instance["os_instance"], instance["os_actual"])
1199
    if instance.has_key("network_port"):
1200
      buf.write("  Allocated network port: %s\n" %
1201
                compat.TryToRoman(instance["network_port"],
1202
                                  convert=opts.roman_integers))
1203
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1204

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

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

    
1240
    for idx, device in enumerate(instance["disks"]):
1241
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1242
                  opts.roman_integers), 2)
1243

    
1244
  ToStdout(buf.getvalue().rstrip('\n'))
1245
  return retcode
1246

    
1247

    
1248
def SetInstanceParams(opts, args):
1249
  """Modifies an instance.
1250

1251
  All parameters take effect only at the next restart of the instance.
1252

1253
  @param opts: the command line options selected by the user
1254
  @type args: list
1255
  @param args: should contain only one element, the instance name
1256
  @rtype: int
1257
  @return: the desired exit code
1258

1259
  """
1260
  if not (opts.nics or opts.disks or opts.disk_template or
1261
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1262
    ToStderr("Please give at least one of the parameters.")
1263
    return 1
1264

    
1265
  for param in opts.beparams:
1266
    if isinstance(opts.beparams[param], basestring):
1267
      if opts.beparams[param].lower() == "default":
1268
        opts.beparams[param] = constants.VALUE_DEFAULT
1269

    
1270
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1271
                      allowed_values=[constants.VALUE_DEFAULT])
1272

    
1273
  for param in opts.hvparams:
1274
    if isinstance(opts.hvparams[param], basestring):
1275
      if opts.hvparams[param].lower() == "default":
1276
        opts.hvparams[param] = constants.VALUE_DEFAULT
1277

    
1278
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1279
                      allowed_values=[constants.VALUE_DEFAULT])
1280

    
1281
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1282
    try:
1283
      nic_op = int(nic_op)
1284
      opts.nics[idx] = (nic_op, nic_dict)
1285
    except (TypeError, ValueError):
1286
      pass
1287

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

    
1300
  if (opts.disk_template and
1301
      opts.disk_template in constants.DTS_NET_MIRROR and
1302
      not opts.node):
1303
    ToStderr("Changing the disk template to a mirrored one requires"
1304
             " specifying a secondary node")
1305
    return 1
1306

    
1307
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1308
                                   nics=opts.nics,
1309
                                   disks=opts.disks,
1310
                                   disk_template=opts.disk_template,
1311
                                   remote_node=opts.node,
1312
                                   hvparams=opts.hvparams,
1313
                                   beparams=opts.beparams,
1314
                                   os_name=opts.os,
1315
                                   osparams=opts.osparams,
1316
                                   force_variant=opts.force_variant,
1317
                                   force=opts.force)
1318

    
1319
  # even if here we process the result, we allow submit only
1320
  result = SubmitOrSend(op, opts)
1321

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

    
1330

    
1331
# multi-instance selection options
1332
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1333
                           help="Do not ask for confirmation when more than"
1334
                           " one instance is affected",
1335
                           action="store_true", default=False)
1336

    
1337
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1338
                            help="Filter by nodes (primary only)",
1339
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1340

    
1341
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1342
                            help="Filter by nodes (secondary only)",
1343
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1344

    
1345
m_node_opt = cli_option("--node", dest="multi_mode",
1346
                        help="Filter by nodes (primary and secondary)",
1347
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1348

    
1349
m_clust_opt = cli_option("--all", dest="multi_mode",
1350
                         help="Select all instances in the cluster",
1351
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1352

    
1353
m_inst_opt = cli_option("--instance", dest="multi_mode",
1354
                        help="Filter by instance name [default]",
1355
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1356

    
1357
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1358
                             help="Filter by node tag",
1359
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1360
                             action="store_const")
1361

    
1362
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1363
                                 help="Filter by primary node tag",
1364
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1365
                                 action="store_const")
1366

    
1367
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1368
                                 help="Filter by secondary node tag",
1369
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1370
                                 action="store_const")
1371

    
1372
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1373
                             help="Filter by instance tag",
1374
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1375
                             action="store_const")
1376

    
1377
# this is defined separately due to readability only
1378
add_opts = [
1379
  NOSTART_OPT,
1380
  OS_OPT,
1381
  FORCE_VARIANT_OPT,
1382
  NO_INSTALL_OPT,
1383
  ]
1384

    
1385
commands = {
1386
  'add': (
1387
    AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts,
1388
    "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1389
    "Creates and adds a new instance to the cluster"),
1390
  'batch-create': (
1391
    BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1392
    "<instances.json>",
1393
    "Create a bunch of instances based on specs in the file."),
1394
  'console': (
1395
    ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1396
    [SHOWCMD_OPT, PRIORITY_OPT],
1397
    "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1398
  'failover': (
1399
    FailoverInstance, ARGS_ONE_INSTANCE,
1400
    [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1401
     DRY_RUN_OPT, PRIORITY_OPT],
1402
    "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1403
    " using the remote mirror (only for instances of type drbd)"),
1404
  'migrate': (
1405
    MigrateInstance, ARGS_ONE_INSTANCE,
1406
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1407
     PRIORITY_OPT],
1408
    "[-f] <instance>", "Migrate instance to its secondary node"
1409
    " (only for instances of type drbd)"),
1410
  'move': (
1411
    MoveInstance, ARGS_ONE_INSTANCE,
1412
    [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1413
     DRY_RUN_OPT, PRIORITY_OPT],
1414
    "[-f] <instance>", "Move instance to an arbitrary node"
1415
    " (only for instances of type file and lv)"),
1416
  'info': (
1417
    ShowInstanceConfig, ARGS_MANY_INSTANCES,
1418
    [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1419
    "[-s] {--all | <instance>...}",
1420
    "Show information on the specified instance(s)"),
1421
  'list': (
1422
    ListInstances, ARGS_MANY_INSTANCES,
1423
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
1424
    "[<instance>...]",
1425
    "Lists the instances and their status. The available fields are"
1426
    " (see the man page for details): status, oper_state, oper_ram,"
1427
    " oper_vcpus, name, os, pnode, snodes, admin_state, admin_ram,"
1428
    " disk_template, ip, mac, nic_mode, nic_link, sda_size, sdb_size,"
1429
    " vcpus, serial_no,"
1430
    " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
1431
    " nic.macs, nic.ips, nic.modes, nic.links,"
1432
    " disk.count, disk.size/N, disk.sizes,"
1433
    " hv/NAME, be/memory, be/vcpus, be/auto_balance,"
1434
    " hypervisor."
1435
    " The default field"
1436
    " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS),
1437
    ),
1438
  'reinstall': (
1439
    ReinstallInstance, [ArgInstance()],
1440
    [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1441
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1442
     m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1443
     SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT],
1444
    "[-f] <instance>", "Reinstall a stopped instance"),
1445
  'remove': (
1446
    RemoveInstance, ARGS_ONE_INSTANCE,
1447
    [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1448
     DRY_RUN_OPT, PRIORITY_OPT],
1449
    "[-f] <instance>", "Shuts down the instance and removes it"),
1450
  'rename': (
1451
    RenameInstance,
1452
    [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1453
    [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1454
    "<instance> <new_name>", "Rename the instance"),
1455
  'replace-disks': (
1456
    ReplaceDisks, ARGS_ONE_INSTANCE,
1457
    [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1458
     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1459
     DRY_RUN_OPT, PRIORITY_OPT],
1460
    "[-s|-p|-n NODE|-I NAME] <instance>",
1461
    "Replaces all disks for the instance"),
1462
  'modify': (
1463
    SetInstanceParams, ARGS_ONE_INSTANCE,
1464
    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1465
     DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1466
     OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1467
    "<instance>", "Alters the parameters of an instance"),
1468
  'shutdown': (
1469
    GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1470
    [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1471
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1472
     m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1473
     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1474
    "<instance>", "Stops an instance"),
1475
  'startup': (
1476
    GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1477
    [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1478
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1479
     m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1480
     BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1481
    "<instance>", "Starts an instance"),
1482
  'reboot': (
1483
    GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1484
    [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1485
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1486
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1487
     m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1488
    "<instance>", "Reboots an instance"),
1489
  'activate-disks': (
1490
    ActivateDisks, ARGS_ONE_INSTANCE,
1491
    [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1492
    "<instance>", "Activate an instance's disks"),
1493
  'deactivate-disks': (
1494
    DeactivateDisks, ARGS_ONE_INSTANCE,
1495
    [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1496
    "<instance>", "Deactivate an instance's disks"),
1497
  'recreate-disks': (
1498
    RecreateDisks, ARGS_ONE_INSTANCE,
1499
    [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1500
    "<instance>", "Recreate an instance's disks"),
1501
  'grow-disk': (
1502
    GrowDisk,
1503
    [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1504
     ArgUnknown(min=1, max=1)],
1505
    [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1506
    "<instance> <disk> <size>", "Grow an instance's disk"),
1507
  'list-tags': (
1508
    ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1509
    "<instance_name>", "List the tags of the given instance"),
1510
  'add-tags': (
1511
    AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1512
    [TAG_SRC_OPT, PRIORITY_OPT],
1513
    "<instance_name> tag...", "Add tags to the given instance"),
1514
  'remove-tags': (
1515
    RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1516
    [TAG_SRC_OPT, PRIORITY_OPT],
1517
    "<instance_name> tag...", "Remove tags from given instance"),
1518
  }
1519

    
1520
#: dictionary with aliases for commands
1521
aliases = {
1522
  'start': 'startup',
1523
  'stop': 'shutdown',
1524
  }
1525

    
1526

    
1527
def Main():
1528
  return GenericMain(commands, aliases=aliases,
1529
                     override={"tag_type": constants.TAG_INSTANCE})