backend.py 142 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
#
Iustin Pop's avatar
Iustin Pop committed
2
3
#

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#
# 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
"""Functions used by the node daemon

@var _ALLOWED_UPLOAD_FILES: denotes which files are accepted in
     the L{UploadFile} function
26
27
@var _ALLOWED_CLEAN_DIRS: denotes which directories are accepted
     in the L{_CleanDirectory} function
28
29

"""
Iustin Pop's avatar
Iustin Pop committed
30

31
# pylint: disable=E1103,C0302
Iustin Pop's avatar
Iustin Pop committed
32
33
34
35
36

# E1103: %s %r has no %r member (but some types could not be
# inferred), because the _TryOSFromDisk returns either (True, os_obj)
# or (False, "string") which confuses pylint

37
38
# C0302: This module has become too big and should be split up

Iustin Pop's avatar
Iustin Pop committed
39
40
41
42
43
44
45
46

import os
import os.path
import shutil
import time
import stat
import errno
import re
47
import random
48
import logging
49
import tempfile
50
51
import zlib
import base64
52
import signal
Iustin Pop's avatar
Iustin Pop committed
53
54
55
56
57
58

from ganeti import errors
from ganeti import utils
from ganeti import ssh
from ganeti import hypervisor
from ganeti import constants
59
60
from ganeti.storage import bdev
from ganeti.storage import drbd
61
from ganeti.storage import filestorage
Iustin Pop's avatar
Iustin Pop committed
62
from ganeti import objects
63
from ganeti import ssconf
64
from ganeti import serializer
65
from ganeti import netutils
66
from ganeti import runtime
67
from ganeti import compat
68
from ganeti import pathutils
69
from ganeti import vcluster
70
from ganeti import ht
71
72
from ganeti.storage.base import BlockDev
from ganeti.storage.drbd import DRBD8
73
from ganeti import hooksmaster
Iustin Pop's avatar
Iustin Pop committed
74
75


76
_BOOT_ID_PATH = "/proc/sys/kernel/random/boot_id"
77
_ALLOWED_CLEAN_DIRS = compat.UniqueFrozenset([
78
79
80
81
  pathutils.DATA_DIR,
  pathutils.JOB_QUEUE_ARCHIVE_DIR,
  pathutils.QUEUE_DIR,
  pathutils.CRYPTO_KEYS_DIR,
82
  ])
83
84
85
_MAX_SSL_CERT_VALIDITY = 7 * 24 * 60 * 60
_X509_KEY_FILE = "key"
_X509_CERT_FILE = "cert"
86
87
88
_IES_STATUS_FILE = "status"
_IES_PID_FILE = "pid"
_IES_CA_FILE = "ca"
89

90
#: Valid LVS output line regex
Michele Tartara's avatar
Michele Tartara committed
91
_LVSLINE_REGEX = re.compile(r"^ *([^|]+)\|([^|]+)\|([0-9.]+)\|([^|]{6,})\|?$")
92

93
94
95
96
# Actions for the master setup script
_MASTER_START = "start"
_MASTER_STOP = "stop"

97
#: Maximum file permissions for restricted command directory and executables
98
99
100
101
_RCMD_MAX_MODE = (stat.S_IRWXU |
                  stat.S_IRGRP | stat.S_IXGRP |
                  stat.S_IROTH | stat.S_IXOTH)

102
#: Delay before returning an error for restricted commands
103
104
_RCMD_INVALID_DELAY = 10

105
#: How long to wait to acquire lock for restricted commands (shorter than
106
107
108
109
#: L{_RCMD_INVALID_DELAY}) to reduce blockage of noded forks when many
#: command requests arrive
_RCMD_LOCK_TIMEOUT = _RCMD_INVALID_DELAY * 0.8

110

111
112
113
114
115
116
117
class RPCFail(Exception):
  """Class denoting RPC failure.

  Its argument is the error message.

  """

118

119
def _GetInstReasonFilename(instance_name):
120
121
122
123
124
125
126
127
128
129
130
  """Path of the file containing the reason of the instance status change.

  @type instance_name: string
  @param instance_name: The name of the instance
  @rtype: string
  @return: The path of the file

  """
  return utils.PathJoin(pathutils.INSTANCE_REASON_DIR, instance_name)


131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
def _StoreInstReasonTrail(instance_name, trail):
  """Serialize a reason trail related to an instance change of state to file.

  The exact location of the file depends on the name of the instance and on
  the configuration of the Ganeti cluster defined at deploy time.

  @type instance_name: string
  @param instance_name: The name of the instance
  @rtype: None

  """
  json = serializer.DumpJson(trail)
  filename = _GetInstReasonFilename(instance_name)
  utils.WriteFile(filename, data=json)


147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
def _Fail(msg, *args, **kwargs):
  """Log an error and the raise an RPCFail exception.

  This exception is then handled specially in the ganeti daemon and
  turned into a 'failed' return type. As such, this function is a
  useful shortcut for logging the error and returning it to the master
  daemon.

  @type msg: string
  @param msg: the text of the exception
  @raise RPCFail

  """
  if args:
    msg = msg % args
162
163
164
165
166
  if "log" not in kwargs or kwargs["log"]: # if we should log this error
    if "exc" in kwargs and kwargs["exc"]:
      logging.exception(msg)
    else:
      logging.error(msg)
167
168
169
  raise RPCFail(msg)


Michael Hanselmann's avatar
Michael Hanselmann committed
170
def _GetConfig():
Iustin Pop's avatar
Iustin Pop committed
171
  """Simple wrapper to return a SimpleStore.
Iustin Pop's avatar
Iustin Pop committed
172

Iustin Pop's avatar
Iustin Pop committed
173
174
  @rtype: L{ssconf.SimpleStore}
  @return: a SimpleStore instance
Iustin Pop's avatar
Iustin Pop committed
175
176

  """
Iustin Pop's avatar
Iustin Pop committed
177
  return ssconf.SimpleStore()
Michael Hanselmann's avatar
Michael Hanselmann committed
178
179


