Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ b44bd844

History | View | Annotate | Download (53.3 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 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 compat
38
from ganeti import utils
39
from ganeti import errors
40
from ganeti import netutils
41

    
42

    
43
_SHUTDOWN_CLUSTER = "cluster"
44
_SHUTDOWN_NODES_BOTH = "nodes"
45
_SHUTDOWN_NODES_PRI = "nodes-pri"
46
_SHUTDOWN_NODES_SEC = "nodes-sec"
47
_SHUTDOWN_NODES_BOTH_BY_TAGS = "nodes-by-tags"
48
_SHUTDOWN_NODES_PRI_BY_TAGS = "nodes-pri-by-tags"
49
_SHUTDOWN_NODES_SEC_BY_TAGS = "nodes-sec-by-tags"
50
_SHUTDOWN_INSTANCES = "instances"
51
_SHUTDOWN_INSTANCES_BY_TAGS = "instances-by-tags"
52

    
53
_SHUTDOWN_NODES_TAGS_MODES = (
54
    _SHUTDOWN_NODES_BOTH_BY_TAGS,
55
    _SHUTDOWN_NODES_PRI_BY_TAGS,
56
    _SHUTDOWN_NODES_SEC_BY_TAGS)
57

    
58

    
59
_VALUE_TRUE = "true"
60

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

    
66

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

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

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

    
89
  """
90
  # pylint: disable-msg=W0142
91

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

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

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

    
143
  return inames
144

    
145

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

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

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

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

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

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

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

    
183

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

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

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

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

    
205

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

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

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

    
236

    
237
def ListInstances(opts, args):
238
  """List instances and their properties.
239

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

    
246
  """
247
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
248

    
249
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
250

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

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

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

    
332
  data = GenerateTable(separator=opts.separator, headers=headers,
333
                       fields=selected_fields, unitfields=unitfields,
334
                       numfields=numfields, data=output, units=opts.units)
335

    
336
  for line in data:
337
    ToStdout(line)
338

    
339
  return 0
340

    
341

    
342
def AddInstance(opts, args):
343
  """Add an instance to the cluster.
344

    
345
  This is just a wrapper over GenericInstanceCreate.
346

    
347
  """
348
  return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
349

    
350

    
351
def BatchCreate(opts, args):
352
  """Create instances using a definition file.
353

    
354
  This function reads a json file with instances defined
355
  in the form::
356

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

    
369
  Note that I{primary_node} and I{secondary_node} have precedence over
370
  I{iallocator}.
371

    
372
  @param opts: the command line options selected by the user
373
  @type args: list
374
  @param args: should contain one element, the json filename
375
  @rtype: int
376
  @return: the desired exit code
377

    
378
  """
379
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
380
                    "backend": {},
381
                    "iallocator": None,
382
                    "primary_node": None,
383
                    "secondary_node": None,
384
                    "nics": None,
385
                    "start": True,
386
                    "ip_check": True,
387
                    "name_check": True,
388
                    "hypervisor": None,
389
                    "hvparams": {},
390
                    "file_storage_dir": None,
391
                    "force_variant": False,
392
                    "file_driver": 'loop'}
393

    
394
  def _PopulateWithDefaults(spec):
395
    """Returns a new hash combined with default values."""
396
    mydict = _DEFAULT_SPECS.copy()
397
    mydict.update(spec)
398
    return mydict
399

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

    
419
    if (spec['hvparams'] and
420
        not isinstance(spec['hvparams'], dict)):
421
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.',
422
                                 errors.ECODE_INVAL)
423

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

    
431
  if not isinstance(instance_data, dict):
432
    ToStderr("The instance definition file is not in dict format.")
433
    return 1
434

    
435
  jex = JobExecutor(opts=opts)
436

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

    
446
    hypervisor = specs['hypervisor']
447
    hvparams = specs['hvparams']
448

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

    
459
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
460
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
461

    
462
    tmp_nics = []
463
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
464
      if field in specs:
465
        if not tmp_nics:
466
          tmp_nics.append({})
467
        tmp_nics[0][field] = specs[field]
468

    
469
    if specs['nics'] is not None and tmp_nics:
470
      raise errors.OpPrereqError("'nics' list incompatible with using"
471
                                 " individual nic fields as well",
472
                                 errors.ECODE_INVAL)
473
    elif specs['nics'] is not None:
474
      tmp_nics = specs['nics']
475
    elif not tmp_nics:
476
      tmp_nics = [{}]
477

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

    
498
    jex.QueueJob(name, op)
499
  # we never want to wait, just show the submitted job IDs
500
  jex.WaitOrShow(False)
501

    
502
  return 0
503

    
504

    
505
def ReinstallInstance(opts, args):
506
  """Reinstall an instance.
507

    
508
  @param opts: the command line options selected by the user
509
  @type args: list
510
  @param args: should contain only one element, the name of the
511
      instance to be reinstalled
512
  @rtype: int
513
  @return: the desired exit code
514

    
515
  """
516
  # first, compute the desired name list
517
  if opts.multi_mode is None:
518
    opts.multi_mode = _SHUTDOWN_INSTANCES
519

    
520
  inames = _ExpandMultiNames(opts.multi_mode, args)
521
  if not inames:
522
    raise errors.OpPrereqError("Selection filter does not match any instances",
523
                               errors.ECODE_INVAL)
524

    
525
  # second, if requested, ask for an OS
526
  if opts.select_os is True:
527
    op = opcodes.OpDiagnoseOS(output_fields=["name", "variants"], names=[])
528
    result = SubmitOpCode(op, opts=opts)
529

    
530
    if not result:
531
      ToStdout("Can't get the OS list")
532
      return 1
533

    
534
    ToStdout("Available OS templates:")
535
    number = 0
536
    choices = []
537
    for (name, variants) in result:
538
      for entry in CalculateOSNames(name, variants):
539
        ToStdout("%3s: %s", number, entry)
540
        choices.append(("%s" % number, entry, entry))
541
        number += 1
542

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

    
547
    if selected == 'exit':
548
      ToStderr("User aborted reinstall, exiting")
549
      return 1
550

    
551
    os_name = selected
552
  else:
553
    os_name = opts.os
554

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

    
571
  jex = JobExecutor(verbose=multi_on, opts=opts)
572
  for instance_name in inames:
573
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
574
                                     os_type=os_name,
575
                                     force_variant=opts.force_variant)
576
    jex.QueueJob(instance_name, op)
577

    
578
  jex.WaitOrShow(not opts.submit_only)
579
  return 0
580

    
581

    
582
def RemoveInstance(opts, args):
583
  """Remove an instance.
584

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

    
592
  """
593
  instance_name = args[0]
594
  force = opts.force
595
  cl = GetClient()
596

    
597
  if not force:
598
    _EnsureInstancesExist(cl, [instance_name])
599

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

    
606
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
607
                                ignore_failures=opts.ignore_failures,
608
                                shutdown_timeout=opts.shutdown_timeout)
609
  SubmitOrSend(op, opts, cl=cl)
610
  return 0
611

    
612

    
613
def RenameInstance(opts, args):
614
  """Rename an instance.
615

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

    
623
  """
624
  if not opts.name_check:
625
    if not AskUser("As you disabled the check of the DNS entry, please verify"
626
                   " that '%s' is a FQDN. Continue?" % args[1]):
627
      return 1
628

    
629
  op = opcodes.OpRenameInstance(instance_name=args[0],
630
                                new_name=args[1],
631
                                ip_check=opts.ip_check,
632
                                name_check=opts.name_check)
633
  result = SubmitOrSend(op, opts)
634

    
635
  if result:
636
    ToStdout("Instance '%s' renamed to '%s'", args[0], result)
637

    
638
  return 0
639

    
640

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

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

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

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

    
664

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

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

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

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

    
683

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

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

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

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

    
709

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

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

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

    
734

    
735
def _StartupInstance(name, opts):
736
  """Startup instances.
737

    
738
  This returns the opcode to start an instance, and its decorator will
739
  wrap this into a loop starting all desired instances.
740

    
741
  @param name: the name of the instance to act on
742
  @param opts: the command line options selected by the user
743
  @return: the opcode needed for the operation
744

    
745
  """
746
  op = opcodes.OpStartupInstance(instance_name=name,
747
                                 force=opts.force,
748
                                 ignore_offline_nodes=opts.ignore_offline)
749
  # do not add these parameters to the opcode unless they're defined
750
  if opts.hvparams:
751
    op.hvparams = opts.hvparams
752
  if opts.beparams:
753
    op.beparams = opts.beparams
754
  return op
755

    
756

    
757
def _RebootInstance(name, opts):
758
  """Reboot instance(s).
759

    
760
  This returns the opcode to reboot an instance, and its decorator
761
  will wrap this into a loop rebooting all desired instances.
762

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

    
767
  """
768
  return opcodes.OpRebootInstance(instance_name=name,
769
                                  reboot_type=opts.reboot_type,
770
                                  ignore_secondaries=opts.ignore_secondaries,
771
                                  shutdown_timeout=opts.shutdown_timeout)
772

    
773

    
774
def _ShutdownInstance(name, opts):
775
  """Shutdown an instance.
776

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

    
780
  @param name: the name of the instance to act on
781
  @param opts: the command line options selected by the user
782
  @return: the opcode needed for the operation
783

    
784
  """
785
  return opcodes.OpShutdownInstance(instance_name=name,
786
                                    timeout=opts.timeout,
787
                                    ignore_offline_nodes=opts.ignore_offline)
788

    
789

    
790
def ReplaceDisks(opts, args):
791
  """Replace the disks of an instance
792

    
793
  @param opts: the command line options selected by the user
794
  @type args: list
795
  @param args: should contain only one element, the instance name
796
  @rtype: int
797
  @return: the desired exit code
798

    
799
  """
800
  new_2ndary = opts.dst_node
801
  iallocator = opts.iallocator
802
  if opts.disks is None:
803
    disks = []
804
  else:
805
    try:
806
      disks = [int(i) for i in opts.disks.split(",")]
807
    except (TypeError, ValueError), err:
808
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err),
809
                                 errors.ECODE_INVAL)
810
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
811
         new_2ndary is not None, iallocator is not None].count(True)
812
  if cnt != 1:
813
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
814
                               " options must be passed", errors.ECODE_INVAL)
815
  elif opts.on_primary:
816
    mode = constants.REPLACE_DISK_PRI
817
  elif opts.on_secondary:
818
    mode = constants.REPLACE_DISK_SEC
819
  elif opts.auto:
820
    mode = constants.REPLACE_DISK_AUTO
821
    if disks:
822
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
823
                                 " mode", errors.ECODE_INVAL)
824
  elif new_2ndary is not None or iallocator is not None:
825
    # replace secondary
826
    mode = constants.REPLACE_DISK_CHG
827

    
828
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
829
                              remote_node=new_2ndary, mode=mode,
830
                              iallocator=iallocator,
831
                              early_release=opts.early_release)
832
  SubmitOrSend(op, opts)
833
  return 0
834

    
835

    
836
def FailoverInstance(opts, args):
837
  """Failover an instance.
838

    
839
  The failover is done by shutting it down on its present node and
840
  starting it on the secondary.
841

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

    
848
  """
849
  cl = GetClient()
850
  instance_name = args[0]
851
  force = opts.force
852

    
853
  if not force:
854
    _EnsureInstancesExist(cl, [instance_name])
855

    
856
    usertext = ("Failover will happen to image %s."
857
                " This requires a shutdown of the instance. Continue?" %
858
                (instance_name,))
859
    if not AskUser(usertext):
860
      return 1
861

    
862
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
863
                                  ignore_consistency=opts.ignore_consistency,
864
                                  shutdown_timeout=opts.shutdown_timeout)
865
  SubmitOrSend(op, opts, cl=cl)
866
  return 0
867

    
868

    
869
def MigrateInstance(opts, args):
870
  """Migrate an instance.
871

    
872
  The migrate is done without shutdown.
873

    
874
  @param opts: the command line options selected by the user
875
  @type args: list
876
  @param args: should contain only one element, the instance name
877
  @rtype: int
878
  @return: the desired exit code
879

    
880
  """
881
  cl = GetClient()
882
  instance_name = args[0]
883
  force = opts.force
884

    
885
  if not force:
886
    _EnsureInstancesExist(cl, [instance_name])
887

    
888
    if opts.cleanup:
889
      usertext = ("Instance %s will be recovered from a failed migration."
890
                  " Note that the migration procedure (including cleanup)" %
891
                  (instance_name,))
892
    else:
893
      usertext = ("Instance %s will be migrated. Note that migration" %
894
                  (instance_name,))
895
    usertext += (" might impact the instance if anything goes wrong"
896
                 " (e.g. due to bugs in the hypervisor). Continue?")
897
    if not AskUser(usertext):
898
      return 1
899

    
900
  # this should be removed once --non-live is deprecated
901
  if not opts.live and opts.migration_mode is not None:
902
    raise errors.OpPrereqError("Only one of the --non-live and "
903
                               "--migration-mode options can be passed",
904
                               errors.ECODE_INVAL)
905
  if not opts.live: # --non-live passed
906
    mode = constants.HT_MIGRATION_NONLIVE
907
  else:
908
    mode = opts.migration_mode
909

    
910
  op = opcodes.OpMigrateInstance(instance_name=instance_name, mode=mode,
911
                                 cleanup=opts.cleanup)
912
  SubmitOpCode(op, cl=cl, opts=opts)
913
  return 0
914

    
915

    
916
def MoveInstance(opts, args):
917
  """Move an instance.
918

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

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

    
930
  if not force:
931
    usertext = ("Instance %s will be moved."
932
                " This requires a shutdown of the instance. Continue?" %
933
                (instance_name,))
934
    if not AskUser(usertext):
935
      return 1
936

    
937
  op = opcodes.OpMoveInstance(instance_name=instance_name,
938
                              target_node=opts.node,
939
                              shutdown_timeout=opts.shutdown_timeout)
940
  SubmitOrSend(op, opts, cl=cl)
941
  return 0
942

    
943

    
944
def ConnectToInstanceConsole(opts, args):
945
  """Connect to the console of an instance.
946

    
947
  @param opts: the command line options selected by the user
948
  @type args: list
949
  @param args: should contain only one element, the instance name
950
  @rtype: int
951
  @return: the desired exit code
952

    
953
  """
954
  instance_name = args[0]
955

    
956
  op = opcodes.OpConnectConsole(instance_name=instance_name)
957
  cmd = SubmitOpCode(op, opts=opts)
958

    
959
  if opts.show_command:
960
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
961
  else:
962
    try:
963
      os.execvp(cmd[0], cmd)
964
    finally:
965
      ToStderr("Can't run console command %s with arguments:\n'%s'",
966
               cmd[0], " ".join(cmd))
967
      os._exit(1) # pylint: disable-msg=W0212
968

    
969

    
970
def _FormatLogicalID(dev_type, logical_id, roman):
971
  """Formats the logical_id of a disk.
972

    
973
  """
974
  if dev_type == constants.LD_DRBD8:
975
    node_a, node_b, port, minor_a, minor_b, key = logical_id
976
    data = [
977
      ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a,
978
                                                            convert=roman))),
