Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ cf29cfb6

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 += (" might impact the instance if anything goes wrong"
880
                 " (e.g. due to bugs in the hypervisor). Continue?")
881
    if not AskUser(usertext):
882
      return 1
883

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

    
889

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

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

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

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

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

    
917

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

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

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

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

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

    
943

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

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

    
962
  return data
963

    
964

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

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

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

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

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

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

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

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

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

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

    
1077

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

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

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

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

    
1106

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

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

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

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

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

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

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

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

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

    
1200

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1282

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1484

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