180
def _GetSshRunner(cluster_name):
Iustin Pop's avatar
Iustin Pop committed
181
182
183
184
185
186
187
188
189
  """Simple wrapper to return an SshRunner.

  @type cluster_name: str
  @param cluster_name: the cluster name, which is needed
      by the SshRunner constructor
  @rtype: L{ssh.SshRunner}
  @return: an SshRunner instance

  """
190
  return ssh.SshRunner(cluster_name)
191
192


193
194
195
196
197
198
199
200
201
def _Decompress(data):
  """Unpacks data compressed by the RPC client.

  @type data: list or tuple
  @param data: Data sent by RPC client
  @rtype: str
  @return: Decompressed data

  """
202
  assert isinstance(data, (list, tuple))
203
204
205
206
207
208
209
210
211
212
  assert len(data) == 2
  (encoding, content) = data
  if encoding == constants.RPC_ENCODING_NONE:
    return content
  elif encoding == constants.RPC_ENCODING_ZLIB_BASE64:
    return zlib.decompress(base64.b64decode(content))
  else:
    raise AssertionError("Unknown data encoding")


213
def _CleanDirectory(path, exclude=None):
214
215
  """Removes all regular files in a directory.

Iustin Pop's avatar
Iustin Pop committed
216
217
  @type path: str
  @param path: the directory to clean
218
  @type exclude: list
Iustin Pop's avatar
Iustin Pop committed
219
220
  @param exclude: list of files to be excluded, defaults
      to the empty list
221
222

  """
223
224
225
226
  if path not in _ALLOWED_CLEAN_DIRS:
    _Fail("Path passed to _CleanDirectory not in allowed clean targets: '%s'",
          path)

227
228
  if not os.path.isdir(path):
    return
229
230
231
232
233
  if exclude is None:
    exclude = []
  else:
    # Normalize excluded paths
    exclude = [os.path.normpath(i) for i in exclude]
234

235
  for rel_name in utils.ListVisibleFiles(path):
236
    full_name = utils.PathJoin(path, rel_name)
237
238
    if full_name in exclude:
      continue
239
240
241
242
    if os.path.isfile(full_name) and not os.path.islink(full_name):
      utils.RemoveFile(full_name)


243
244
245
246
247
248
def _BuildUploadFileList():
  """Build the list of allowed upload files.

  This is abstracted so that it's built only once at module import time.

  """
249
  allowed_files = set([
250
    pathutils.CLUSTER_CONF_FILE,
251
    pathutils.ETC_HOSTS,
252
253
254
255
256
257
258
259
    pathutils.SSH_KNOWN_HOSTS_FILE,
    pathutils.VNC_PASSWORD_FILE,
    pathutils.RAPI_CERT_FILE,
    pathutils.SPICE_CERT_FILE,
    pathutils.SPICE_CACERT_FILE,
    pathutils.RAPI_USERS_FILE,
    pathutils.CONFD_HMAC_KEY,
    pathutils.CLUSTER_DOMAIN_SECRET_FILE,
260
261
262
    ])

  for hv_name in constants.HYPER_TYPES:
263
    hv_class = hypervisor.GetHypervisorClass(hv_name)
264
    allowed_files.update(hv_class.GetAncillaryFiles()[0])
265

266
267
268
  assert pathutils.FILE_STORAGE_PATHS_FILE not in allowed_files, \
    "Allowed file storage paths should never be uploaded via RPC"

269
  return frozenset(allowed_files)
270
271
272
273
274


_ALLOWED_UPLOAD_FILES = _BuildUploadFileList()


275
def JobQueuePurge():
Iustin Pop's avatar
Iustin Pop committed
276
277
  """Removes job queue files and archived jobs.

278
279
  @rtype: tuple
  @return: True, None
280
281

  """
282
283
  _CleanDirectory(pathutils.QUEUE_DIR, exclude=[pathutils.JOB_QUEUE_LOCK_FILE])
  _CleanDirectory(pathutils.JOB_QUEUE_ARCHIVE_DIR)
284
285


286
287
288
289
290
291
292
def GetMasterInfo():
  """Returns master information.

  This is an utility function to compute master information, either
  for consumption here or from the node daemon.

  @rtype: tuple
293
294
  @return: master_netdev, master_ip, master_name, primary_ip_family,
    master_netmask
295
  @raise RPCFail: in case of errors
296
297
298

  """
  try:
Michael Hanselmann's avatar
Michael Hanselmann committed
299
300
301
    cfg = _GetConfig()
    master_netdev = cfg.GetMasterNetdev()
    master_ip = cfg.GetMasterIP()
302
    master_netmask = cfg.GetMasterNetmask()
Michael Hanselmann's avatar
Michael Hanselmann committed
303
    master_node = cfg.GetMasterNode()
304
    primary_ip_family = cfg.GetPrimaryIPFamily()
305
  except errors.ConfigurationError, err:
Iustin Pop's avatar
Iustin Pop committed
306
    _Fail("Cluster configuration incomplete: %s", err, exc=True)
307
  return (master_netdev, master_ip, master_node, primary_ip_family,
Iustin Pop's avatar
Iustin Pop committed
308
          master_netmask)
309
310


311
312
313
314
315
316
317
318
319
def RunLocalHooks(hook_opcode, hooks_path, env_builder_fn):
  """Decorator that runs hooks before and after the decorated function.

  @type hook_opcode: string
  @param hook_opcode: opcode of the hook
  @type hooks_path: string
  @param hooks_path: path of the hooks
  @type env_builder_fn: function
  @param env_builder_fn: function that returns a dictionary containing the
320
321
    environment variables for the hooks. Will get all the parameters of the
    decorated function.
322
323
324
325
326
327
328
329
  @raise RPCFail: in case of pre-hook failure

  """
  def decorator(fn):
    def wrapper(*args, **kwargs):
      _, myself = ssconf.GetMasterAndMyself()
      nodes = ([myself], [myself])  # these hooks run locally

330
331
      env_fn = compat.partial(env_builder_fn, *args, **kwargs)

332
333
      cfg = _GetConfig()
      hr = HooksRunner()
334
      hm = hooksmaster.HooksMaster(hook_opcode, hooks_path, nodes,
335
                                   hr.RunLocalHooks, None, env_fn, None,
336
337
                                   logging.warning, cfg.GetClusterName(),
                                   cfg.GetMasterNode())
