Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ db5a8a2d

History | View | Annotate | Download (53.1 kB)

1
#!/usr/bin/python
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 sys
29
import os
30
import itertools
31
import simplejson
32
from cStringIO import StringIO
33

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

    
42

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

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

    
58

    
59
_VALUE_TRUE = "true"
60

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

    
66

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

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

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

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

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

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

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

    
143
  return inames
144

    
145

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

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

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

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

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

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

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

    
183

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

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

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

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

    
205

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

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

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

    
236

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

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

    
246
  """
247
  if opts.output is None:
248
    selected_fields = _LIST_DEF_FIELDS
249
  elif opts.output.startswith("+"):
250
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
251
  else:
252
    selected_fields = opts.output.split(",")
253

    
254
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
255

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

    
294
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
295
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
296
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
297

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

    
337
  data = GenerateTable(separator=opts.separator, headers=headers,
338
                       fields=selected_fields, unitfields=unitfields,
339
                       numfields=numfields, data=output, units=opts.units)
340

    
341
  for line in data:
342
    ToStdout(line)
343

    
344
  return 0
345

    
346

    
347
def AddInstance(opts, args):
348
  """Add an instance to the cluster.
349

    
350
  This is just a wrapper over GenericInstanceCreate.
351

    
352
  """
353
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
354

    
355

    
356
def BatchCreate(opts, args):
357
  """Create instances using a definition file.
358

    
359
  This function reads a json file with instances defined
360
  in the form::
361

    
362
    {"instance-name":{
363
      "disk_size": [20480],
364
      "template": "drbd",
365
      "backend": {
366
        "memory": 512,
367
        "vcpus": 1 },
368
      "os": "debootstrap",
369
      "primary_node": "firstnode",
370
      "secondary_node": "secondnode",
371
      "iallocator": "dumb"}
372
    }
373

    
374
  Note that I{primary_node} and I{secondary_node} have precedence over
375
  I{iallocator}.
376

    
377
  @param opts: the command line options selected by the user
378
  @type args: list
379
  @param args: should contain one element, the json filename
380
  @rtype: int
381
  @return: the desired exit code
382

    
383
  """
384
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
385
                    "backend": {},
386
                    "iallocator": None,
387
                    "primary_node": None,
388
                    "secondary_node": None,
389
                    "nics": None,
390
                    "start": True,
391
                    "ip_check": True,
392
                    "name_check": True,
393
                    "hypervisor": None,
394
                    "hvparams": {},
395
                    "file_storage_dir": None,
396
                    "force_variant": False,
397
                    "file_driver": 'loop'}
398

    
399
  def _PopulateWithDefaults(spec):
400
    """Returns a new hash combined with default values."""
401
    mydict = _DEFAULT_SPECS.copy()
402
    mydict.update(spec)
403
    return mydict
404

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

    
424
    if (spec['hvparams'] and
425
        not isinstance(spec['hvparams'], dict)):
426
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
427
                                 errors.ECODE_INVAL)
428

    
429
  json_filename = args[0]
430
  try:
431
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
432
  except Exception, err: # pylint: disable-msg=W0703
433
    ToStderr("Can't parse the instance definition file: %s" % str(err))
434
    return 1
435

    
436
  if not isinstance(instance_data, dict):
437
    ToStderr("The instance definition file is not in dict format.")
438
    return 1
439

    
440
  jex = JobExecutor(opts=opts)
441

    
442
  # Iterate over the instances and do:
443
  #  * Populate the specs with default value
444
  #  * Validate the instance specs
445
  i_names = utils.NiceSort(instance_data.keys()) # pylint: disable-msg=E1103
446
  for name in i_names:
447
    specs = instance_data[name]
448
    specs = _PopulateWithDefaults(specs)
449
    _Validate(specs)
450

    
451
    hypervisor = specs['hypervisor']
452
    hvparams = specs['hvparams']
453

    
454
    disks = []
455
    for elem in specs['disk_size']:
456
      try:
457
        size = utils.ParseUnit(elem)
458
      except (TypeError, ValueError), err:
459
        raise errors.OpPrereqError("Invalid disk size '%s' for"
460
                                   " instance %s: %s" %
461
                                   (elem, name, err), errors.ECODE_INVAL)
462
      disks.append({"size": size})
463

    
464
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
465
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
466

    
467
    tmp_nics = []
468
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
469
      if field in specs:
470
        if not tmp_nics:
471
          tmp_nics.append({})
472
        tmp_nics[0][field] = specs[field]
473

    
474
    if specs['nics'] is not None and tmp_nics:
475
      raise errors.OpPrereqError("'nics' list incompatible with using"
476
                                 " individual nic fields as well",
477
                                 errors.ECODE_INVAL)
478
    elif specs['nics'] is not None:
479
      tmp_nics = specs['nics']
480
    elif not tmp_nics:
481
      tmp_nics = [{}]
482

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

    
503
    jex.QueueJob(name, op)
504
  # we never want to wait, just show the submitted job IDs
505
  jex.WaitOrShow(False)
506

    
507
  return 0
508

    
509

    
510
def ReinstallInstance(opts, args):
511
  """Reinstall an instance.
