gnt-instance 59.5 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/python
#

# Copyright (C) 2006, 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


22
23
24
25
# pylint: disable-msg=W0401,W0614
# W0401: Wildcard import ganeti.cli
# W0614: Unused import %s from wildcard import (since we need cli)

Iustin Pop's avatar
Iustin Pop committed
26
27
import sys
import os
28
import itertools
29
import simplejson
30
import time
Iustin Pop's avatar
Iustin Pop committed
31
32
33
34
from optparse import make_option
from cStringIO import StringIO

from ganeti.cli import *
35
from ganeti import cli
Iustin Pop's avatar
Iustin Pop committed
36
37
38
from ganeti import opcodes
from ganeti import constants
from ganeti import utils
39
40
41
42
43
44
45
46
47
from ganeti import errors


_SHUTDOWN_CLUSTER = "cluster"
_SHUTDOWN_NODES_BOTH = "nodes"
_SHUTDOWN_NODES_PRI = "nodes-pri"
_SHUTDOWN_NODES_SEC = "nodes-sec"
_SHUTDOWN_INSTANCES = "instances"

48

49
50
_VALUE_TRUE = "true"

51
#: default list of options for L{ListInstances}
52
_LIST_DEF_FIELDS = [
53
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
54
55
  ]

56

57
def _ExpandMultiNames(mode, names, client=None):
58
59
60
61
  """Expand the given names using the passed mode.

  For _SHUTDOWN_CLUSTER, all instances will be returned. For
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
62
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
63
64
65
66
  instances having those nodes as either primary or secondary will be
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
  returned.

67
68
69
70
71
72
73
74
75
76
77
78
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
      L{_SHUTDOWN_INSTANCES}
  @param names: a list of names; for cluster, it must be empty,
      and for node and instance it must be a list of valid item
      names (short names are valid as usual, e.g. node1 instead of
      node1.example.com)
  @rtype: list
  @return: the list of names after the expansion
  @raise errors.ProgrammerError: for unknown selection type
  @raise errors.OpPrereqError: for invalid input parameters

79
  """
80
81
  if client is None:
    client = GetClient()
82
83
84
  if mode == _SHUTDOWN_CLUSTER:
    if names:
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
85
    idata = client.QueryInstances([], ["name"], False)
86
87
88
89
90
91
92
    inames = [row[0] for row in idata]

  elif mode in (_SHUTDOWN_NODES_BOTH,
                _SHUTDOWN_NODES_PRI,
                _SHUTDOWN_NODES_SEC):
    if not names:
      raise errors.OpPrereqError("No node names passed")
93
    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
94
                              False)
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
    ipri = [row[1] for row in ndata]
    pri_names = list(itertools.chain(*ipri))
    isec = [row[2] for row in ndata]
    sec_names = list(itertools.chain(*isec))
    if mode == _SHUTDOWN_NODES_BOTH:
      inames = pri_names + sec_names
    elif mode == _SHUTDOWN_NODES_PRI:
      inames = pri_names
    elif mode == _SHUTDOWN_NODES_SEC:
      inames = sec_names
    else:
      raise errors.ProgrammerError("Unhandled shutdown type")

  elif mode == _SHUTDOWN_INSTANCES:
    if not names:
      raise errors.OpPrereqError("No instance names passed")
111
    idata = client.QueryInstances(names, ["name"], False)
112
113
114
115
116
117
    inames = [row[0] for row in idata]

  else:
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)

  return inames
Iustin Pop's avatar
Iustin Pop committed
118
119


120
def _ConfirmOperation(inames, text, extra=""):
121
122
123
124
125
  """Ask the user to confirm an operation on a list of instances.

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

126
127
128
129
130
131
132
133
  @type inames: list
  @param inames: the list of names that we display when
      we ask for confirmation
  @type text: str
  @param text: the operation that the user should confirm
      (e.g. I{shutdown} or I{startup})
  @rtype: boolean
  @return: True or False depending on user's confirmation.
134
135
136

  """
  count = len(inames)
137
138
  msg = ("The %s will operate on %d instances.\n%s"
         "Do you want to continue?" % (text, count, extra))
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
  affected = ("\nAffected instances:\n" +
              "\n".join(["  %s" % name for name in inames]))

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

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

  choice = AskUser(ask, choices)
  if choice == 'v':
    choices.pop(1)
154
    choice = AskUser(msg + affected, choices)
