Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 8d8c4eff

History | View | Annotate | Download (53.4 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Instance related commands"""
22

    
23
# pylint: disable-msg=W0401,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0614: Unused import %s from wildcard import (since we need cli)
26
# C0103: Invalid name gnt-instance
27

    
28
import sys
29
import os
30
import itertools
31
import simplejson
32
from cStringIO import StringIO
33

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

    
42

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

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

    
58

    
59
_VALUE_TRUE = "true"
60

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

    
66

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

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

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

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

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

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

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

    
143
  return inames
144

    
145

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

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

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

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

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

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

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

    
183

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

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

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

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

    
205

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

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

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

    
236

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

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

    
246
  """
247
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
248

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

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

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

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

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

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

    
339
  return 0
340

    
341

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

    
345
  This is just a wrapper over GenericInstanceCreate.
346

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

    
350

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

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

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

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

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

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

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

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

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

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

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

    
435
  jex = JobExecutor(opts=opts)
436

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

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

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

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

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

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

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

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

    
502
  return 0
503

    
504

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

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

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

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

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

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

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

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

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

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

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

    
571
  jex = JobExecutor(verbose=multi_on, opts=opts)
572
  for instance_name in inames:
573
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
574
                                     os_type=os_name,
575
                                     force_variant=opts.force_variant,
576
                                     osparams=opts.osparams)
577
    jex.QueueJob(instance_name, op)
578

    
579
  jex.WaitOrShow(not opts.submit_only)
580
  return 0
581

    
582

    
583
def RemoveInstance(opts, args):
584
  """Remove an instance.
585

    
586
  @param opts: the command line options selected by the user
587
  @type args: list
588
  @param args: should contain only one element, the name of
589
      the instance to be removed
590
  @rtype: int
591
  @return: the desired exit code
592

    
593
  """
594
  instance_name = args[0]
595
  force = opts.force
596
  cl = GetClient()
597

    
598
  if not force:
599
    _EnsureInstancesExist(cl, [instance_name])
600

    
601
    usertext = ("This will remove the volumes of the instance %s"
602
                " (including mirrors), thus removing all the data"
603
                " of the instance. Continue?") % instance_name
604
    if not AskUser(usertext):
605
      return 1
606

    
607
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
608
                                ignore_failures=opts.ignore_failures,
609
                                shutdown_timeout=opts.shutdown_timeout)
610
  SubmitOrSend(op, opts, cl=cl)
611
  return 0
612

    
613

    
614
def RenameInstance(opts, args):
615
  """Rename an instance.
616

    
617
  @param opts: the command line options selected by the user
618
  @type args: list
619
  @param args: should contain two elements, the old and the
620
      new instance names
621
  @rtype: int
622
  @return: the desired exit code
623

    
624
  """
625
  if not opts.name_check:
626
    if not AskUser("As you disabled the check of the DNS entry, please verify"
627
                   " that '%s' is a FQDN. Continue?" % args[1]):
628
      return 1
629

    
630
  op = opcodes.OpRenameInstance(instance_name=args[0],
631
                                new_name=args[1],
632
                                ip_check=opts.ip_check,
633
                                name_check=opts.name_check)
634
  result = SubmitOrSend(op, opts)
635

    
636
  if result:
637
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
638

    
639
  return 0
640

    
641

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

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

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

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

    
665

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

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

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

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

    
684

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

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

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

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

    
710

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

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

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

    
735

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

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

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

    
746
  """
747
  op = opcodes.OpStartupInstance(instance_name=name,
748
                                 force=opts.force,
749
                                 ignore_offline_nodes=opts.ignore_offline)
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
                                    ignore_offline_nodes=opts.ignore_offline)
789

    
790

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

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

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

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

    
836

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

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

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

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

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

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

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

    
869

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

    
873
  The migrate is done without shutdown.
874

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

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

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

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

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

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

    
916

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

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

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

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

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

    
944

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

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

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

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

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

    
970

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

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

    
991
  return data
992

    
993

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

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

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

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

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

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

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

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

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

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

    
1108

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

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

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

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

    
1137

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

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

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

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

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

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

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

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

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

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

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

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

    
1249

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1332

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

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

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

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

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

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

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

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

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

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

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

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

    
1540
#: dictionary with aliases for commands
1541
aliases = {
1542
  'start': 'startup',
1543
  'stop': 'shutdown',
1544
  }
1545

    
1546

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