Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 064c21f8

History | View | Annotate | Download (48.9 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

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os
28
import itertools
29
import simplejson
30
import time
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import cli
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import utils
38
from ganeti import errors
39

    
40

    
41
_SHUTDOWN_CLUSTER = "cluster"
42
_SHUTDOWN_NODES_BOTH = "nodes"
43
_SHUTDOWN_NODES_PRI = "nodes-pri"
44
_SHUTDOWN_NODES_SEC = "nodes-sec"
45
_SHUTDOWN_INSTANCES = "instances"
46

    
47

    
48
_VALUE_TRUE = "true"
49

    
50
#: default list of options for L{ListInstances}
51
_LIST_DEF_FIELDS = [
52
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
53
  ]
54

    
55

    
56
def _ExpandMultiNames(mode, names, client=None):
57
  """Expand the given names using the passed mode.
58

    
59
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
60
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
61
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
62
  instances having those nodes as either primary or secondary will be
63
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
64
  returned.
65

    
66
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
67
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
68
      L{_SHUTDOWN_INSTANCES}
69
  @param names: a list of names; for cluster, it must be empty,
70
      and for node and instance it must be a list of valid item
71
      names (short names are valid as usual, e.g. node1 instead of
72
      node1.example.com)
73
  @rtype: list
74
  @return: the list of names after the expansion
75
  @raise errors.ProgrammerError: for unknown selection type
76
  @raise errors.OpPrereqError: for invalid input parameters
77

    
78
  """
79
  if client is None:
80
    client = GetClient()
81
  if mode == _SHUTDOWN_CLUSTER:
82
    if names:
83
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
84
    idata = client.QueryInstances([], ["name"], False)
85
    inames = [row[0] for row in idata]
86

    
87
  elif mode in (_SHUTDOWN_NODES_BOTH,
88
                _SHUTDOWN_NODES_PRI,
89
                _SHUTDOWN_NODES_SEC):
90
    if not names:
91
      raise errors.OpPrereqError("No node names passed")
92
    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
93
                              False)
94
    ipri = [row[1] for row in ndata]
95
    pri_names = list(itertools.chain(*ipri))
96
    isec = [row[2] for row in ndata]
97
    sec_names = list(itertools.chain(*isec))
98
    if mode == _SHUTDOWN_NODES_BOTH:
99
      inames = pri_names + sec_names
100
    elif mode == _SHUTDOWN_NODES_PRI:
101
      inames = pri_names
102
    elif mode == _SHUTDOWN_NODES_SEC:
103
      inames = sec_names
104
    else:
105
      raise errors.ProgrammerError("Unhandled shutdown type")
106

    
107
  elif mode == _SHUTDOWN_INSTANCES:
108
    if not names:
109
      raise errors.OpPrereqError("No instance names passed")
110
    idata = client.QueryInstances(names, ["name"], False)
111
    inames = [row[0] for row in idata]
112

    
113
  else:
114
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
115

    
116
  return inames
117

    
118

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

    
122
  This function is used to request confirmation for doing an operation
123
  on a given list of instances.
124

    
125
  @type inames: list
126
  @param inames: the list of names that we display when
127
      we ask for confirmation
128
  @type text: str
129
  @param text: the operation that the user should confirm
130
      (e.g. I{shutdown} or I{startup})
131
  @rtype: boolean
132
  @return: True or False depending on user's confirmation.
133

    
134
  """
135
  count = len(inames)
136
  msg = ("The %s will operate on %d instances.\n%s"
137
         "Do you want to continue?" % (text, count, extra))
138
  affected = ("\nAffected instances:\n" +
139
              "\n".join(["  %s" % name for name in inames]))
140

    
141
  choices = [('y', True, 'Yes, execute the %s' % text),
142
             ('n', False, 'No, abort the %s' % text)]
143

    
144
  if count > 20:
145
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
146
    ask = msg
147
  else:
148
    ask = msg + affected
149

    
150
  choice = AskUser(ask, choices)
151
  if choice == 'v':
152
    choices.pop(1)
153
    choice = AskUser(msg + affected, choices)
154
  return choice
155

    
156

    
157
def _EnsureInstancesExist(client, names):
158
  """Check for and ensure the given instance names exist.
159

    
160
  This function will raise an OpPrereqError in case they don't
161
  exist. Otherwise it will exit cleanly.
162

    
163
  @type client: L{ganeti.luxi.Client}
164
  @param client: the client to use for the query
165
  @type names: list
166
  @param names: the list of instance names to query
167
  @raise errors.OpPrereqError: in case any instance is missing
168

    
169
  """
170
  # TODO: change LUQueryInstances to that it actually returns None
171
  # instead of raising an exception, or devise a better mechanism
172
  result = client.QueryInstances(names, ["name"], False)
173
  for orig_name, row in zip(names, result):
174
    if row[0] is None:
175
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
176

    
177

    
178
def ListInstances(opts, args):
179
  """List instances and their properties.
180

    
181
  @param opts: the command line options selected by the user
182
  @type args: list
183
  @param args: should be an empty list
184
  @rtype: int
185
  @return: the desired exit code
