# Copyright 2013 OpenStack Foundation.
# All Rights Reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.

"""
LVM class for performing LVM operations.
"""

import itertools
import math
import os
import re

from oslo_concurrency import processutils as putils
from oslo_log import log as logging
from oslo_utils import excutils

from cinder.brick import exception
from cinder.brick import executor
from cinder.i18n import _LE, _LI
from cinder import utils


LOG = logging.getLogger(__name__)


class LVM(executor.Executor):
    """LVM object to enable various LVM related operations."""
    LVM_CMD_PREFIX = ['env', 'LC_ALL=C']

    def __init__(self, vg_name, root_helper, create_vg=False,
                 physical_volumes=None, lvm_type='default',
                 executor=putils.execute, lvm_conf=None):

        """Initialize the LVM object.

        The LVM object is based on an LVM VolumeGroup, one instantiation
        for each VolumeGroup you have/use.

        :param vg_name: Name of existing VG or VG to create
        :param root_helper: Execution root_helper method to use
        :param create_vg: Indicates the VG doesn't exist
                          and we want to create it
        :param physical_volumes: List of PVs to build VG on
        :param lvm_type: VG and Volume type (default, or thin)
        :param executor: Execute method to use, None uses common/processutils

        """
        super(LVM, self).__init__(execute=executor, root_helper=root_helper)
        self.vg_name = vg_name
        self.pv_list = []
        self.vg_size = 0.0
        self.vg_free_space = 0.0
        self.vg_lv_count = 0
        self.vg_uuid = None
        self.vg_thin_pool = None
        self.vg_thin_pool_size = 0.0
        self.vg_thin_pool_free_space = 0.0
        self._supports_snapshot_lv_activation = None
        self._supports_lvchange_ignoreskipactivation = None
        self.vg_provisioned_capacity = 0.0

        if create_vg and physical_volumes is not None:
            self.pv_list = physical_volumes

            try:
                self._create_vg(physical_volumes)
            except putils.ProcessExecutionError as err:
                LOG.exception(_LE('Error creating Volume Group'))
                LOG.error(_LE('Cmd     :%s'), err.cmd)
                LOG.error(_LE('StdOut  :%s'), err.stdout)
                LOG.error(_LE('StdErr  :%s'), err.stderr)
                raise exception.VolumeGroupCreationFailed(vg_name=self.vg_name)

        if self._vg_exists() is False:
            LOG.error(_LE('Unable to locate Volume Group %s'), vg_name)
            raise exception.VolumeGroupNotFound(vg_name=vg_name)

        # NOTE: we assume that the VG has been activated outside of Cinder

        if lvm_type == 'thin':
            pool_name = "%s-pool" % self.vg_name
            if self.get_volume(pool_name) is None:
                try:
                    self.create_thin_pool(pool_name)
                except putils.ProcessExecutionError:
                    # Maybe we just lost the race against another copy of
                    # this driver being in init in parallel - e.g.
                    # cinder-volume and cinder-backup starting in parallel
                    if self.get_volume(pool_name) is None:
                        raise

            self.vg_thin_pool = pool_name
            self.activate_lv(self.vg_thin_pool)
        self.pv_list = self.get_all_physical_volumes(root_helper, vg_name)
        if lvm_conf and os.path.isfile(lvm_conf):
            LVM.LVM_CMD_PREFIX = ['env',
                                  'LC_ALL=C',
                                  'LVM_SYSTEM_DIR=/etc/cinder']

    def _vg_exists(self):
        """Simple check to see if VG exists.

        :returns: True if vg specified in object exists, else False

        """
        exists = False
        cmd = LVM.LVM_CMD_PREFIX + ['vgs', '--noheadings',
                                    '-o', 'name', self.vg_name]
        (out, _err) = self._execute(*cmd,
                                    root_helper=self._root_helper,
                                    run_as_root=True)

        if out is not None:
            volume_groups = out.split()
            if self.vg_name in volume_groups:
                exists = True

        return exists

    def _create_vg(self, pv_list):
        cmd = ['vgcreate', self.vg_name, ','.join(pv_list)]
        self._execute(*cmd, root_helper=self._root_helper, run_as_root=True)

    def _get_vg_uuid(self):
        cmd = LVM.LVM_CMD_PREFIX + ['vgs', '--noheadings',
                                    '-o', 'u
uid', self.vg_name]
        (out, _err) = self._execute(*cmd,
                           
         root_helper=self._root_helper,
                                    run_as_root=True)
        if out is not None:
            return out.split()
        else:
            return []

    def _get_thin_pool_free_space(self, vg_name, thin_pool_name):
        """Returns available thin pool free space.

        :param vg_name: the vg where the pool is placed
        :param thin_pool_name: the thin pool to gather info for
        :returns: Free space in GB (float), calculated using data_percent

        """
        cmd = LVM.LVM_CMD_PREFIX +\
            ['lvs', '--noheadings', '--unit=g',
             '-o', 'size,data_percent', '--separator',
             ':', '--nosuffix']
        # NOTE(gfidente): data_percent only applies to some types of LV so we
        # make sure to append the actual thin pool name
        cmd.append("/dev/%s/%s" % (vg_name, thin_pool_name))

        free_space = 0.0

        try:
            (out, err) = self._execute(*cmd,
                                       root_helper=self._root_helper,
                                       run_as_root=True)
            if out is not None:
                out = out.strip()
                data = out.split(':')
                pool_size = float(data[0])
                data_percent = float(data[1])
                consumed_space = pool_size / 100 * data_percent
                free_space = pool_size - consumed_space
                free_space = round(free_space, 2)
        except putils.ProcessExecutionError as err:
            LOG.exception(_LE('Error querying thin pool about data_percent'))
            LOG.error(_LE('Cmd     :%s'), err.cmd)
            LOG.error(_LE('StdOut  :%s'), err.stdout)
            LOG.error(_LE('StdErr  :%s'), err.stderr)

        return free_space

    @staticmethod
    def get_lvm_version(root_helper):
        """Static method to get LVM version from system.

        :param root_helper: root_helper to use for execute
        :returns: version 3-tuple

        """

        cmd = LVM.LVM_CMD_PREFIX + ['vgs', '--version']
        (out, _err) = putils.execute(*cmd,
                                     root_helper=root_helper,
                                     run_as_root=True)
        lines = out.split('\n')

        for line in lines:
            if 'LVM version' in line:
                version_list = line.split()
                # NOTE(gfidente): version is formatted as follows:
                # major.minor.patchlevel(library API version)[-customisation]
                version = version_list[2]
                version_filter = r"(\d+)\.(\d+)\.(\d+).*"
                r = re.search(version_filter, version)
                version_tuple = tuple(map(int, r.group(1, 2, 3)))
                return version_tuple

    @staticmethod
    def supports_thin_provisioning(root_helper):
        """Static method to check for thin LVM support on a system.

        :param root_helper: root_helper to use for execute
        :returns: 