979
      ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b,
980
                                                            convert=roman))),
981
      ("port", compat.TryToRoman(port, convert=roman)),
982
      ("auth key", key),
983
      ]
984
  elif dev_type == constants.LD_LV:
985
    vg_name, lv_name = logical_id
986
    data = ["%s/%s" % (vg_name, lv_name)]
987
  else:
988
    data = [str(logical_id)]
989

    
990
  return data
991

    
992

    
993
def _FormatBlockDevInfo(idx, top_level, dev, static, roman):
994
  """Show block device information.
995

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

    
999
  @type idx: int
1000
  @param idx: the index of the current disk
1001
  @type top_level: boolean
1002
  @param top_level: if this a top-level disk?
1003
  @type dev: dict
1004
  @param dev: dictionary with disk information
1005
  @type static: boolean
1006
  @param static: wheter the device information doesn't contain
1007
      runtime information but only static data
1008
  @type roman: boolean
1009
  @param roman: whether to try to use roman integers
1010
  @return: a list of either strings, tuples or lists
1011
      (which should be formatted at a higher indent level)
1012

    
1013
  """
1014
  def helper(dtype, status):
1015
    """Format one line for physical device status.
1016

    
1017
    @type dtype: str
1018
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1019
    @type status: tuple
1020
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1021
    @return: the string representing the status
1022

    
1023
    """
