Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 25a8792c

History | View | Annotate | Download (50.1 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 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_NODES_BOTH_BY_TAGS = "nodes-by-tags"
46
_SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
47
_SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
48
_SHUTDOWN_INSTANCES = "instances"
49
_SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
50

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

    
56

    
57
_VALUE_TRUE = "true"
58

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

    
64

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

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

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

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

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

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

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

    
141
  return inames
142

    
143

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

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

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

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

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

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

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

    
181

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

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

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

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

    
203

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

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

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

    
233

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

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

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

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

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

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

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

    
328
  data = GenerateTable(separator=opts.separator, headers=headers,
329
                       fields=selected_fields, unitfields=unitfields,
330
                       numfields=numfields, data=output, units=opts.units)
331

    
332
  for line in data:
333
    ToStdout(line)
334

    
335
  return 0
336

    
337

    
338
def AddInstance(opts, args):
339
  """Add an instance to the cluster.
340

    
341
  This is just a wrapper over GenericInstanceCreate.
342

    
343
  """
344
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
345

    
346

    
347
def BatchCreate(opts, args):
348
  """Create instances using a definition file.
349

    
350
  This function reads a json file with instances defined
351
  in the form::
352

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

    
365
  Note that I{primary_node} and I{secondary_node} have precedence over
366
  I{iallocator}.
367

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

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

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

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

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

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

    
426
  jex = JobExecutor(opts=opts)
427

    
428
  # Iterate over the instances and do:
429
  #  * Populate the specs with default value
430
  #  * Validate the instance specs
431
  i_names = utils.NiceSort(instance_data.keys())
432
  for name in i_names:
433
    specs = instance_data[name]
434
    specs = _PopulateWithDefaults(specs)
435
    _Validate(specs)
436

    
437
    hypervisor = specs['hypervisor']
438
    hvparams = specs['hvparams']
439

    
440
    disks = []
441
    for elem in specs['disk_size']:
442
      try:
443
        size = utils.ParseUnit(elem)
444
      except (TypeError, ValueError), err:
445
        raise errors.OpPrereqError("Invalid disk size '%s' for"
446
                                   " instance %s: %s" %
447
                                   (elem, name, err), errors.ECODE_INVAL)
448
      disks.append({"size": size})
449

    
450
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
451
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
452

    
453
    tmp_nics = []
454
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
455
      if field in specs:
456
        if not tmp_nics:
457
          tmp_nics.append({})
458
        tmp_nics[0][field] = specs[field]
459

    
460
    if specs['nics'] is not None and tmp_nics:
461
      raise errors.OpPrereqError("'nics' list incompatible with using"
462
                                 " individual nic fields as well",
463
                                 errors.ECODE_INVAL)
464
    elif specs['nics'] is not None:
465
      tmp_nics = specs['nics']
466
    elif not tmp_nics:
467
      tmp_nics = [{}]
468

    
469
    op = opcodes.OpCreateInstance(instance_name=name,
470
                                  disks=disks,
471
                                  disk_template=specs['template'],
472
                                  mode=constants.INSTANCE_CREATE,
473
                                  os_type=specs['os'],
474
                                  force_variant=opts.force_variant,
475
                                  pnode=specs['primary_node'],
476
                                  snode=specs['secondary_node'],
477
                                  nics=tmp_nics,
478
                                  start=specs['start'],
479
                                  ip_check=specs['ip_check'],
480
                                  name_check=specs['name_check'],
481
                                  wait_for_sync=True,
482
                                  iallocator=specs['iallocator'],
483
                                  hypervisor=hypervisor,
484
                                  hvparams=hvparams,
485
                                  beparams=specs['backend'],
486
                                  file_storage_dir=specs['file_storage_dir'],
487
                                  file_driver=specs['file_driver'])
488

    
489
    jex.QueueJob(name, op)
490
  # we never want to wait, just show the submitted job IDs
491
  jex.WaitOrShow(False)
492

    
493
  return 0
494

    
495

    
496
def ReinstallInstance(opts, args):
497
  """Reinstall an instance.
498

    
499
  @param opts: the command line options selected by the user
500
  @type args: list
501
  @param args: should contain only one element, the name of the
502
      instance to be reinstalled
503
  @rtype: int
504
  @return: the desired exit code
505

    
506
  """
507
  # first, compute the desired name list
508
  if opts.multi_mode is None:
509
    opts.multi_mode = _SHUTDOWN_INSTANCES
510

    
511
  inames = _ExpandMultiNames(opts.multi_mode, args)
512
  if not inames:
513
    raise errors.OpPrereqError("Selection filter does not match any instances",
514
                               errors.ECODE_INVAL)
515

    
516
  # second, if requested, ask for an OS
517
  if opts.select_os is True:
518
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid", "variants"],
519
                              names=[])
520
    result = SubmitOpCode(op, opts=opts)
521

    
522
    if not result:
523
      ToStdout("Can't get the OS list")
524
      return 1
525

    
526
    ToStdout("Available OS templates:")
527
    number = 0
528
    choices = []
529
    for (name, valid, variants) in result:
530
      if valid:
531
        for entry in CalculateOSNames(name, variants):
532
          ToStdout("%3s: %s", number, entry)
533
          choices.append(("%s" % number, entry, entry))
534
          number += 1
535

    
536
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
537
    selected = AskUser("Enter OS template number (or x to abort):",
538
                       choices)
539

    
540
    if selected == 'exit':
541
      ToStderr("User aborted reinstall, exiting")
542
      return 1
543

    
544
    os_name = selected
545
  else:
546
    os_name = opts.os
547

    
548
  # third, get confirmation: multi-reinstall requires --force-multi
549
  # *and* --force, single-reinstall just --force
550
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
551
  if multi_on:
552
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
553
    if not ((opts.force_multi and opts.force) or
554
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
555
      return 1
556
  else:
557
    if not opts.force:
558
      usertext = ("This will reinstall the instance %s and remove"
559
                  " all data. Continue?") % inames[0]
560
      if not AskUser(usertext):
561
        return 1
562

    
563
  jex = JobExecutor(verbose=multi_on, opts=opts)
564
  for instance_name in inames:
565
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
566
                                     os_type=os_name,
567
                                     force_variant=opts.force_variant)
568
    jex.QueueJob(instance_name, op)
569

    
570
  jex.WaitOrShow(not opts.submit_only)
571
  return 0
572

    
573

    
574
def RemoveInstance(opts, args):
575
  """Remove an instance.
576

    
577
  @param opts: the command line options selected by the user
578
  @type args: list
579
  @param args: should contain only one element, the name of
580
      the instance to be removed
581
  @rtype: int
582
  @return: the desired exit code
583

    
584
  """
585
  instance_name = args[0]
586
  force = opts.force
587
  cl = GetClient()
588

    
589
  if not force:
590
    _EnsureInstancesExist(cl, [instance_name])
591

    
592
    usertext = ("This will remove the volumes of the instance %s"
593
                " (including mirrors), thus removing all the data"
594
                " of the instance. Continue?") % instance_name
595
    if not AskUser(usertext):
596
      return 1
597

    
598
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
599
                                ignore_failures=opts.ignore_failures,
600
                                shutdown_timeout=opts.shutdown_timeout)
601
  SubmitOrSend(op, opts, cl=cl)
602
  return 0
603

    
604

    
605
def RenameInstance(opts, args):
606
  """Rename an instance.
607

    
608
  @param opts: the command line options selected by the user
609
  @type args: list
610
  @param args: should contain two elements, the old and the
611
      new instance names
612
  @rtype: int
613
  @return: the desired exit code
614

    
615
  """
616
  op = opcodes.OpRenameInstance(instance_name=args[0],
617
                                new_name=args[1],
618
                                ignore_ip=not opts.ip_check)
619
  SubmitOrSend(op, opts)
620
  return 0
621

    
622

    
623
def ActivateDisks(opts, args):
624
  """Activate an instance's disks.
625

    
626
  This serves two purposes:
627
    - it allows (as long as the instance is not running)
628
      mounting the disks and modifying them from the node
629
    - it repairs inactive secondary drbds
630

    
631
  @param opts: the command line options selected by the user
632
  @type args: list
633
  @param args: should contain only one element, the instance name
634
  @rtype: int
635
  @return: the desired exit code
636

    
637
  """
638
  instance_name = args[0]
639
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
640
                                       ignore_size=opts.ignore_size)
641
  disks_info = SubmitOrSend(op, opts)
642
  for host, iname, nname in disks_info:
643
    ToStdout("%s:%s:%s", host, iname, nname)
644
  return 0
645

    
646

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

    
650
  This function takes the instance name, looks for its primary node
651
  and the tries to shutdown its block devices on that node.
652

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

    
659
  """
660
  instance_name = args[0]
661
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
662
  SubmitOrSend(op, opts)
663
  return 0
664

    
665

    
666
def RecreateDisks(opts, args):
667
  """Recreate an instance's disks.
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
  if opts.disks:
678
    try:
679
      opts.disks = [int(v) for v in opts.disks.split(",")]
680
    except (ValueError, TypeError), err:
681
      ToStderr("Invalid disks value: %s" % str(err))
682
      return 1
683
  else:
684
    opts.disks = []
685

    
686
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
687
                                       disks=opts.disks)
688
  SubmitOrSend(op, opts)
689
  return 0
690

    
691

    
692
def GrowDisk(opts, args):
693
  """Grow an instance's disks.
694

    
695
  @param opts: the command line options selected by the user
696
  @type args: list
697
  @param args: should contain two elements, the instance name
698
      whose disks we grow and the disk name, e.g. I{sda}
699
  @rtype: int
700
  @return: the desired exit code
701

    
702
  """
703
  instance = args[0]
704
  disk = args[1]
705
  try:
706
    disk = int(disk)
707
  except (TypeError, ValueError), err:
708
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err),
709
                               errors.ECODE_INVAL)
710
  amount = utils.ParseUnit(args[2])
711
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
712
                          wait_for_sync=opts.wait_for_sync)
713
  SubmitOrSend(op, opts)
714
  return 0
715

    
716

    
717
def _StartupInstance(name, opts):
718
  """Startup instances.
719

    
720
  This returns the opcode to start an instance, and its decorator will
721
  wrap this into a loop starting all desired instances.
722

    
723
  @param name: the name of the instance to act on
724
  @param opts: the command line options selected by the user
725
  @return: the opcode needed for the operation
726

    
727
  """
728
  op = opcodes.OpStartupInstance(instance_name=name,
729
                                 force=opts.force)
730
  # do not add these parameters to the opcode unless they're defined
731
  if opts.hvparams:
732
    op.hvparams = opts.hvparams
733
  if opts.beparams:
734
    op.beparams = opts.beparams
735
  return op
736

    
737

    
738
def _RebootInstance(name, opts):
739
  """Reboot instance(s).
740

    
741
  This returns the opcode to reboot an instance, and its decorator
742
  will wrap this into a loop rebooting all desired instances.
743

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

    
748
  """
749
  return opcodes.OpRebootInstance(instance_name=name,
750
                                  reboot_type=opts.reboot_type,
751
                                  ignore_secondaries=opts.ignore_secondaries,
752
                                  shutdown_timeout=opts.shutdown_timeout)
753

    
754

    
755
def _ShutdownInstance(name, opts):
756
  """Shutdown an instance.
757

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

    
761
  @param name: the name of the instance to act on
762
  @param opts: the command line options selected by the user
763
  @return: the opcode needed for the operation
764

    
765
  """
766
  return opcodes.OpShutdownInstance(instance_name=name,
767
                                    timeout=opts.timeout)
768

    
769

    
770
def ReplaceDisks(opts, args):
771
  """Replace the disks of an instance
772

    
773
  @param opts: the command line options selected by the user
774
  @type args: list
775
  @param args: should contain only one element, the instance name
776
  @rtype: int
777
  @return: the desired exit code
778

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

    
808
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
809
                              remote_node=new_2ndary, mode=mode,
810
                              iallocator=iallocator,
811
                              early_release=opts.early_release)