155
156
157
  return choice


158
159
160
161
162
163
def _EnsureInstancesExist(client, names):
  """Check for and ensure the given instance names exist.

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

Iustin Pop's avatar
Iustin Pop committed
164
  @type client: L{ganeti.luxi.Client}
165
166
167
168
169
170
171
172
  @param client: the client to use for the query
  @type names: list
  @param names: the list of instance names to query
  @raise errors.OpPrereqError: in case any instance is missing

  """
  # TODO: change LUQueryInstances to that it actually returns None
  # instead of raising an exception, or devise a better mechanism
173
  result = client.QueryInstances(names, ["name"], False)
174
175
176
177
178
  for orig_name, row in zip(names, result):
    if row[0] is None:
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)


Iustin Pop's avatar
Iustin Pop committed
179
def ListInstances(opts, args):
Oleksiy Mishchenko's avatar
Oleksiy Mishchenko committed
180
  """List instances and their properties.
Iustin Pop's avatar
Iustin Pop committed
181

182
183
184
185
186
187
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
188
189
  """
  if opts.output is None:
190
191
192
    selected_fields = _LIST_DEF_FIELDS
  elif opts.output.startswith("+"):
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
Iustin Pop's avatar
Iustin Pop committed
193
194
195
  else:
    selected_fields = opts.output.split(",")

196
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
197
198

  if not opts.no_headers:
199
200
201
    headers = {
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
Iustin Pop's avatar
Iustin Pop committed
202
      "oper_state": "Running",
203
      "oper_ram": "Memory", "disk_template": "Disk_template",
204
      "ip": "IP_address", "mac": "MAC_address",
205
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
Iustin Pop's avatar
Iustin Pop committed
206
      "bridge": "Bridge",
207
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
208
      "disk_usage": "DiskUsage",
209
      "status": "Status", "tags": "Tags",
210
      "network_port": "Network_port",
211
212
213
214
215
216
217
218
      "hv/kernel_path": "Kernel_path",
      "hv/initrd_path": "Initrd_path",
      "hv/boot_order": "HVM_boot_order",
      "hv/acpi": "HVM_ACPI",
      "hv/pae": "HVM_PAE",
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
      "hv/nic_type": "HVM_NIC_type",
      "hv/disk_type": "HVM_Disk_type",
219
      "hv/vnc_bind_address": "VNC_bind_address",
220
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
221
      "hvparams": "Hypervisor_parameters",
Iustin Pop's avatar
Iustin Pop committed
222
223
      "be/memory": "Configured_memory",
      "be/vcpus": "VCPUs",
224
      "vcpus": "VCPUs",
225
      "be/auto_balance": "Auto_balance",
Iustin Pop's avatar
Iustin Pop committed
226
227
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
228
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
Iustin Pop's avatar
Iustin Pop committed
229
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
230
      "ctime": "CTime", "mtime": "MTime",
231
      }
232
233
234
  else:
    headers = None

235
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
236
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
Iustin Pop's avatar
Iustin Pop committed
237
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
238

239
240
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
                      "nic.modes", "nic.links", "nic.bridges")
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
      if field == "snodes":
        val = ",".join(val) or "-"
      elif field == "admin_state":
        if val:
          val = "yes"
        else:
          val = "no"
      elif field == "oper_state":
        if val is None:
          val = "(node down)"
        elif val: # True
          val = "running"
        else:
          val = "stopped"
      elif field == "oper_ram":
        if val is None:
          val = "(node down)"
      elif field == "sda_size" or field == "sdb_size":
        if val is None:
          val = "N/A"
265
266
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
267
      elif field in list_type_fields:
Iustin Pop's avatar
Iustin Pop committed
268
        val = ",".join(str(item) for item in val)
269
270
      elif val is None:
        val = "-"
271
272
      row[idx] = str(val)

273
274
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
275
                       numfields=numfields, data=output, units=opts.units)
276
277

  for line in data:
278
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
279
280
281
282
283
284
285

  return 0


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