338
339
340
341
342
343
344
345
346
      hm.RunPhase(constants.HOOKS_PHASE_PRE)
      result = fn(*args, **kwargs)
      hm.RunPhase(constants.HOOKS_PHASE_POST)

      return result
    return wrapper
  return decorator


347
def _BuildMasterIpEnv(master_params, use_external_mip_script=None):
348
349
  """Builds environment variables for master IP hooks.

350
351
  @type master_params: L{objects.MasterNetworkParameters}
  @param master_params: network parameters of the master
352
353
354
355
  @type use_external_mip_script: boolean
  @param use_external_mip_script: whether to use an external master IP
    address setup script (unused, but necessary per the implementation of the
    _RunLocalHooks decorator)
356

357
  """
358
  # pylint: disable=W0613
359
  ver = netutils.IPAddress.GetVersionFromAddressFamily(master_params.ip_family)
360
  env = {
361
362
    "MASTER_NETDEV": master_params.netdev,
    "MASTER_IP": master_params.ip,
363
    "MASTER_NETMASK": str(master_params.netmask),
364
    "CLUSTER_IP_VERSION": str(ver),
365
366
367
368
369
  }

  return env


370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
def _RunMasterSetupScript(master_params, action, use_external_mip_script):
  """Execute the master IP address setup script.

  @type master_params: L{objects.MasterNetworkParameters}
  @param master_params: network parameters of the master
  @type action: string
  @param action: action to pass to the script. Must be one of
    L{backend._MASTER_START} or L{backend._MASTER_STOP}
  @type use_external_mip_script: boolean
  @param use_external_mip_script: whether to use an external master IP
    address setup script
  @raise backend.RPCFail: if there are errors during the execution of the
    script

  """
  env = _BuildMasterIpEnv(master_params)

  if use_external_mip_script:
388
    setup_script = pathutils.EXTERNAL_MASTER_SETUP_SCRIPT
389
  else:
390
    setup_script = pathutils.DEFAULT_MASTER_SETUP_SCRIPT
391
392
393
394

  result = utils.RunCmd([setup_script, action], env=env, reset_env=True)

  if result.failed:
395
396
    _Fail("Failed to %s the master IP. Script return value: %s, output: '%s'" %
          (action, result.exit_code, result.output), log=True)
397
398


399
@RunLocalHooks(constants.FAKE_OP_MASTER_TURNUP, "master-ip-turnup",
400
               _BuildMasterIpEnv)
401
def ActivateMasterIp(master_params, use_external_mip_script):
402
403
  """Activate the IP address of the master daemon.

404
405
  @type master_params: L{objects.MasterNetworkParameters}
  @param master_params: network parameters of the master
406
407
408
  @type use_external_mip_script: boolean
  @param use_external_mip_script: whether to use an external master IP
    address setup script
409
  @raise RPCFail: in case of errors during the IP startup
410

411
  """
412
413
  _RunMasterSetupScript(master_params, _MASTER_START,
                        use_external_mip_script)
414
415
416


def StartMasterDaemons(no_voting):
Iustin Pop's avatar
Iustin Pop committed
417
418
  """Activate local node as master node.

419
  The function will start the master daemons (ganeti-masterd and ganeti-rapi).
Iustin Pop's avatar
Iustin Pop committed
420

421
422
  @type no_voting: boolean
  @param no_voting: whether to start ganeti-masterd without a node vote
423
      but still non-interactively
Iustin Pop's avatar
Iustin Pop committed
424
  @rtype: None
Iustin Pop's avatar
Iustin Pop committed
425
426
427

  """

428
429
430
431
  if no_voting:
    masterd_args = "--no-voting --yes-do-it"
  else:
    masterd_args = ""
432

433
434
435
436
  env = {
    "EXTRA_MASTERD_ARGS": masterd_args,
    }

437
  result = utils.RunCmd([pathutils.DAEMON_UTIL, "start-master"], env=env)
438
439
440
441
  if result.failed:
    msg = "Can't start Ganeti master: %s" % result.output
    logging.error(msg)
    _Fail(msg)
442

443

444
@RunLocalHooks(constants.FAKE_OP_MASTER_TURNDOWN, "master-ip-turndown",
445
               _BuildMasterIpEnv)
446
def DeactivateMasterIp(master_params, use_external_mip_script):
447
  """Deactivate the master IP on this node.
Iustin Pop's avatar
Iustin Pop committed
448

449
450
  @type master_params: L{objects.MasterNetworkParameters}
  @param master_params: network parameters of the master
451
452
453
  @type use_external_mip_script: boolean
  @param use_external_mip_script: whether to use an external master IP
    address setup script
454
  @raise RPCFail: in case of errors during the IP turndown
455

Iustin Pop's avatar
Iustin Pop committed
456
  """
457
458
  _RunMasterSetupScript(master_params, _MASTER_STOP,
                        use_external_mip_script)
459

460
461
462
463
464
465
466
467
468
469
470
471

def StopMasterDaemons():
  """Stop the master daemons on this node.

  Stop the master daemons (ganeti-masterd and ganeti-rapi) on this node.

  @rtype: None

  """
  # TODO: log and report back to the caller the error failures; we
  # need to decide in which case we fail the RPC for this

472
  result = utils.RunCmd([pathutils.DAEMON_UTIL, "stop-master"])
473
474
475
476
  if result.failed:
    logging.error("Could not stop Ganeti master, command %s had exitcode %s"
                  " and error %s",
                  result.cmd, result.exit_code, result.output)
Iustin Pop's avatar
Iustin Pop committed
477
478


479
def ChangeMasterNetmask(old_netmask, netmask, master_ip, master_netdev):
480
481
  """Change the netmask of the master IP.

482
483
484
485
486
  @param old_netmask: the old value of the netmask
  @param netmask: the new value of the netmask
  @param master_ip: the master IP
  @param master_netdev: the master network device

487
488
489
490
  """
  if old_netmask == netmask:
    return

491
492
493
494
  if not netutils.IPAddress.Own(master_ip):
    _Fail("The master IP address is not up, not attempting to change its"
          " netmask")

495
496
497
498
499
  result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "add",
                         "%s/%s" % (master_ip, netmask),
                         "dev", master_netdev, "label",
                         "%s:0" % master_netdev])
  if result.failed:
500
    _Fail("Could not set the new netmask on the master IP address")
501
502
503
504
505
506

  result = utils.RunCmd([constants.IP_COMMAND_PATH, "address", "del",
                         "%s/%s" % (master_ip, old_netmask),
                         "dev", master_netdev, "label",
                         "%s:0" % master_netdev])
  if result.failed:
507
    _Fail("Could not bring down the master IP address with the old netmask")
508
509


510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
def EtcHostsModify(mode, host, ip):
  """Modify a host entry in /etc/hosts.

  @param mode: The mode to operate. Either add or remove entry
  @param host: The host to operate on
  @param ip: The ip associated with the entry

  """
  if mode == constants.ETC_HOSTS_ADD:
    if not ip:
      RPCFail("Mode 'add' needs 'ip' parameter, but parameter not"
              " present")
    utils.AddHostToEtcHosts(host, ip)
  elif mode == constants.ETC_HOSTS_REMOVE:
    if ip:
      RPCFail("Mode 'remove' does not allow 'ip' parameter, but"
              " parameter is present")
    utils.RemoveHostFromEtcHosts(host)
  else:
    RPCFail("Mode not supported")


532
def LeaveCluster(modify_ssh_setup):
Iustin Pop's avatar
Iustin Pop committed
533
534
535
536
537
538
  """Cleans up and remove the current node.

  This function cleans up and prepares the current node to be removed
  from the cluster.

  If processing is successful, then it raises an
Iustin Pop's avatar
Iustin Pop committed
539
  L{errors.QuitGanetiException} which is used as a special case to
Iustin Pop's avatar
Iustin Pop committed
540
  shutdown the node daemon.
Iustin Pop's avatar
Iustin Pop committed
541

542
543
  @param modify_ssh_setup: boolean

Iustin Pop's avatar
Iustin Pop committed
544
  """
545
546
  _CleanDirectory(pathutils.DATA_DIR)
  _CleanDirectory(pathutils.CRYPTO_KEYS_DIR)
547
  JobQueuePurge()
548

549
550
  if modify_ssh_setup:
    try:
Michael Hanselmann's avatar
Michael Hanselmann committed
551
      priv_key, pub_key, auth_keys = ssh.GetUserFiles(constants.SSH_LOGIN_USER)
552

553
      utils.RemoveAuthorizedKey(auth_keys, utils.ReadFile(pub_key))
Iustin Pop's avatar
Iustin Pop committed
554

555
556
557
558
      utils.RemoveFile(priv_key)
      utils.RemoveFile(pub_key)
    except errors.OpExecError:
      logging.exception("Error while processing ssh files")
Iustin Pop's avatar
Iustin Pop committed
559

560
  try:
561
562
563
564
565
    utils.RemoveFile(pathutils.CONFD_HMAC_KEY)
    utils.RemoveFile(pathutils.RAPI_CERT_FILE)
    utils.RemoveFile(pathutils.SPICE_CERT_FILE)
    utils.RemoveFile(pathutils.SPICE_CACERT_FILE)
    utils.RemoveFile(pathutils.NODED_CERT_FILE)
566
  except: # pylint: disable=W0702
567
568
    logging.exception("Error while removing cluster secrets")

569
  result = utils.RunCmd([pathutils.DAEMON_UTIL, "stop", constants.CONFD])
570
571
572
  if result.failed:
    logging.error("Command %s failed with exitcode %s and error %s",
                  result.cmd, result.exit_code, result.output)
573

574
  # Raise a custom exception (handled in ganeti-noded)
Iustin Pop's avatar
Iustin Pop committed
575
  raise errors.QuitGanetiException(True, "Shutdown scheduled")
576

Iustin Pop's avatar
Iustin Pop committed
577

578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
def _CheckStorageParams(params, num_params):
  """Performs sanity checks for storage parameters.

  @type params: list
  @param params: list of storage parameters
  @type num_params: int
  @param num_params: expected number of parameters

  """
  if params is None:
    raise errors.ProgrammerError("No storage parameters for storage"
                                 " reporting is provided.")
  if not isinstance(params, list):
    raise errors.ProgrammerError("The storage parameters are not of type"
                                 " list: '%s'" % params)
  if not len(params) == num_params:
    raise errors.ProgrammerError("Did not receive the expected number of"
                                 "storage parameters: expected %s,"
                                 " received '%s'" % (num_params, len(params)))


599
600
601
602
603
604
605
606
607
608
609
610
611
612
def _CheckLvmStorageParams(params):
  """Performs sanity check for the 'exclusive storage' flag.

  @see: C{_CheckStorageParams}

  """
  _CheckStorageParams(params, 1)
  excl_stor = params[0]
  if not isinstance(params[0], bool):
    raise errors.ProgrammerError("Exclusive storage parameter is not"
                                 " boolean: '%s'." % excl_stor)
  return excl_stor


613
614
615
616
617
618
619
620
621
622
def _GetLvmVgSpaceInfo(name, params):
  """Wrapper around C{_GetVgInfo} which checks the storage parameters.

  @type name: string
  @param name: name of the volume group
  @type params: list
  @param params: list of storage parameters, which in this case should be
    containing only one for exclusive storage

  """
623
  excl_stor = _CheckLvmStorageParams(params)
624
625
626
  return _GetVgInfo(name, excl_stor)


Helga Velroyen's avatar
Helga Velroyen committed
627
628
def _GetVgInfo(
    name, excl_stor, info_fn=bdev.LogicalVolume.GetVGInfo):
629
630
631
632
  """Retrieves information about a LVM volume group.

  """
  # TODO: GetVGInfo supports returning information for multiple VGs at once
Helga Velroyen's avatar
Helga Velroyen committed
633
  vginfo = info_fn([name], excl_stor)
634
635
636
637
638
639
640
641
  if vginfo:
    vg_free = int(round(vginfo[0][0], 0))
    vg_size = int(round(vginfo[0][1], 0))
  else:
    vg_free = None
    vg_size = None

  return {
642
    "type": constants.ST_LVM_VG,
643
    "name": name,
644
645
    "storage_free": vg_free,
    "storage_size": vg_size,
646
647
648
    }