186

    
187
  """
188
  if opts.output is None:
189
    selected_fields = _LIST_DEF_FIELDS
190
  elif opts.output.startswith("+"):
191
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
192
  else:
193
    selected_fields = opts.output.split(",")
194

    
195
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
196

    
197
  if not opts.no_headers:
198
    headers = {
199
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
200
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
201
      "oper_state": "Running",
202
      "oper_ram": "Memory", "disk_template": "Disk_template",
203
      "ip": "IP_address", "mac": "MAC_address",
204
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
205
      "bridge": "Bridge",
206
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
207
      "disk_usage": "DiskUsage",
208
      "status": "Status", "tags": "Tags",
209
      "network_port": "Network_port",
210
      "hv/kernel_path": "Kernel_path",
211
      "hv/initrd_path": "Initrd_path",
212
      "hv/boot_order": "HVM_boot_order",
213
      "hv/acpi": "HVM_ACPI",
214
      "hv/pae": "HVM_PAE",
215
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
216
      "hv/nic_type": "HVM_NIC_type",
217
      "hv/disk_type": "HVM_Disk_type",
218
      "hv/vnc_bind_address": "VNC_bind_address",
219
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
220
      "hvparams": "Hypervisor_parameters",
221
      "be/memory": "Configured_memory",
222
      "be/vcpus": "VCPUs",
223
      "vcpus": "VCPUs",
224
      "be/auto_balance": "Auto_balance",
225
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
226
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
227
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
228
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
229
      "ctime": "CTime", "mtime": "MTime",
230
      }
231
  else:
232
    headers = None
233

    
234
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
235
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
236
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
237

    
238
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
239
                      "nic.modes", "nic.links", "nic.bridges")
240
  # change raw values to nicer strings
241
  for row in output:
242
    for idx, field in enumerate(selected_fields):
243
      val = row[idx]
244
      if field == "snodes":
245
        val = ",".join(val) or "-"
246
      elif field == "admin_state":
247
        if val:
248
          val = "yes"
249
        else:
250
          val = "no"
251
      elif field == "oper_state":
252
        if val is None:
253
          val = "(node down)"
254
        elif val: # True
255
          val = "running"
256
        else:
257
          val = "stopped"
258
      elif field == "oper_ram":
259
        if val is None:
260
          val = "(node down)"
261
      elif field == "sda_size" or field == "sdb_size":
262
        if val is None:
263
          val = "N/A"
264
      elif field == "ctime" or field == "mtime":
265
        val = utils.FormatTime(val)
266
      elif field in list_type_fields:
267
        val = ",".join(str(item) for item in val)
268
      elif val is None:
269
        val = "-"
270
      row[idx] = str(val)
271

    
272
  data = GenerateTable(separator=opts.separator, headers=headers,
273
                       fields=selected_fields, unitfields=unitfields,
274
                       numfields=numfields, data=output, units=opts.units)
275

    
276
  for line in data:
277
    ToStdout(line)
278

    
279
  return 0
280

    
281

    
282
def AddInstance(opts, args):
283
  """Add an instance to the cluster.
284

    
285
  @param opts: the command line options selected by the user
286
  @type args: list
287
  @param args: should contain only one element, the new instance name
288
  @rtype: int
289
  @return: the desired exit code
290

    
291
  """
292
  instance = args[0]
293

    
294
  (pnode, snode) = SplitNodeOption(opts.node)
295

    
296
  hypervisor = None
297
  hvparams = {}
298
  if opts.hypervisor:
299
    hypervisor, hvparams = opts.hypervisor
300

    
301
  if opts.nics:
302
    try:
303
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
304
    except ValueError, err:
305
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
306
    nics = [{}] * nic_max
307
    for nidx, ndict in opts.nics:
308
      nidx = int(nidx)
309
      if not isinstance(ndict, dict):
310
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
311
        raise errors.OpPrereqError(msg)
312
      nics[nidx] = ndict
313
  elif opts.no_nics:
314
    # no nics
315
    nics = []
316
  else:
317
    # default of one nic, all auto
318
    nics = [{}]
319

    
320
  if opts.disk_template == constants.DT_DISKLESS:
321
    if opts.disks or opts.sd_size is not None:
322
      raise errors.OpPrereqError("Diskless instance but disk"
323
                                 " information passed")
324
    disks = []
325
  else:
326
    if not opts.disks and not opts.sd_size:
327
      raise errors.OpPrereqError("No disk information specified")
328
    if opts.disks and opts.sd_size is not None:
329
      raise errors.OpPrereqError("Please use either the '--disk' or"
330
                                 " '-s' option")
331
    if opts.sd_size is not None:
332
      opts.disks = [(0, {"size": opts.sd_size})]
333
    try:
334
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
335
    except ValueError, err:
336
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
337
    disks = [{}] * disk_max
338
    for didx, ddict in opts.disks:
339
      didx = int(didx)
340
      if not isinstance(ddict, dict):
341
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
342
        raise errors.OpPrereqError(msg)
343
      elif "size" not in ddict:
344
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
345
      try:
346
        ddict["size"] = utils.ParseUnit(ddict["size"])
347
      except ValueError, err:
348
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
349
                                   (didx, err))
350
      disks[didx] = ddict
351

    
352
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
353
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
354

    
355
  op = opcodes.OpCreateInstance(instance_name=instance,
356
                                disks=disks,
357
                                disk_template=opts.disk_template,
358
                                nics=nics,
359
                                mode=constants.INSTANCE_CREATE,
360
                                os_type=opts.os, pnode=pnode,
361
                                snode=snode,
362
                                start=opts.start, ip_check=opts.ip_check,
363
                                wait_for_sync=opts.wait_for_sync,
364
                                hypervisor=hypervisor,
365
                                hvparams=hvparams,
366
                                beparams=opts.beparams,
367
                                iallocator=opts.iallocator,
368
                                file_storage_dir=opts.file_storage_dir,
369
                                file_driver=opts.file_driver,
370
                                )
371

    
372
  SubmitOrSend(op, opts)
373
  return 0
374

    
375

    
376
def BatchCreate(opts, args):
377
  """Create instances using a definition file.