1024
    if not status:
1025
      return "not active"
1026
    txt = ""
1027
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1028
    if major is None:
1029
      major_string = "N/A"
1030
    else:
1031
      major_string = str(compat.TryToRoman(major, convert=roman))
1032

    
1033
    if minor is None:
1034
      minor_string = "N/A"
1035
    else:
1036
      minor_string = str(compat.TryToRoman(minor, convert=roman))
1037

    
1038
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1039
    if dtype in (constants.LD_DRBD8, ):
1040
      if syncp is not None:
1041
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1042
        if estt:
1043
          sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman)
1044
        else:
1045
          sync_text += " ETA unknown"
1046
      else:
1047
        sync_text = "in sync"
1048
      if degr:
1049
        degr_text = "*DEGRADED*"
1050
      else:
1051
        degr_text = "ok"
1052
      if ldisk_status == constants.LDS_FAULTY:
1053
        ldisk_text = " *MISSING DISK*"
1054
      elif ldisk_status == constants.LDS_UNKNOWN:
1055
        ldisk_text = " *UNCERTAIN STATE*"
1056
      else:
1057
        ldisk_text = ""
1058
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1059
    elif dtype == constants.LD_LV:
1060
      if ldisk_status == constants.LDS_FAULTY:
1061
        ldisk_text = " *FAILED* (failed drive?)"