649
650
651
def _GetLvmPvSpaceInfo(name, params):
  """Wrapper around C{_GetVgSpindlesInfo} with sanity checks.

652
  @see: C{_GetLvmVgSpaceInfo}
653
654
655
656

  """
  excl_stor = _CheckLvmStorageParams(params)
  return _GetVgSpindlesInfo(name, excl_stor)
Helga Velroyen's avatar
Helga Velroyen committed
657

658

659
660
def _GetVgSpindlesInfo(
    name, excl_stor, info_fn=bdev.LogicalVolume.GetVgSpindlesInfo):
661
662
663
664
665
666
667
668
669
670
671
672
  """Retrieves information about spindles in an LVM volume group.

  @type name: string
  @param name: VG name
  @type excl_stor: bool
  @param excl_stor: exclusive storage
  @rtype: dict
  @return: dictionary whose keys are "name", "vg_free", "vg_size" for VG name,
      free spindles, total spindles respectively

  """
  if excl_stor:
673
    (vg_free, vg_size) = info_fn(name)
674
675
676
677
  else:
    vg_free = 0
    vg_size = 0
  return {
678
    "type": constants.ST_LVM_PV,
679
    "name": name,
680
681
    "storage_free": vg_free,
    "storage_size": vg_size,
682
683
684
    }


685
def _GetHvInfo(name, hvparams, get_hv_fn=hypervisor.GetHypervisor):
686
687
688
689
690
691
692
693
694
695
696
  """Retrieves node information from a hypervisor.

  The information returned depends on the hypervisor. Common items:

    - vg_size is the size of the configured volume group in MiB
    - vg_free is the free size of the volume group in MiB
    - memory_dom0 is the memory allocated for domain0 in MiB
    - memory_free is the currently available (free) ram in MiB
    - memory_total is the total number of ram in MiB
    - hv_version: the hypervisor version, if available

697
698
699
  @type hvparams: dict of string
  @param hvparams: the hypervisor's hvparams

700
  """
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
  return get_hv_fn(name).GetNodeInfo(hvparams=hvparams)


def _GetHvInfoAll(hv_specs, get_hv_fn=hypervisor.GetHypervisor):
  """Retrieves node information for all hypervisors.

  See C{_GetHvInfo} for information on the output.

  @type hv_specs: list of pairs (string, dict of strings)
  @param hv_specs: list of pairs of a hypervisor's name and its hvparams

  """
  if hv_specs is None:
    return None

  result = []
  for hvname, hvparams in hv_specs:
    result.append(_GetHvInfo(hvname, hvparams, get_hv_fn))
  return result
720
721
722
723
724
725
726
727
728
729
730


def _GetNamedNodeInfo(names, fn):
  """Calls C{fn} for all names in C{names} and returns a dictionary.

  @rtype: None or dict

  """
  if names is None:
    return None
  else:
731
    return map(fn, names)
732
733


734
def GetNodeInfo(storage_units, hv_specs):
Michael Hanselmann's avatar
Michael Hanselmann committed
735
  """Gives back a hash with different information about the node.
Iustin Pop's avatar
Iustin Pop committed
736

737
738
739
740
741
  @type storage_units: list of tuples (string, string, list)
  @param storage_units: List of tuples (storage unit, identifier, parameters) to
    ask for disk space information. In case of lvm-vg, the identifier is
    the VG name. The parameters can contain additional, storage-type-specific
    parameters, for example exclusive storage for lvm storage.
742
743
  @type hv_specs: list of pairs (string, dict of strings)
  @param hv_specs: list of pairs of a hypervisor's name and its hvparams
744
745
746
  @rtype: tuple; (string, None/dict, None/dict)
  @return: Tuple containing boot ID, volume group information and hypervisor
    information
Iustin Pop's avatar
Iustin Pop committed
747

748
  """
749
  bootid = utils.ReadFile(_BOOT_ID_PATH, size=128).rstrip("\n")
750
751
  storage_info = _GetNamedNodeInfo(
    storage_units,
752
753
    (lambda (storage_type, storage_key, storage_params):
        _ApplyStorageInfoFunction(storage_type, storage_key, storage_params)))
754
  hv_info = _GetHvInfoAll(hv_specs)
755
756
757
  return (bootid, storage_info, hv_info)


758
def _GetFileStorageSpaceInfo(path, params):
759
760
761
762
763
764
765
766
767
768
  """Wrapper around filestorage.GetSpaceInfo.

  The purpose of this wrapper is to call filestorage.GetFileStorageSpaceInfo
  and ignore the *args parameter to not leak it into the filestorage
  module's code.

  @see: C{filestorage.GetFileStorageSpaceInfo} for description of the
    parameters.

  """
769
  _CheckStorageParams(params, 0)
770
771
772
  return filestorage.GetFileStorageSpaceInfo(path)


773
774
775
776
777
# FIXME: implement storage reporting for all missing storage types.
_STORAGE_TYPE_INFO_FN = {
  constants.ST_BLOCK: None,
  constants.ST_DISKLESS: None,
  constants.ST_EXT: None,
778
  constants.ST_FILE: _GetFileStorageSpaceInfo,
779
  constants.ST_LVM_PV: _GetLvmPvSpaceInfo,
780
  constants.ST_LVM_VG: _GetLvmVgSpaceInfo,
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
  constants.ST_RADOS: None,
}


def _ApplyStorageInfoFunction(storage_type, storage_key, *args):
  """Looks up and applies the correct function to calculate free and total
  storage for the given storage type.

  @type storage_type: string
  @param storage_type: the storage type for which the storage shall be reported.
  @type storage_key: string
  @param storage_key: identifier of a storage unit, e.g. the volume group name
    of an LVM storage unit
  @type args: any
  @param args: various parameters that can be used for storage reporting. These
    parameters and their semantics vary from storage type to storage type and
    are just propagated in this function.
  @return: the results of the application of the storage space function (see
    _STORAGE_TYPE_INFO_FN) if storage space reporting is implemented for that
    storage type
  @raises NotImplementedError: for storage types who don't support space
    reporting yet
  """
  fn = _STORAGE_TYPE_INFO_FN[storage_type]
  if fn is not None:
    return fn(storage_key, *args)
  else:
    raise NotImplementedError
