Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_instance.py @ eb28ecf6

History | View | Annotate | Download (53 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 os
29
import itertools
30
import simplejson
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

    
41

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

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

    
57

    
58
_VALUE_TRUE = "true"
59

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

    
65

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

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

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

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

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

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

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

    
142
  return inames
143

    
144

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

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

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

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

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

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

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

    
182

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

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

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

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

    
204

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

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

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

    
235

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

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

245
  """
246
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
247

    
248
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
249

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

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

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

    
331
  data = GenerateTable(separator=opts.separator, headers=headers,
332
                       fields=selected_fields, unitfields=unitfields,
333
                       numfields=numfields, data=output, units=opts.units)
334

    
335
  for line in data:
336
    ToStdout(line)
337

    
338
  return 0
339

    
340

    
341
def AddInstance(opts, args):
342
  """Add an instance to the cluster.
343

344
  This is just a wrapper over GenericInstanceCreate.
345

346
  """
347
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
348

    
349

    
350
def BatchCreate(opts, args):
351
  """Create instances using a definition file.
352

353
  This function reads a json file with instances defined
354
  in the form::
355

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

368
  Note that I{primary_node} and I{secondary_node} have precedence over
369
  I{iallocator}.
370

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

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

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

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

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

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

    
430
  if not isinstance(instance_data, dict):
431
    ToStderr("The instance definition file is not in dict format.")
432
    return 1
433

    
434
  jex = JobExecutor(opts=opts)
435

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

    
445
    hypervisor = specs['hypervisor']
446
    hvparams = specs['hvparams']
447

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

    
458
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
459
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
460

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

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

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

    
497
    jex.QueueJob(name, op)
498
  # we never want to wait, just show the submitted job IDs
499
  jex.WaitOrShow(False)
500

    
501
  return 0
502

    
503

    
504
def ReinstallInstance(opts, args):
505
  """Reinstall an instance.
506

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

514
  """
515
  # first, compute the desired name list
516
  if opts.multi_mode is None:
517
    opts.multi_mode = _SHUTDOWN_INSTANCES
518

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

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

    
529
    if not result:
530
      ToStdout("Can't get the OS list")
531
      return 1
532

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

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

    
546
    if selected == 'exit':
547
      ToStderr("User aborted reinstall, exiting")
548
      return 1
549

    
550
    os_name = selected
551
  else:
552
    os_name = opts.os
553

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

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

    
578
  jex.WaitOrShow(not opts.submit_only)
579
  return 0
580

    
581

    
582
def RemoveInstance(opts, args):
583
  """Remove an instance.
584

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

592
  """
593
  instance_name = args[0]
594
  force = opts.force
595
  cl = GetClient()
596

    
597
  if not force:
598
    _EnsureInstancesExist(cl, [instance_name])
599

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

    
606
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
607
                                ignore_failures=opts.ignore_failures,
608
                                shutdown_timeout=opts.shutdown_timeout)
609
  SubmitOrSend(op, opts, cl=cl)
610
  return 0
611

    
612

    
613
def RenameInstance(opts, args):
614
  """Rename an instance.
615

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

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

    
629
  op = opcodes.OpRenameInstance(instance_name=args[0],
630
                                new_name=args[1],
631
                                ip_check=opts.ip_check,
632
                                name_check=opts.name_check)
633
  result = SubmitOrSend(op, opts)
634

    
635
  if result:
636
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
637

    
638
  return 0
639

    
640

    
641
def ActivateDisks(opts, args):
642
  """Activate an instance's disks.
643

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

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

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

    
664

    
665
def DeactivateDisks(opts, args):
666
  """Deactivate an instance's disks.
667

668
  This function takes the instance name, looks for its primary node
669
  and the tries to shutdown its block devices on that node.
670

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

677
  """
678
  instance_name = args[0]
679
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
680
  SubmitOrSend(op, opts)
681
  return 0
682

    
683

    
684
def RecreateDisks(opts, args):
685
  """Recreate an instance's disks.
686

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

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

    
704
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
705
                                       disks=opts.disks)
706
  SubmitOrSend(op, opts)
707
  return 0
708

    
709

    
710
def GrowDisk(opts, args):
711
  """Grow an instance's disks.
712

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

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

    
734

    
735
def _StartupInstance(name, opts):
736
  """Startup instances.
737

738
  This returns the opcode to start an instance, and its decorator will
739
  wrap this into a loop starting all desired instances.
740

741
  @param name: the name of the instance to act on
742
  @param opts: the command line options selected by the user
743
  @return: the opcode needed for the operation
744

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

    
756

    
757
def _RebootInstance(name, opts):
758
  """Reboot instance(s).
759

760
  This returns the opcode to reboot an instance, and its decorator
761
  will wrap this into a loop rebooting all desired instances.
762

763
  @param name: the name of the instance to act on