512

    
513
  @param opts: the command line options selected by the user
514
  @type args: list
515
  @param args: should contain only one element, the name of the
516
      instance to be reinstalled
517
  @rtype: int
518
  @return: the desired exit code
519

    
520
  """
521
  # first, compute the desired name list
522
  if opts.multi_mode is None:
523
    opts.multi_mode = _SHUTDOWN_INSTANCES
524

    
525
  inames = _ExpandMultiNames(opts.multi_mode, args)
526
  if not inames:
527
    raise errors.OpPrereqError("Selection filter does not match any instances",
528
                               errors.ECODE_INVAL)
529

    
530
  # second, if requested, ask for an OS
531
  if opts.select_os is True:
532
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
533
                              names=[])
534
    result = SubmitOpCode(op, opts=opts)
535

    
536
    if not result:
537
      ToStdout("Can't get the OS list")
538
      return 1
539

    
540
    ToStdout("Available OS templates:")
541
    number = 0
542
    choices = []
543
    for (name, valid, variants) in result:
544
      if valid:
545
        for entry in CalculateOSNames(name, variants):
546
          ToStdout("%3s: %s", number, entry)
547
          choices.append(("%s" % number, entry, entry))
548
          number += 1
549

    
550
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
551
    selected = AskUser("Enter OS template number (or x to abort):",
552
                       choices)
553

    
554
    if selected == 'exit':
555
      ToStderr("User aborted reinstall, exiting")
556
      return 1
557

    
558
    os_name = selected
559
  else:
560
    os_name = opts.os
561

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

    
578
  jex = JobExecutor(verbose=multi_on, opts=opts)
579
  for instance_name in inames:
580
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
581
                                     os_type=os_name,
582
                                     force_variant=opts.force_variant)
583
    jex.QueueJob(instance_name, op)
584

    
585
  jex.WaitOrShow(not opts.submit_only)
586
  return 0
587

    
588

    
589
def RemoveInstance(opts, args):
590
  """Remove an instance.
591

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

    
599
  """
600
  instance_name = args[0]
601
  force = opts.force
602
  cl = GetClient()
603

    
604
  if not force:
605
    _EnsureInstancesExist(cl, [instance_name])
606

    
607
    usertext = ("This will remove the volumes of the instance %s"
608
                " (including mirrors), thus removing all the data"
609
                " of the instance. Continue?") % instance_name
610
    if not AskUser(usertext):
611
      return 1
612

    
613
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
614
                                ignore_failures=opts.ignore_failures,
615
                                shutdown_timeout=opts.shutdown_timeout)
616
  SubmitOrSend(op, opts, cl=cl)
617
  return 0
618

    
619

    
620
def RenameInstance(opts, args):
621
  """Rename an instance.
622

    
623
  @param opts: the command line options selected by the user
624
  @type args: list
625
  @param args: should contain two elements, the old and the
626
      new instance names
627
  @rtype: int
628
  @return: the desired exit code