Iustin Pop's avatar
Iustin Pop committed
809
810


811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
def _CheckExclusivePvs(pvi_list):
  """Check that PVs are not shared among LVs

  @type pvi_list: list of L{objects.LvmPvInfo} objects
  @param pvi_list: information about the PVs

  @rtype: list of tuples (string, list of strings)
  @return: offending volumes, as tuples: (pv_name, [lv1_name, lv2_name...])

  """
  res = []
  for pvi in pvi_list:
    if len(pvi.lv_list) > 1:
      res.append((pvi.name, pvi.lv_list))
  return res


828
829
830
831
832
833
834
def _VerifyHypervisors(what, vm_capable, result, all_hvparams,
                       get_hv_fn=hypervisor.GetHypervisor):
  """Verifies the hypervisor. Appends the results to the 'results' list.

  @type what: C{dict}
  @param what: a dictionary of things to check
  @type vm_capable: boolean
835
  @param vm_capable: whether or not this node is vm capable
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
  @type result: dict
  @param result: dictionary of verification results; results of the
    verifications in this function will be added here
  @type all_hvparams: dict of dict of string
  @param all_hvparams: dictionary mapping hypervisor names to hvparams
  @type get_hv_fn: function
  @param get_hv_fn: function to retrieve the hypervisor, to improve testability

  """
  if not vm_capable:
    return

  if constants.NV_HYPERVISOR in what:
    result[constants.NV_HYPERVISOR] = {}
    for hv_name in what[constants.NV_HYPERVISOR]:
      hvparams = all_hvparams[hv_name]
      try:
        val = get_hv_fn(hv_name).Verify(hvparams=hvparams)
      except errors.HypervisorError, err:
        val = "Error while checking hypervisor: %s" % str(err)
      result[constants.NV_HYPERVISOR][hv_name] = val


def _VerifyHvparams(what, vm_capable, result,
                    get_hv_fn=hypervisor.GetHypervisor):
  """Verifies the hvparams. Appends the results to the 'results' list.

  @type what: C{dict}
  @param what: a dictionary of things to check
  @type vm_capable: boolean
866
  @param vm_capable: whether or not this node is vm capable
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
  @type result: dict
  @param result: dictionary of verification results; results of the
    verifications in this function will be added here
  @type get_hv_fn: function
  @param get_hv_fn: function to retrieve the hypervisor, to improve testability

  """
  if not vm_capable:
    return

  if constants.NV_HVPARAMS in what:
    result[constants.NV_HVPARAMS] = []
    for source, hv_name, hvparms in what[constants.NV_HVPARAMS]:
      try:
        logging.info("Validating hv %s, %s", hv_name, hvparms)
        get_hv_fn(hv_name).ValidateParameters(hvparms)
      except errors.HypervisorError, err:
        result[constants.NV_HVPARAMS].append((source, hv_name, str(err)))


887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
def _VerifyInstanceList(what, vm_capable, result, all_hvparams):
  """Verifies the instance list.

  @type what: C{dict}
  @param what: a dictionary of things to check
  @type vm_capable: boolean
  @param vm_capable: whether or not this node is vm capable
  @type result: dict
  @param result: dictionary of verification results; results of the
    verifications in this function will be added here
  @type all_hvparams: dict of dict of string
  @param all_hvparams: dictionary mapping hypervisor names to hvparams

  """
  if constants.NV_INSTANCELIST in what and vm_capable:
    # GetInstanceList can fail
    try:
      val = GetInstanceList(what[constants.NV_INSTANCELIST],
                            all_hvparams=all_hvparams)
    except RPCFail, err:
      val = str(err)
    result[constants.NV_INSTANCELIST] = val


def _VerifyNodeInfo(what, vm_capable, result, all_hvparams):
  """Verifies the node info.

  @type what: C{dict}
  @param what: a dictionary of things to check
  @type vm_capable: boolean
  @param vm_capable: whether or not this node is vm capable
  @type result: dict
  @param result: dictionary of verification results; results of the
    verifications in this function will be added here
  @type all_hvparams: dict of dict of string
  @param all_hvparams: dictionary mapping hypervisor names to hvparams

  """
  if constants.NV_HVINFO in what and vm_capable:
    hvname = what[constants.NV_HVINFO]
    hyper = hypervisor.GetHypervisor(hvname)
    hvparams = all_hvparams[hvname]
    result[constants.NV_HVINFO] = hyper.GetNodeInfo(hvparams=hvparams)


def VerifyNode(what, cluster_name, all_hvparams):
Iustin Pop's avatar
Iustin Pop committed
933
934
  """Verify the status of the local node.

935
936
937
938
939
940
941
942
943
  Based on the input L{what} parameter, various checks are done on the
  local node.

  If the I{filelist} key is present, this list of
  files is checksummed and the file/checksum pairs are returned.

  If the I{nodelist} key is present, we check that we have
  connectivity via ssh with the target nodes (and check the hostname
  report).
Iustin Pop's avatar
Iustin Pop committed
944

945
946
947
948
949
950
951
952
953
954
955
  If the I{node-net-test} key is present, we check that we have
  connectivity to the given nodes via both primary IP and, if
  applicable, secondary IPs.

  @type what: C{dict}
  @param what: a dictionary of things to check:
      - filelist: list of files for which to compute checksums
      - nodelist: list of nodes we should check ssh communication with
      - node-net-test: list of nodes we should check node daemon port
        connectivity with
      - hypervisor: list with hypervisors to run the verify for
956
957
958
959
  @type cluster_name: string
  @param cluster_name: the cluster's name
  @type all_hvparams: dict of dict of strings
  @param all_hvparams: a dictionary mapping hypervisor names to hvparams
Iustin Pop's avatar
Iustin Pop committed
960
961
962
  @rtype: dict
  @return: a dictionary with the same keys as the input dict, and
      values representing the result of the checks
Iustin Pop's avatar
Iustin Pop committed
963
964
965

  """
  result = {}
966
  my_name = netutils.Hostname.GetSysName()
967
  port = netutils.GetDaemonPort(constants.NODED)
968
  vm_capable = my_name not in what.get(constants.NV_VMNODES, [])