764
  @param opts: the command line options selected by the user
765
  @return: the opcode needed for the operation
766

767
  """
768
  return opcodes.OpRebootInstance(instance_name=name,
769
                                  reboot_type=opts.reboot_type,
770
                                  ignore_secondaries=opts.ignore_secondaries,
771
                                  shutdown_timeout=opts.shutdown_timeout)
772

    
773

    
774
def _ShutdownInstance(name, opts):
775
  """Shutdown an instance.
776

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

780
  @param name: the name of the instance to act on
781
  @param opts: the command line options selected by the user
782
  @return: the opcode needed for the operation
783

784
  """
785
  return opcodes.OpShutdownInstance(instance_name=name,
786
                                    timeout=opts.timeout,
787
                                    ignore_offline_nodes=opts.ignore_offline)
788

    
789

    
790
def ReplaceDisks(opts, args):
791
  """Replace the disks of an instance
792

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

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

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

    
835

    
836
def FailoverInstance(opts, args):
837
  """Failover an instance.
838

839
  The failover is done by shutting it down on its present node and
840
  starting it on the secondary.
841

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

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

    
853
  if not force:
854
    _EnsureInstancesExist(cl, [instance_name])
855

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

    
862
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
863
                                  ignore_consistency=opts.ignore_consistency,
864
                                  shutdown_timeout=opts.shutdown_timeout)
865
  SubmitOrSend(op, opts, cl=cl)
866
  return 0
867

    
868

    
869
def MigrateInstance(opts, args):
870
  """Migrate an instance.
871

872
  The migrate is done without shutdown.
873

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

880
  """
881
  cl = GetClient()
882
  instance_name = args[0]
883
  force = opts.force
884

    
885
  if not force:
886
    _EnsureInstancesExist(cl, [instance_name])
887

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

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

    
910
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
911
                                 cleanup=opts.cleanup)
912
  SubmitOpCode(op, cl=cl, opts=opts)
913
  return 0
914

    
915

    
916
def MoveInstance(opts, args):
917
  """Move an instance.
918

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

925
  """
926
  cl = GetClient()
927
  instance_name = args[0]
928
  force = opts.force
929

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

    
937
  op = opcodes.OpMoveInstance(instance_name=instance_name,
938
                              target_node=opts.node,
939
                              shutdown_timeout=opts.shutdown_timeout)
940
  SubmitOrSend(op, opts, cl=cl)
941
  return 0
942

    
943

    
944
def ConnectToInstanceConsole(opts, args):
945
  """Connect to the console of an instance.
946

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

953
  """
954
  instance_name = args[0]
955

    
956
  op = opcodes.OpConnectConsole(instance_name=instance_name)
957
  cmd = SubmitOpCode(op, opts=opts)
958

    
959
  if opts.show_command:
960
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
961
  else:
962
    try:
963
      os.execvp(cmd[0], cmd)
964
    finally:
965
      ToStderr("Can't run console command %s with arguments:\n'%s'",
966
               cmd[0], " ".join(cmd))
967
      os._exit(1) # pylint: disable-msg=W0212
968

    
969

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

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

    
990
  return data
991

    
992

    
993
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
994
  """Show block device information.
995

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

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

1013
  """
1014
  def helper(dtype, status):
1015
    """Format one line for physical device status.
1016

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

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

    
1033
    if minor is None:
1034
      minor_string = "N/A"
1035
    else:
1036
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1037

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

    
1067
  # the header
1068
  if top_level:
1069
    if dev["iv_name"] is not None:
1070
      txt = dev["iv_name"]
1071
    else:
1072
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1073
  else:
1074
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1075
  if isinstance(dev["size"], int):
1076
    nice_size = utils.FormatUnit(dev["size"], "h")
1077
  else:
1078
    nice_size = dev["size"]
1079
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1080
  data = []
1081
  if top_level:
1082
    data.append(("access mode", dev["mode"]))
1083
  if dev["logical_id"] is not None:
1084
    try:
1085
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1086
    except ValueError:
1087
      l_id = [str(dev["logical_id"])]
1088
    if len(l_id) == 1:
1089
      data.append(("logical_id", l_id[0]))
1090
    else:
1091
      data.extend(l_id)
1092
  elif dev["physical_id"] is not None:
1093
    data.append("physical_id:")
1094
    data.append([dev["physical_id"]])
1095
  if not static:
1096
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1097
  if dev["sstatus"] and not static:
1098
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1099

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

    
1107

    
1108
def _FormatList(buf, data, indent_level):
1109
  """Formats a list of data at a given indent level.
1110

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

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

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

    
1136

    
1137
def _FormatParameterDict(buf, per_inst, actual):
1138
  """Formats a parameter dictionary.
1139

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

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

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

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

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

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

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

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

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

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

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

    
1248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1331

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1527

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