286
287
288
289
290
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the new instance name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
291
292
293
294

  """
  instance = args[0]

295
296
  (pnode, snode) = SplitNodeOption(opts.node)

297
298
299
300
  hypervisor = None
  hvparams = {}
  if opts.hypervisor:
    hypervisor, hvparams = opts.hypervisor
301

302
303
304
305
306
307
  if opts.nics:
    try:
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
    except ValueError, err:
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
    nics = [{}] * nic_max
308
    for nidx, ndict in opts.nics:
309
      nidx = int(nidx)
310
311
312
      if not isinstance(ndict, dict):
        msg = "Invalid nic/%d value: expected dict, got %s" % (nidx, ndict)
        raise errors.OpPrereqError(msg)
313
      nics[nidx] = ndict
Iustin Pop's avatar
Iustin Pop committed
314
315
316
  elif opts.no_nics:
    # no nics
    nics = []
317
318
319
320
  else:
    # default of one nic, all auto
    nics = [{}]

Iustin Pop's avatar
Iustin Pop committed
321
  if opts.disk_template == constants.DT_DISKLESS:
322
    if opts.disks or opts.sd_size is not None:
Iustin Pop's avatar
Iustin Pop committed
323
324
325
      raise errors.OpPrereqError("Diskless instance but disk"
                                 " information passed")
    disks = []
326
  else:
327
    if not opts.disks and not opts.sd_size:
Iustin Pop's avatar
Iustin Pop committed
328
      raise errors.OpPrereqError("No disk information specified")
329
330
331
332
333
    if opts.disks and opts.sd_size is not None:
      raise errors.OpPrereqError("Please use either the '--disk' or"
                                 " '-s' option")
    if opts.sd_size is not None:
      opts.disks = [(0, {"size": opts.sd_size})]
334
335
336
337
338
339
340
    try:
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
    except ValueError, err:
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
    disks = [{}] * disk_max
    for didx, ddict in opts.disks:
      didx = int(didx)
341
342
343
344
      if not isinstance(ddict, dict):
        msg = "Invalid disk/%d value: expected dict, got %s" % (didx, ddict)
        raise errors.OpPrereqError(msg)
      elif "size" not in ddict:
345
346
347
348
349
350
351
352
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
      try:
        ddict["size"] = utils.ParseUnit(ddict["size"])
      except ValueError, err:
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
                                   (didx, err))
      disks[didx] = ddict

353
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
354
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
355

Iustin Pop's avatar
Iustin Pop committed
356
  op = opcodes.OpCreateInstance(instance_name=instance,
357
                                disks=disks,
Iustin Pop's avatar
Iustin Pop committed
358
                                disk_template=opts.disk_template,
359
                                nics=nics,
Iustin Pop's avatar
Iustin Pop committed
360
                                mode=constants.INSTANCE_CREATE,
361
                                os_type=opts.os, pnode=pnode,
Iustin Pop's avatar
Iustin Pop committed
362
                                snode=snode,
363
                                start=opts.start, ip_check=opts.ip_check,
364
                                wait_for_sync=opts.wait_for_sync,
365
366
                                hypervisor=hypervisor,
                                hvparams=hvparams,
Iustin Pop's avatar
Iustin Pop committed
367
                                beparams=opts.beparams,
368
                                iallocator=opts.iallocator,
369
370
                                file_storage_dir=opts.file_storage_dir,
                                file_driver=opts.file_driver,
371
                                )
372

373
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
374
375
376
  return 0


377
def BatchCreate(opts, args):
378
379
380
381
382
383
  """Create instances using a definition file.

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

    {"instance-name":{
384
      "disk_size": [20480],
385
386
387
388
      "template": "drbd",
      "backend": {
        "memory": 512,
        "vcpus": 1 },
389
      "os": "debootstrap",
390
391
392
393
394
395
396
397
398
399
400
401
402
      "primary_node": "firstnode",
      "secondary_node": "secondnode",
      "iallocator": "dumb"}
    }

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

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain one element, the json filename
  @rtype: int
  @return: the desired exit code