Iustin Pop's avatar
Iustin Pop committed
969

970
971
  _VerifyHypervisors(what, vm_capable, result, all_hvparams)
  _VerifyHvparams(what, vm_capable, result)
972

973
  if constants.NV_FILELIST in what:
974
975
976
977
978
    fingerprints = utils.FingerprintFiles(map(vcluster.LocalizeVirtualPath,
                                              what[constants.NV_FILELIST]))
    result[constants.NV_FILELIST] = \
      dict((vcluster.MakeVirtualPath(key), value)
           for (key, value) in fingerprints.items())
979
980

  if constants.NV_NODELIST in what:
981
982
983
984
985
986
987
988
989
990
991
992
993
994
    (nodes, bynode) = what[constants.NV_NODELIST]

    # Add nodes from other groups (different for each node)
    try:
      nodes.extend(bynode[my_name])
    except KeyError:
      pass

    # Use a random order
    random.shuffle(nodes)

    # Try to contact all nodes
    val = {}
    for node in nodes:
995
      success, message = _GetSshRunner(cluster_name).VerifyNodeHostname(node)
Iustin Pop's avatar
Iustin Pop committed
996
      if not success:
997
998
999
        val[node] = message

    result[constants.NV_NODELIST] = val
1000
1001
1002

  if constants.NV_NODENETTEST in what:
    result[constants.NV_NODENETTEST] = tmp = {}
1003
    my_pip = my_sip = None
1004
    for name, pip, sip in what[constants.NV_NODENETTEST]:
1005
1006
1007
1008
1009
      if name == my_name:
        my_pip = pip
        my_sip = sip
        break
    if not my_pip:
1010
1011
      tmp[my_name] = ("Can't find my own primary/secondary IP"
                      " in the node list")
1012
    else:
1013
      for name, pip, sip in what[constants.NV_NODENETTEST]:
1014
        fail = []
1015
        if not netutils.TcpPing(pip, port, source=my_pip):
1016
1017
          fail.append("primary")
        if sip != pip:
1018
          if not netutils.TcpPing(sip, port, source=my_sip):
1019
1020
            fail.append("secondary")
        if fail:
1021
1022
1023
          tmp[name] = ("failure using the %s interface(s)" %
                       " and ".join(fail))

1024
1025
1026
1027
1028
  if constants.NV_MASTERIP in what:
    # FIXME: add checks on incoming data structures (here and in the
    # rest of the function)
    master_name, master_ip = what[constants.NV_MASTERIP]
    if master_name == my_name:
1029
      source = constants.IP4_ADDRESS_LOCALHOST
1030
1031
    else:
      source = None
1032
    result[constants.NV_MASTERIP] = netutils.TcpPing(master_ip, port,
Iustin Pop's avatar
Iustin Pop committed
1033
                                                     source=source)
1034

1035
1036
1037
  if constants.NV_USERSCRIPTS in what:
    result[constants.NV_USERSCRIPTS] = \
      [script for script in what[constants.NV_USERSCRIPTS]
1038
       if not utils.IsExecutable(script)]
1039

1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
  if constants.NV_OOB_PATHS in what:
    result[constants.NV_OOB_PATHS] = tmp = []
    for path in what[constants.NV_OOB_PATHS]:
      try:
        st = os.stat(path)
      except OSError, err:
        tmp.append("error stating out of band helper: %s" % err)
      else:
        if stat.S_ISREG(st.st_mode):
          if stat.S_IMODE(st.st_mode) & stat.S_IXUSR:
            tmp.append(None)
          else:
            tmp.append("out of band helper %s is not executable" % path)
        else:
          tmp.append("out of band helper %s is not a file" % path)

1056
  if constants.NV_LVLIST in what and vm_capable:
1057
    try:
1058
      val = GetVolumeList(utils.ListVolumeGroups().keys())
1059
1060
1061
    except RPCFail, err:
      val = str(err)
    result[constants.NV_LVLIST] = val
1062

1063
  _VerifyInstanceList(what, vm_capable, result, all_hvparams)
1064

1065
  if constants.NV_VGLIST in what and vm_capable:
1066
    result[constants.NV_VGLIST] = utils.ListVolumeGroups()
1067

1068
  if constants.NV_PVLIST in what and vm_capable:
1069
    check_exclusive_pvs = constants.NV_EXCLUSIVEPVS in what
1070
    val = bdev.LogicalVolume.GetPVInfo(what[constants.NV_PVLIST],
1071
1072
1073
1074
1075
1076
1077
                                       filter_allocatable=False,
                                       include_lvs=check_exclusive_pvs)
    if check_exclusive_pvs:
      result[constants.NV_EXCLUSIVEPVS] = _CheckExclusivePvs(val)
      for pvi in val:
        # Avoid sending useless data on the wire
        pvi.lv_list = []
1078
    result[constants.NV_PVLIST] = map(objects.LvmPvInfo.ToDict, val)
1079

1080
  if constants.NV_VERSION in what:
1081
1082
    result[constants.NV_VERSION] = (constants.PROTOCOL_VERSION,
                                    constants.RELEASE_VERSION)
1083

1084
  _VerifyNodeInfo(what, vm_capable, result, all_hvparams)
1085

1086
1087
  if constants.NV_DRBDVERSION in what and vm_capable:
    try:
1088
      drbd_version = DRBD8.GetProcInfo().GetVersionString()
1089
1090
1091
1092
1093
    except errors.BlockDeviceError, err:
      logging.warning("Can't get DRBD version", exc_info=True)
      drbd_version = str(err)
    result[constants.NV_DRBDVERSION] = drbd_version

1094
  if constants.NV_DRBDLIST in what and vm_capable:
1095
    try:
1096
      used_minors = drbd.DRBD8.GetUsedDevs()
1097
    except errors.BlockDeviceError, err:
1098
      logging.warning("Can't get used minors list", exc_info=True)
1099
      used_minors = str(err)
1100
1101
    result[constants.NV_DRBDLIST] = used_minors

1102
  if constants.NV_DRBDHELPER in what and vm_capable:
1103
1104
    status = True
    try:
1105
      payload = drbd.DRBD8.GetUsermodeHelper()
