Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ fe7c59d5

History | View | Annotate | Download (50.3 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
  if not isinstance(instance_data, dict):
427
    ToStderr("The instance definition file is not in dict format.")
428
    return 1
429

    
430
  jex = JobExecutor(opts=opts)
431

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

    
441
    hypervisor = specs['hypervisor']
442
    hvparams = specs['hvparams']
443

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

    
454
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
455
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
456

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

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

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

    
493
    jex.QueueJob(name, op)
494
  # we never want to wait, just show the submitted job IDs
495
  jex.WaitOrShow(False)
496

    
497
  return 0
498

    
499

    
500
def ReinstallInstance(opts, args):
501
  """Reinstall an instance.
502

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

    
510
  """
511
  # first, compute the desired name list
512
  if opts.multi_mode is None:
513
    opts.multi_mode = _SHUTDOWN_INSTANCES
514

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

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

    
526
    if not result:
527
      ToStdout("Can't get the OS list")
528
      return 1
529

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

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

    
544
    if selected == 'exit':
545
      ToStderr("User aborted reinstall, exiting")
546
      return 1
547

    
548
    os_name = selected
549
  else:
550
    os_name = opts.os
551

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

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

    
574
  jex.WaitOrShow(not opts.submit_only)
575
  return 0
576

    
577

    
578
def RemoveInstance(opts, args):
579
  """Remove an instance.
580

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

    
588
  """
589
  instance_name = args[0]
590
  force = opts.force
591
  cl = GetClient()
592

    
593
  if not force:
594
    _EnsureInstancesExist(cl, [instance_name])
595

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

    
602
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
603
                                ignore_failures=opts.ignore_failures,
604
                                shutdown_timeout=opts.shutdown_timeout)
605
  SubmitOrSend(op, opts, cl=cl)
606
  return 0
607

    
608

    
609
def RenameInstance(opts, args):
610
  """Rename an instance.
611

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

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

    
626

    
627
def ActivateDisks(opts, args):
628
  """Activate an instance's disks.
629

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

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

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

    
650

    
651
def DeactivateDisks(opts, args):
652
  """Deactivate an instance's disks.
653

    
654
  This function takes the instance name, looks for its primary node
655
  and the tries to shutdown its block devices on that node.
656

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

    
663
  """
664
  instance_name = args[0]
665
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
666
  SubmitOrSend(op, opts)
667
  return 0
668

    
669

    
670
def RecreateDisks(opts, args):
671
  """Recreate an instance's disks.
672

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

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

    
690
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
691
                                       disks=opts.disks)
692
  SubmitOrSend(op, opts)
693
  return 0
694

    
695

    
696
def GrowDisk(opts, args):
697
  """Grow an instance's disks.
698

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

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

    
720

    
721
def _StartupInstance(name, opts):
722
  """Startup instances.
723

    
724
  This returns the opcode to start an instance, and its decorator will
725
  wrap this into a loop starting all desired instances.
726

    
727
  @param name: the name of the instance to act on
728
  @param opts: the command line options selected by the user
729
  @return: the opcode needed for the operation
730

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

    
741

    
742
def _RebootInstance(name, opts):
743
  """Reboot instance(s).
744

    
745
  This returns the opcode to reboot an instance, and its decorator
746
  will wrap this into a loop rebooting all desired instances.
747

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

    
752
  """
753
  return opcodes.OpRebootInstance(instance_name=name,
754
                                  reboot_type=opts.reboot_type,
755
                                  ignore_secondaries=opts.ignore_secondaries,
756
                                  shutdown_timeout=opts.shutdown_timeout)
757

    
758

    
759
def _ShutdownInstance(name, opts):
760
  """Shutdown an instance.
761

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

    
765
  @param name: the name of the instance to act on
766
  @param opts: the command line options selected by the user
767
  @return: the opcode needed for the operation
768

    
769
  """
770
  return opcodes.OpShutdownInstance(instance_name=name,
771
                                    timeout=opts.timeout)
772

    
773

    
774
def ReplaceDisks(opts, args):
775
  """Replace the disks of an instance
776

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

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

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

    
819

    
820
def FailoverInstance(opts, args):
821
  """Failover an instance.
822

    
823
  The failover is done by shutting it down on its present node and
824
  starting it on the secondary.
825

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

    
832
  """
833
  cl = GetClient()
834
  instance_name = args[0]
835
  force = opts.force
836

    
837
  if not force:
838
    _EnsureInstancesExist(cl, [instance_name])
839

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

    
846
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
847
                                  ignore_consistency=opts.ignore_consistency,
848
                                  shutdown_timeout=opts.shutdown_timeout)
849
  SubmitOrSend(op, opts, cl=cl)
850
  return 0
851

    
852

    
853
def MigrateInstance(opts, args):
854
  """Migrate an instance.
855

    
856
  The migrate is done without shutdown.
857

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

    
864
  """
865
  cl = GetClient()
866
  instance_name = args[0]
867
  force = opts.force
868

    
869
  if not force:
870
    _EnsureInstancesExist(cl, [instance_name])
871

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

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

    
890

    
891
def MoveInstance(opts, args):
892
  """Move an instance.
893

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

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

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

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

    
918

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

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

    
928
  """
929
  instance_name = args[0]
930

    
931
  op = opcodes.OpConnectConsole(instance_name=instance_name)