378

    
379
  This function reads a json file with instances defined
380
  in the form::
381

    
382
    {"instance-name":{
383
      "disk_size": [20480],
384
      "template": "drbd",
385
      "backend": {
386
        "memory": 512,
387
        "vcpus": 1 },
388
      "os": "debootstrap",
389
      "primary_node": "firstnode",
390
      "secondary_node": "secondnode",
391
      "iallocator": "dumb"}
392
    }
393

    
394
  Note that I{primary_node} and I{secondary_node} have precedence over
395
  I{iallocator}.
396

    
397
  @param opts: the command line options selected by the user
398
  @type args: list
399
  @param args: should contain one element, the json filename
400
  @rtype: int
401
  @return: the desired exit code
402

    
403
  """
404
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
405
                    "backend": {},
406
                    "iallocator": None,
407
                    "primary_node": None,
408
                    "secondary_node": None,
409
                    "nics": None,
410
                    "start": True,
411
                    "ip_check": True,
412
                    "hypervisor": None,
413
                    "hvparams": {},
414
                    "file_storage_dir": None,
415
                    "file_driver": 'loop'}
416

    
417
  def _PopulateWithDefaults(spec):
418
    """Returns a new hash combined with default values."""
419
    mydict = _DEFAULT_SPECS.copy()
420
    mydict.update(spec)
421
    return mydict
422

    
423
  def _Validate(spec):
424
    """Validate the instance specs."""
425
    # Validate fields required under any circumstances
426
    for required_field in ('os', 'template'):
427
      if required_field not in spec:
428
        raise errors.OpPrereqError('Required field "%s" is missing.' %
429
                                   required_field)
430
    # Validate special fields
431
    if spec['primary_node'] is not None:
432
      if (spec['template'] in constants.DTS_NET_MIRROR and
433
          spec['secondary_node'] is None):
434
        raise errors.OpPrereqError('Template requires secondary node, but'
435
                                   ' there was no secondary provided.')
436
    elif spec['iallocator'] is None:
437
      raise errors.OpPrereqError('You have to provide at least a primary_node'
438
                                 ' or an iallocator.')
439

    
440
    if (spec['hvparams'] and
441
        not isinstance(spec['hvparams'], dict)):
442
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
443

    
444
  json_filename = args[0]
445
  try:
446
    instance_data = simplejson.loads(utils.ReadFile(json_filename))
447
  except Exception, err:
448
    ToStderr("Can't parse the instance definition file: %s" % str(err))
449
    return 1
450

    
451
  jex = JobExecutor()
452

    
453
  # Iterate over the instances and do:
454
  #  * Populate the specs with default value
455
  #  * Validate the instance specs
456
  i_names = utils.NiceSort(instance_data.keys())
457
  for name in i_names:
458
    specs = instance_data[name]
459
    specs = _PopulateWithDefaults(specs)
460
    _Validate(specs)
461

    
462
    hypervisor = specs['hypervisor']
463
    hvparams = specs['hvparams']
464

    
465
    disks = []
466
    for elem in specs['disk_size']:
467
      try:
468
        size = utils.ParseUnit(elem)
469
      except ValueError, err:
470
        raise errors.OpPrereqError("Invalid disk size '%s' for"
471
                                   " instance %s: %s" %
472
                                   (elem, name, err))
473
      disks.append({"size": size})
474

    
475
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
476
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
477

    
478
    tmp_nics = []
479
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
480
      if field in specs:
481
        if not tmp_nics:
482
          tmp_nics.append({})
483
        tmp_nics[0][field] = specs[field]
484

    
485
    if specs['nics'] is not None and tmp_nics:
486
      raise errors.OpPrereqError("'nics' list incompatible with using"
487
                                 " individual nic fields as well")
488
    elif specs['nics'] is not None:
489
      tmp_nics = specs['nics']
490
    elif not tmp_nics:
491
      tmp_nics = [{}]
492

    
493
    op = opcodes.OpCreateInstance(instance_name=name,
494
                                  disks=disks,
495
                                  disk_template=specs['template'],
496
                                  mode=constants.INSTANCE_CREATE,
497
                                  os_type=specs['os'],
498
                                  pnode=specs['primary_node'],
499
                                  snode=specs['secondary_node'],
500
                                  nics=tmp_nics,
501
                                  start=specs['start'],
502
                                  ip_check=specs['ip_check'],
503
                                  wait_for_sync=True,
504
                                  iallocator=specs['iallocator'],
505
                                  hypervisor=hypervisor,
506
                                  hvparams=hvparams,
507
                                  beparams=specs['backend'],
508
                                  file_storage_dir=specs['file_storage_dir'],
509
                                  file_driver=specs['file_driver'])
510

    
511
    jex.QueueJob(name, op)
512
  # we never want to wait, just show the submitted job IDs
513
  jex.WaitOrShow(False)
514

    
515
  return 0
516

    
517

    
518
def ReinstallInstance(opts, args):
519
  """Reinstall an instance.
