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