Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 6a016df9

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
  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
  # *and* --force, single-reinstall just --force
564
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
565
  if multi_on:
566
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
567
    if not ((opts.force_multi and opts.force) or
568
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
569
      return 1
570
  else:
571
    if not opts.force:
572
      usertext = ("This will reinstall the instance %s and remove"
573
                  " all data. Continue?") % inames[0]
574
      if not AskUser(usertext):
575
        return 1
576

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

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

    
587

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

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

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

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

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

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

    
618

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

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

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

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

    
641
  ToStdout("Instance '%s' renamed to '%s'", args[0], result)
642

    
643
  return 0
644

    
645

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

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

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

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

    
669

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

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

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

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

    
688

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

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

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

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

    
714

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

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

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

    
739

    
740
def _StartupInstance(name, opts):
741
  """Startup instances.
742

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

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

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

    
760

    
761
def _RebootInstance(name, opts):
762
  """Reboot instance(s).
763

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

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

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

    
777

    
778
def _ShutdownInstance(name, opts):
779
  """Shutdown an instance.
780

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

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

    
788
  """
789
  return opcodes.OpShutdownInstance(instance_name=name,
790
                                    timeout=opts.timeout)
791

    
792

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

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

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

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

    
838

    
839
def FailoverInstance(opts, args):
840
  """Failover an instance.
841

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

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

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

    
856
  if not force:
857
    _EnsureInstancesExist(cl, [instance_name])
858

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

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

    
871

    
872
def MigrateInstance(opts, args):
873
  """Migrate an instance.
874

    
875
  The migrate is done without shutdown.
876

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

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

    
888
  if not force:
889
    _EnsureInstancesExist(cl, [instance_name])
890

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

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

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

    
918

    
919
def MoveInstance(opts, args):
920
  """Move an instance.
921

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

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

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

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

    
946

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

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

    
956
  """
957
  instance_name = args[0]
958

    
959
  op = opcodes.OpConnectConsole(instance_name=instance_name)
960
  cmd = SubmitOpCode(op, opts=opts)
961

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

    
972

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

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

    
993
  return data
994

    
995

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

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

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

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

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

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

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

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

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

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

    
1110

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

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

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

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

    
1139

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

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

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

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

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

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

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

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

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

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

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

    
1248
  ToStdout(buf.getvalue().rstrip('\n'))
1249
  return retcode
1250

    
1251

    
1252
def SetInstanceParams(opts, args):
1253
  """Modifies an instance.
1254

    
1255
  All parameters take effect only at the next restart of the instance.
1256

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

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

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

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

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

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

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

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

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

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

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

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

    
1334

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

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

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

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

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

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

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

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

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

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

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

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

    
1531
#: dictionary with aliases for commands
1532
aliases = {
1533
  'activate_block_devs': 'activate-disks',
1534
  'replace_disks': 'replace-disks',
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}))