812
  SubmitOrSend(op, opts)
813
  return 0
814

    
815

    
816
def FailoverInstance(opts, args):
817
  """Failover an instance.
818

    
819
  The failover is done by shutting it down on its present node and
820
  starting it on the secondary.
821

    
822
  @param opts: the command line options selected by the user
823
  @type args: list
824
  @param args: should contain only one element, the instance name
825
  @rtype: int
826
  @return: the desired exit code
827

    
828
  """
829
  cl = GetClient()
830
  instance_name = args[0]
831
  force = opts.force
832

    
833
  if not force:
834
    _EnsureInstancesExist(cl, [instance_name])
835

    
836
    usertext = ("Failover will happen to image %s."
837
                " This requires a shutdown of the instance. Continue?" %
838
                (instance_name,))
839
    if not AskUser(usertext):
840
      return 1
841

    
842
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
843
                                  ignore_consistency=opts.ignore_consistency,
844
                                  shutdown_timeout=opts.shutdown_timeout)
845
  SubmitOrSend(op, opts, cl=cl)
846
  return 0
847

    
848

    
849
def MigrateInstance(opts, args):
850
  """Migrate an instance.
851

    
852
  The migrate is done without shutdown.
853

    
854
  @param opts: the command line options selected by the user
855
  @type args: list
856
  @param args: should contain only one element, the instance name
857
  @rtype: int
858
  @return: the desired exit code
859

    
860
  """