403
404

  """
405
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
406
407
408
409
                    "backend": {},
                    "iallocator": None,
                    "primary_node": None,
                    "secondary_node": None,
410
                    "nics": None,
411
412
413
                    "start": True,
                    "ip_check": True,
                    "hypervisor": None,
Iustin Pop's avatar
Iustin Pop committed
414
                    "hvparams": {},
415
416
417
418
419
                    "file_storage_dir": None,
                    "file_driver": 'loop'}

  def _PopulateWithDefaults(spec):
    """Returns a new hash combined with default values."""
420
421
422
    mydict = _DEFAULT_SPECS.copy()
    mydict.update(spec)
    return mydict
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440

  def _Validate(spec):
    """Validate the instance specs."""
    # Validate fields required under any circumstances
    for required_field in ('os', 'template'):
      if required_field not in spec:
        raise errors.OpPrereqError('Required field "%s" is missing.' %
                                   required_field)
    # Validate special fields
    if spec['primary_node'] is not None:
      if (spec['template'] in constants.DTS_NET_MIRROR and
          spec['secondary_node'] is None):
        raise errors.OpPrereqError('Template requires secondary node, but'
                                   ' there was no secondary provided.')
    elif spec['iallocator'] is None:
      raise errors.OpPrereqError('You have to provide at least a primary_node'
                                 ' or an iallocator.')

Iustin Pop's avatar
Iustin Pop committed
441
442
    if (spec['hvparams'] and
        not isinstance(spec['hvparams'], dict)):
443
444
445
446
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')

  json_filename = args[0]
  try:
Iustin Pop's avatar
Iustin Pop committed
447
    fd = open(json_filename, 'r')
448
449
    instance_data = simplejson.load(fd)
    fd.close()
Iustin Pop's avatar
Iustin Pop committed
450
451
452
  except Exception, err:
    ToStderr("Can't parse the instance definition file: %s" % str(err))
    return 1
453

454
455
  jex = JobExecutor()

456
457
458
  # Iterate over the instances and do:
  #  * Populate the specs with default value
  #  * Validate the instance specs
459
460
461
  i_names = utils.NiceSort(instance_data.keys())
  for name in i_names:
    specs = instance_data[name]
462
463
464
    specs = _PopulateWithDefaults(specs)
    _Validate(specs)

Iustin Pop's avatar
Iustin Pop committed
465
466
    hypervisor = specs['hypervisor']
    hvparams = specs['hvparams']
467

468
469
470
471
472
473
474
475
476
477
    disks = []
    for elem in specs['disk_size']:
      try:
        size = utils.ParseUnit(elem)
      except ValueError, err:
        raise errors.OpPrereqError("Invalid disk size '%s' for"
                                   " instance %s: %s" %
                                   (elem, name, err))
      disks.append({"size": size})

478
479
480
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)

481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
    tmp_nics = []
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
      if field in specs:
        if not tmp_nics:
          tmp_nics.append({})
        tmp_nics[0][field] = specs[field]

    if specs['nics'] is not None and tmp_nics:
      raise errors.OpPrereqError("'nics' list incompatible with using"
                                 " individual nic fields as well")
    elif specs['nics'] is not None:
      tmp_nics = specs['nics']
    elif not tmp_nics:
      tmp_nics = [{}]

496
    op = opcodes.OpCreateInstance(instance_name=name,
497
                                  disks=disks,
498
499
500
501
502
                                  disk_template=specs['template'],
                                  mode=constants.INSTANCE_CREATE,
                                  os_type=specs['os'],
                                  pnode=specs['primary_node'],
                                  snode=specs['secondary_node'],
503
                                  nics=tmp_nics,
504
505
506
507
508
509
510
511
512
513
                                  start=specs['start'],
                                  ip_check=specs['ip_check'],
                                  wait_for_sync=True,
                                  iallocator=specs['iallocator'],
                                  hypervisor=hypervisor,
                                  hvparams=hvparams,
                                  beparams=specs['backend'],
                                  file_storage_dir=specs['file_storage_dir'],
                                  file_driver=specs['file_driver'])

514
515
516
    jex.QueueJob(name, op)
  # we never want to wait, just show the submitted job IDs
  jex.WaitOrShow(False)
517
518
519
520

  return 0


521
522
523
def ReinstallInstance(opts, args):
  """Reinstall an instance.

524
525
526
527
528
529
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the name of the
      instance to be reinstalled
  @rtype: int
  @return: the desired exit code