520

    
521
  @param opts: the command line options selected by the user
522
  @type args: list
523
  @param args: should contain only one element, the name of the
524
      instance to be reinstalled
525
  @rtype: int
526
  @return: the desired exit code
527

    
528
  """
529
  # first, compute the desired name list
530
  if opts.multi_mode is None:
531
    opts.multi_mode = _SHUTDOWN_INSTANCES
532

    
533
  inames = _ExpandMultiNames(opts.multi_mode, args)
534
  if not inames:
535
    raise errors.OpPrereqError("Selection filter does not match any instances")
536

    
537
  # second, if requested, ask for an OS
538
  if opts.select_os is True:
539
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
540
    result = SubmitOpCode(op)
541

    
542
    if not result:
543
      ToStdout("Can't get the OS list")
544
      return 1
545

    
546
    ToStdout("Available OS templates:")
547
    number = 0
548
    choices = []
549
    for entry in result:
550
      ToStdout("%3s: %s", number, entry[0])
551
      choices.append(("%s" % number, entry[0], entry[0]))
552
      number = number + 1
553

    
554
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
555
    selected = AskUser("Enter OS template number (or x to abort):",
556
                       choices)
557

    
558
    if selected == 'exit':
559
      ToStderr("User aborted reinstall, exiting")
560
      return 1
561

    
562
    os_name = selected
563
  else:
564
    os_name = opts.os
565

    
566
  # third, get confirmation: multi-reinstall requires --force-multi
567
  # *and* --force, single-reinstall just --force
568
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
569
  if multi_on:
570
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
571
    if not ((opts.force_multi and opts.force) or
572
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
573
      return 1
574
  else:
575
    if not opts.force:
576
      usertext = ("This will reinstall the instance %s and remove"
577
                  " all data. Continue?") % inames[0]
578
      if not AskUser(usertext):
579
        return 1
580

    
581
  jex = JobExecutor(verbose=multi_on)
582
  for instance_name in inames:
583
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
584
                                     os_type=os_name)
585
    jex.QueueJob(instance_name, op)
586

    
587
  jex.WaitOrShow(not opts.submit_only)
588
  return 0
589

    
590

    
591
def RemoveInstance(opts, args):
592
  """Remove an instance.
593

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

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

    
606
  if not force:
607
    _EnsureInstancesExist(cl, [instance_name])
608

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

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

    
620

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

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

    
631
  """
632
  op = opcodes.OpRenameInstance(instance_name=args[0],
633
                                new_name=args[1],
634
                                ignore_ip=opts.ignore_ip)
635
  SubmitOrSend(op, opts)
636
  return 0
637

    
638

    
639
def ActivateDisks(opts, args):
640
  """Activate an instance's disks.
641

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

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

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

    
662

    
663
def DeactivateDisks(opts, args):
664
  """Deactivate an instance's disks.
665

    
666
  This function takes the instance name, looks for its primary node
667
  and the tries to shutdown its block devices on that node.
668

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

    
675
  """
676
  instance_name = args[0]
677
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
678
  SubmitOrSend(op, opts)
679
  return 0
680

    
681

    
682
def RecreateDisks(opts, args):
683
  """Recreate an instance's disks.
684

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

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

    
702
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
703
                                       disks=opts.disks)
704
  SubmitOrSend(op, opts)
705
  return 0
706

    
707

    
708
def GrowDisk(opts, args):
709
  """Grow an instance's disks.
710

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

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

    
731

    
732
def StartupInstance(opts, args):
733
  """Startup instances.
734

    
735
  Depending on the options given, this will start one or more
736
  instances.
737

    
738
  @param opts: the command line options selected by the user
739
  @type args: list
740
  @param args: the instance or node names based on which we
741
      create the final selection (in conjunction with the
742
      opts argument)
743
  @rtype: int
744
  @return: the desired exit code
745

    
746
  """
747
  cl = GetClient()
748
  if opts.multi_mode is None:
749
    opts.multi_mode = _SHUTDOWN_INSTANCES
750
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
751
  if not inames:
752
    raise errors.OpPrereqError("Selection filter does not match any instances")
753
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
754
  if not (opts.force_multi or not multi_on
755
          or _ConfirmOperation(inames, "startup")):
756
    return 1
757
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
758
  for name in inames:
759
    op = opcodes.OpStartupInstance(instance_name=name,
760
                                   force=opts.force)
761
    # do not add these parameters to the opcode unless they're defined
762
    if opts.hvparams:
763
      op.hvparams = opts.hvparams
764
    if opts.beparams:
765
      op.beparams = opts.beparams
766
    jex.QueueJob(name, op)
767
  jex.WaitOrShow(not opts.submit_only)
768
  return 0
769

    
770

    
771
def RebootInstance(opts, args):
772
  """Reboot instance(s).
773

    
774
  Depending on the parameters given, this will reboot one or more
775
  instances.
776

    
777
  @param opts: the command line options selected by the user
778
  @type args: list
779
  @param args: the instance or node names based on which we
780
      create the final selection (in conjunction with the
781
      opts argument)
782
  @rtype: int
783
  @return: the desired exit code
784

    
785
  """
786
  cl = GetClient()