629

    
630
  """
631
  if not opts.name_check:
632
    if not AskUser("As you disabled the check of the DNS entry, please verify"
633
                   " that '%s' is a FQDN. Continue?" % args[1]):
634
      return 1
635

    
636
  op = opcodes.OpRenameInstance(instance_name=args[0],
637
                                new_name=args[1],
638
                                ip_check=opts.ip_check,
639
                                name_check=opts.name_check)
640
  result = SubmitOrSend(op, opts)
641

    
642
  if result:
643
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
644

    
645
  return 0
646

    
647

    
648
def ActivateDisks(opts, args):
649
  """Activate an instance's disks.
650

    
651
  This serves two purposes:
652
    - it allows (as long as the instance is not running)
653
      mounting the disks and modifying them from the node
654
    - it repairs inactive secondary drbds
655

    
656
  @param opts: the command line options selected by the user
657
  @type args: list
658
  @param args: should contain only one element, the instance name
659
  @rtype: int
660
  @return: the desired exit code
661

    
662
  """
663
  instance_name = args[0]
664
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
665
                                       ignore_size=opts.ignore_size)
666
  disks_info = SubmitOrSend(op, opts)
667
  for host, iname, nname in disks_info:
668
    ToStdout("%s:%s:%s", host, iname, nname)
669
  return 0
670

    
671

    
672
def DeactivateDisks(opts, args):
673
  """Deactivate an instance's disks.
674

    
675
  This function takes the instance name, looks for its primary node
676
  and the tries to shutdown its block devices on that node.
677

    
678
  @param opts: the command line options selected by the user
679
  @type args: list
680
  @param args: should contain only one element, the instance name
681
  @rtype: int
682
  @return: the desired exit code
683

    
684
  """
685
  instance_name = args[0]
686
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
687
  SubmitOrSend(op, opts)
688
  return 0
689

    
690

    
691
def RecreateDisks(opts, args):
692
  """Recreate an instance's disks.
693

    
694
  @param opts: the command line options selected by the user
695
  @type args: list
696
  @param args: should contain only one element, the instance name
697
  @rtype: int
698
  @return: the desired exit code
699

    
700
  """
701
  instance_name = args[0]
702
  if opts.disks:
703
    try:
704
      opts.disks = [int(v) for v in opts.disks.split(",")]
705
    except (ValueError, TypeError), err:
706
      ToStderr("Invalid disks value: %s" % str(err))
707
      return 1
708
  else:
709
    opts.disks = []
710

    
711
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
712
                                       disks=opts.disks)
713
  SubmitOrSend(op, opts)
714
  return 0
715

    
716

    
717
def GrowDisk(opts, args):
718
  """Grow an instance's disks.
719

    
720
  @param opts: the command line options selected by the user
721
  @type args: list
722
  @param args: should contain two elements, the instance name
723
      whose disks we grow and the disk name, e.g. I{sda}
724
  @rtype: int
725
  @return: the desired exit code
726

    
727
  """
728
  instance = args[0]
729
  disk = args[1]
730
  try:
731
    disk = int(disk)
732
  except (TypeError, ValueError), err:
733
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
734
                               errors.ECODE_INVAL)
735
  amount = utils.ParseUnit(args[2])
736
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
737
                          wait_for_sync=opts.wait_for_sync)
738
  SubmitOrSend(op, opts)
739
  return 0
740

    
741

    
742
def _StartupInstance(name, opts):
743
  """Startup instances.
744

    
745
  This returns the opcode to start an instance, and its decorator will
746
  wrap this into a loop starting all desired instances.
747

    
748
  @param name: the name of the instance to act on
749
  @param opts: the command line options selected by the user
750
  @return: the opcode needed for the operation
751

    
752
  """
753
  op = opcodes.OpStartupInstance(instance_name=name,
754
                                 force=opts.force)
755
  # do not add these parameters to the opcode unless they're defined
756
  if opts.hvparams:
757
    op.hvparams = opts.hvparams
758
  if opts.beparams:
759
    op.beparams = opts.beparams
760
  return op
761

    
762

    
763
def _RebootInstance(name, opts):
764
  """Reboot instance(s).
765

    
766
  This returns the opcode to reboot an instance, and its decorator
767
  will wrap this into a loop rebooting all desired instances.
