1# Copyright 2013 OpenStack Foundation. 2# All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); you may 5# not use this file except in compliance with the License. You may obtain 6# a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13# License for the specific language governing permissions and limitations 14# under the License. 15 16"""LVM class for performing LVM operations.""" 17 18import math 19import os 20import re 21 22from oslo_concurrency import processutils as putils 23from oslo_log import log as logging 24from oslo_utils import excutils 25 26from os_brick import exception 27from os_brick import executor 28from os_brick.privileged import rootwrap as priv_rootwrap 29from os_brick import utils 30 31LOG = logging.getLogger(__name__) 32 33 34class LVM(executor.Executor): 35 """LVM object to enable various LVM related operations.""" 36 37 LVM_CMD_PREFIX = ['env', 'LC_ALL=C'] 38 39 def __init__(self, vg_name, root_helper, create_vg=False, 40 physical_volumes=None, lvm_type='default', 41 executor=None, lvm_conf=None, 42 suppress_fd_warn=False): 43 44 """Initialize the LVM object. 45 46 The LVM object is based on an LVM VolumeGroup, one instantiation 47 for each VolumeGroup you have/use. 48 49 :param vg_name: Name of existing VG or VG to create 50 :param root_helper: Execution root_helper method to use 51 :param create_vg: Indicates the VG doesn't exist 52 and we want to create it 53 :param physical_volumes: List of PVs to build VG on 54 :param lvm_type: VG and Volume type (default, or thin) 55 :param executor: Execute method to use, None uses 56 oslo_concurrency.processutils 57 :param suppress_fd_warn: Add suppress FD Warn to LVM env 58 """ 59 super(LVM, self).__init__(execute=executor, root_helper=root_helper) 60 self.vg_name = vg_name 61 self.pv_list = [] 62 self.vg_size = 0.0 63 self.vg_free_space = 0.0 64 self.vg_lv_count = 0 65 self.vg_uuid = None 66 self.vg_thin_pool = None 67 self.vg_thin_pool_size = 0.0 68 self.vg_thin_pool_free_space = 0.0 69 self._supports_snapshot_lv_activation = None 70 self._supports_lvchange_ignoreskipactivation = None 71 self.vg_provisioned_capacity = 0.0 72 73 # Ensure LVM_SYSTEM_DIR has been added to LVM.LVM_CMD_PREFIX 74 # before the first LVM command is executed, and use the directory 75 # where the specified lvm_conf file is located as the value. 76 77 # NOTE(jdg): We use the temp var here becuase LVM_CMD_PREFIX is a 78 # class global and if you use append here, you'll literally just keep 79 # appending values to the global. 80 _lvm_cmd_prefix = ['env', 'LC_ALL=C'] 81 82 if lvm_conf and os.path.isfile(lvm_conf): 83 lvm_sys_dir = os.path.dirname(lvm_conf) 84 _lvm_cmd_prefix.append('LVM_SYSTEM_DIR=' + lvm_sys_dir) 85 86 if suppress_fd_warn: 87 _lvm_cmd_prefix.append('LVM_SUPPRESS_FD_WARNINGS=1') 88 LVM.LVM_CMD_PREFIX = _lvm_cmd_prefix 89 90 if create_vg and physical_volumes is not None: 91 try: 92 self._create_vg(physical_volumes) 93 except putils.ProcessExecutionError as err: 94 LOG.exception('Error creating Volume Group') 95 LOG.error('Cmd :%s', err.cmd) 96 LOG.error('StdOut :%s', err.stdout) 97 LOG.error('StdErr :%s', err.stderr) 98 raise exception.VolumeGroupCreationFailed(vg_name=self.vg_name) 99 100 if self._vg_exists() is False: 101 LOG.error('Unable to locate Volume Group %s', vg_name) 102 raise exception.VolumeGroupNotFound(vg_name=vg_name) 103 104 # NOTE: we assume that the VG has been activated outside of Cinder 105 106 if lvm_type == 'thin': 107 pool_name = "%s-pool" % self.vg_name 108 if self.get_volume(pool_name) is None: 109 try: 110 self.create_thin_pool(pool_name) 111 except putils.ProcessExecutionError: 112 # Maybe we just lost the race against another copy of 113 # this driver being in init in parallel - e.g. 114 # cinder-volume and cinder-backup starting in parallel 115 if self.get_volume(pool_name) is None: 116 raise 117 118 self.vg_thin_pool = pool_name 119 self.activate_lv(self.vg_thin_pool) 120 self.pv_list = self.get_all_physical_volumes(root_helper, vg_name) 121 122 def _vg_exists(self): 123 """Simple check to see if VG exists. 124 125 :returns: True if vg specified in object exists, else False 126 127 """ 128 exists = False 129 cmd = LVM.LVM_CMD_PREFIX + ['vgs', '--noheadings', 130 '-o', 'name', self.vg_name] 131 (out, _err) = self._execute(*cmd, 132 root_helper=self._root_helper, 133 run_as_root=True) 134 135 if out is not None: 136 volume_groups = out.split() 137 if self.vg_name in volume_groups: 138 exists = True 139 140 return exists 141 142 def _create_vg(self, pv_list): 143 cmd = ['vgcreate', self.vg_name, ','.join(pv_list)] 144 self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) 145 146 @utils.retry(retry=utils.retry_if_exit_code, retry_param=139, interval=0.5, 147 backoff_rate=0.5) 148 def _run_lvm_command(self, 149 cmd_arg_list: list, 150 root_helper: str = None, 151 run_as_root: bool = True) -> tuple: 152 """Run LVM commands with a retry on code 139 to work around LVM bugs. 153 154 Refer to LP bug 1901783, LP bug 1932188. 155 """ 156 if not root_helper: 157 root_helper = self._root_helper 158 159 (out, err) = self._execute(*cmd_arg_list, 160 root_helper=root_helper, 161 run_as_root=run_as_root) 162 return (out, err) 163 164 def _get_vg_uuid(self): 165 cmd = LVM.LVM_CMD_PREFIX + ['vgs', '--noheadings', 166 '-o', 'uuid', self.vg_name] 167 (out, _err) = self._execute(*cmd, 168 root_helper=self._root_helper, 169 run_as_root=True) 170 if out is not None: 171 return out.split() 172 else: 173 return [] 174 175 def _get_thin_pool_free_space(self, vg_name, thin_pool_name): 176 """Returns available thin pool free space. 177 178 :param vg_name: the vg where the pool is placed 179 :param thin_pool_name: the thin pool to gather info for 180 :returns: Free space in GB (float), calculated using data_percent 181 182 """ 183 cmd = LVM.LVM_CMD_PREFIX + ['lvs', '--noheadings', '--unit=g', 184 '-o', 'size,data_percent', '--separator', 185 ':', '--nosuffix'] 186 # NOTE(gfidente): data_percent only applies to some types of LV so we 187 # make sure to append the actual thin pool name 188 cmd.append("/dev/%s/%s" % (vg_name, thin_pool_name)) 189 190 free_space = 0.0 191 192 try: 193 (out, err) = self._run_lvm_command(cmd) 194 195 if out is not None: 196 out = out.strip() 197 data = out.split(':') 198 pool_size = float(data[0]) 199 data_percent = float(data[1]) 200 consumed_space = pool_size / 100 * data_percent 201 free_space = pool_size - consumed_space 202 free_space = round(free_space, 2) 203 # Need noqa due to a false error about the 'err' variable being unused 204 # even though it is used in the logging. Possibly related to 205 # https://github.com/PyCQA/pyflakes/issues/378. 206 except putils.ProcessExecutionError as err: # noqa 207 LOG.exception('Error querying thin pool about data_percent') 208 LOG.error('Cmd :%s', err.cmd) 209 LOG.error('StdOut :%s', err.stdout) 210 LOG.error('StdErr :%s', err.stderr) 211 212 return free_space 213 214 @staticmethod 215 def get_lvm_version(root_helper): 216 """Static method to get LVM version from system. 217 218 :param root_helper: root_helper to use for execute 219 :returns: version 3-tuple 220 221 """ 222 223 cmd = LVM.LVM_CMD_PREFIX + ['vgs', '--version'] 224 (out, _err) = priv_rootwrap.execute(*cmd, 225 root_helper=root_helper, 226 run_as_root=True) 227 lines = out.split('\n') 228 229 for line in lines: 230 if 'LVM version' in line: 231 version_list = line.split() 232 # NOTE(gfidente): version is formatted as follows: 233 # major.minor.patchlevel(library API version)[-customisation] 234 version = version_list[2] 235 version_filter = r"(\d+)\.(\d+)\.(\d+).*" 236 r = re.search(version_filter, version) 237 version_tuple = tuple(map(int, r.group(1, 2, 3))) 238 return version_tuple 239 240 @staticmethod 241 def supports_thin_provisioning(root_helper): 242 """Static method to check for thin LVM support on a system. 243 244 :param root_helper: root_helper to use for execute 245 :returns: True if supported, False otherwise 246 247 """ 248 249 return LVM.get_lvm_version(root_helper) >= (2, 2, 95) 250 251 @property 252 def supports_snapshot_lv_activation(self): 253 """Property indicating whether snap activation changes are supported. 254 255 Check for LVM version >= 2.02.91. 256 (LVM2 git: e8a40f6 Allow to activate snapshot) 257 258 :returns: True/False indicating support 259 """ 260 261 if self._supports_snapshot_lv_activation is not None: 262 return self._supports_snapshot_lv_activation 263 264 self._supports_snapshot_lv_activation = ( 265 self.get_lvm_version(self._root_helper) >= (2, 2, 91)) 266 267 return self._supports_snapshot_lv_activation 268 269 @property 270 def supports_lvchange_ignoreskipactivation(self): 271 """Property indicating whether lvchange can ignore skip activation. 272 273 Check for LVM version >= 2.02.99. 274 (LVM2 git: ab789c1bc add --ignoreactivationskip to lvchange) 275 """ 276 277 if self._supports_lvchange_ignoreskipactivation is not None: 278 return self._supports_lvchange_ignoreskipactivation 279 280 self._supports_lvchange_ignoreskipactivation = ( 281 self.get_lvm_version(self._root_helper) >= (2, 2, 99)) 282 283 return self._supports_lvchange_ignoreskipactivation 284 285 @property 286 def supports_full_pool_create(self): 287 """Property indicating whether 100% pool creation is supported. 288 289 Check for LVM version >= 2.02.115. 290 Ref: https://bugzilla.redhat.com/show_bug.cgi?id=998347 291 """ 292 293 if self.get_lvm_version(self._root_helper) >= (2, 2, 115): 294 return True 295 else: 296 return False 297 298 @staticmethod 299 @utils.retry(retry=utils.retry_if_exit_code, retry_param=139, interval=0.5, 300 backoff_rate=0.5) # Bug#1901783 301 def get_lv_info(root_helper, vg_name=None, lv_name=None): 302 """Retrieve info about LVs (all, in a VG, or a single LV). 303 304 :param root_helper: root_helper to use for execute 305 :param vg_name: optional, gathers info for only the specified VG 306 :param lv_name: optional, gathers info for only the specified LV 307 :returns: List of Dictionaries with LV info 308 309 """ 310 311 cmd = LVM.LVM_CMD_PREFIX + ['lvs', '--noheadings', '--unit=g', 312 '-o', 'vg_name,name,size', '--nosuffix'] 313 if lv_name is not None and vg_name is not None: 314 cmd.append("%s/%s" % (vg_name, lv_name)) 315 elif vg_name is not None: 316 cmd.append(vg_name) 317 318 try: 319 (out, _err) = priv_rootwrap.execute(*cmd, 320 root_helper=root_helper, 321 run_as_root=True) 322 except putils.ProcessExecutionError as err: 323 with excutils.save_and_reraise_exception(reraise=True) as ctx: 324 if "not found" in err.stderr or "Failed to find" in err.stderr: 325 ctx.reraise = False 326 LOG.info("Logical Volume not found when querying " 327 "LVM info. (vg_name=%(vg)s, lv_name=%(lv)s", 328 {'vg': vg_name, 'lv': lv_name}) 329 out = None 330 331 lv_list = [] 332 if out is not None: 333 volumes = out.split() 334 iterator = zip(*[iter(volumes)] * 3) # pylint: disable=E1101 335 for vg, name, size in iterator: 336 lv_list.append({"vg": vg, "name": name, "size": size}) 337 338 return lv_list 339 340 def get_volumes(self, lv_name=None): 341 """Get all LV's associated with this instantiation (VG). 342 343 :returns: List of Dictionaries with LV info 344 345 """ 346 return self.get_lv_info(self._root_helper, 347 self.vg_name, 348 lv_name) 349 350 def get_volume(self, name): 351 """Get reference object of volume specified by name. 352 353 :returns: dict representation of Logical Volume if exists 354 355 """ 356 ref_list = self.get_volumes(name) 357 for r in ref_list: 358 if r['name'] == name: 359 return r 360 return None 361 362 @staticmethod 363 def get_all_physical_volumes(root_helper, vg_name=None): 364 """Static method to get all PVs on a system. 365 366 :param root_helper: root_helper to use for execute 367 :param vg_name: optional, gathers info for only the specified VG 368 :returns: List of Dictionaries with PV info 369 370 """ 371 field_sep = '|' 372 cmd = LVM.LVM_CMD_PREFIX + ['pvs', '--noheadings', 373 '--unit=g', 374 '-o', 'vg_name,name,size,free', 375 '--separator', field_sep, 376 '--nosuffix'] 377 (out, _err) = priv_rootwrap.execute(*cmd, 378 root_helper=root_helper, 379 run_as_root=True) 380 381 pvs = out.split() 382 if vg_name is not None: 383 pvs = [pv for pv in pvs if vg_name == pv.split(field_sep)[0]] 384 385 pv_list = [] 386 for pv in pvs: 387 fields = pv.split(field_sep) 388 pv_list.append({'vg': fields[0], 389 'name': fields[1], 390 'size': float(fields[2]), 391 'available': float(fields[3])}) 392 return pv_list 393 394 def get_physical_volumes(self): 395 """Get all PVs associated with this instantiation (VG). 396 397 :returns: List of Dictionaries with PV info 398 399 """ 400 self.pv_list = self.get_all_physical_volumes(self._root_helper, 401 self.vg_name) 402 return self.pv_list 403 404 @staticmethod 405 def get_all_volume_groups(root_helper, vg_name=None): 406 """Static method to get all VGs on a system. 407 408 :param root_helper: root_helper to use for execute 409 :param vg_name: optional, gathers info for only the specified VG 410 :returns: List of Dictionaries with VG info 411 412 """ 413 cmd = LVM.LVM_CMD_PREFIX + ['vgs', '--noheadings', 414 '--unit=g', '-o', 415 'name,size,free,lv_count,uuid', 416 '--separator', ':', 417 '--nosuffix'] 418 if vg_name is not None: 419 cmd.append(vg_name) 420 421 (out, _err) = priv_rootwrap.execute(*cmd, 422 root_helper=root_helper, 423 run_as_root=True) 424 vg_list = [] 425 if out is not None: 426 vgs = out.split() 427 for vg in vgs: 428 fields = vg.split(':') 429 vg_list.append({'name': fields[0], 430 'size': float(fields[1]), 431 'available': float(fields[2]), 432 'lv_count': int(fields[3]), 433 'uuid': fields[4]}) 434 435 return vg_list 436 437 def update_volume_group_info(self): 438 """Update VG info for this instantiation. 439 440 Used to update member fields of object and 441 provide a dict of info for caller. 442 443 :returns: Dictionaries of VG info 444 445 """ 446 vg_list = self.get_all_volume_groups(self._root_helper, self.vg_name) 447 448 if len(vg_list) != 1: 449 LOG.error('Unable to find VG: %s', self.vg_name) 450 raise exception.VolumeGroupNotFound(vg_name=self.vg_name) 451 452 self.vg_size = float(vg_list[0]['size']) 453 self.vg_free_space = float(vg_list[0]['available']) 454 self.vg_lv_count = int(vg_list[0]['lv_count']) 455 self.vg_uuid = vg_list[0]['uuid'] 456 457 total_vols_size = 0.0 458 if self.vg_thin_pool is not None: 459 # NOTE(xyang): If providing only self.vg_name, 460 # get_lv_info will output info on the thin pool and all 461 # individual volumes. 462 # get_lv_info(self._root_helper, 'stack-vg') 463 # sudo lvs --noheadings --unit=g -o vg_name,name,size 464 # --nosuffix stack-vg 465 # stack-vg stack-pool 9.51 466 # stack-vg volume-13380d16-54c3-4979-9d22-172082dbc1a1 1.00 467 # stack-vg volume-629e13ab-7759-46a5-b155-ee1eb20ca892 1.00 468 # stack-vg volume-e3e6281c-51ee-464c-b1a7-db6c0854622c 1.00 469 # 470 # If providing both self.vg_name and self.vg_thin_pool, 471 # get_lv_info will output only info on the thin pool, but not 472 # individual volumes. 473 # get_lv_info(self._root_helper, 'stack-vg', 'stack-pool') 474 # sudo lvs --noheadings --unit=g -o vg_name,name,size 475 # --nosuffix stack-vg/stack-pool 476 # stack-vg stack-pool 9.51 477 # 478 # We need info on both the thin pool and the volumes, 479 # therefore we should provide only self.vg_name, but not 480 # self.vg_thin_pool here. 481 for lv in self.get_lv_info(self._root_helper, 482 self.vg_name): 483 lvsize = lv['size'] 484 # get_lv_info runs "lvs" command with "--nosuffix". 485 # This removes "g" from "1.00g" and only outputs "1.00". 486 # Running "lvs" command without "--nosuffix" will output 487 # "1.00g" if "g" is the unit. 488 # Remove the unit if it is in lv['size']. 489 if not lv['size'][-1].isdigit(): 490 lvsize = lvsize[:-1] 491 if lv['name'] == self.vg_thin_pool: 492 self.vg_thin_pool_size = float(lvsize) 493 tpfs = self._get_thin_pool_free_space(self.vg_name, 494 self.vg_thin_pool) 495 self.vg_thin_pool_free_space = tpfs 496 else: 497 total_vols_size = total_vols_size + float(lvsize) 498 total_vols_size = round(total_vols_size, 2) 499 500 self.vg_provisioned_capacity = total_vols_size 501 502 def _calculate_thin_pool_size(self): 503 """Calculates the correct size for a thin pool. 504 505 Ideally we would use 100% of the containing volume group and be done. 506 But the 100%VG notation to lvcreate is not implemented and thus cannot 507 be used. See https://bugzilla.redhat.com/show_bug.cgi?id=998347 508 509 Further, some amount of free space must remain in the volume group for 510 metadata for the contained logical volumes. The exact amount depends 511 on how much volume sharing you expect. 512 513 :returns: An lvcreate-ready string for the number of calculated bytes. 514 """ 515 516 # make sure volume group information is current 517 self.update_volume_group_info() 518 519 if LVM.supports_full_pool_create: 520 return ["-l", "100%FREE"] 521 522 # leave 5% free for metadata 523 return ["-L", "%sg" % (self.vg_free_space * 0.95)] 524 525 def create_thin_pool(self, name=None): 526 """Creates a thin provisioning pool for this VG. 527 528 The syntax here is slightly different than the default 529 lvcreate -T, so we'll just write a custom cmd here 530 and do it. 531 532 :param name: Name to use for pool, default is "<vg-name>-pool" 533 :returns: The size string passed to the lvcreate command 534 535 """ 536 537 if not LVM.supports_thin_provisioning(self._root_helper): 538 LOG.error('Requested to setup thin provisioning, ' 539 'however current LVM version does not ' 540 'support it.') 541 return None 542 543 if name is None: 544 name = '%s-pool' % self.vg_name 545 546 vg_pool_name = '%s/%s' % (self.vg_name, name) 547 548 size_args = self._calculate_thin_pool_size() 549 550 cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-T'] 551 cmd.extend(size_args) 552 cmd.append(vg_pool_name) 553 554 LOG.debug("Creating thin pool '%(pool)s' with size %(size)s of " 555 "total %(free)sg", {'pool': vg_pool_name, 556 'size': size_args, 557 'free': self.vg_free_space}) 558 559 self._run_lvm_command(cmd) 560 561 self.vg_thin_pool = name 562 563 return 564 565 def create_volume(self, name, size_str, lv_type='default', mirror_count=0): 566 """Creates a logical volume on the object's VG. 567 568 :param name: Name to use when creating Logical Volume 569 :param size_str: Size to use when creating Logical Volume 570 :param lv_type: Type of Volume (default or thin) 571 :param mirror_count: Use LVM mirroring with specified count 572 573 """ 574 575 if lv_type == 'thin': 576 pool_path = '%s/%s' % (self.vg_name, self.vg_thin_pool) 577 cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-T', '-V', size_str, '-n', 578 name, pool_path] 579 else: 580 cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-n', name, self.vg_name, 581 '-L', size_str] 582 583 if mirror_count > 0: 584 cmd.extend(['-m', mirror_count, '--nosync', 585 '--mirrorlog', 'mirrored']) 586 terras = int(size_str[:-1]) / 1024.0 587 if terras >= 1.5: 588 rsize = int(2 ** math.ceil(math.log(terras) / math.log(2))) 589 # NOTE(vish): Next power of two for region size. See: 590 # http://red.ht/U2BPOD 591 cmd.extend(['-R', str(rsize)]) 592 593 try: 594 self._run_lvm_command(cmd) 595 except putils.ProcessExecutionError as err: 596 LOG.exception('Error creating Volume') 597 LOG.error('Cmd :%s', err.cmd) 598 LOG.error('StdOut :%s', err.stdout) 599 LOG.error('StdErr :%s', err.stderr) 600 raise 601 602 @utils.retry(putils.ProcessExecutionError) 603 def create_lv_snapshot(self, name, source_lv_name, lv_type='default'): 604 """Creates a snapshot of a logical volume. 605 606 :param name: Name to assign to new snapshot 607 :param source_lv_name: Name of Logical Volume to snapshot 608 :param lv_type: Type of LV (default or thin) 609 610 """ 611 source_lvref = self.get_volume(source_lv_name) 612 if source_lvref is None: 613 LOG.error("Trying to create snapshot by non-existent LV: %s", 614 source_lv_name) 615 raise exception.VolumeDeviceNotFound(device=source_lv_name) 616 cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '--name', name, 617 '-k', 'y', '--snapshot', 618 '%s/%s' % (self.vg_name, source_lv_name)] 619 if lv_type != 'thin': 620 size = source_lvref['size'] 621 cmd.extend(['-L', '%sg' % (size)]) 622 623 try: 624 self._run_lvm_command(cmd) 625 except putils.ProcessExecutionError as err: 626 LOG.exception('Error creating snapshot') 627 LOG.error('Cmd :%s', err.cmd) 628 LOG.error('StdOut :%s', err.stdout) 629 LOG.error('StdErr :%s', err.stderr) 630 raise 631 632 def _mangle_lv_name(self, name): 633 # Linux LVM reserves name that starts with snapshot, so that 634 # such volume name can't be created. Mangle it. 635 if not name.startswith('snapshot'): 636 return name 637 return '_' + name 638 639 def _lv_is_active(self, name): 640 cmd = LVM.LVM_CMD_PREFIX + ['lvdisplay', '--noheading', '-C', '-o', 641 'Attr', '%s/%s' % (self.vg_name, name)] 642 out, _err = self._run_lvm_command(cmd) 643 if out: 644 out = out.strip() 645 # An example output might be '-wi-a----'; the 4th index specifies 646 # the status of the volume. 'a' for active, '-' for inactive. 647 if (out[4] == 'a'): 648 return True 649 return False 650 651 def deactivate_lv(self, name): 652 lv_path = self.vg_name + '/' + self._mangle_lv_name(name) 653 cmd = ['lvchange', '-a', 'n'] 654 cmd.append(lv_path) 655 try: 656 self._execute(*cmd, 657 root_helper=self._root_helper, 658 run_as_root=True) 659 except putils.ProcessExecutionError as err: 660 LOG.exception('Error deactivating LV') 661 LOG.error('Cmd :%s', err.cmd) 662 LOG.error('StdOut :%s', err.stdout) 663 LOG.error('StdErr :%s', err.stderr) 664 raise 665 666 # Wait until lv is deactivated to return in 667 # order to prevent a race condition. 668 self._wait_for_volume_deactivation(name) 669 670 @utils.retry(retry_param=exception.VolumeNotDeactivated, retries=5, 671 backoff_rate=2) 672 def _wait_for_volume_deactivation(self, name): 673 LOG.debug("Checking to see if volume %s has been deactivated.", 674 name) 675 if self._lv_is_active(name): 676 LOG.debug("Volume %s is still active.", name) 677 raise exception.VolumeNotDeactivated(name=name) 678 else: 679 LOG.debug("Volume %s has been deactivated.", name) 680 681 def activate_lv(self, name, is_snapshot=False, permanent=False): 682 """Ensure that logical volume/snapshot logical volume is activated. 683 684 :param name: Name of LV to activate 685 :param is_snapshot: whether LV is a snapshot 686 :param permanent: whether we should drop skipactivation flag 687 :raises: putils.ProcessExecutionError 688 """ 689 690 # This is a no-op if requested for a snapshot on a version 691 # of LVM that doesn't support snapshot activation. 692 # (Assume snapshot LV is always active.) 693 if is_snapshot and not self.supports_snapshot_lv_activation: 694 return 695 696 lv_path = self.vg_name + '/' + self._mangle_lv_name(name) 697 698 # Must pass --yes to activate both the snap LV and its origin LV. 699 # Otherwise lvchange asks if you would like to do this interactively, 700 # and fails. 701 cmd = ['lvchange', '-a', 'y', '--yes'] 702 703 if self.supports_lvchange_ignoreskipactivation: 704 cmd.append('-K') 705 # If permanent=True is specified, drop the skipactivation flag in 706 # order to make this LV automatically activated after next reboot. 707 if permanent: 708 cmd += ['-k', 'n'] 709 710 cmd.append(lv_path) 711 712 try: 713 self._execute(*cmd, 714 root_helper=self._root_helper, 715 run_as_root=True) 716 except putils.ProcessExecutionError as err: 717 LOG.exception('Error activating LV') 718 LOG.error('Cmd :%s', err.cmd) 719 LOG.error('StdOut :%s', err.stdout) 720 LOG.error('StdErr :%s', err.stderr) 721 raise 722 723 @utils.retry(putils.ProcessExecutionError) 724 def delete(self, name): 725 """Delete logical volume or snapshot. 726 727 :param name: Name of LV to delete 728 729 """ 730 731 def run_udevadm_settle(): 732 self._execute('udevadm', 'settle', 733 root_helper=self._root_helper, run_as_root=True, 734 check_exit_code=False) 735 736 # LV removal seems to be a race with other writers or udev in 737 # some cases (see LP #1270192), so we enable retry deactivation 738 LVM_CONFIG = 'activation { retry_deactivation = 1} ' 739 740 try: 741 self._execute( 742 'lvremove', 743 '--config', LVM_CONFIG, 744 '-f', 745 '%s/%s' % (self.vg_name, name), 746 root_helper=self._root_helper, run_as_root=True) 747 except putils.ProcessExecutionError as err: 748 LOG.debug('Error reported running lvremove: CMD: %(command)s, ' 749 'RESPONSE: %(response)s', 750 {'command': err.cmd, 'response': err.stderr}) 751 752 LOG.debug('Attempting udev settle and retry of lvremove...') 753 run_udevadm_settle() 754 755 # The previous failing lvremove -f might leave behind 756 # suspended devices; when lvmetad is not available, any 757 # further lvm command will block forever. 758 # Therefore we need to skip suspended devices on retry. 759 LVM_CONFIG += 'devices { ignore_suspended_devices = 1}' 760 761 self._execute( 762 'lvremove', 763 '--config', LVM_CONFIG, 764 '-f', 765 '%s/%s' % (self.vg_name, name), 766 root_helper=self._root_helper, run_as_root=True) 767 LOG.debug('Successfully deleted volume: %s after ' 768 'udev settle.', name) 769 770 def revert(self, snapshot_name): 771 """Revert an LV from snapshot. 772 773 :param snapshot_name: Name of snapshot to revert 774 775 """ 776 self._execute('lvconvert', '--merge', 777 snapshot_name, root_helper=self._root_helper, 778 run_as_root=True) 779 780 def lv_has_snapshot(self, name): 781 cmd = LVM.LVM_CMD_PREFIX + ['lvdisplay', '--noheading', '-C', '-o', 782 'Attr', '--readonly', 783 '%s/%s' % (self.vg_name, name)] 784 out, _err = self._run_lvm_command(cmd) 785 if out: 786 out = out.strip() 787 if (out[0] == 'o') or (out[0] == 'O'): 788 return True 789 return False 790 791 def extend_volume(self, lv_name, new_size): 792 """Extend the size of an existing volume.""" 793 # Volumes with snaps have attributes 'o' or 'O' and will be 794 # deactivated, but Thin Volumes with snaps have attribute 'V' 795 # and won't be deactivated because the lv_has_snapshot method looks 796 # for 'o' or 'O' 797 if self.lv_has_snapshot(lv_name): 798 self.deactivate_lv(lv_name) 799 try: 800 cmd = LVM.LVM_CMD_PREFIX + ['lvextend', '-L', new_size, 801 '%s/%s' % (self.vg_name, lv_name)] 802 self._execute(*cmd, root_helper=self._root_helper, 803 run_as_root=True) 804 except putils.ProcessExecutionError as err: 805 LOG.exception('Error extending Volume') 806 LOG.error('Cmd :%s', err.cmd) 807 LOG.error('StdOut :%s', err.stdout) 808 LOG.error('StdErr :%s', err.stderr) 809 raise 810 811 def vg_mirror_free_space(self, mirror_count): 812 free_capacity = 0.0 813 814 disks = [] 815 for pv in self.pv_list: 816 disks.append(float(pv['available'])) 817 818 while True: 819 disks = sorted([a for a in disks if a > 0.0], reverse=True) 820 if len(disks) <= mirror_count: 821 break 822 # consume the smallest disk 823 disk = disks[-1] 824 disks = disks[:-1] 825 # match extents for each mirror on the largest disks 826 for index in list(range(mirror_count)): 827 disks[index] -= disk 828 free_capacity += disk 829 830 return free_capacity 831 832 def vg_mirror_size(self, mirror_count): 833 return (self.vg_free_space / (mirror_count + 1)) 834 835 def rename_volume(self, lv_name, new_name): 836 """Change the name of an existing volume.""" 837 838 try: 839 self._execute('lvrename', self.vg_name, lv_name, new_name, 840 root_helper=self._root_helper, 841 run_as_root=True) 842 except putils.ProcessExecutionError as err: 843 LOG.exception('Error renaming logical volume') 844 LOG.error('Cmd :%s', err.cmd) 845 LOG.error('StdOut :%s', err.stdout) 846 LOG.error('StdErr :%s', err.stderr) 847 raise 848