1062
      else:
1063
        ldisk_text = ""
1064
      txt += ldisk_text
1065
    return txt
1066

    
1067
  # the header
1068
  if top_level:
1069
    if dev["iv_name"] is not None:
1070
      txt = dev["iv_name"]
1071
    else:
1072
      txt = "disk %s" % compat.TryToRoman(idx, convert=roman)
1073
  else:
1074
    txt = "child %s" % compat.TryToRoman(idx, convert=roman)
1075
  if isinstance(dev["size"], int):
1076
    nice_size = utils.FormatUnit(dev["size"], "h")
1077
  else:
1078
    nice_size = dev["size"]
1079
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1080
  data = []
1081
  if top_level:
1082
    data.append(("access mode", dev["mode"]))
1083
  if dev["logical_id"] is not None:
1084
    try:
1085
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman)
1086
    except ValueError:
1087
      l_id = [str(dev["logical_id"])]
1088
    if len(l_id) == 1:
1089
      data.append(("logical_id", l_id[0]))
1090
    else:
1091
      data.extend(l_id)
1092
  elif dev["physical_id"] is not None:
1093
    data.append("physical_id:")
1094
    data.append([dev["physical_id"]])
1095
  if not static:
1096
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1097
  if dev["sstatus"] and not static:
1098
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1099

    
1100
  if dev["children"]:
1101
    data.append("child devices:")
1102
    for c_idx, child in enumerate(dev["children"]):
1103
      data.append(_FormatBlockDevInfo(c_idx, False, child, static, roman))
1104
  d1.append(data)
1105
  return d1
1106

    
1107

    
1108
def _FormatList(buf, data, indent_level):
1109
  """Formats a list of data at a given indent level.
1110

    
1111
  If the element of the list is:
1112
    - a string, it is simply formatted as is
1113
    - a tuple, it will be split into key, value and the all the
1114
      values in a list will be aligned all at the same start column
1115
    - a list, will be recursively formatted
1116

    
1117
  @type buf: StringIO
1118
  @param buf: the buffer into which we write the output
1119
  @param data: the list to format
1120
  @type indent_level: int
1121
  @param indent_level: the indent level to format at
1122

    
1123
  """
1124
  max_tlen = max([len(elem[0]) for elem in data
1125
                 if isinstance(elem, tuple)] or [0])
1126
  for elem in data:
1127
    if isinstance(elem, basestring):
1128
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1129
    elif isinstance(elem, tuple):
1130
      key, value = elem
1131
      spacer = "%*s" % (max_tlen - len(key), "")
1132
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1133
    elif isinstance(elem, list):
1134
      _FormatList(buf, elem, indent_level+1)
1135

    
1136

    
1137
def _FormatParameterDict(buf, per_inst, actual):
1138
  """Formats a parameter dictionary.
1139

    
1140
  @type buf: L{StringIO}
1141
  @param buf: the buffer into which to write
1142
  @type per_inst: dict
1143
  @param per_inst: the instance's own parameters
1144
  @type actual: dict
1145
  @param actual: the current parameter set (including defaults)
1146

    
1147
  """
1148
  for key in sorted(actual):
1149
    val = per_inst.get(key, "default (%s)" % actual[key])
1150
    buf.write("    - %s: %s\n" % (key, val))
1151

    
1152
def ShowInstanceConfig(opts, args):
1153
  """Compute instance run-time status.
1154

    
1155
  @param opts: the command line options selected by the user
1156
  @type args: list
1157
  @param args: either an empty list, and then we query all
1158
      instances, or should contain a list of instance names
1159
  @rtype: int
1160
  @return: the desired exit code
1161

    
1162
  """
1163
  if not args and not opts.show_all:
1164
    ToStderr("No instance selected."
1165
             " Please pass in --all if you want to query all instances.\n"
1166
             "Note that this can take a long time on a big cluster.")
1167
    return 1
1168
  elif args and opts.show_all:
1169
    ToStderr("Cannot use --all if you specify instance names.")
1170
    return 1
1171

    
1172
  retcode = 0
1173
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1174
  result = SubmitOpCode(op, opts=opts)
1175
  if not result:
1176
    ToStdout("No instances.")
1177
    return 1
1178

    
1179
  buf = StringIO()
1180
  retcode = 0
1181
  for instance_name in result:
1182
    instance = result[instance_name]
1183
    buf.write("Instance name: %s\n" % instance["name"])
1184
    buf.write("UUID: %s\n" % instance["uuid"])