530
531

  """
532
533
534
535
536
537
538
  # first, compute the desired name list
  if opts.multi_mode is None:
    opts.multi_mode = _SHUTDOWN_INSTANCES

  inames = _ExpandMultiNames(opts.multi_mode, args)
  if not inames:
    raise errors.OpPrereqError("Selection filter does not match any instances")
539

540
  # second, if requested, ask for an OS
541
542
543
544
545
  if opts.select_os is True:
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
    result = SubmitOpCode(op)

    if not result:
546
      ToStdout("Can't get the OS list")
547
548
      return 1

549
    ToStdout("Available OS templates:")
550
551
552
    number = 0
    choices = []
    for entry in result:
553
      ToStdout("%3s: %s", number, entry[0])
554
555
556
557
      choices.append(("%s" % number, entry[0], entry[0]))
      number = number + 1

    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
558
    selected = AskUser("Enter OS template number (or x to abort):",
559
560
561
                       choices)

    if selected == 'exit':
562
      ToStderr("User aborted reinstall, exiting")
563
564
      return 1

565
    os_name = selected
566
  else:
567
    os_name = opts.os
568

569
570
571
572
573
574
575
  # third, get confirmation: multi-reinstall requires --force-multi
  # *and* --force, single-reinstall just --force
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
  if multi_on:
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
    if not ((opts.force_multi and opts.force) or
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
576
      return 1
577
578
579
  else:
    if not opts.force:
      usertext = ("This will reinstall the instance %s and remove"
Iustin Pop's avatar
Iustin Pop committed
580
                  " all data. Continue?") % inames[0]
581
582
583
584
585
586
587
588
      if not AskUser(usertext):
        return 1

  jex = JobExecutor(verbose=multi_on)
  for instance_name in inames:
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
                                     os_type=os_name)
    jex.QueueJob(instance_name, op)
589

590
  jex.WaitOrShow(not opts.submit_only)
591
592
593
  return 0


Iustin Pop's avatar
Iustin Pop committed
594
595
596
def RemoveInstance(opts, args):
  """Remove an instance.

597
598
599
600
601
602
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the name of
      the instance to be removed
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
603
604
605
606

  """
  instance_name = args[0]
  force = opts.force
607
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
608
609

  if not force:
610
611
    _EnsureInstancesExist(cl, [instance_name])

Iustin Pop's avatar
Iustin Pop committed
612
613
614
    usertext = ("This will remove the volumes of the instance %s"
                " (including mirrors), thus removing all the data"
                " of the instance. Continue?") % instance_name
615
    if not AskUser(usertext):
Iustin Pop's avatar
Iustin Pop committed
616
617
      return 1

Iustin Pop's avatar
Iustin Pop committed
618
619
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
                                ignore_failures=opts.ignore_failures)
620
  SubmitOrSend(op, opts, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
621
622
623
  return 0


624
def RenameInstance(opts, args):
Guido Trotter's avatar
Guido Trotter committed
625
  """Rename an instance.
626

627
628
629
630
631
632
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain two elements, the old and the
      new instance names
  @rtype: int
  @return: the desired exit code
633
634
635
636
637

  """
  op = opcodes.OpRenameInstance(instance_name=args[0],
                                new_name=args[1],
                                ignore_ip=opts.ignore_ip)
638
  SubmitOrSend(op, opts)
639
640
641
  return 0


Iustin Pop's avatar
Iustin Pop committed
642
643
644
645
def ActivateDisks(opts, args):
  """Activate an instance's disks.

  This serves two purposes:
646
647
    - it allows (as long as the instance is not running)
      mounting the disks and modifying them from the node
Iustin Pop's avatar
Iustin Pop committed
648
649
    - it repairs inactive secondary drbds

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

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


def DeactivateDisks(opts, args):
Iustin Pop's avatar
Iustin Pop committed
667
  """Deactivate an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
668
669
670
671

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

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

Iustin Pop's avatar
Iustin Pop committed
678
679
680
  """
  instance_name = args[0]
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
681
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
682
683
684
  return 0


Iustin Pop's avatar
Iustin Pop committed
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
def RecreateDisks(opts, args):
  """Recreate an instance's disks.

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

  """
  instance_name = args[0]
  if opts.disks:
    try:
      opts.disks = [int(v) for v in opts.disks.split(",")]
    except (ValueError, TypeError), err:
      ToStderr("Invalid disks value: %s" % str(err))
      return 1
  else:
    opts.disks = []

  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
                                       disks=opts.disks)
  SubmitOrSend(op, opts)
  return 0


Iustin Pop's avatar
Iustin Pop committed
711
def GrowDisk(opts, args):
712
  """Grow an instance's disks.
Iustin Pop's avatar
Iustin Pop committed
713

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

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


Iustin Pop's avatar
Iustin Pop committed
735
def StartupInstance(opts, args):
736
  """Startup instances.
Iustin Pop's avatar
Iustin Pop committed
737