787
  if opts.multi_mode is None:
788
    opts.multi_mode = _SHUTDOWN_INSTANCES
789
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
790
  if not inames:
791
    raise errors.OpPrereqError("Selection filter does not match any instances")
792
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
793
  if not (opts.force_multi or not multi_on
794
          or _ConfirmOperation(inames, "reboot")):
795
    return 1
796
  jex = JobExecutor(verbose=multi_on, cl=cl)
797
  for name in inames:
798
    op = opcodes.OpRebootInstance(instance_name=name,
799
                                  reboot_type=opts.reboot_type,
800
                                  ignore_secondaries=opts.ignore_secondaries)
801
    jex.QueueJob(name, op)
802
  jex.WaitOrShow(not opts.submit_only)
803
  return 0
804

    
805

    
806
def ShutdownInstance(opts, args):
807
  """Shutdown an instance.
808

    
809
  @param opts: the command line options selected by the user
810
  @type args: list
811
  @param args: the instance or node names based on which we
812
      create the final selection (in conjunction with the
813
      opts argument)
814
  @rtype: int
815
  @return: the desired exit code
816

    
817
  """
818
  cl = GetClient()
819
  if opts.multi_mode is None:
820
    opts.multi_mode = _SHUTDOWN_INSTANCES
821
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
822
  if not inames:
823
    raise errors.OpPrereqError("Selection filter does not match any instances")
824
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
825
  if not (opts.force_multi or not multi_on
826
          or _ConfirmOperation(inames, "shutdown")):
827
    return 1
828

    
829
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
830
  for name in inames:
831
    op = opcodes.OpShutdownInstance(instance_name=name)
832
    jex.QueueJob(name, op)
833
  jex.WaitOrShow(not opts.submit_only)
834
  return 0
835

    
836

    
837
def ReplaceDisks(opts, args):
838
  """Replace the disks of an instance
839

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

    
846
  """
847
  instance_name = args[0]
848
  new_2ndary = opts.dst_node
849
  iallocator = opts.iallocator
850
  if opts.disks is None:
851
    disks = []
852
  else:
853
    try:
854
      disks = [int(i) for i in opts.disks.split(",")]
855
    except ValueError, err:
856
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
857
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
858
         new_2ndary is not None, iallocator is not None].count(True)
859
  if cnt != 1:
860
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
861
                               " options must be passed")
862
  elif opts.on_primary:
863
    mode = constants.REPLACE_DISK_PRI
864
  elif opts.on_secondary:
865
    mode = constants.REPLACE_DISK_SEC
866
  elif opts.auto:
867
    mode = constants.REPLACE_DISK_AUTO
868
    if disks:
869
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
870
                                 " mode")
871
  elif new_2ndary is not None or iallocator is not None:
872
    # replace secondary
873
    mode = constants.REPLACE_DISK_CHG
874

    
875
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
876
                              remote_node=new_2ndary, mode=mode,
877
                              iallocator=iallocator)
878
  SubmitOrSend(op, opts)
879
  return 0
880

    
881

    
882
def FailoverInstance(opts, args):
883
  """Failover an instance.
884

    
885
  The failover is done by shutting it down on its present node and
886
  starting it on the secondary.
887

    
888
  @param opts: the command line options selected by the user
889
  @type args: list
890
  @param args: should contain only one element, the instance name
891
  @rtype: int
892
  @return: the desired exit code
893

    
894
  """
895
  cl = GetClient()
896
  instance_name = args[0]
897
  force = opts.force
898

    
899
  if not force:
900
    _EnsureInstancesExist(cl, [instance_name])
901

    
902
    usertext = ("Failover will happen to image %s."
903
                " This requires a shutdown of the instance. Continue?" %
904
                (instance_name,))
905
    if not AskUser(usertext):
906
      return 1
907

    
908
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
909
                                  ignore_consistency=opts.ignore_consistency)
910
  SubmitOrSend(op, opts, cl=cl)
911
  return 0
912

    
913

    
914
def MigrateInstance(opts, args):
915
  """Migrate an instance.
916

    
917
  The migrate is done without shutdown.
918

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

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

    
930
  if not force:
931
    _EnsureInstancesExist(cl, [instance_name])
932

    
933
    if opts.cleanup:
934
      usertext = ("Instance %s will be recovered from a failed migration."
935
                  " Note that the migration procedure (including cleanup)" %
936
                  (instance_name,))
937
    else:
938
      usertext = ("Instance %s will be migrated. Note that migration" %
939
                  (instance_name,))
940
    usertext += (" is **experimental** in this version."
941
                " This might impact the instance if anything goes wrong."
942
                " Continue?")
943
    if not AskUser(usertext):
944
      return 1
945

    
946
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
947
                                 cleanup=opts.cleanup)
948
  SubmitOpCode(op, cl=cl)
949
  return 0
950

    
951

    
952
def MoveInstance(opts, args):
953
  """Move an instance.
954

    
955
  @param opts: the command line options selected by the user
956
  @type args: list
957
  @param args: should contain only one element, the instance name
958
  @rtype: int
959
  @return: the desired exit code