1185
    buf.write("Serial number: %s\n" %
1186
              compat.TryToRoman(instance["serial_no"],
1187
                                convert=opts.roman_integers))
1188
    buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"]))
1189
    buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"]))
1190
    buf.write("State: configured to be %s" % instance["config_state"])
1191
    if not opts.static:
1192
      buf.write(", actual state is %s" % instance["run_state"])
1193
    buf.write("\n")
1194
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1195
    ##          instance["auto_balance"])
1196
    buf.write("  Nodes:\n")
1197
    buf.write("    - primary: %s\n" % instance["pnode"])
1198
    buf.write("    - secondaries: %s\n" % utils.CommaJoin(instance["snodes"]))
1199
    buf.write("  Operating system: %s\n" % instance["os"])
1200
    _FormatParameterDict(buf, instance["os_instance"], instance["os_actual"])
1201
    if instance.has_key("network_port"):
1202
      buf.write("  Allocated network port: %s\n" %
1203
                compat.TryToRoman(instance["network_port"],
1204
                                  convert=opts.roman_integers))
1205
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1206

    
1207
    # custom VNC console information
1208
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1209
                                                 None)
1210
    if vnc_bind_address:
1211
      port = instance["network_port"]
1212
      display = int(port) - constants.VNC_BASE_PORT
1213
      if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY:
1214
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1215
                                                   port,
1216
                                                   display)
1217
      elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address):
1218
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1219
                             (vnc_bind_address, port,
1220
                              instance["pnode"], display))
1221
      else:
1222
        # vnc bind address is a file
1223
        vnc_console_port = "%s:%s" % (instance["pnode"],
1224
                                      vnc_bind_address)
1225
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1226

    
1227
    _FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"])
1228
    buf.write("  Hardware:\n")
1229
    buf.write("    - VCPUs: %s\n" %
1230
              compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS],
1231
                                convert=opts.roman_integers))
1232
    buf.write("    - memory: %sMiB\n" %
1233
              compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY],
1234
                                convert=opts.roman_integers))
1235
    buf.write("    - NICs:\n")
1236
    for idx, (ip, mac, mode, link) in enumerate(instance["nics"]):
1237
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1238
                (idx, mac, ip, mode, link))
1239
    buf.write("  Disks:\n")
1240

    
1241
    for idx, device in enumerate(instance["disks"]):
1242
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static,
1243
                  opts.roman_integers), 2)
1244

    
1245
  ToStdout(buf.getvalue().rstrip('\n'))
1246
  return retcode
1247

    
1248

    
1249
def SetInstanceParams(opts, args):
1250
  """Modifies an instance.
1251

    
1252
  All parameters take effect only at the next restart of the instance.
1253

    
1254
  @param opts: the command line options selected by the user
1255
  @type args: list
1256
  @param args: should contain only one element, the instance name
1257
  @rtype: int
1258
  @return: the desired exit code
1259

    
1260
  """
1261
  if not (opts.nics or opts.disks or opts.disk_template or
1262
          opts.hvparams or opts.beparams or opts.os or opts.osparams):
1263
    ToStderr("Please give at least one of the parameters.")
1264
    return 1
1265

    
1266
  for param in opts.beparams:
1267
    if isinstance(opts.beparams[param], basestring):
1268
      if opts.beparams[param].lower() == "default":
1269
        opts.beparams[param] = constants.VALUE_DEFAULT
1270

    
1271
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1272
                      allowed_values=[constants.VALUE_DEFAULT])
1273

    
1274
  for param in opts.hvparams:
1275
    if isinstance(opts.hvparams[param], basestring):
1276
      if opts.hvparams[param].lower() == "default":
1277
        opts.hvparams[param] = constants.VALUE_DEFAULT
1278

    
1279
  utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES,
1280
                      allowed_values=[constants.VALUE_DEFAULT])
1281

    
1282
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1283
    try:
1284
      nic_op = int(nic_op)
1285
      opts.nics[idx] = (nic_op, nic_dict)
1286
    except (TypeError, ValueError):
1287
      pass
1288

    
1289
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1290
    try:
1291
      disk_op = int(disk_op)
1292
      opts.disks[idx] = (disk_op, disk_dict)
1293
    except (TypeError, ValueError):
1294
      pass
1295
    if disk_op == constants.DDM_ADD:
1296
      if 'size' not in disk_dict:
1297
        raise errors.OpPrereqError("Missing required parameter 'size'",
1298
                                   errors.ECODE_INVAL)
1299
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1300

    
1301
  if (opts.disk_template and
1302
      opts.disk_template in constants.DTS_NET_MIRROR and
1303
      not opts.node):