738
739
740
741
742
743
744
745
746
747
  Depending on the options given, this will start one or more
  instances.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: the instance or node names based on which we
      create the final selection (in conjunction with the
      opts argument)
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
748
749

  """
750
  cl = GetClient()
751
752
  if opts.multi_mode is None:
    opts.multi_mode = _SHUTDOWN_INSTANCES
753
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
754
755
  if not inames:
    raise errors.OpPrereqError("Selection filter does not match any instances")
756
757
758
759
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
  if not (opts.force_multi or not multi_on
          or _ConfirmOperation(inames, "startup")):
    return 1
760
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
761
762
  for name in inames:
    op = opcodes.OpStartupInstance(instance_name=name,
763
                                   force=opts.force)
764
765
766
767
768
    # do not add these parameters to the opcode unless they're defined
    if opts.hvparams:
      op.hvparams = opts.hvparams
    if opts.beparams:
      op.beparams = opts.beparams
769
770
    jex.QueueJob(name, op)
  jex.WaitOrShow(not opts.submit_only)
Iustin Pop's avatar
Iustin Pop committed
771
772
  return 0

773

774
def RebootInstance(opts, args):
775
776
777
778
  """Reboot instance(s).

  Depending on the parameters given, this will reboot one or more
  instances.
779

780
781
782
783
784
785
786
  @param opts: the command line options selected by the user
  @type args: list
  @param args: the instance or node names based on which we
      create the final selection (in conjunction with the
      opts argument)
  @rtype: int
  @return: the desired exit code
787
788

  """
789
  cl = GetClient()
790
791
  if opts.multi_mode is None:
    opts.multi_mode = _SHUTDOWN_INSTANCES
792
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
793
794
  if not inames:
    raise errors.OpPrereqError("Selection filter does not match any instances")
795
796
797
798
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
  if not (opts.force_multi or not multi_on
          or _ConfirmOperation(inames, "reboot")):
    return 1
799
  jex = JobExecutor(verbose=multi_on, cl=cl)
800
801
802
803
  for name in inames:
    op = opcodes.OpRebootInstance(instance_name=name,
                                  reboot_type=opts.reboot_type,
                                  ignore_secondaries=opts.ignore_secondaries)
804
805
    jex.QueueJob(name, op)
  jex.WaitOrShow(not opts.submit_only)
806
  return 0
Iustin Pop's avatar
Iustin Pop committed
807

808

Iustin Pop's avatar
Iustin Pop committed
809
810
811
def ShutdownInstance(opts, args):
  """Shutdown an instance.

812
813
814
815
816
817
818
  @param opts: the command line options selected by the user
  @type args: list
  @param args: the instance or node names based on which we
      create the final selection (in conjunction with the
      opts argument)
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
819
820

  """
821
  cl = GetClient()
822
823
  if opts.multi_mode is None:
    opts.multi_mode = _SHUTDOWN_INSTANCES
824
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
825
826
  if not inames:
    raise errors.OpPrereqError("Selection filter does not match any instances")
827
828
829
830
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
  if not (opts.force_multi or not multi_on
          or _ConfirmOperation(inames, "shutdown")):
    return 1
831
832

  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
833
834
  for name in inames:
    op = opcodes.OpShutdownInstance(instance_name=name)
835
836
    jex.QueueJob(name, op)
  jex.WaitOrShow(not opts.submit_only)
Iustin Pop's avatar
Iustin Pop committed
837
838
839
840
841
842
  return 0


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

843
844
845
846
847
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
848
849
850

  """
  instance_name = args[0]
851
  new_2ndary = opts.new_secondary
852
  iallocator = opts.iallocator
853
  if opts.disks is None:
854
    disks = []
855
  else:
856
857
858
859
    try:
      disks = [int(i) for i in opts.disks.split(",")]
    except ValueError, err:
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
860
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
861
862
         new_2ndary is not None, iallocator is not None].count(True)
  if cnt != 1:
863
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
864
865
                               " options must be passed")
  elif opts.on_primary:
866
    mode = constants.REPLACE_DISK_PRI
867
  elif opts.on_secondary:
868
    mode = constants.REPLACE_DISK_SEC
869
870
871
872
873
  elif opts.auto:
    mode = constants.REPLACE_DISK_AUTO
    if disks:
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
                                 " mode")
874
875
876
  elif new_2ndary is not None or iallocator is not None:
    # replace secondary
    mode = constants.REPLACE_DISK_CHG