768

    
769
  @param name: the name of the instance to act on
770
  @param opts: the command line options selected by the user
771
  @return: the opcode needed for the operation
772

    
773
  """
774
  return opcodes.OpRebootInstance(instance_name=name,
775
                                  reboot_type=opts.reboot_type,
776
                                  ignore_secondaries=opts.ignore_secondaries,
777
                                  shutdown_timeout=opts.shutdown_timeout)
778

    
779

    
780
def _ShutdownInstance(name, opts):
781
  """Shutdown an instance.
782

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

    
786
  @param name: the name of the instance to act on
787
  @param opts: the command line options selected by the user
788
  @return: the opcode needed for the operation
789

    
790
  """
791
  return opcodes.OpShutdownInstance(instance_name=name,
792
                                    timeout=opts.timeout)
793

    
794

    
795
def ReplaceDisks(opts, args):
796
  """Replace the disks of an instance
797

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

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

    
833
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
834
                              remote_node=new_2ndary, mode=mode,
835
                              iallocator=iallocator,
836
                              early_release=opts.early_release)
837
  SubmitOrSend(op, opts)
838
  return 0
839

    
840

    
841
def FailoverInstance(opts, args):
842
  """Failover an instance.
843

    
844
  The failover is done by shutting it down on its present node and
845
  starting it on the secondary.
846

    
847
  @param opts: the command line options selected by the user
848
  @type args: list
849
  @param args: should contain only one element, the instance name
850
  @rtype: int
851
  @return: the desired exit code
852

    
853
  """
854
  cl = GetClient()
855
  instance_name = args[0]
856
  force = opts.force
857

    
858
  if not force:
859
    _EnsureInstancesExist(cl, [instance_name])
860

    
861
    usertext = ("Failover will happen to image %s."
862
                " This requires a shutdown of the instance. Continue?" %
863
                (instance_name,))
864
    if not AskUser(usertext):
865
      return 1
866

    
867
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
868
                                  ignore_consistency=opts.ignore_consistency,
869
                                  shutdown_timeout=opts.shutdown_timeout)
870
  SubmitOrSend(op, opts, cl=cl)
871
  return 0
872

    
873

    
874
def MigrateInstance(opts, args):
875
  """Migrate an instance.
876

    
877
  The migrate is done without shutdown.
878

    
879
  @param opts: the command line options selected by the user
880
  @type args: list
881
  @param args: should contain only one element, the instance name
882
  @rtype: int
883
  @return: the desired exit code
884

    
885
  """
886
  cl = GetClient()
887
  instance_name = args[0]
888
  force = opts.force
889

    
890
  if not force:
891
    _EnsureInstancesExist(cl, [instance_name])
892

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

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

    
915
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
916
                                 cleanup=opts.cleanup)
917
  SubmitOpCode(op, cl=cl, opts=opts)
918
  return 0
919

    
920

    
921
def MoveInstance(opts, args):
922
  """Move an instance.
923

    
924
  @param opts: the command line options selected by the user
925
  @type args: list
926
  @param args: should contain only one element, the instance name
927
  @rtype: int
928
  @return: the desired exit code
929

    
930
  """
931
  cl = GetClient()
932
  instance_name = args[0]
933
  force = opts.force
934

    
935
  if not force:
936
    usertext = ("Instance %s will be moved."
937
                " This requires a shutdown of the instance. Continue?" %
938
                (instance_name,))
939
    if not AskUser(usertext):
940
      return 1
941

    
942
  op = opcodes.OpMoveInstance(instance_name=instance_name,
943
                              target_node=opts.node,
944
                              shutdown_timeout=opts.shutdown_timeout)
945
  SubmitOrSend(op, opts, cl=cl)
946
  return 0
947

    
948

    
949
def ConnectToInstanceConsole(opts, args):
950
  """Connect to the console of an instance.
951

    
952
  @param opts: the command line options selected by the user
953
  @type args: list
954
  @param args: should contain only one element, the instance name
955
  @rtype: int
956
  @return: the desired exit code
957

    
958
  """
959
  instance_name = args[0]