861
  cl = GetClient()
862
  instance_name = args[0]
863
  force = opts.force
864

    
865
  if not force:
866
    _EnsureInstancesExist(cl, [instance_name])
867

    
868
    if opts.cleanup:
869
      usertext = ("Instance %s will be recovered from a failed migration."
870
                  " Note that the migration procedure (including cleanup)" %
871
                  (instance_name,))
872
    else:
873
      usertext = ("Instance %s will be migrated. Note that migration" %
874
                  (instance_name,))
875
    usertext += (" is **experimental** in this version."
876
                " This might impact the instance if anything goes wrong."
877
                " Continue?")
878
    if not AskUser(usertext):
879
      return 1
880

    
881
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
882
                                 cleanup=opts.cleanup)
883
  SubmitOpCode(op, cl=cl, opts=opts)
884
  return 0
885

    
886

    
887
def MoveInstance(opts, args):
888
  """Move an instance.
889

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

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

    
901
  if not force:
902
    usertext = ("Instance %s will be moved."
903
                " This requires a shutdown of the instance. Continue?" %
904
                (instance_name,))
905
    if not AskUser(usertext):
906
      return 1
907

    
908
  op = opcodes.OpMoveInstance(instance_name=instance_name,
909
                              target_node=opts.node,
910
                              shutdown_timeout=opts.shutdown_timeout)
911
  SubmitOrSend(op, opts, cl=cl)
912
  return 0
913

    
914

    
915
def ConnectToInstanceConsole(opts, args):
916
  """Connect to the console of an instance.
