Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 39dfd93e

History | View | Annotate | Download (49.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)
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()
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 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)
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)
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=opts.ignore_ip)
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 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 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
  SubmitOrSend(op, opts)
812
  return 0
813

    
814

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

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

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

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

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

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

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

    
847

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

    
851
  The migrate is done without shutdown.
852

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

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

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

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

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

    
885

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

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

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

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

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

    
913

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

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

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

    
926
  op = opcodes.OpConnectConsole(instance_name=instance_name)
927
  cmd = SubmitOpCode(op)
928

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

    
939

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

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

    
958
  return data
959

    
960

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

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

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

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

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

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

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

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

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

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

    
1073

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

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

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

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

    
1102

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

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

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

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

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

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

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

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

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

    
1196

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

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

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

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

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

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

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

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

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

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

    
1249
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1250
                                   nics=opts.nics,
1251
                                   disks=opts.disks,
1252
                                   hvparams=opts.hvparams,
1253
                                   beparams=opts.beparams,
1254
                                   force=opts.force)
1255

    
1256
  # even if here we process the result, we allow submit only
1257
  result = SubmitOrSend(op, opts)
1258

    
1259
  if result:
1260
    ToStdout("Modified instance %s", args[0])
1261
    for param, data in result:
1262
      ToStdout(" - %-5s -> %s", param, data)
1263
    ToStdout("Please don't forget that these parameters take effect"
1264
             " only at the next start of the instance.")
1265
  return 0
1266

    
1267

    
1268
# multi-instance selection options
1269
m_force_multi = cli_option("--force-multiple", dest="force_multi",
1270
                           help="Do not ask for confirmation when more than"
1271
                           " one instance is affected",
1272
                           action="store_true", default=False)
1273

    
1274
m_pri_node_opt = cli_option("--primary", dest="multi_mode",
1275
                            help="Filter by nodes (primary only)",
1276
                            const=_SHUTDOWN_NODES_PRI, action="store_const")
1277

    
1278
m_sec_node_opt = cli_option("--secondary", dest="multi_mode",
1279
                            help="Filter by nodes (secondary only)",
1280
                            const=_SHUTDOWN_NODES_SEC, action="store_const")
1281

    
1282
m_node_opt = cli_option("--node", dest="multi_mode",
1283
                        help="Filter by nodes (primary and secondary)",
1284
                        const=_SHUTDOWN_NODES_BOTH, action="store_const")
1285

    
1286
m_clust_opt = cli_option("--all", dest="multi_mode",
1287
                         help="Select all instances in the cluster",
1288
                         const=_SHUTDOWN_CLUSTER, action="store_const")
1289

    
1290
m_inst_opt = cli_option("--instance", dest="multi_mode",
1291
                        help="Filter by instance name [default]",
1292
                        const=_SHUTDOWN_INSTANCES, action="store_const")
1293

    
1294
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1295
                             help="Filter by node tag",
1296
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1297
                             action="store_const")
1298

    
1299
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1300
                                 help="Filter by primary node tag",
1301
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1302
                                 action="store_const")
1303

    
1304
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1305
                                 help="Filter by secondary node tag",
1306
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1307
                                 action="store_const")
1308

    
1309
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1310
                             help="Filter by instance tag",
1311
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1312
                             action="store_const")
1313

    
1314
# this is defined separately due to readability only
1315
add_opts = [
1316
  BACKEND_OPT,
1317
  DISK_OPT,
1318
  DISK_TEMPLATE_OPT,
1319
  FILESTORE_DIR_OPT,
1320
  FILESTORE_DRIVER_OPT,
1321
  HYPERVISOR_OPT,
1322
  IALLOCATOR_OPT,
1323
  NET_OPT,
1324
  NODE_PLACEMENT_OPT,
1325
  NOIPCHECK_OPT,
1326
  NONAMECHECK_OPT,
1327
  NONICS_OPT,
1328
  NOSTART_OPT,
1329
  NWSYNC_OPT,
1330
  OS_OPT,
1331
  FORCE_VARIANT_OPT,
1332
  OS_SIZE_OPT,
1333
  SUBMIT_OPT,
1334
  ]
1335

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

    
1459
#: dictionary with aliases for commands
1460
aliases = {
1461
  'activate_block_devs': 'activate-disks',
1462
  'replace_disks': 'replace-disks',
1463
  'start': 'startup',
1464
  'stop': 'shutdown',
1465
  }
1466

    
1467

    
1468
if __name__ == '__main__':
1469
  sys.exit(GenericMain(commands, aliases=aliases,
1470
                       override={"tag_type": constants.TAG_INSTANCE}))