877
878

  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
879
880
                              remote_node=new_2ndary, mode=mode,
                              iallocator=iallocator)
881
  SubmitOrSend(op, opts)
Iustin Pop's avatar
Iustin Pop committed
882
883
884
885
886
887
888
889
890
  return 0


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

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

891
892
893
894
895
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
896
897

  """
898
  cl = GetClient()
899
900
  instance_name = args[0]
  force = opts.force
Iustin Pop's avatar
Iustin Pop committed
901

902
  if not force:
903
904
    _EnsureInstancesExist(cl, [instance_name])

905
906
907
908
909
    usertext = ("Failover will happen to image %s."
                " This requires a shutdown of the instance. Continue?" %
                (instance_name,))
    if not AskUser(usertext):
      return 1
Iustin Pop's avatar
Iustin Pop committed
910

911
912
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
                                  ignore_consistency=opts.ignore_consistency)
913
  SubmitOrSend(op, opts, cl=cl)
914
  return 0
Iustin Pop's avatar
Iustin Pop committed
915
916


917
918
919
920
921
def MigrateInstance(opts, args):
  """Migrate an instance.

  The migrate is done without shutdown.

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

  """
929
  cl = GetClient()
930
931
932
933
  instance_name = args[0]
  force = opts.force

  if not force:
934
935
    _EnsureInstancesExist(cl, [instance_name])

936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
    if opts.cleanup:
      usertext = ("Instance %s will be recovered from a failed migration."
                  " Note that the migration procedure (including cleanup)" %
                  (instance_name,))
    else:
      usertext = ("Instance %s will be migrated. Note that migration" %
                  (instance_name,))
    usertext += (" is **experimental** in this version."
                " This might impact the instance if anything goes wrong."
                " Continue?")
    if not AskUser(usertext):
      return 1

  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
                                 cleanup=opts.cleanup)
951
  SubmitOpCode(op, cl=cl)
952
953
954
  return 0


Iustin Pop's avatar
Iustin Pop committed
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
def MoveInstance(opts, args):
  """Move an instance.

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

  """
  cl = GetClient()
  instance_name = args[0]
  force = opts.force

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

  op = opcodes.OpMoveInstance(instance_name=instance_name,
                              target_node=opts.target_node)
  SubmitOrSend(op, opts, cl=cl)
  return 0


Iustin Pop's avatar
Iustin Pop committed
982
983
984
def ConnectToInstanceConsole(opts, args):
  """Connect to the console of an instance.

985
986
987
988
989
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the instance name
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
990
991
992
993
994

  """
  instance_name = args[0]

  op = opcodes.OpConnectConsole(instance_name=instance_name)
995
  cmd = SubmitOpCode(op)
996
997

  if opts.show_command:
998
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
999
1000
1001
1002
  else:
    try:
      os.execvp(cmd[0], cmd)
    finally:
1003
      ToStderr("Can't run console command %s with arguments:\n'%s'",
1004
               cmd[0], " ".join(cmd))
1005
      os._exit(1)
Iustin Pop's avatar
Iustin Pop committed
1006
1007


1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
def _FormatLogicalID(dev_type, logical_id):
  """Formats the logical_id of a disk.

  """
  if dev_type == constants.LD_DRBD8:
    node_a, node_b, port, minor_a, minor_b, key = logical_id
    data = [
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
      ("port", port),
      ("auth key", key),
      ]
  elif dev_type == constants.LD_LV:
    vg_name, lv_name = logical_id
    data = ["%s/%s" % (vg_name, lv_name)]
  else:
    data = [str(logical_id)]

  return data


def _FormatBlockDevInfo(idx, top_level, dev, static):
Iustin Pop's avatar
Iustin Pop committed
1030
1031
  """Show block device information.

1032
  This is only used by L{ShowInstanceConfig}, but it's too big to be
Iustin Pop's avatar
Iustin Pop committed
1033
1034
  left for an inline definition.

1035
1036
1037
1038
  @type idx: int
  @param idx: the index of the current disk
  @type top_level: boolean
  @param top_level: if this a top-level disk?
1039
1040
1041
1042
1043
  @type dev: dict
  @param dev: dictionary with disk information
  @type static: boolean
  @param static: wheter the device information doesn't contain
      runtime information but only static data
1044
1045
  @return: a list of either strings, tuples or lists
      (which should be formatted at a higher indent level)
1046