917

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

    
924
  """
925
  instance_name = args[0]
926

    
927
  op = opcodes.OpConnectConsole(instance_name=instance_name)
928
  cmd = SubmitOpCode(op, opts=opts)
929

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

    
940

    
941
def _FormatLogicalID(dev_type, logical_id):
942
  """Formats the logical_id of a disk.
943

    
944
  """
945
  if dev_type == constants.LD_DRBD8:
946
    node_a, node_b, port, minor_a, minor_b, key = logical_id
947
    data = [
948
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
949
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
950
      ("port", port),
951
      ("auth key", key),
952
      ]
953
  elif dev_type == constants.LD_LV:
954
    vg_name, lv_name = logical_id
955
    data = ["%s/%s" % (vg_name, lv_name)]
956
  else:
957
    data = [str(logical_id)]
958

    
959
  return data
960

    
961

    
962
def _FormatBlockDevInfo(idx, top_level, dev, static):
963
  """Show block device information.
964

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

    
968
  @type idx: int
969
  @param idx: the index of the current disk
970
  @type top_level: boolean
971
  @param top_level: if this a top-level disk?
972
  @type dev: dict
973
  @param dev: dictionary with disk information
974
  @type static: boolean
975
  @param static: wheter the device information doesn't contain
976
      runtime information but only static data
977
  @return: a list of either strings, tuples or lists
978
      (which should be formatted at a higher indent level)
979

    
980
  """
981
  def helper(dtype, status):
982
    """Format one line for physical device status.
983

    
984
    @type dtype: str
985
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
986
    @type status: tuple
987
    @param status: a tuple as returned from L{backend.FindBlockDevice}
988
    @return: the string representing the status
