Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 8b312c1d

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

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

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

    
588

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

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

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

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

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

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

    
619

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

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

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

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

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

    
644
  return 0
645

    
646

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

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

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

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

    
670

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

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

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

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

    
689

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

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

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

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

    
715

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

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

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

    
740

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

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

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

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

    
761

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

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

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

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

    
778

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

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

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

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

    
793

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

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

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

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

    
839

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

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

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

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

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

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

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

    
872

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

    
876
  The migrate is done without shutdown.
877

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

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

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

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

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

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

    
919

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

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

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

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

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

    
947

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

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

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

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

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

    
973

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

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

    
994
  return data
995

    
996

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

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

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

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

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

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

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

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

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

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

    
1111

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

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

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

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

    
1140

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

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

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

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

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

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

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

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

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

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

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

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

    
1252

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1335

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

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

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

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

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

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

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

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

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

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

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

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

    
1532
#: dictionary with aliases for commands
1533
aliases = {
1534
  'activate_block_devs': 'activate-disks',
1535
  'replace_disks': 'replace-disks',
1536
  'start': 'startup',
1537
  'stop': 'shutdown',
1538
  }
1539

    
1540

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