Iustin Pop's avatar
Iustin Pop committed
1047
  """
1048
  def helper(dtype, status):
1049
1050
1051
1052
1053
1054
    """Format one line for physical device status.

    @type dtype: str
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
    @type status: tuple
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1055
    @return: the string representing the status
1056
1057

    """
Iustin Pop's avatar
Iustin Pop committed
1058
    if not status:
1059
1060
      return "not active"
    txt = ""
1061
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1062
1063
    if major is None:
      major_string = "N/A"
Iustin Pop's avatar
Iustin Pop committed
1064
    else:
1065
      major_string = str(major)
1066

1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
    if minor is None:
      minor_string = "N/A"
    else:
      minor_string = str(minor)

    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
    if dtype in (constants.LD_DRBD8, ):
      if syncp is not None:
        sync_text = "*RECOVERING* %5.2f%%," % syncp
        if estt:
          sync_text += " ETA %ds" % estt
1078
        else:
1079
1080
1081
1082
1083
1084
1085
          sync_text += " ETA unknown"
      else:
        sync_text = "in sync"
      if degr:
        degr_text = "*DEGRADED*"
      else:
        degr_text = "ok"
1086
      if ldisk_status == constants.LDS_FAULTY:
1087
        ldisk_text = " *MISSING DISK*"
1088
1089
      elif ldisk_status == constants.LDS_UNKNOWN:
        ldisk_text = " *UNCERTAIN STATE*"
1090
1091
1092
1093
      else:
        ldisk_text = ""
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
    elif dtype == constants.LD_LV:
1094
      if ldisk_status == constants.LDS_FAULTY:
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
        ldisk_text = " *FAILED* (failed drive?)"
      else:
        ldisk_text = ""
      txt += ldisk_text
    return txt

  # the header
  if top_level:
    if dev["iv_name"] is not None:
      txt = dev["iv_name"]
    else:
      txt = "disk %d" % idx
Iustin Pop's avatar
Iustin Pop committed
1107
  else:
1108
    txt = "child %d" % idx
Iustin Pop's avatar
Iustin Pop committed
1109
1110
1111
1112
1113
  if isinstance(dev["size"], int):
    nice_size = utils.FormatUnit(dev["size"], "h")
  else:
    nice_size = dev["size"]
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1114
1115
1116
  data = []
  if top_level:
    data.append(("access mode", dev["mode"]))
Iustin Pop's avatar
Iustin Pop committed
1117
  if dev["logical_id"] is not None:
1118
1119
1120
1121
1122
1123
1124
1125
    try:
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
    except ValueError:
      l_id = [str(dev["logical_id"])]
    if len(l_id) == 1:
      data.append(("logical_id", l_id[0]))
    else:
      data.extend(l_id)
Iustin Pop's avatar
Iustin Pop committed
1126
  elif dev["physical_id"] is not None:
1127
1128
    data.append("physical_id:")
    data.append([dev["physical_id"]])
1129
  if not static:
1130
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1131
  if dev["sstatus"] and not static:
1132
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
Iustin Pop's avatar
Iustin Pop committed
1133
1134

  if dev["children"]:
1135
1136
1137
1138
1139
    data.append("child devices:")
    for c_idx, child in enumerate(dev["children"]):
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
  d1.append(data)
  return d1
Iustin Pop's avatar
Iustin Pop committed
1140
1141


1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
def _FormatList(buf, data, indent_level):
  """Formats a list of data at a given indent level.

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

  @type buf: StringIO
  @param buf: the buffer into which we write the output
  @param data: the list to format
  @type indent_level: int
  @param indent_level: the indent level to format at

  """
  max_tlen = max([len(elem[0]) for elem in data
                 if isinstance(elem, tuple)] or [0])
  for elem in data:
    if isinstance(elem, basestring):
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
    elif isinstance(elem, tuple):
      key, value = elem
      spacer = "%*s" % (max_tlen - len(key), "")
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
    elif isinstance(elem, list):
      _FormatList(buf, elem, indent_level+1)

1170

Iustin Pop's avatar
Iustin Pop committed
1171
1172
1173
def ShowInstanceConfig(opts, args):
  """Compute instance run-time status.

1174
1175
1176
1177
1178
1179
1180
  @param opts: the command line options selected by the user
  @type args: list
  @param args: either an empty list, and then we query all
      instances, or should contain a list of instance names
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
1181
  """