989

    
990
    """
991
    if not status:
992
      return "not active"
993
    txt = ""
994
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
995
    if major is None:
996
      major_string = "N/A"
997
    else:
998
      major_string = str(major)
999

    
1000
    if minor is None:
1001
      minor_string = "N/A"
1002
    else:
1003
      minor_string = str(minor)
1004

    
1005
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1006
    if dtype in (constants.LD_DRBD8, ):
1007
      if syncp is not None:
1008
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1009
        if estt:
1010
          sync_text += " ETA %ds" % estt
1011
        else:
1012
          sync_text += " ETA unknown"
1013
      else:
1014
        sync_text = "in sync"
1015
      if degr:
1016
        degr_text = "*DEGRADED*"
1017
      else:
1018
        degr_text = "ok"
1019
      if ldisk_status == constants.LDS_FAULTY:
1020
        ldisk_text = " *MISSING DISK*"
1021
      elif ldisk_status == constants.LDS_UNKNOWN:
1022
        ldisk_text = " *UNCERTAIN STATE*"
1023
      else:
1024
        ldisk_text = ""
1025
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1026
    elif dtype == constants.LD_LV:
1027
      if ldisk_status == constants.LDS_FAULTY:
1028
        ldisk_text = " *FAILED* (failed drive?)"
1029
      else:
1030
        ldisk_text = ""
1031
      txt += ldisk_text
1032
    return txt
1033

    
1034
  # the header
1035
  if top_level:
1036
    if dev["iv_name"] is not None:
1037
      txt = dev["iv_name"]
1038
    else:
1039
      txt = "disk %d" % idx
1040
  else:
1041
    txt = "child %d" % idx
1042
  if isinstance(dev["size"], int):
1043
    nice_size = utils.FormatUnit(dev["size"], "h")
1044
  else:
1045
    nice_size = dev["size"]
1046
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1047
  data = []
1048
  if top_level:
1049
    data.append(("access mode", dev["mode"]))
1050
  if dev["logical_id"] is not None:
1051
    try:
1052
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1053
    except ValueError:
1054
      l_id = [str(dev["logical_id"])]
1055
    if len(l_id) == 1:
1056
      data.append(("logical_id", l_id[0]))
1057
    else:
1058
      data.extend(l_id)
1059
  elif dev["physical_id"] is not None:
1060
    data.append("physical_id:")
1061
    data.append([dev["physical_id"]])
1062
  if not static:
1063
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1064
  if dev["sstatus"] and not static:
1065
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1066

    
1067
  if dev["children"]:
1068
    data.append("child devices:")
1069
    for c_idx, child in enumerate(dev["children"]):
1070
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1071
  d1.append(data)
1072
  return d1
1073

    
1074

    
1075
def _FormatList(buf, data, indent_level):
1076
  """Formats a list of data at a given indent level.
1077

    
1078
  If the element of the list is:
1079
    - a string, it is simply formatted as is
1080
    - a tuple, it will be split into key, value and the all the
1081
      values in a list will be aligned all at the same start column
1082
    - a list, will be recursively formatted
1083

    
1084
  @type buf: StringIO
1085
  @param buf: the buffer into which we write the output
1086
  @param data: the list to format
1087
  @type indent_level: int
1088
  @param indent_level: the indent level to format at
1089

    
1090
  """
1091
  max_tlen = max([len(elem[0]) for elem in data
1092
                 if isinstance(elem, tuple)] or [0])
1093
  for elem in data:
1094
    if isinstance(elem, basestring):
1095
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1096
    elif isinstance(elem, tuple):
1097
      key, value = elem
1098
      spacer = "%*s" % (max_tlen - len(key), "")
1099
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1100
    elif isinstance(elem, list):
1101
      _FormatList(buf, elem, indent_level+1)
1102

    
1103

    
1104
def ShowInstanceConfig(opts, args):
1105
  """Compute instance run-time status.
1106

    
1107
  @param opts: the command line options selected by the user
1108
  @type args: list
1109
  @param args: either an empty list, and then we query all
1110
      instances, or should contain a list of instance names
1111
  @rtype: int
1112
  @return: the desired exit code