960

    
961
  op = opcodes.OpConnectConsole(instance_name=instance_name)
962
  cmd = SubmitOpCode(op, opts=opts)
963

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

    
974

    
975
def _FormatLogicalID(dev_type, logical_id, roman):
976
  """Formats the logical_id of a disk.
977

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

    
995
  return data
996

    
997

    
998
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
999
  """Show block device information.
1000

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

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

    
1018
  """
1019
  def helper(dtype, status):
1020
    """Format one line for physical device status.
1021

    
1022
    @type dtype: str
1023
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1024
    @type status: tuple
1025
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1026
    @return: the string representing the status
1027

    
1028
    """
1029
    if not status:
1030
      return "not active"
1031
    txt = ""
1032
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1033
    if major is None:
1034
      major_string = "N/A"
1035
    else:
1036
      major_string = str(compat.TryToRoman(major, convert=roman))
1037

    
1038
    if minor is None:
1039
      minor_string = "N/A"
1040
    else:
1041
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1042

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

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

    
1105
  if dev["children"]:
1106
    data.append("child devices:")
1107
    for c_idx, child in enumerate(dev["children"]):
1108
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1109
  d1.append(data)
1110
  return d1
1111

    
1112

    
1113
def _FormatList(buf, data, indent_level):
1114
  """Formats a list of data at a given indent level.
1115

    
1116
  If the element of the list is:
1117
    - a string, it is simply formatted as is
1118
    - a tuple, it will be split into key, value and the all the
1119
      values in a list will be aligned all at the same start column
1120
    - a list, will be recursively formatted
1121

    
1122
  @type buf: StringIO
1123
  @param buf: the buffer into which we write the output
1124
  @param data: the list to format
1125
  @type indent_level: int
1126
  @param indent_level: the indent level to format at
1127

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

    
1141

    
1142
def _FormatParameterDict(buf, per_inst, actual):
1143
  """Formats a parameter dictionary.
1144

    
1145
  @type buf: L{StringIO}
1146
  @param buf: the buffer into which to write
1147
  @type per_inst: dict
1148
  @param per_inst: the instance's own parameters
1149
  @type actual: dict
1150
  @param actual: the current parameter set (including defaults)
1151

    
1152
  """
1153
  for key in sorted(actual):
1154
    val = per_inst.get(key, "default (%s)" % actual[key])
1155
    buf.write("    - %s: %s\n" % (key, val))
1156

    
1157
def ShowInstanceConfig(opts, args):
1158
  """Compute instance run-time status.
1159

    
1160
  @param opts: the command line options selected by the user
1161
  @type args: list
1162
  @param args: either an empty list, and then we query all
1163
      instances, or should contain a list of instance names
1164
  @rtype: int
1165
  @return: the desired exit code
1166

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

    
1177
  retcode = 0
1178
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1179
  result = SubmitOpCode(op, opts=opts)
1180
  if not result:
1181
    ToStdout("No instances.")
1182
    return 1
1183

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

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

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

    
1246
    for idx, device in enumerate(instance["disks"]):
1247
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1248
                  opts.roman_integers), 2)
1249

    
1250
  ToStdout(buf.getvalue().rstrip('\n'))
1251
  return retcode
1252

    
1253

    
1254
def SetInstanceParams(opts, args):
1255
  """Modifies an instance.
1256

    
1257
  All parameters take effect only at the next restart of the instance.
1258

    
1259
  @param opts: the command line options selected by the user
1260
  @type args: list
1261
  @param args: should contain only one element, the instance name
1262
  @rtype: int
1263
  @return: the desired exit code