960

    
961
  """
962
  cl = GetClient()
963
  instance_name = args[0]
964
  force = opts.force
965

    
966
  if not force:
967
    usertext = ("Instance %s will be moved."
968
                " This requires a shutdown of the instance. Continue?" %
969
                (instance_name,))
970
    if not AskUser(usertext):
971
      return 1
972

    
973
  op = opcodes.OpMoveInstance(instance_name=instance_name,
974
                              target_node=opts.node)
975
  SubmitOrSend(op, opts, cl=cl)
976
  return 0
977

    
978

    
979
def ConnectToInstanceConsole(opts, args):
980
  """Connect to the console of an instance.
981

    
982
  @param opts: the command line options selected by the user
983
  @type args: list
984
  @param args: should contain only one element, the instance name
985
  @rtype: int
986
  @return: the desired exit code
987

    
988
  """
989
  instance_name = args[0]
990

    
991
  op = opcodes.OpConnectConsole(instance_name=instance_name)
992
  cmd = SubmitOpCode(op)
993

    
994
  if opts.show_command:
995
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
996
  else:
997
    try:
998
      os.execvp(cmd[0], cmd)
999
    finally:
1000
      ToStderr("Can't run console command %s with arguments:\n'%s'",
1001
               cmd[0], " ".join(cmd))
1002
      os._exit(1)
1003

    
1004

    
1005
def _FormatLogicalID(dev_type, logical_id):
1006
  """Formats the logical_id of a disk.
1007

    
1008
  """
1009
  if dev_type == constants.LD_DRBD8:
1010
    node_a, node_b, port, minor_a, minor_b, key = logical_id
1011
    data = [
1012
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
1013
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
1014
      ("port", port),
1015
      ("auth key", key),
1016
      ]
1017
  elif dev_type == constants.LD_LV:
1018
    vg_name, lv_name = logical_id
1019
    data = ["%s/%s" % (vg_name, lv_name)]
1020
  else:
1021
    data = [str(logical_id)]
1022

    
1023
  return data
1024

    
1025

    
1026
def _FormatBlockDevInfo(idx, top_level, dev, static):
1027
  """Show block device information.
1028

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

    
1032
  @type idx: int
1033
  @param idx: the index of the current disk
1034
  @type top_level: boolean
1035
  @param top_level: if this a top-level disk?
1036
  @type dev: dict
1037
  @param dev: dictionary with disk information
1038
  @type static: boolean
1039
  @param static: wheter the device information doesn't contain
1040
      runtime information but only static data
1041
  @return: a list of either strings, tuples or lists
1042
      (which should be formatted at a higher indent level)
1043

    
1044
  """
1045
  def helper(dtype, status):
1046
    """Format one line for physical device status.
1047

    
1048
    @type dtype: str
1049
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1050
    @type status: tuple
1051
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1052
    @return: the string representing the status
1053

    
1054
    """
1055
    if not status:
1056
      return "not active"
1057
    txt = ""
1058
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1059
    if major is None:
1060
      major_string = "N/A"
1061
    else:
1062
      major_string = str(major)
1063

    
1064
    if minor is None:
1065
      minor_string = "N/A"
1066
    else:
1067
      minor_string = str(minor)
1068

    
1069
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1070
    if dtype in (constants.LD_DRBD8, ):
1071
      if syncp is not None:
1072
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1073
        if estt:
1074
          sync_text += " ETA %ds" % estt
1075
        else:
1076
          sync_text += " ETA unknown"
1077
      else:
1078
        sync_text = "in sync"
1079
      if degr:
1080
        degr_text = "*DEGRADED*"
1081
      else:
1082
        degr_text = "ok"
1083
      if ldisk_status == constants.LDS_FAULTY:
1084
        ldisk_text = " *MISSING DISK*"
1085
      elif ldisk_status == constants.LDS_UNKNOWN:
1086
        ldisk_text = " *UNCERTAIN STATE*"
1087
      else:
1088
        ldisk_text = ""
1089
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1090
    elif dtype == constants.LD_LV:
1091
      if ldisk_status == constants.LDS_FAULTY:
1092
        ldisk_text = " *FAILED* (failed drive?)"
1093
      else:
1094
        ldisk_text = ""
1095
      txt += ldisk_text
1096
    return txt
1097

    
1098
  # the header
1099
  if top_level:
1100
    if dev["iv_name"] is not None:
1101
      txt = dev["iv_name"]
1102
    else:
1103
      txt = "disk %d" % idx
1104
  else:
1105
    txt = "child %d" % idx
1106
  if isinstance(dev["size"], int):
1107
    nice_size = utils.FormatUnit(dev["size"], "h")
1108
  else:
1109
    nice_size = dev["size"]
1110
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1111
  data = []
1112
  if top_level:
1113
    data.append(("access mode", dev["mode"]))
1114
  if dev["logical_id"] is not None:
1115
    try:
1116
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1117
    except ValueError:
1118
      l_id = [str(dev["logical_id"])]
1119
    if len(l_id) == 1:
1120
      data.append(("logical_id", l_id[0]))
1121
    else:
1122
      data.extend(l_id)
1123
  elif dev["physical_id"] is not None:
1124
    data.append("physical_id:")
1125
    data.append([dev["physical_id"]])
1126
  if not static:
1127
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1128
  if dev["sstatus"] and not static:
1129
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1130

    
1131
  if dev["children"]:
1132
    data.append("child devices:")
1133
    for c_idx, child in enumerate(dev["children"]):
1134
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1135
  d1.append(data)
1136
  return d1
1137

    
1138

    
1139
def _FormatList(buf, data, indent_level):
1140
  """Formats a list of data at a given indent level.
