Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 3fe11ba3

History | View | Annotate | Download (52.7 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
  SubmitOrSend(op, opts)
640
  return 0
641

    
642

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

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

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

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

    
666

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

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

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

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

    
685

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

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

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

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

    
711

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

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

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

    
736

    
737
def _StartupInstance(name, opts):
738
  """Startup instances.
739

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

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

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

    
757

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

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

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

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

    
774

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

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

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

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

    
789

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

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

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

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

    
835

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

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

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

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

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

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

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

    
868

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

    
872
  The migrate is done without shutdown.
873

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

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

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

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

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

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

    
915

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

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

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

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

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

    
943

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

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

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

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

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

    
969

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

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

    
990
  return data
991

    
992

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

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

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

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

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

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

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

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

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

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

    
1107

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

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

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

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

    
1136

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

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

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

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

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

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

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

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

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

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

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

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

    
1248

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1331

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

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

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

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

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

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

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

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

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

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

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

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

    
1528
#: dictionary with aliases for commands
1529
aliases = {
1530
  'activate_block_devs': 'activate-disks',
1531
  'replace_disks': 'replace-disks',
1532
  'start': 'startup',
1533
  'stop': 'shutdown',
1534
  }
1535

    
1536

    
1537
if __name__ == '__main__':
1538
  sys.exit(GenericMain(commands, aliases=aliases,
1539
                       override={"tag_type": constants.TAG_INSTANCE}))