Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ d22dfef7

History | View | Annotate | Download (52.8 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
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
248

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

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

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

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

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

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

    
339
  return 0
340

    
341

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

    
345
  This is just a wrapper over GenericInstanceCreate.
346

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

    
350

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

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

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

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

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

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

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

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

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

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

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

    
435
  jex = JobExecutor(opts=opts)
436

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

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

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

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

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

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

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

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

    
502
  return 0
503

    
504

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

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

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

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

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

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

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

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

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

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

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

    
571
  jex = JobExecutor(verbose=multi_on, opts=opts)
572
  for instance_name in inames:
573
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
574
                                     os_type=os_name,
575
                                     force_variant=opts.force_variant)
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
  # do not add these parameters to the opcode unless they're defined
749
  if opts.hvparams:
750
    op.hvparams = opts.hvparams
751
  if opts.beparams:
752
    op.beparams = opts.beparams
753
  return op
754

    
755

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

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

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

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

    
772

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

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

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

    
783
  """
784
  return opcodes.OpShutdownInstance(instance_name=name,
785
                                    timeout=opts.timeout)
786

    
787

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

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

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

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

    
833

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

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

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

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

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

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

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

    
866

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

    
870
  The migrate is done without shutdown.
871

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

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

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

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

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

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

    
913

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

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

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

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

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

    
941

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

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

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

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

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

    
967

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

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

    
988
  return data
989

    
990

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

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

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

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

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

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

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

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

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

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

    
1105

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

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

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

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

    
1134

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

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

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

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

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

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

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

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

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

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

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

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

    
1246

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1329

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

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

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

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

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

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

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

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

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

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

    
1376
# this is defined separately due to readability only
1377
add_opts = [
1378
  BACKEND_OPT,
1379
  DISK_OPT,
1380
  DISK_TEMPLATE_OPT,
1381
  FILESTORE_DIR_OPT,
1382
  FILESTORE_DRIVER_OPT,
1383
  HYPERVISOR_OPT,
1384
  IALLOCATOR_OPT,
1385
  NET_OPT,
1386
  NODE_PLACEMENT_OPT,
1387
  NOIPCHECK_OPT,
1388
  NONAMECHECK_OPT,
1389
  NONICS_OPT,
1390
  NOSTART_OPT,
1391
  NWSYNC_OPT,
1392
  OSPARAMS_OPT,
1393
  OS_OPT,
1394
  FORCE_VARIANT_OPT,
1395
  NO_INSTALL_OPT,
1396
  OS_SIZE_OPT,
1397
  SUBMIT_OPT,
1398
  DRY_RUN_OPT,
1399
  ]
1400

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

    
1533
#: dictionary with aliases for commands
1534
aliases = {
1535
  'start': 'startup',
1536
  'stop': 'shutdown',
1537
  }
1538

    
1539

    
1540
if __name__ == '__main__':
1541
  sys.exit(GenericMain(commands, aliases=aliases,
1542
                       override={"tag_type": constants.TAG_INSTANCE}))