1141

    
1142
  If the element of the list is:
1143
    - a string, it is simply formatted as is
1144
    - a tuple, it will be split into key, value and the all the
1145
      values in a list will be aligned all at the same start column
1146
    - a list, will be recursively formatted
1147

    
1148
  @type buf: StringIO
1149
  @param buf: the buffer into which we write the output
1150
  @param data: the list to format
1151
  @type indent_level: int
1152
  @param indent_level: the indent level to format at
1153

    
1154
  """
1155
  max_tlen = max([len(elem[0]) for elem in data
1156
                 if isinstance(elem, tuple)] or [0])
1157
  for elem in data:
1158
    if isinstance(elem, basestring):
1159
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1160
    elif isinstance(elem, tuple):
1161
      key, value = elem
1162
      spacer = "%*s" % (max_tlen - len(key), "")
1163
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1164
    elif isinstance(elem, list):
1165
      _FormatList(buf, elem, indent_level+1)
1166

    
1167

    
1168
def ShowInstanceConfig(opts, args):
1169
  """Compute instance run-time status.
1170

    
1171
  @param opts: the command line options selected by the user
1172
  @type args: list
1173
  @param args: either an empty list, and then we query all
1174
      instances, or should contain a list of instance names
1175
  @rtype: int
1176
  @return: the desired exit code
1177

    
1178
  """
1179
  if not args and not opts.show_all:
1180
    ToStderr("No instance selected."
1181
             " Please pass in --all if you want to query all instances.\n"
1182
             "Note that this can take a long time on a big cluster.")
1183
    return 1
1184
  elif args and opts.show_all:
1185
    ToStderr("Cannot use --all if you specify instance names.")
1186
    return 1
1187

    
1188
  retcode = 0
1189
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1190
  result = SubmitOpCode(op)
1191
  if not result:
1192
    ToStdout("No instances.")
1193
    return 1
1194

    
1195
  buf = StringIO()
1196
  retcode = 0
1197
  for instance_name in result:
1198
    instance = result[instance_name]
1199
    buf.write("Instance name: %s\n" % instance["name"])
1200
    buf.write("Serial number: %s\n" % instance["serial_no"])
1201
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1202
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1203
    buf.write("State: configured to be %s" % instance["config_state"])
1204
    if not opts.static:
1205
      buf.write(", actual state is %s" % instance["run_state"])
1206
    buf.write("\n")
1207
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1208
    ##          instance["auto_balance"])
1209
    buf.write("  Nodes:\n")
1210
    buf.write("    - primary: %s\n" % instance["pnode"])
1211
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1212
    buf.write("  Operating system: %s\n" % instance["os"])
1213
    if instance.has_key("network_port"):
1214
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1215
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1216

    
1217
    # custom VNC console information
1218
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1219
                                                 None)
1220
    if vnc_bind_address:
1221
      port = instance["network_port"]
1222
      display = int(port) - constants.VNC_BASE_PORT
1223
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1224
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1225
                                                   port,
1226
                                                   display)
1227
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1228
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1229
                             (vnc_bind_address, port,
1230
                              instance["pnode"], display))
1231
      else:
1232
        # vnc bind address is a file
1233
        vnc_console_port = "%s:%s" % (instance["pnode"],
1234
                                      vnc_bind_address)
1235
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1236

    
1237
    for key in instance["hv_actual"]:
1238
      if key in instance["hv_instance"]:
1239
        val = instance["hv_instance"][key]
1240
      else:
1241
        val = "default (%s)" % instance["hv_actual"][key]
1242
      buf.write("    - %s: %s\n" % (key, val))
1243
    buf.write("  Hardware:\n")
1244
    buf.write("    - VCPUs: %d\n" %
1245
              instance["be_actual"][constants.BE_VCPUS])
1246
    buf.write("    - memory: %dMiB\n" %
1247
              instance["be_actual"][constants.BE_MEMORY])
1248
    buf.write("    - NICs:\n")
1249
    for idx, (mac, ip, mode, link) in enumerate(instance["nics"]):
1250
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1251
                (idx, mac, ip, mode, link))
1252
    buf.write("  Disks:\n")
1253

    
1254
    for idx, device in enumerate(instance["disks"]):
1255
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1256

    
1257
  ToStdout(buf.getvalue().rstrip('\n'))
1258
  return retcode
1259

    
1260

    
1261
def SetInstanceParams(opts, args):
1262
  """Modifies an instance.
1263

    
1264
  All parameters take effect only at the next restart of the instance.
1265

    
1266
  @param opts: the command line options selected by the user
1267
  @type args: list
1268
  @param args: should contain only one element, the instance name
1269
  @rtype: int
1270
  @return: the desired exit code