1106
1107
1108
1109
1110
1111
    except errors.BlockDeviceError, err:
      logging.error("Can't get DRBD usermode helper: %s", str(err))
      status = False
      payload = str(err)
    result[constants.NV_DRBDHELPER] = (status, payload)

1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
  if constants.NV_NODESETUP in what:
    result[constants.NV_NODESETUP] = tmpr = []
    if not os.path.isdir("/sys/block") or not os.path.isdir("/sys/class/net"):
      tmpr.append("The sysfs filesytem doesn't seem to be mounted"
                  " under /sys, missing required directories /sys/block"
                  " and /sys/class/net")
    if (not os.path.isdir("/proc/sys") or
        not os.path.isfile("/proc/sysrq-trigger")):
      tmpr.append("The procfs filesystem doesn't seem to be mounted"
                  " under /proc, missing required directory /proc/sys and"
                  " the file /proc/sysrq-trigger")
1123
1124
1125
1126

  if constants.NV_TIME in what:
    result[constants.NV_TIME] = utils.SplitTime(time.time())

1127
  if constants.NV_OSLIST in what and vm_capable:
1128
1129
    result[constants.NV_OSLIST] = DiagnoseOS()

1130
1131
1132
1133
  if constants.NV_BRIDGES in what and vm_capable:
    result[constants.NV_BRIDGES] = [bridge
                                    for bridge in what[constants.NV_BRIDGES]
                                    if not utils.BridgeExists(bridge)]
1134

1135
1136
1137
  if what.get(constants.NV_ACCEPTED_STORAGE_PATHS) == my_name:
    result[constants.NV_ACCEPTED_STORAGE_PATHS] = \
        filestorage.ComputeWrongFileStoragePaths()
1138

Helga Velroyen's avatar
Helga Velroyen committed
1139
1140
1141
1142
1143
1144
  if what.get(constants.NV_FILE_STORAGE_PATH):
    pathresult = filestorage.CheckFileStoragePath(
        what[constants.NV_FILE_STORAGE_PATH])
    if pathresult:
      result[constants.NV_FILE_STORAGE_PATH] = pathresult

1145
1146
1147
1148
1149
1150
  if what.get(constants.NV_SHARED_FILE_STORAGE_PATH):
    pathresult = filestorage.CheckFileStoragePath(
        what[constants.NV_SHARED_FILE_STORAGE_PATH])
    if pathresult:
      result[constants.NV_SHARED_FILE_STORAGE_PATH] = pathresult

1151
  return result
Iustin Pop's avatar
Iustin Pop committed
1152
1153


1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
def GetBlockDevSizes(devices):
  """Return the size of the given block devices

  @type devices: list
  @param devices: list of block device nodes to query
  @rtype: dict
  @return:
    dictionary of all block devices under /dev (key). The value is their
    size in MiB.

    {'/dev/disk/by-uuid/123456-12321231-312312-312': 124}

  """
  DEV_PREFIX = "/dev/"
  blockdevs = {}

  for devpath in devices:
1171
    if not utils.IsBelowDir(DEV_PREFIX, devpath):
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
      continue

    try:
      st = os.stat(devpath)
    except EnvironmentError, err:
      logging.warning("Error stat()'ing device %s: %s", devpath, str(err))
      continue

    if stat.S_ISBLK(st.st_mode):
      result = utils.RunCmd(["blockdev", "--getsize64", devpath])
      if result.failed:
        # We don't want to fail, just do not list this device as available
        logging.warning("Cannot get size for block device %s", devpath)
        continue

      size = int(result.stdout) / (1024 * 1024)
      blockdevs[devpath] = size
  return blockdevs


1192
def GetVolumeList(vg_names):
Iustin Pop's avatar
Iustin Pop committed
1193
1194
  """Compute list of logical volumes and their size.

1195
  @type vg_names: list
1196
1197
  @param vg_names: the volume groups whose LVs we should list, or
      empty for all volume groups
Iustin Pop's avatar
Iustin Pop committed
1198
1199
1200
1201
1202
  @rtype: dict
  @return:
      dictionary of all partions (key) with value being a tuple of
      their size (in MiB), inactive and online status::

1203
        {'xenvg/test1': ('20.06', True, True)}
Iustin Pop's avatar
Iustin Pop committed
1204
1205
1206

      in case of errors, a string is returned with the error
      details.
Iustin Pop's avatar
Iustin Pop committed
1207
1208

  """
1209
  lvs = {}
Iustin Pop's avatar
Iustin Pop committed
1210
  sep = "|"
1211
1212
  if not vg_names:
    vg_names = []
1213
1214
  result = utils.RunCmd(["lvs", "--noheadings", "--units=m", "--nosuffix",
                         "--separator=%s" % sep,
1215
                         "-ovg_name,lv_name,lv_size,lv_attr"] + vg_names)
Iustin Pop's avatar
Iustin Pop committed
1216
  if result.failed:
1217
    _Fail("Failed to list logical volumes, lvs output: %s", result.output)
1218
1219

  for line in result.stdout.splitlines():
1220
    line = line.strip()
1221
    match = _LVSLINE_REGEX.match(line)
1222
    if not match:
1223
      logging.error("Invalid line returned from lvs output: '%s'", line)
1224
      continue
1225
    vg_name, name, size, attr = match.groups()
Iustin Pop's avatar
Iustin Pop committed
1226
1227
1228
    inactive = attr[4] == "-"
    online = attr[5] == "o"
    virtual = attr[0] == "v"
Iustin Pop's avatar
Iustin Pop committed
1229
1230
1231
1232
    if virtual:
      # we don't want to report such volumes as existing, since they
      # don't really hold data
      continue
Michael Hanselmann's avatar
Michael Hanselmann committed
1233
    lvs[vg_name + "/" + name] = (size, inactive, online)
1234
1235

  return lvs
Iustin Pop's avatar
Iustin Pop committed
1236
1237
1238


def ListVolumeGroups():
Alexander Schreiber's avatar
Alexander Schreiber committed
1239
  """List the volume groups and their size.
Iustin Pop's avatar
Iustin Pop committed
1240

Iustin Pop's avatar
Iustin Pop committed
1241
1242
1243
  @rtype: dict