Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 062a7100

History | View | Annotate | Download (51.6 kB)

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

    
4
# Copyright (C) 2006, 2007 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

    
41

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

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

    
57

    
58
_VALUE_TRUE = "true"
59

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

    
65

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

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

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

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

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

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

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

    
142
  return inames
143

    
144

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

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

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

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

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

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

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

    
182

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

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

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

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

    
204

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

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

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

    
234

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

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

    
244
  """
245
  if opts.output is None:
246
    selected_fields = _LIST_DEF_FIELDS
247
  elif opts.output.startswith("+"):
248
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
249
  else:
250
    selected_fields = opts.output.split(",")
251

    
252
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
253

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

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

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

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

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

    
338
  return 0
339

    
340

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

    
344
  This is just a wrapper over GenericInstanceCreate.
345

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

    
349

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

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

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

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

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

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

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

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

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

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

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

    
433
  jex = JobExecutor(opts=opts)
434

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

    
444
    hypervisor = specs['hypervisor']
445
    hvparams = specs['hvparams']
446

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

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

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

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

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

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

    
500
  return 0
501

    
502

    
503
def ReinstallInstance(opts, args):
504
  """Reinstall an instance.
505

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

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

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

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

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

    
533
    ToStdout("Available OS templates:")
534
    number = 0
535
    choices = []
536
    for (name, valid, variants) in result:
537
      if valid:
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
  # *and* --force, single-reinstall just --force
557
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
558
  if multi_on:
559
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
560
    if not ((opts.force_multi and opts.force) or
561
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
562
      return 1
563
  else:
564
    if not opts.force:
565
      usertext = ("This will reinstall the instance %s and remove"
566
                  " all data. Continue?") % inames[0]
567
      if not AskUser(usertext):
568
        return 1
569

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

    
577
  jex.WaitOrShow(not opts.submit_only)
578
  return 0
579

    
580

    
581
def RemoveInstance(opts, args):
582
  """Remove an instance.
583

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

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

    
596
  if not force:
597
    _EnsureInstancesExist(cl, [instance_name])
598

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

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

    
611

    
612
def RenameInstance(opts, args):
613
  """Rename an instance.
614

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

    
622
  """
623
  op = opcodes.OpRenameInstance(instance_name=args[0],
624
                                new_name=args[1],
625
                                ignore_ip=not opts.ip_check)
626
  SubmitOrSend(op, opts)
627
  return 0
628

    
629

    
630
def ActivateDisks(opts, args):
631
  """Activate an instance's disks.
632

    
633
  This serves two purposes:
634
    - it allows (as long as the instance is not running)
635
      mounting the disks and modifying them from the node
636
    - it repairs inactive secondary drbds
637

    
638
  @param opts: the command line options selected by the user
639
  @type args: list
640
  @param args: should contain only one element, the instance name
641
  @rtype: int
642
  @return: the desired exit code
643

    
644
  """
645
  instance_name = args[0]
646
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
647
                                       ignore_size=opts.ignore_size)
648
  disks_info = SubmitOrSend(op, opts)
649
  for host, iname, nname in disks_info:
650
    ToStdout("%s:%s:%s", host, iname, nname)
651
  return 0
652

    
653

    
654
def DeactivateDisks(opts, args):
655
  """Deactivate an instance's disks.
656

    
657
  This function takes the instance name, looks for its primary node
658
  and the tries to shutdown its block devices on that node.
659

    
660
  @param opts: the command line options selected by the user
661
  @type args: list
662
  @param args: should contain only one element, the instance name
663
  @rtype: int
664
  @return: the desired exit code
665

    
666
  """
667
  instance_name = args[0]
668
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
669
  SubmitOrSend(op, opts)
670
  return 0
671

    
672

    
673
def RecreateDisks(opts, args):
674
  """Recreate an instance's disks.
675

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

    
682
  """
683
  instance_name = args[0]
684
  if opts.disks:
685
    try:
686
      opts.disks = [int(v) for v in opts.disks.split(",")]
687
    except (ValueError, TypeError), err:
688
      ToStderr("Invalid disks value: %s" % str(err))
689
      return 1
690
  else:
691
    opts.disks = []
692

    
693
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
694
                                       disks=opts.disks)
695
  SubmitOrSend(op, opts)
696
  return 0
697

    
698

    
699
def GrowDisk(opts, args):
700
  """Grow an instance's disks.
701

    
702
  @param opts: the command line options selected by the user
703
  @type args: list
704
  @param args: should contain two elements, the instance name
705
      whose disks we grow and the disk name, e.g. I{sda}
706
  @rtype: int
707
  @return: the desired exit code
708

    
709
  """
710
  instance = args[0]
711
  disk = args[1]
712
  try:
713
    disk = int(disk)
714
  except (TypeError, ValueError), err:
715
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
716
                               errors.ECODE_INVAL)
717
  amount = utils.ParseUnit(args[2])
718
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
719
                          wait_for_sync=opts.wait_for_sync)
720
  SubmitOrSend(op, opts)
721
  return 0
722

    
723

    
724
def _StartupInstance(name, opts):
725
  """Startup instances.
726

    
727
  This returns the opcode to start an instance, and its decorator will
728
  wrap this into a loop starting all desired instances.
729

    
730
  @param name: the name of the instance to act on
731
  @param opts: the command line options selected by the user
732
  @return: the opcode needed for the operation
733

    
734
  """
735
  op = opcodes.OpStartupInstance(instance_name=name,
736
                                 force=opts.force)
737
  # do not add these parameters to the opcode unless they're defined
738
  if opts.hvparams:
739
    op.hvparams = opts.hvparams
740
  if opts.beparams:
741
    op.beparams = opts.beparams
742
  return op
743

    
744

    
745
def _RebootInstance(name, opts):
746
  """Reboot instance(s).
747

    
748
  This returns the opcode to reboot an instance, and its decorator
749
  will wrap this into a loop rebooting all desired instances.
750

    
751
  @param name: the name of the instance to act on
752
  @param opts: the command line options selected by the user
753
  @return: the opcode needed for the operation
754

    
755
  """
756
  return opcodes.OpRebootInstance(instance_name=name,
757
                                  reboot_type=opts.reboot_type,
758
                                  ignore_secondaries=opts.ignore_secondaries,
759
                                  shutdown_timeout=opts.shutdown_timeout)
760

    
761

    
762
def _ShutdownInstance(name, opts):
763
  """Shutdown an instance.
764

    
765
  This returns the opcode to shutdown an instance, and its decorator
766
  will wrap this into a loop shutting down 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.OpShutdownInstance(instance_name=name,
774
                                    timeout=opts.timeout)
775

    
776

    
777
def ReplaceDisks(opts, args):
778
  """Replace the disks of an instance
779

    
780
  @param opts: the command line options selected by the user
781
  @type args: list
782
  @param args: should contain only one element, the instance name
783
  @rtype: int
784
  @return: the desired exit code
785

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

    
815
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
816
                              remote_node=new_2ndary, mode=mode,
817
                              iallocator=iallocator,
818
                              early_release=opts.early_release)
819
  SubmitOrSend(op, opts)
820
  return 0
821

    
822

    
823
def FailoverInstance(opts, args):
824
  """Failover an instance.
825

    
826
  The failover is done by shutting it down on its present node and
827
  starting it on the secondary.
828

    
829
  @param opts: the command line options selected by the user
830
  @type args: list
831
  @param args: should contain only one element, the instance name
832
  @rtype: int
833
  @return: the desired exit code
834

    
835
  """
836
  cl = GetClient()
837
  instance_name = args[0]
838
  force = opts.force
839

    
840
  if not force:
841
    _EnsureInstancesExist(cl, [instance_name])
842

    
843
    usertext = ("Failover will happen to image %s."
844
                " This requires a shutdown of the instance. Continue?" %
845
                (instance_name,))
846
    if not AskUser(usertext):
847
      return 1
848

    
849
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
850
                                  ignore_consistency=opts.ignore_consistency,
851
                                  shutdown_timeout=opts.shutdown_timeout)
852
  SubmitOrSend(op, opts, cl=cl)
853
  return 0
854

    
855

    
856
def MigrateInstance(opts, args):
857
  """Migrate an instance.
858

    
859
  The migrate is done without shutdown.
860

    
861
  @param opts: the command line options selected by the user
862
  @type args: list
863
  @param args: should contain only one element, the instance name
864
  @rtype: int
865
  @return: the desired exit code
866

    
867
  """
868
  cl = GetClient()
869
  instance_name = args[0]
870
  force = opts.force
871

    
872
  if not force:
873
    _EnsureInstancesExist(cl, [instance_name])
874

    
875
    if opts.cleanup:
876
      usertext = ("Instance %s will be recovered from a failed migration."
877
                  " Note that the migration procedure (including cleanup)" %
878
                  (instance_name,))
879
    else:
880
      usertext = ("Instance %s will be migrated. Note that migration" %
881
                  (instance_name,))
882
    usertext += (" might impact the instance if anything goes wrong"
883
                 " (e.g. due to bugs in the hypervisor). Continue?")
884
    if not AskUser(usertext):
885
      return 1
886

    
887
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
888
                                 cleanup=opts.cleanup)
889
  SubmitOpCode(op, cl=cl, opts=opts)
890
  return 0
891

    
892

    
893
def MoveInstance(opts, args):
894
  """Move an instance.
895

    
896
  @param opts: the command line options selected by the user
897
  @type args: list
898
  @param args: should contain only one element, the instance name
899
  @rtype: int
900
  @return: the desired exit code
901

    
902
  """
903
  cl = GetClient()
904
  instance_name = args[0]
905
  force = opts.force
906

    
907
  if not force:
908
    usertext = ("Instance %s will be moved."
909
                " This requires a shutdown of the instance. Continue?" %
910
                (instance_name,))
911
    if not AskUser(usertext):
912
      return 1
913

    
914
  op = opcodes.OpMoveInstance(instance_name=instance_name,
915
                              target_node=opts.node,
916
                              shutdown_timeout=opts.shutdown_timeout)
917
  SubmitOrSend(op, opts, cl=cl)
918
  return 0
919

    
920

    
921
def ConnectToInstanceConsole(opts, args):
922
  """Connect to the console of an instance.
923

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

    
930
  """
931
  instance_name = args[0]
932

    
933
  op = opcodes.OpConnectConsole(instance_name=instance_name)
934
  cmd = SubmitOpCode(op, opts=opts)
935

    
936
  if opts.show_command:
937
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
938
  else:
939
    try:
940
      os.execvp(cmd[0], cmd)
941
    finally:
942
      ToStderr("Can't run console command %s with arguments:\n'%s'",
943
               cmd[0], " ".join(cmd))
944
      os._exit(1) # pylint: disable-msg=W0212
945

    
946

    
947
def _FormatLogicalID(dev_type, logical_id, roman):
948
  """Formats the logical_id of a disk.
949

    
950
  """
951
  if dev_type == constants.LD_DRBD8:
952
    node_a, node_b, port, minor_a, minor_b, key = logical_id
953
    data = [
954
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
955
                                                            convert=roman))),
956
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
957
                                                            convert=roman))),
958
      ("port", compat.TryToRoman(port, convert=roman)),
959
      ("auth key", key),
960
      ]
961
  elif dev_type == constants.LD_LV:
962
    vg_name, lv_name = logical_id
963
    data = ["%s/%s" % (vg_name, lv_name)]
964
  else:
965
    data = [str(logical_id)]
966

    
967
  return data
968

    
969

    
970
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
971
  """Show block device information.
972

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

    
976
  @type idx: int
977
  @param idx: the index of the current disk
978
  @type top_level: boolean
979
  @param top_level: if this a top-level disk?
980
  @type dev: dict
981
  @param dev: dictionary with disk information
982
  @type static: boolean
983
  @param static: wheter the device information doesn't contain
984
      runtime information but only static data
985
  @type roman: boolean
986
  @param roman: whether to try to use roman integers
987
  @return: a list of either strings, tuples or lists
988
      (which should be formatted at a higher indent level)
989

    
990
  """
991
  def helper(dtype, status):
992
    """Format one line for physical device status.
993

    
994
    @type dtype: str
995
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
996
    @type status: tuple
997
    @param status: a tuple as returned from L{backend.FindBlockDevice}
998
    @return: the string representing the status
999

    
1000
    """
1001
    if not status:
1002
      return "not active"
1003
    txt = ""
1004
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1005
    if major is None:
1006
      major_string = "N/A"
1007
    else:
1008
      major_string = str(compat.TryToRoman(major, convert=roman))
1009

    
1010
    if minor is None:
1011
      minor_string = "N/A"
1012
    else:
1013
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1014

    
1015
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1016
    if dtype in (constants.LD_DRBD8, ):
1017
      if syncp is not None:
1018
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1019
        if estt:
1020
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1021
        else:
1022
          sync_text += " ETA unknown"
1023
      else:
1024
        sync_text = "in sync"
1025
      if degr:
1026
        degr_text = "*DEGRADED*"
1027
      else:
1028
        degr_text = "ok"
1029
      if ldisk_status == constants.LDS_FAULTY:
1030
        ldisk_text = " *MISSING DISK*"
1031
      elif ldisk_status == constants.LDS_UNKNOWN:
1032
        ldisk_text = " *UNCERTAIN STATE*"
1033
      else:
1034
        ldisk_text = ""
1035
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1036
    elif dtype == constants.LD_LV:
1037
      if ldisk_status == constants.LDS_FAULTY:
1038
        ldisk_text = " *FAILED* (failed drive?)"
1039
      else:
1040
        ldisk_text = ""
1041
      txt += ldisk_text
1042
    return txt
1043

    
1044
  # the header
1045
  if top_level:
1046
    if dev["iv_name"] is not None:
1047
      txt = dev["iv_name"]
1048
    else:
1049
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1050
  else:
1051
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1052
  if isinstance(dev["size"], int):
1053
    nice_size = utils.FormatUnit(dev["size"], "h")
1054
  else:
1055
    nice_size = dev["size"]
1056
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1057
  data = []
1058
  if top_level:
1059
    data.append(("access mode", dev["mode"]))
1060
  if dev["logical_id"] is not None:
1061
    try:
1062
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1063
    except ValueError:
1064
      l_id = [str(dev["logical_id"])]
1065
    if len(l_id) == 1:
1066
      data.append(("logical_id", l_id[0]))
1067
    else:
1068
      data.extend(l_id)
1069
  elif dev["physical_id"] is not None:
1070
    data.append("physical_id:")
1071
    data.append([dev["physical_id"]])
1072
  if not static:
1073
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1074
  if dev["sstatus"] and not static:
1075
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1076

    
1077
  if dev["children"]:
1078
    data.append("child devices:")
1079
    for c_idx, child in enumerate(dev["children"]):
1080
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1081
  d1.append(data)
1082
  return d1
1083

    
1084

    
1085
def _FormatList(buf, data, indent_level):
1086
  """Formats a list of data at a given indent level.
1087

    
1088
  If the element of the list is:
1089
    - a string, it is simply formatted as is
1090
    - a tuple, it will be split into key, value and the all the
1091
      values in a list will be aligned all at the same start column
1092
    - a list, will be recursively formatted
1093

    
1094
  @type buf: StringIO
1095
  @param buf: the buffer into which we write the output
1096
  @param data: the list to format
1097
  @type indent_level: int
1098
  @param indent_level: the indent level to format at
1099

    
1100
  """
1101
  max_tlen = max([len(elem[0]) for elem in data
1102
                 if isinstance(elem, tuple)] or [0])
1103
  for elem in data:
1104
    if isinstance(elem, basestring):
1105
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1106
    elif isinstance(elem, tuple):
1107
      key, value = elem
1108
      spacer = "%*s" % (max_tlen - len(key), "")
1109
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1110
    elif isinstance(elem, list):
1111
      _FormatList(buf, elem, indent_level+1)
1112

    
1113

    
1114
def _FormatParameterDict(buf, per_inst, actual):
1115
  """Formats a parameter dictionary.
1116

    
1117
  @type buf: L{StringIO}
1118
  @param buf: the buffer into which to write
1119
  @type per_inst: dict
1120
  @param per_inst: the instance's own parameters
1121
  @type actual: dict
1122
  @param actual: the current parameter set (including defaults)
1123

    
1124
  """
1125
  for key in sorted(actual):
1126
    val = per_inst.get(key, "default (%s)" % actual[key])
1127
    buf.write("    - %s: %s\n" % (key, val))
1128

    
1129
def ShowInstanceConfig(opts, args):
1130
  """Compute instance run-time status.
1131

    
1132
  @param opts: the command line options selected by the user
1133
  @type args: list
1134
  @param args: either an empty list, and then we query all
1135
      instances, or should contain a list of instance names
1136
  @rtype: int
1137
  @return: the desired exit code
1138

    
1139
  """
1140
  if not args and not opts.show_all:
1141
    ToStderr("No instance selected."
1142
             " Please pass in --all if you want to query all instances.\n"
1143
             "Note that this can take a long time on a big cluster.")
1144
    return 1
1145
  elif args and opts.show_all:
1146
    ToStderr("Cannot use --all if you specify instance names.")
1147
    return 1
1148

    
1149
  retcode = 0
1150
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1151
  result = SubmitOpCode(op, opts=opts)
1152
  if not result:
1153
    ToStdout("No instances.")
1154
    return 1
1155

    
1156
  buf = StringIO()
1157
  retcode = 0
1158
  for instance_name in result:
1159
    instance = result[instance_name]
1160
    buf.write("Instance name: %s\n" % instance["name"])
1161
    buf.write("UUID: %s\n" % instance["uuid"])
1162
    buf.write("Serial number: %s\n" %
1163
              compat.TryToRoman(instance["serial_no"],
1164
                                convert=opts.roman_integers))
1165
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1166
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1167
    buf.write("State: configured to be %s" % instance["config_state"])
1168
    if not opts.static:
1169
      buf.write(", actual state is %s" % instance["run_state"])
1170
    buf.write("\n")
1171
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1172
    ##          instance["auto_balance"])
1173
    buf.write("  Nodes:\n")
1174
    buf.write("    - primary: %s\n" % instance["pnode"])
1175
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1176
    buf.write("  Operating system: %s\n" % instance["os"])
1177
    _FormatParameterDict(buf, instance["os_instance"], instance["os_actual"])
1178
    if instance.has_key("network_port"):
1179
      buf.write("  Allocated network port: %s\n" %
1180
                compat.TryToRoman(instance["network_port"],
1181
                                  convert=opts.roman_integers))
1182
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1183

    
1184
    # custom VNC console information
1185
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1186
                                                 None)
1187
    if vnc_bind_address:
1188
      port = instance["network_port"]
1189
      display = int(port) - constants.VNC_BASE_PORT
1190
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1191
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1192
                                                   port,
1193
                                                   display)
1194
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1195
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1196
                             (vnc_bind_address, port,
1197
                              instance["pnode"], display))
1198
      else:
1199
        # vnc bind address is a file
1200
        vnc_console_port = "%s:%s" % (instance["pnode"],
1201
                                      vnc_bind_address)
1202
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1203

    
1204
    _FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"])
1205
    buf.write("  Hardware:\n")
1206
    buf.write("    - VCPUs: %s\n" %
1207
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1208
                                convert=opts.roman_integers))
1209
    buf.write("    - memory: %sMiB\n" %
1210
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1211
                                convert=opts.roman_integers))
1212
    buf.write("    - NICs:\n")
1213
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1214
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1215
                (idx, mac, ip, mode, link))
1216
    buf.write("  Disks:\n")
1217

    
1218
    for idx, device in enumerate(instance["disks"]):
1219
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1220
                  opts.roman_integers), 2)
1221

    
1222
  ToStdout(buf.getvalue().rstrip('\n'))
1223
  return retcode
1224

    
1225

    
1226
def SetInstanceParams(opts, args):
1227
  """Modifies an instance.
1228

    
1229
  All parameters take effect only at the next restart of the instance.
1230

    
1231
  @param opts: the command line options selected by the user
1232
  @type args: list
1233
  @param args: should contain only one element, the instance name
1234
  @rtype: int
1235
  @return: the desired exit code
1236

    
1237
  """
1238
  if not (opts.nics or opts.disks or opts.disk_template or
1239
          opts.hvparams or opts.beparams or opts.os):
1240
    ToStderr("Please give at least one of the parameters.")
1241
    return 1
1242

    
1243
  for param in opts.beparams:
1244
    if isinstance(opts.beparams[param], basestring):
1245
      if opts.beparams[param].lower() == "default":
1246
        opts.beparams[param] = constants.VALUE_DEFAULT
1247

    
1248
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1249
                      allowed_values=[constants.VALUE_DEFAULT])
1250

    
1251
  for param in opts.hvparams:
1252
    if isinstance(opts.hvparams[param], basestring):
1253
      if opts.hvparams[param].lower() == "default":
1254
        opts.hvparams[param] = constants.VALUE_DEFAULT
1255

    
1256
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1257
                      allowed_values=[constants.VALUE_DEFAULT])
1258

    
1259
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1260
    try:
1261
      nic_op = int(nic_op)
1262
      opts.nics[idx] = (nic_op, nic_dict)
1263
    except (TypeError, ValueError):
1264
      pass
1265

    
1266
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1267
    try:
1268
      disk_op = int(disk_op)
1269
      opts.disks[idx] = (disk_op, disk_dict)
1270
    except (TypeError, ValueError):
1271
      pass
1272
    if disk_op == constants.DDM_ADD:
1273
      if 'size' not in disk_dict:
1274
        raise errors.OpPrereqError("Missing required parameter 'size'",
1275
                                   errors.ECODE_INVAL)
1276
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1277

    
1278
  if (opts.disk_template and
1279
      opts.disk_template in constants.DTS_NET_MIRROR and
1280
      not opts.node):
1281
    ToStderr("Changing the disk template to a mirrored one requires"
1282
             " specifying a secondary node")
1283
    return 1
1284

    
1285
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1286
                                   nics=opts.nics,
1287
                                   disks=opts.disks,
1288
                                   disk_template=opts.disk_template,
1289
                                   remote_node=opts.node,
1290
                                   hvparams=opts.hvparams,
1291
                                   beparams=opts.beparams,
1292
                                   os_name=opts.os,
1293
                                   force_variant=opts.force_variant,
1294
                                   force=opts.force)
1295

    
1296
  # even if here we process the result, we allow submit only
1297
  result = SubmitOrSend(op, opts)
1298

    
1299
  if result:
1300
    ToStdout("Modified instance %s", args[0])
1301
    for param, data in result:
1302
      ToStdout(" - %-5s -> %s", param, data)
1303
    ToStdout("Please don't forget that most parameters take effect"
1304
             " only at the next start of the instance.")
1305
  return 0
1306

    
1307

    
1308
# multi-instance selection options
1309
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1310
                           help="Do not ask for confirmation when more than"
1311
                           " one instance is affected",
1312
                           action="store_true", default=False)
1313

    
1314
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1315
                            help="Filter by nodes (primary only)",
1316
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1317

    
1318
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1319
                            help="Filter by nodes (secondary only)",
1320
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1321

    
1322
m_node_opt = cli_option("--node", dest="multi_mode",
1323
                        help="Filter by nodes (primary and secondary)",
1324
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1325

    
1326
m_clust_opt = cli_option("--all", dest="multi_mode",
1327
                         help="Select all instances in the cluster",
1328
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1329

    
1330
m_inst_opt = cli_option("--instance", dest="multi_mode",
1331
                        help="Filter by instance name [default]",
1332
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1333

    
1334
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1335
                             help="Filter by node tag",
1336
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1337
                             action="store_const")
1338

    
1339
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1340
                                 help="Filter by primary node tag",
1341
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1342
                                 action="store_const")
1343

    
1344
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1345
                                 help="Filter by secondary node tag",
1346
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1347
                                 action="store_const")
1348

    
1349
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1350
                             help="Filter by instance tag",
1351
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1352
                             action="store_const")
1353

    
1354
# this is defined separately due to readability only
1355
add_opts = [
1356
  BACKEND_OPT,
1357
  DISK_OPT,
1358
  DISK_TEMPLATE_OPT,
1359
  FILESTORE_DIR_OPT,
1360
  FILESTORE_DRIVER_OPT,
1361
  HYPERVISOR_OPT,
1362
  IALLOCATOR_OPT,
1363
  NET_OPT,
1364
  NODE_PLACEMENT_OPT,
1365
  NOIPCHECK_OPT,
1366
  NONAMECHECK_OPT,
1367
  NONICS_OPT,
1368
  NOSTART_OPT,
1369
  NWSYNC_OPT,
1370
  OSPARAMS_OPT,
1371
  OS_OPT,
1372
  FORCE_VARIANT_OPT,
1373
  NO_INSTALL_OPT,
1374
  OS_SIZE_OPT,
1375
  SUBMIT_OPT,
1376
  ]
1377

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

    
1502
#: dictionary with aliases for commands
1503
aliases = {
1504
  'activate_block_devs': 'activate-disks',
1505
  'replace_disks': 'replace-disks',
1506
  'start': 'startup',
1507
  'stop': 'shutdown',
1508
  }
1509

    
1510

    
1511
if __name__ == '__main__':
1512
  sys.exit(GenericMain(commands, aliases=aliases,
1513
                       override={"tag_type": constants.TAG_INSTANCE}))