1271

    
1272
  """
1273
  if not (opts.nics or opts.disks or
1274
          opts.hvparams or opts.beparams):
1275
    ToStderr("Please give at least one of the parameters.")
1276
    return 1
1277

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

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

    
1286
  for param in opts.hvparams:
1287
    if isinstance(opts.hvparams[param], basestring):
1288
      if opts.hvparams[param].lower() == "default":
1289
        opts.hvparams[param] = constants.VALUE_DEFAULT
1290

    
1291
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1292
                      allowed_values=[constants.VALUE_DEFAULT])
1293

    
1294
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1295
    try:
1296
      nic_op = int(nic_op)
1297
      opts.nics[idx] = (nic_op, nic_dict)
1298
    except ValueError:
1299
      pass
1300

    
1301
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1302
    try:
1303
      disk_op = int(disk_op)
1304
      opts.disks[idx] = (disk_op, disk_dict)
1305
    except ValueError:
1306
      pass
1307
    if disk_op == constants.DDM_ADD:
1308
      if 'size' not in disk_dict:
1309
        raise errors.OpPrereqError("Missing required parameter 'size'")
1310
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1311

    
1312
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1313
                                   nics=opts.nics,
1314
                                   disks=opts.disks,
1315
                                   hvparams=opts.hvparams,
1316
                                   beparams=opts.beparams,
1317
                                   force=opts.force)
1318

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

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

    
1330

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

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

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

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

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

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

    
1357

    
1358
# this is defined separately due to readability only
1359
add_opts = [
1360
  BACKEND_OPT,
1361
  DISK_OPT,
1362
  DISK_TEMPLATE_OPT,
1363
  FILESTORE_DIR_OPT,
1364
  FILESTORE_DRIVER_OPT,
1365
  HYPERVISOR_OPT,
1366
  IALLOCATOR_OPT,
1367
  NET_OPT,
1368
  NODE_PLACEMENT_OPT,
1369
  NOIPCHECK_OPT,
1370
  NONICS_OPT,
1371
  NOSTART_OPT,
1372
  NWSYNC_OPT,
1373
  OS_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],
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],
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],
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],
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, mode, link, sda_size, sdb_size, vcpus, serial_no,"
1419
    " hypervisor."
1420
    " The default field"
1421
    " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1422
    ),
1423
  'reinstall': (
1424
    ReinstallInstance, [ArgInstance(min=1)],
1425
    [FORCE_OPT, OS_OPT, m_force_multi, m_node_opt, m_pri_node_opt,
1426
     m_sec_node_opt, m_clust_opt, m_inst_opt, SELECT_OS_OPT, SUBMIT_OPT],
1427
    "[-f] <instance>", "Reinstall a stopped instance"),
1428
  'remove': (
1429
    RemoveInstance, ARGS_ONE_INSTANCE,
1430
    [FORCE_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT],
1431
    "[-f] <instance>", "Shuts down the instance and removes it"),
1432
  'rename': (
1433
    RenameInstance,
1434
    [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1435
    [NOIPCHECK_OPT, SUBMIT_OPT],
1436
    "<instance> <new_name>", "Rename the instance"),
1437
  'replace-disks': (
1438
    ReplaceDisks, ARGS_ONE_INSTANCE,
1439
    [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT,
1440
     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT],
1441
    "[-s|-p|-n NODE|-I NAME] <instance>",
1442
    "Replaces all disks for the instance"),
1443
  'modify': (
1444
    SetInstanceParams, ARGS_ONE_INSTANCE,
1445
    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT],
1446
    "<instance>", "Alters the parameters of an instance"),
1447
  'shutdown': (
1448
    ShutdownInstance, [ArgInstance(min=1)],
1449
    [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1450
     m_inst_opt, m_force_multi, SUBMIT_OPT],
1451
    "<instance>", "Stops an instance"),
1452
  'startup': (
1453
    StartupInstance, [ArgInstance(min=1)],
1454
    [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt,
1455
     m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1456
     BACKEND_OPT],
1457
    "<instance>", "Starts an instance"),
1458
  'reboot': (
1459
    RebootInstance, [ArgInstance(min=1)],
1460
    [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1461
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT],
1462
    "<instance>", "Reboots an instance"),
1463
  'activate-disks': (
1464
    ActivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, IGNORE_SIZE_OPT],
1465
    "<instance>", "Activate an instance's disks"),
1466
  'deactivate-disks': (
1467
    DeactivateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT],
1468
    "<instance>", "Deactivate an instance's disks"),
1469
  'recreate-disks': (
1470
    RecreateDisks, ARGS_ONE_INSTANCE, [SUBMIT_OPT, DISKIDX_OPT],
1471
    "<instance>", "Recreate an instance's disks"),
1472
  'grow-disk': (
1473
    GrowDisk,
1474
    [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1475
     ArgUnknown(min=1, max=1)],
1476
    [SUBMIT_OPT, NWSYNC_OPT],
1477
    "<instance> <disk> <size>", "Grow an instance's disk"),
1478
  'list-tags': (
1479
    ListTags, ARGS_ONE_INSTANCE, [],
1480
    "<instance_name>", "List the tags of the given instance"),
1481
  'add-tags': (
1482
    AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1483
    [TAG_SRC_OPT],
1484
    "<instance_name> tag...", "Add tags to the given instance"),
1485
  'remove-tags': (
1486
    RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1487
    [TAG_SRC_OPT],
1488
    "<instance_name> tag...", "Remove tags from given instance"),
1489
  }
1490

    
1491
#: dictionary with aliases for commands
1492
aliases = {
1493
  'activate_block_devs': 'activate-disks',
1494
  'replace_disks': 'replace-disks',
1495
  'start': 'startup',
1496
  'stop': 'shutdown',
1497
  }
1498

    
1499

    
1500
if __name__ == '__main__':
1501
  sys.exit(GenericMain(commands, aliases=aliases,
1502
                       override={"tag_type": constants.TAG_INSTANCE}))