1304
    ToStderr("Changing the disk template to a mirrored one requires"
1305
             " specifying a secondary node")
1306
    return 1
1307

    
1308
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1309
                                   nics=opts.nics,
1310
                                   disks=opts.disks,
1311
                                   disk_template=opts.disk_template,
1312
                                   remote_node=opts.node,
1313
                                   hvparams=opts.hvparams,
1314
                                   beparams=opts.beparams,
1315
                                   os_name=opts.os,
1316
                                   osparams=opts.osparams,
1317
                                   force_variant=opts.force_variant,
1318
                                   force=opts.force)
1319

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

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

    
1331

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

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

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

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

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

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

    
1358
m_node_tags_opt = cli_option("--node-tags", dest="multi_mode",
1359
                             help="Filter by node tag",
1360
                             const=_SHUTDOWN_NODES_BOTH_BY_TAGS,
1361
                             action="store_const")
1362

    
1363
m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode",
1364
                                 help="Filter by primary node tag",
1365
                                 const=_SHUTDOWN_NODES_PRI_BY_TAGS,
1366
                                 action="store_const")
1367

    
1368
m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode",
1369
                                 help="Filter by secondary node tag",
1370
                                 const=_SHUTDOWN_NODES_SEC_BY_TAGS,
1371
                                 action="store_const")
1372

    
1373
m_inst_tags_opt = cli_option("--tags", dest="multi_mode",
1374
                             help="Filter by instance tag",
1375
                             const=_SHUTDOWN_INSTANCES_BY_TAGS,
1376
                             action="store_const")
1377

    
1378
# this is defined separately due to readability only
1379
add_opts = [
1380
  BACKEND_OPT,
1381
  DISK_OPT,
1382
  DISK_TEMPLATE_OPT,
1383
  FILESTORE_DIR_OPT,
1384
  FILESTORE_DRIVER_OPT,
1385
  HYPERVISOR_OPT,
1386
  IALLOCATOR_OPT,
1387
  NET_OPT,
1388
  NODE_PLACEMENT_OPT,
1389
  NOIPCHECK_OPT,
1390
  NONAMECHECK_OPT,
1391
  NONICS_OPT,
1392
  NOSTART_OPT,
1393
  NWSYNC_OPT,
1394
  OSPARAMS_OPT,
1395
  OS_OPT,
1396
  FORCE_VARIANT_OPT,
1397
  NO_INSTALL_OPT,
1398
  OS_SIZE_OPT,
1399
  SUBMIT_OPT,
1400
  DRY_RUN_OPT,
1401
  PRIORITY_OPT,
1402
  ]
1403

    
1404
commands = {
1405
  'add': (
1406
    AddInstance, [ArgHost(min=1, max=1)], add_opts,
1407
    "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1408
    "Creates and adds a new instance to the cluster"),
1409
  'batch-create': (
1410
    BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT],
1411
    "<instances.json>",
1412
    "Create a bunch of instances based on specs in the file."),
1413
  'console': (
1414
    ConnectToInstanceConsole, ARGS_ONE_INSTANCE,
1415
    [SHOWCMD_OPT, PRIORITY_OPT],
1416
    "[--show-cmd] <instance>", "Opens a console on the specified instance"),
1417
  'failover': (
1418
    FailoverInstance, ARGS_ONE_INSTANCE,
1419
    [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT,
1420
     DRY_RUN_OPT, PRIORITY_OPT],
1421
    "[-f] <instance>", "Stops the instance and starts it on the backup node,"
1422
    " using the remote mirror (only for instances of type drbd)"),
1423
  'migrate': (
1424
    MigrateInstance, ARGS_ONE_INSTANCE,
1425
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT,
1426
     PRIORITY_OPT],
1427
    "[-f] <instance>", "Migrate instance to its secondary node"
1428
    " (only for instances of type drbd)"),
1429
  'move': (
1430
    MoveInstance, ARGS_ONE_INSTANCE,
1431
    [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT,
1432
     DRY_RUN_OPT, PRIORITY_OPT],
1433
    "[-f] <instance>", "Move instance to an arbitrary node"
1434
    " (only for instances of type file and lv)"),
1435
  'info': (
1436
    ShowInstanceConfig, ARGS_MANY_INSTANCES,
1437
    [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT],
1438
    "[-s] {--all | <instance>...}",
1439
    "Show information on the specified instance(s)"),