1113

    
1114
  """
1115
  if not args and not opts.show_all:
1116
    ToStderr("No instance selected."
1117
             " Please pass in --all if you want to query all instances.\n"
1118
             "Note that this can take a long time on a big cluster.")
1119
    return 1
1120
  elif args and opts.show_all:
1121
    ToStderr("Cannot use --all if you specify instance names.")
1122
    return 1
1123

    
1124
  retcode = 0
1125
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1126
  result = SubmitOpCode(op, opts=opts)
1127
  if not result:
1128
    ToStdout("No instances.")
1129
    return 1
1130

    
1131
  buf = StringIO()
1132
  retcode = 0
1133
  for instance_name in result:
1134
    instance = result[instance_name]
1135
    buf.write("Instance name: %s\n" % instance["name"])
1136
    buf.write("UUID: %s\n" % instance["uuid"])
1137
    buf.write("Serial number: %s\n" % instance["serial_no"])
1138
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1139
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1140
    buf.write("State: configured to be %s" % instance["config_state"])
1141
    if not opts.static:
1142
      buf.write(", actual state is %s" % instance["run_state"])
1143
    buf.write("\n")
1144
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1145
    ##          instance["auto_balance"])
1146
    buf.write("  Nodes:\n")
1147
    buf.write("    - primary: %s\n" % instance["pnode"])
1148
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1149
    buf.write("  Operating system: %s\n" % instance["os"])
1150
    if instance.has_key("network_port"):
1151
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1152
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1153

    
1154
    # custom VNC console information
1155
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1156
                                                 None)
1157
    if vnc_bind_address:
1158
      port = instance["network_port"]
1159
      display = int(port) - constants.VNC_BASE_PORT
1160
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1161
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1162
                                                   port,
1163
                                                   display)
1164
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1165
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1166
                             (vnc_bind_address, port,
1167
                              instance["pnode"], display))
1168
      else:
1169
        # vnc bind address is a file
1170
        vnc_console_port = "%s:%s" % (instance["pnode"],
1171
                                      vnc_bind_address)
1172
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1173

    
1174
    for key in instance["hv_actual"]:
1175
      if key in instance["hv_instance"]:
1176
        val = instance["hv_instance"][key]
1177
      else:
1178
        val = "default (%s)" % instance["hv_actual"][key]
1179
      buf.write("    - %s: %s\n" % (key, val))
1180
    buf.write("  Hardware:\n")
1181
    buf.write("    - VCPUs: %d\n" %
1182
              instance["be_actual"][constants.BE_VCPUS])
1183
    buf.write("    - memory: %dMiB\n" %
1184
              instance["be_actual"][constants.BE_MEMORY])
1185
    buf.write("    - NICs:\n")
1186
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1187
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1188
                (idx, mac, ip, mode, link))
1189
    buf.write("  Disks:\n")
1190

    
1191
    for idx, device in enumerate(instance["disks"]):
1192
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1193

    
1194
  ToStdout(buf.getvalue().rstrip('\n'))
1195
  return retcode
1196

    
1197

    
1198
def SetInstanceParams(opts, args):
1199
  """Modifies an instance.
1200

    
1201
  All parameters take effect only at the next restart of the instance.
1202

    
1203
  @param opts: the command line options selected by the user
1204
  @type args: list
1205
  @param args: should contain only one element, the instance name
1206
  @rtype: int
1207
  @return: the desired exit code