1264

    
1265
  """
1266
  if not (opts.nics or opts.disks or opts.disk_template or
1267
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1268
    ToStderr("Please give at least one of the parameters.")
1269
    return 1
1270

    
1271
  for param in opts.beparams:
1272
    if isinstance(opts.beparams[param], basestring):
1273
      if opts.beparams[param].lower() == "default":
1274
        opts.beparams[param] = constants.VALUE_DEFAULT
1275

    
1276
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1277
                      allowed_values=[constants.VALUE_DEFAULT])
1278

    
1279
  for param in opts.hvparams:
1280
    if isinstance(opts.hvparams[param], basestring):
1281
      if opts.hvparams[param].lower() == "default":
1282
        opts.hvparams[param] = constants.VALUE_DEFAULT
1283

    
1284
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1285
                      allowed_values=[constants.VALUE_DEFAULT])
1286

    
1287
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1288
    try:
1289
      nic_op = int(nic_op)
1290
      opts.nics[idx] = (nic_op, nic_dict)
1291
    except (TypeError, ValueError):
1292
      pass
1293

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

    
1306
  if (opts.disk_template and
1307
      opts.disk_template in constants.DTS_NET_MIRROR and
1308
      not opts.node):
1309
    ToStderr("Changing the disk template to a mirrored one requires"
1310
             " specifying a secondary node")
1311
    return 1
1312

    
1313
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1314
                                   nics=opts.nics,
1315
                                   disks=opts.disks,
1316
                                   disk_template=opts.disk_template,
1317
                                   remote_node=opts.node,
1318
                                   hvparams=opts.hvparams,
1319
                                   beparams=opts.beparams,
1320
                                   os_name=opts.os,
1321
                                   osparams=opts.osparams,
1322
                                   force_variant=opts.force_variant,
1323
                                   force=opts.force)
1324

    
1325
  # even if here we process the result, we allow submit only
1326
  result = SubmitOrSend(op, opts)
1327

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

    
1336

    
1337
# multi-instance selection options
1338
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1339
                           help="Do not ask for confirmation when more than"
1340
                           " one instance is affected",
1341
                           action="store_true", default=False)
1342

    
1343
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1344
                            help="Filter by nodes (primary only)",
1345
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1346

    
1347
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1348
                            help="Filter by nodes (secondary only)",
1349
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1350

    
1351
m_node_opt = cli_option("--node", dest="multi_mode",
1352
                        help="Filter by nodes (primary and secondary)",
1353
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1354

    
1355
m_clust_opt = cli_option("--all", dest="multi_mode",
1356
                         help="Select all instances in the cluster",
1357
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1358

    
1359
m_inst_opt = cli_option("--instance", dest="multi_mode",
1360
                        help="Filter by instance name [default]",
1361
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1362

    
1363
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1364
                             help="Filter by node tag",
1365
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1366
                             action="store_const")
1367

    
1368
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1369
                                 help="Filter by primary node tag",
1370
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1371
                                 action="store_const")
1372

    
1373
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1374
                                 help="Filter by secondary node tag",
1375
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1376
                                 action="store_const")
1377

    
1378
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1379
                             help="Filter by instance tag",
1380
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1381
                             action="store_const")
1382

    
1383
# this is defined separately due to readability only
1384
add_opts = [
1385
  BACKEND_OPT,
1386
  DISK_OPT,
1387
  DISK_TEMPLATE_OPT,
1388
  FILESTORE_DIR_OPT,
1389
  FILESTORE_DRIVER_OPT,
1390
  HYPERVISOR_OPT,
1391
  IALLOCATOR_OPT,
1392
  NET_OPT,
1393
  NODE_PLACEMENT_OPT,
1394
  NOIPCHECK_OPT,
1395
  NONAMECHECK_OPT,
1396
  NONICS_OPT,
1397
  NOSTART_OPT,
1398
  NWSYNC_OPT,
1399
  OSPARAMS_OPT,
1400
  OS_OPT,
1401
  FORCE_VARIANT_OPT,
1402
  NO_INSTALL_OPT,
1403
  OS_SIZE_OPT,
1404
  SUBMIT_OPT,
1405
  DRY_RUN_OPT,
1406
  ]
1407

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

    
1540
#: dictionary with aliases for commands
1541
aliases = {
1542
  'activate_block_devs': 'activate-disks',
1543
  'replace_disks': 'replace-disks',
1544
  'start': 'startup',
1545
  'stop': 'shutdown',
1546
  }
1547

    
1548

    
1549
if __name__ == '__main__':
1550
  sys.exit(GenericMain(commands, aliases=aliases,
1551
                       override={"tag_type": constants.TAG_INSTANCE}))