1440
  'list': (
1441
    ListInstances, ARGS_MANY_INSTANCES,
1442
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
1443
    "[<instance>...]",
1444
    "Lists the instances and their status. The available fields are"
1445
    " (see the man page for details): status, oper_state, oper_ram,"
1446
    " oper_vcpus, name, os, pnode, snodes, admin_state, admin_ram,"
1447
    " disk_template, ip, mac, nic_mode, nic_link, sda_size, sdb_size,"
1448
    " vcpus, serial_no,"
1449
    " nic.count, nic.mac/N, nic.ip/N, nic.mode/N, nic.link/N,"
1450
    " nic.macs, nic.ips, nic.modes, nic.links,"
1451
    " disk.count, disk.size/N, disk.sizes,"
1452
    " hv/NAME, be/memory, be/vcpus, be/auto_balance,"
1453
    " hypervisor."
1454
    " The default field"
1455
    " list is (in order): %s." % utils.CommaJoin(_LIST_DEF_FIELDS),
1456
    ),
1457
  'reinstall': (
1458
    ReinstallInstance, [ArgInstance()],
1459
    [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt,
1460
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt,
1461
     m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT,
1462
     SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1463
    "[-f] <instance>", "Reinstall a stopped instance"),
1464
  'remove': (
1465
    RemoveInstance, ARGS_ONE_INSTANCE,
1466
    [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT,
1467
     DRY_RUN_OPT, PRIORITY_OPT],
1468
    "[-f] <instance>", "Shuts down the instance and removes it"),
1469
  'rename': (
1470
    RenameInstance,
1471
    [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)],
1472
    [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1473
    "<instance> <new_name>", "Rename the instance"),
1474
  'replace-disks': (
1475
    ReplaceDisks, ARGS_ONE_INSTANCE,
1476
    [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT,
1477
     NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT,
1478
     DRY_RUN_OPT, PRIORITY_OPT],
1479
    "[-s|-p|-n NODE|-I NAME] <instance>",
1480
    "Replaces all disks for the instance"),
1481
  'modify': (
1482
    SetInstanceParams, ARGS_ONE_INSTANCE,
1483
    [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT,
1484
     DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT,
1485
     OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1486
    "<instance>", "Alters the parameters of an instance"),
1487
  'shutdown': (
1488
    GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()],
1489
    [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt,
1490
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1491
     m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT,
1492
     DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1493
    "<instance>", "Stops an instance"),
1494
  'startup': (
1495
    GenericManyOps("startup", _StartupInstance), [ArgInstance()],
1496
    [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1497
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1498
     m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT,
1499
     BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT],
1500
    "<instance>", "Starts an instance"),
1501
  'reboot': (
1502
    GenericManyOps("reboot", _RebootInstance), [ArgInstance()],
1503
    [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt,
1504
     m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT,
1505
     m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt,
1506
     m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1507
    "<instance>", "Reboots an instance"),
1508
  'activate-disks': (
1509
    ActivateDisks, ARGS_ONE_INSTANCE,
1510
    [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT],
1511
    "<instance>", "Activate an instance's disks"),
1512
  'deactivate-disks': (
1513
    DeactivateDisks, ARGS_ONE_INSTANCE,
1514
    [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1515
    "<instance>", "Deactivate an instance's disks"),
1516
  'recreate-disks': (
1517
    RecreateDisks, ARGS_ONE_INSTANCE,
1518
    [SUBMIT_OPT, DISKIDX_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1519
    "<instance>", "Recreate an instance's disks"),
1520
  'grow-disk': (
1521
    GrowDisk,
1522
    [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1),
1523
     ArgUnknown(min=1, max=1)],
1524
    [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT],
1525
    "<instance> <disk> <size>", "Grow an instance's disk"),
1526
  'list-tags': (
1527
    ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT],
1528
    "<instance_name>", "List the tags of the given instance"),
1529
  'add-tags': (
1530
    AddTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1531
    [TAG_SRC_OPT, PRIORITY_OPT],
1532
    "<instance_name> tag...", "Add tags to the given instance"),
1533
  'remove-tags': (
1534
    RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()],
1535
    [TAG_SRC_OPT, PRIORITY_OPT],
1536
    "<instance_name> tag...", "Remove tags from given instance"),
1537
  }
1538

    
1539
#: dictionary with aliases for commands
1540
aliases = {
1541
  'start': 'startup',
1542
  'stop': 'shutdown',
1543
  }
1544

    
1545

    
1546
if __name__ == '__main__':
1547
  sys.exit(GenericMain(commands, aliases=aliases,
1548
                       override={"tag_type": constants.TAG_INSTANCE}))