1208

    
1209
  """
1210
  if not (opts.nics or opts.disks or opts.disk_template or
1211
          opts.hvparams or opts.beparams or opts.os):
1212
    ToStderr("Please give at least one of the parameters.")
1213
    return 1
1214

    
1215
  for param in opts.beparams:
1216
    if isinstance(opts.beparams[param], basestring):
1217
      if opts.beparams[param].lower() == "default":
1218
        opts.beparams[param] = constants.VALUE_DEFAULT
1219

    
1220
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1221
                      allowed_values=[constants.VALUE_DEFAULT])
1222

    
1223
  for param in opts.hvparams:
1224
    if isinstance(opts.hvparams[param], basestring):
1225
      if opts.hvparams[param].lower() == "default":
1226
        opts.hvparams[param] = constants.VALUE_DEFAULT
1227

    
1228
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1229
                      allowed_values=[constants.VALUE_DEFAULT])
1230

    
1231
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1232
    try:
1233
      nic_op = int(nic_op)
1234
      opts.nics[idx] = (nic_op, nic_dict)
1235
    except (TypeError, ValueError):
1236
      pass
1237

    
1238
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1239
    try:
1240
      disk_op = int(disk_op)
1241
      opts.disks[idx] = (disk_op, disk_dict)
1242
    except (TypeError, ValueError):
1243
      pass
1244
    if disk_op == constants.DDM_ADD:
1245
      if 'size' not in disk_dict:
1246
        raise errors.OpPrereqError("Missing required parameter 'size'",
1247
                                   errors.ECODE_INVAL)
1248
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1249

    
1250
  if (opts.disk_template and
1251
      opts.disk_template in constants.DTS_NET_MIRROR and
1252
      not opts.node):
1253
    ToStderr("Changing the disk template to a mirrored one requires"
1254
             " specifying a secondary node")
1255
    return 1
1256

    
1257
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1258
                                   nics=opts.nics,
1259
                                   disks=opts.disks,
1260
                                   disk_template=opts.disk_template,
1261
                                   remote_node=opts.node,
1262
                                   hvparams=opts.hvparams,
1263
                                   beparams=opts.beparams,
1264
                                   os_name=opts.os,
1265
                                   force_variant=opts.force_variant,
1266
                                   force=opts.force)
1267

    
1268
  # even if here we process the result, we allow submit only
1269
  result = SubmitOrSend(op, opts)
1270

    
1271
  if result:
1272
    ToStdout("Modified instance %s", args[0])
1273
    for param, data in result:
1274
      ToStdout(" - %-5s -> %s", param, data)
1275
    ToStdout("Please don't forget that most parameters take effect"
1276
             " only at the next start of the instance.")
1277
  return 0
1278

    
1279

    
1280
# multi-instance selection options
1281
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1282
                           help="Do not ask for confirmation when more than"
1283
                           " one instance is affected",
1284
                           action="store_true", default=False)
1285

    
1286
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1287
                            help="Filter by nodes (primary only)",
1288
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1289

    
1290
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1291
                            help="Filter by nodes (secondary only)",
1292
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1293

    
1294
m_node_opt = cli_option("--node", dest="multi_mode",
1295
                        help="Filter by nodes (primary and secondary)",
1296
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1297

    
1298
m_clust_opt = cli_option("--all", dest="multi_mode",
1299
                         help="Select all instances in the cluster",
1300
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1301

    
1302
m_inst_opt = cli_option("--instance", dest="multi_mode",
1303
                        help="Filter by instance name [default]",
1304
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1305

    
1306
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1307
                             help="Filter by node tag",
1308
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1309
                             action="store_const")
1310

    
1311
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1312
                                 help="Filter by primary node tag",
1313
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1314
                                 action="store_const")
1315

    
1316
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1317
                                 help="Filter by secondary node tag",
1318
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1319
                                 action="store_const")
1320

    
1321
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1322
                             help="Filter by instance tag",
1323
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1324
                             action="store_const")
1325

    
1326
# this is defined separately due to readability only
1327
add_opts = [
1328
  BACKEND_OPT,
1329
  DISK_OPT,
1330
  DISK_TEMPLATE_OPT,
1331
  FILESTORE_DIR_OPT,
1332
  FILESTORE_DRIVER_OPT,
1333
  HYPERVISOR_OPT,
1334
  IALLOCATOR_OPT,
1335
  NET_OPT,
1336
  NODE_PLACEMENT_OPT,
1337
  NOIPCHECK_OPT,
1338
  NONAMECHECK_OPT,
1339
  NONICS_OPT,
1340
  NOSTART_OPT,
1341
  NWSYNC_OPT,
1342
  OS_OPT,
1343
  FORCE_VARIANT_OPT,
1344
  NO_INSTALL_OPT,
1345
  OS_SIZE_OPT,
1346
  SUBMIT_OPT,
1347
  ]
1348

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

    
1473
#: dictionary with aliases for commands
1474
aliases = {
1475
  'activate_block_devs': 'activate-disks',
1476
  'replace_disks': 'replace-disks',
1477
  'start': 'startup',
1478
  'stop': 'shutdown',
1479
  }
1480

    
1481

    
1482
if __name__ == '__main__':
1483
  sys.exit(GenericMain(commands, aliases=aliases,
1484
                       override={"tag_type": constants.TAG_INSTANCE}))