932
  cmd = SubmitOpCode(op, opts=opts)
933

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

    
944

    
945
def _FormatLogicalID(dev_type, logical_id):
946
  """Formats the logical_id of a disk.
947

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

    
963
  return data
964

    
965

    
966
def _FormatBlockDevInfo(idx, top_level, dev, static):
967
  """Show block device information.
968

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

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

    
984
  """
985
  def helper(dtype, status):
986
    """Format one line for physical device status.
987

    
988
    @type dtype: str
989
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
990
    @type status: tuple
991
    @param status: a tuple as returned from L{backend.FindBlockDevice}
992
    @return: the string representing the status
993

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

    
1004
    if minor is None:
1005
      minor_string = "N/A"
1006
    else:
1007
      minor_string = str(minor)
1008

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

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

    
1071
  if dev["children"]:
1072
    data.append("child devices:")
1073
    for c_idx, child in enumerate(dev["children"]):
1074
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1075
  d1.append(data)
1076
  return d1
1077

    
1078

    
1079
def _FormatList(buf, data, indent_level):
1080
  """Formats a list of data at a given indent level.
1081

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

    
1088
  @type buf: StringIO
1089
  @param buf: the buffer into which we write the output
1090
  @param data: the list to format
1091
  @type indent_level: int
1092
  @param indent_level: the indent level to format at
1093

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

    
1107

    
1108
def ShowInstanceConfig(opts, args):
1109
  """Compute instance run-time status.
1110

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

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

    
1128
  retcode = 0
1129
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1130
  result = SubmitOpCode(op, opts=opts)
1131
  if not result:
1132
    ToStdout("No instances.")
1133
    return 1
1134

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

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

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

    
1195
    for idx, device in enumerate(instance["disks"]):
1196
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1197

    
1198
  ToStdout(buf.getvalue().rstrip('\n'))
1199
  return retcode
1200

    
1201

    
1202
def SetInstanceParams(opts, args):
1203
  """Modifies an instance.
1204

    
1205
  All parameters take effect only at the next restart of the instance.
1206

    
1207
  @param opts: the command line options selected by the user
1208
  @type args: list
1209
  @param args: should contain only one element, the instance name
1210
  @rtype: int
1211
  @return: the desired exit code
1212

    
1213
  """
1214
  if not (opts.nics or opts.disks or opts.disk_template or
1215
          opts.hvparams or opts.beparams or opts.os):
1216
    ToStderr("Please give at least one of the parameters.")
1217
    return 1
1218

    
1219
  for param in opts.beparams:
1220
    if isinstance(opts.beparams[param], basestring):
1221
      if opts.beparams[param].lower() == "default":
1222
        opts.beparams[param] = constants.VALUE_DEFAULT
1223

    
1224
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1225
                      allowed_values=[constants.VALUE_DEFAULT])
1226

    
1227
  for param in opts.hvparams:
1228
    if isinstance(opts.hvparams[param], basestring):
1229
      if opts.hvparams[param].lower() == "default":
1230
        opts.hvparams[param] = constants.VALUE_DEFAULT
1231

    
1232
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1233
                      allowed_values=[constants.VALUE_DEFAULT])
1234

    
1235
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1236
    try:
1237
      nic_op = int(nic_op)
1238
      opts.nics[idx] = (nic_op, nic_dict)
1239
    except (TypeError, ValueError):
1240
      pass
1241

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

    
1254
  if (opts.disk_template and
1255
      opts.disk_template in constants.DTS_NET_MIRROR and
1256
      not opts.node):
1257
    ToStderr("Changing the disk template to a mirrored one requires"
1258
             " specifying a secondary node")
1259
    return 1
1260

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

    
1272
  # even if here we process the result, we allow submit only
1273
  result = SubmitOrSend(op, opts)
1274

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

    
1283

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

    
1290
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1291
                            help="Filter by nodes (primary only)",
1292
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1293

    
1294
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1295
                            help="Filter by nodes (secondary only)",
1296
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1297

    
1298
m_node_opt = cli_option("--node", dest="multi_mode",
1299
                        help="Filter by nodes (primary and secondary)",
1300
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1301

    
1302
m_clust_opt = cli_option("--all", dest="multi_mode",
1303
                         help="Select all instances in the cluster",
1304
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1305

    
1306
m_inst_opt = cli_option("--instance", dest="multi_mode",
1307
                        help="Filter by instance name [default]",
1308
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1309

    
1310
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1311
                             help="Filter by node tag",
1312
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1313
                             action="store_const")
1314

    
1315
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1316
                                 help="Filter by primary node tag",
1317
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1318
                                 action="store_const")
1319

    
1320
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1321
                                 help="Filter by secondary node tag",
1322
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1323
                                 action="store_const")
1324

    
1325
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1326
                             help="Filter by instance tag",
1327
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1328
                             action="store_const")
1329

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

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

    
1477
#: dictionary with aliases for commands
1478
aliases = {
1479
  'activate_block_devs': 'activate-disks',
1480
  'replace_disks': 'replace-disks',
1481
  'start': 'startup',
1482
  'stop': 'shutdown',
1483
  }
1484

    
1485

    
1486
if __name__ == '__main__':
1487
  sys.exit(GenericMain(commands, aliases=aliases,
1488
                       override={"tag_type": constants.TAG_INSTANCE}))