
from __future__ import absolute_import

import collections
import os
import errno
import logging
import glob
import fnmatch
import re

from contextlib import contextmanager

import six

from vdsm import utils
from vdsm.common import concurrent
from vdsm.common import supervdsm
from vdsm.common.compat import glob_escape
from vdsm.storage import clusterlock
from vdsm.storage import constants as sc
from vdsm.storage import exception as se
from vdsm.storage import fileUtils
from vdsm.storage import fileVolume
from vdsm.storage import mount
from vdsm.storage import outOfProcess as oop
from vdsm.storage import sd
from vdsm.storage import xlease
from vdsm.storage.persistent import PersistentDict, DictValidator

from vdsm import constants
from vdsm.utils import stripNewLines
from vdsm.storage.constants import LEASE_FILEEXT, UUID_GLOB_PATTERN

REMOTE_PATH = "REMOTE_PATH"

FILE_SD_MD_FIELDS = sd.SD_MD_FIELDS.copy()
FILE_SD_MD_FIELDS[REMOTE_PATH] = (str, str)

METADATA_PERMISSIONS = 0o660

FILE_SPECIAL_VOLUME_SIZES_MIB = sd.SPECIAL_VOLUME_SIZES_MIB.copy()
FILE_SPECIAL_VOLUME_SIZES_MIB.update({
    sd.IDS: 0,
    sd.LEASES: 0,
    sd.XLEASES: 0,
})

_MOUNTLIST_IGNORE = ('/' + sd.BLOCKSD_DIR, '/' + sd.GLUSTERSD_DIR)

getProcPool = oop.getGlobalProcPool


def validateDirAccess(dirPath):
    try:
        getProcPool().fileUtils.validateAccess(dirPath)
        supervdsm.getProxy().validateAccess(
            constants.VDSM_USER,
            (constants.VDSM_GROUP,), dirPath,
            (os.R_OK | os.W_OK | os.X_OK))
        supervdsm.getProxy().validateAccess(
            constants.QEMU_PROCESS_USER,
            (constants.DISKIMAGE_GROUP, constants.METADATA_GROUP), dirPath,
            (os.R_OK | os.X_OK))
    except OSError as e:
        if e.errno == errno.EACCES:
            raise se.StorageServerAccessPermissionError(dirPath)
        raise

    return True


def getDomUuidFromMetafilePath(metafile):

    metaList = os.path.normpath(metafile).split('/')
    return metaList[-3]


class FileMetadataRW(object):

    def __init__(self, metafile):
        self._metafile = metafile
        self._sdUUID = getDomUuidFromMetafilePath(metafile)
        self._oop = oop.getProcessPool(self._sdUUID)

    def readlines(self):
        try:
            return stripNewLines(self._oop.directReadLines(self._metafile))
        except (IOError, OSError) as e:
            if e.errno != errno.ENOENT:
                raise
            return []

    def writelines(self, metadata):
        for i, line in enumerate(metadata):
            if isinstance(line, unicode):
                line = line.encode('utf-8')
            metadata[i] = line

        metadata = [i + '\n' for i in metadata]
        tmpFilePath = self._metafile + ".new"
        try:
            self._oop.writeLines(tmpFilePath, metadata)
        except IOError as e:
            if e.errno != errno.ESTALE:
                raise
            self._oop.writeLines(tmpFilePath, metadata)
        self._oop.os.rename(tmpFilePath, self._metafile)


def FileSDMetadata(metafile):
    return DictValidator(
        PersistentDict(
            FileMetadataRW(metafile)),
        FILE_SD_MD_FIELDS)


class FileStorageDomainManifest(sd.StorageDomainManifest):
    def __init__(self, domainPath, metadata=None):
        self.log.debug("Reading domain in path %s", domainPath)
        self.mountpoint = os.path.dirname(domainPath)
        self.remotePath = os.path.basename(self.mountpoint)
        self.metafile = os.path.join(domainPath, sd.DOMAIN_META_DATA,
                                     sd.METADATA)
        sdUUID = os.path.basename(domainPath)
        domaindir = os.path.join(self.mountpoint, sdUUID)
        if metadata is None:
            metadata = FileSDMetadata(self.metafile)
        sd.StorageDomainManifest.__init__(self, sdUUID, domaindir, metadata)

        if not self.oop.fileUtils.pathExists(self.metafile):
            raise se.StorageDomainMetadataNotFound(self.sdUUID, self.metafile)

    @classmethod
    def special_volumes(cls, version):
        if cls.supports_external_leases(version):
            return sd.SPECIAL_VOLUMES_V4
        else:
            return sd.SPECIAL_VOLUMES_V0

    @property
    def supportsSparseness(self):
        return True

    def getMonitoringPath(self):
        return self.metafile

    def getVSize(self, imgUUID, volUUID):
        volPath = os.path.join(self.mountpoint, self.sdUUID, 'images',
                               imgUUID, volUUID)
        return self.oop.os.stat(volPath).st_size

    def getVAllocSize(self, imgUUID, volUUID):
        volPath = os.path.join(self.mountpoint, self.sdUUID, 'images',
                               imgUUID, volUUID)
        stat = self.oop.os.stat(volPath)

        return stat.st_blocks * sc.STAT_BYTES_PER_BLOCK

    def getLeasesFilePath(self):
        return os.path.join(self.getMDPath(), sd.LEASES)

    def getIdsFilePath(self):
        return os.path.join(self.getMDPath(), sd.IDS)

    def getImagePath(self, imgUUID):
        return os.path.join(self.domaindir, sd.DOMAIN_IMAGES, imgUUID)

    def getVolumeClass(self):
        return fileVolume.FileVolumeManifest

    def getDeletedImagePath(self, imgUUID):
        currImgDir = self.getImagePath(imgUUID)
        dirName, baseName = os.path.split(currImgDir)
        toDelDir = os.path.join(dirName, sc.REMOVED_IMAGE_PREFIX + baseName)
        return toDelDir

    def deleteImage(self, sdUUID, imgUUID, volsImgs):
        currImgDir = self.getImagePath(imgUUID)
        toDelDir = self.getDeletedImagePath(imgUUID)
        self.log.info("Renaming dir %s to %s", currImgDir, toDelDir)
        try:
            self.oop.os.rename(currImgDir, toDelDir)
        except OSError as e:
            self.log.error("image: %s can't be moved", currImgDir)
            raise se.ImageDeleteError("%s %s" % (imgUUID, str(e)))

    def purgeImage(self, sdUUID, imgUUID, volsImgs, discard):
        self.log.debug("Purging image %s", imgUUID)
        if discard:
            raise se.DiscardIsNotSupported(sdUUID, "file storage domain")
        toDelDir = self.getDeletedImagePath(imgUUID)
        for volUUID in volsImgs:
            volPath = os.path.join(toDelDir, volUUID)
            self._deleteVolumeFile(volPath)
            self._deleteVolumeFile(volPath + fileVolume.META_FILEEXT)
            if self.hasVolumeLeases():
                self._deleteVolumeFile(volPath + LEASE_FILEEXT)
        self.log.info("Removing directory: %s", toDelDir)
        try:
            self.oop.os.rmdir(toDelDir)
        except OSError as e:
            if e.errno == errno.ENOENT:
                self.log.warning("removed image dir: %s no longer exists",
                                 toDelDir)
            else:
                self.log.error("removed image dir: %s can't be removed",
                               toDelDir)
                raise se.ImageDeleteError("%s %s" % (imgUUID, str(e)))

    def _deleteVolumeFile(self, path):
        self.log.info("Removing file: %s", path)
        try:
            self.oop.os.remove(path)
        except OSError as e:
            if e.errno == errno.ENOENT:
                self.log.warning("File %r does not exist: %s", path, e)
            else:
                self.log.error("File %r cannot be removed: %s", path, e)

    def getAllVolumes(self):
        volMetaPattern = os.path.join(glob_escape(self.mountpoint),
                                      self.sdUUID,
                                      sd.DOMAIN_IMAGES, "*", "*.meta")
        volMetaPaths = self.oop.glob.glob(volMetaPattern)

        images = collections.defaultdict(list)
        for metaPath in volMetaPaths:
            head, tail = os.path.split(metaPath)
            volUUID, volExt = os.path.splitext(tail)
            imgUUID = os.path.basename(head)
            images[imgUUID].append(volUUID)


        volumes = {}
        for imgUUID, volUUIDs in six.iteritems(images):
            for volUUID in volUUIDs:
                if volUUID in volumes:
                    volumes[volUUID]['parent'] = sd.BLANK_UUID
                    if len(volUUIDs) > 1:
                        volumes[volUUID]['imgs'].append(imgUUID)
                    else:
                        volumes[volUUID]['imgs'].insert(0, imgUUID)
                else:
                    volumes[volUUID] = {'imgs': [imgUUID], 'parent': None}

        return dict((k, sd.ImgsPar(tuple(v['imgs']), v['parent']))
                    for k, v in six.iteritems(volumes))

    def getAllImages(self):
        pattern = os.path.join(self.mountpoint, self.sdUUID, sd.DOMAIN_IMAGES,
                               UUID_GLOB_PATTERN)
        files = self.oop.glob.glob(pattern)
        images = set()
        for i in files:
            if self.oop.os.path.isdir(i):
                images.add(os.path.basename(i))
        return images

    def getVolumeLease(self, imgUUID, volUUID):
        if not self.hasVolumeLeases():
            return clusterlock.Lease(None, None, None)
        vol = self.produceVolume(imgUUID, volUUID)
        path = vol.getVolumePath() + LEASE_FILEEXT
        return clusterlock.Lease(volUUID, path, fileVolume.LEASE_FILEOFFSET)


    @classmethod
    @contextmanager
    def external_leases_backend(cls, lockspace, path):
        dom_oop = oop.getProcessPool(lockspace)
        backend = xlease.InterruptibleDirectFile(path, dom_oop)
        with utils.closing(backend):
            yield backend

    def external_leases_path(self):
        return os.path.join(self.getMDPath(), sd.XLEASES)


class FileStorageDomain(sd.StorageDomain):
    manifestClass = FileStorageDomainManifest

    def __init__(self, domainPath):
        manifest = self.manifestClass(domainPath)

        storage_block_size = self._detect_block_size(
            manifest.sdUUID, manifest.mountpoint)
        self._validate_storage_block_size(
            manifest.block_size, storage_block_size)

        sd.StorageDomain.__init__(self, manifest)
        self.imageGarbageCollector()
        self._registerResourceNamespaces()

    def setMetadataPermissions(self):
        procPool = oop.getProcessPool(self.sdUUID)
        for metaFile in (sd.LEASES, sd.IDS, sd.INBOX, sd.OUTBOX):
            try:
                fpath = os.path.join(self.getMDPath(), metaFile)
                procPool.os.chmod(fpath, METADATA_PERMISSIONS)
            except Exception as e:
                raise se.StorageDomainMetadataCreationError(
                    "Lease permission change file '%s' failed: %s"
                    % (metaFile, e))

    def prepareMailbox(self):
        for mailboxFile in (sd.INBOX, sd.OUTBOX):
            mailboxByteSize = (FILE_SPECIAL_VOLUME_SIZES_MIB[mailboxFile] *
                               constants.MEGAB)
            mailboxFilePath = os.path.join(self.domaindir,
                                           sd.DOMAIN_META_DATA, mailboxFile)

            try:
                mailboxStat = self.oop.os.stat(mailboxFilePath)
            except OSError as e:
                if e.errno != os.errno.ENOENT:
                    raise
                prevMailboxFileSize = None
            else:
                prevMailboxFileSize = mailboxStat.st_size

            if (prevMailboxFileSize is None or
                    prevMailboxFileSize < mailboxByteSize):
                self.log.info('preparing storage domain %s mailbox file %s '
                              '(%s bytes)', self.sdUUID, mailboxFile,
                              mailboxByteSize)
                self.oop.truncateFile(
                    mailboxFilePath, mailboxByteSize, METADATA_PERMISSIONS)

    @classmethod
    def _prepareMetadata(cls, domPath, sdUUID, domainName, domClass,
                         remotePath, storageType, version, alignment,
                         block_size):
        metadataDir = os.path.join(domPath, sd.DOMAIN_META_DATA)

        procPool = oop.getProcessPool(sdUUID)
        cls.log.info("Creating domain metadata directory %r", metadataDir)
        procPool.fileUtils.createdir(metadataDir, 0o775)

        special_volumes = cls.manifestClass.special_volumes(version)
        for name, size_mb in FILE_SPECIAL_VOLUME_SIZES_MIB.iteritems():
            if name in special_volumes:
                try:
                    procPool.truncateFile(
                        os.path.join(metadataDir, name),
                        size_mb * constants.MEGAB, METADATA_PERMISSIONS)
                except Exception as e:
                    raise se.StorageDomainMetadataCreationError(
                        "create meta file '%s' failed: %s" % (name, str(e)))

        if cls.supports_external_leases(version):
            xleases_path = os.path.join(metadataDir, sd.XLEASES)
            cls.format_external_leases(
                sdUUID,
                xleases_path,
                alignment=alignment,
                block_size=block_size)

        metaFile = os.path.join(metadataDir, sd.METADATA)

        md = FileSDMetadata(metaFile)
        metadata = {
            sd.DMDK_VERSION: version,
            sd.DMDK_SDUUID: sdUUID,
            sd.DMDK_TYPE: storageType,
            sd.DMDK_CLASS: domClass,
            sd.DMDK_DESCRIPTION: domainName,
            sd.DMDK_ROLE: sd.REGULAR_DOMAIN,
            sd.DMDK_POOLS: [],
            sd.DMDK_LOCK_POLICY: '',
            sd.DMDK_LOCK_RENEWAL_INTERVAL_SEC:
            sd.DEFAULT_LEASE_PARAMS[sd.DMDK_LOCK_RENEWAL_INTERVAL_SEC],
            sd.DMDK_LEASE_TIME_SEC: sd.DEFAULT_LEASE_PARAMS[
                sd.DMDK_LEASE_TIME_SEC],
            sd.DMDK_IO_OP_TIMEOUT_SEC:
            sd.DEFAULT_LEASE_PARAMS[sd.DMDK_IO_OP_TIMEOUT_SEC],
            sd.DMDK_LEASE_RETRIES:
            sd.DEFAULT_LEASE_PARAMS[sd.DMDK_LEASE_RETRIES],
            REMOTE_PATH: remotePath
        }

        if version > 4:
            metadata[sd.DMDK_ALIGNMENT] = alignment
            metadata[sd.DMDK_BLOCK_SIZE] = block_size

        md.update(metadata)

    def getFileList(self, pattern, caseSensitive):
        basedir = self.getIsoDomainImagesDir()
        filesList = self.oop.simpleWalk(basedir)

        if pattern != '*':
            if caseSensitive:
                filesList = fnmatch.filter(filesList, pattern)
            else:
                regex = fnmatch.translate(pattern)
                reobj = re.compile(regex, re.IGNORECASE)
                filesList = [f for f in filesList if reobj.match(f)]

        filesDict = {}
        filePrefixLen = len(basedir) + 1
        for entry in filesList:
            st = self.oop.os.stat(entry)
            stats = {'size': str(st.st_size), 'ctime': str(st.st_ctime)}

            try:
                self.oop.fileUtils.validateQemuReadable(entry)
            except OSError as e:
                if e.errno != errno.EACCES:
                    raise

                stats['status'] = se.StorageServerAccessPermissionError.code

            fileName = entry[filePrefixLen:]
            filesDict[fileName] = stats
        return filesDict

    def validate(self):
        self.log.info("sdUUID=%s", self.sdUUID)
        self.invalidateMetadata()
        if not len(self.getMetadata()):
            raise se.StorageDomainAccessError(self.sdUUID)

    def validateMasterMount(self):
        return self.oop.fileUtils.pathExists(self.getMasterDir())

    def zeroImage(self, sdUUID, imgUUID, volsImgs, discard):
        self.log.warning("image %s on a fileSD %s won't be zeroed." %
                         (imgUUID, sdUUID))
        if discard:
            raise se.DiscardIsNotSupported(sdUUID, "file storage domain")
        self.deleteImage(sdUUID, imgUUID, volsImgs)

    def deactivateImage(self, imgUUID):
        self.removeImageLinks(imgUUID)

    def linkBCImage(self, imgPath, imgUUID):
        return self.getLinkBCImagePath(imgUUID)

    def unlinkBCImage(self, imgUUID):
        pass

    def createImageLinks(self, srcImgPath, imgUUID):
        sdRunDir = os.path.join(sc.P_VDSM_STORAGE, self.sdUUID)
        self.log.info("Creating domain run directory %r", sdRunDir)
        fileUtils.createdir(sdRunDir)
        imgRunDir = os.path.join(sdRunDir, imgUUID)
        self.log.info("Creating symlink from %s to %s", srcImgPath, imgRunDir)
        try:
            os.symlink(srcImgPath, imgRunDir)
        except OSError as e:
            if e.errno == errno.EEXIST:
                self.log.debug("img run dir already exists: %s", imgRunDir)
            else:
                self.log.error("Failed to create img run dir: %s", imgRunDir)
                raise

        return imgRunDir

    def removeImageLinks(self, imgUUID):
        path = self.getImageRundir(imgUUID)
        self.log.info("Removing image rundir link %r", path)
        try:
            os.unlink(path)
        except OSError as e:
            if e.errno == errno.ENOENT:
                self.log.debug("Image rundir link %r does not exists", path)
            else:
                self.log.error("Cannot remove image rundir link %r: %s",
                               path, e)

    def activateVolumes(self, imgUUID, volUUIDs):
        imgDir = os.path.join(self.mountpoint, self.sdUUID, sd.DOMAIN_IMAGES,
                              imgUUID)
        volPaths = tuple(os.path.join(imgDir, v) for v in volUUIDs)
        for volPath in volPaths:
            self.log.info("Fixing permissions on %s", volPath)
            self.oop.fileUtils.copyUserModeToGroup(volPath)

        return self.createImageLinks(imgDir, imgUUID)

    @classmethod
    def format(cls, sdUUID):
        cls.log.info("Formatting domain %s", sdUUID)
        try:
            domaindir = cls.findDomainPath(sdUUID)
        except (se.StorageDomainDoesNotExist):
            pass
        else:
            try:
                oop.getProcessPool(sdUUID).fileUtils.cleanupdir(
                    domaindir, ignoreErrors=False)
            except RuntimeError as e:
                raise se.MiscDirCleanupFailure(str(e))

        return True

    def getRemotePath(self):
        return self._manifest.remotePath

    def getRealPath(self):
        return ""

    def getInfo(self):
        info = sd.StorageDomain.getInfo(self)
        info['remotePath'] = self.getRealPath()
        return info

    def getStats(self):
        stats = {'disktotal': '',
                 'diskfree': '',
                 'mdavalid': True,
                 'mdathreshold': True,
                 'mdasize': 0,
                 'mdafree': 0}
        try:
            st = self.oop.os.statvfs(self.domaindir)
            stats['disktotal'] = str(st.f_frsize * st.f_blocks)
            stats['diskfree'] = str(st.f_frsize * st.f_bavail)
        except OSError as e:
            self.log.info("sdUUID=%s %s", self.sdUUID, str(e))
            if e.errno == errno.ESTALE:
                raise se.FileStorageDomainStaleNFSHandle
            raise se.StorageDomainAccessError(self.sdUUID)
        return stats

    def mountMaster(self):
        masterdir = os.path.join(self.domaindir, sd.MASTER_FS_DIR)
        if not self.oop.fileUtils.pathExists(masterdir):
            self.log.info("Creating master directory: %s", masterdir)
            self.oop.os.mkdir(masterdir, 0o755)

    def unmountMaster(self):
        pass

    def selftest(self):
        try:
            self.oop.os.statvfs(self.domaindir)
        except OSError as e:
            if e.errno == errno.ESTALE:
                self.log.info("Unmounting stale file system %s",
                              self.mountpoint)
                mount.getMountFromTarget(self.mountpoint).umount()
                raise se.FileStorageDomainStaleNFSHandle()
            raise

    def imageGarbageCollector(self):
        removedPattern = os.path.join(self.domaindir, sd.DOMAIN_IMAGES,
                                      sc.REMOVED_IMAGE_PREFIX + '*')
        removedImages = self.oop.glob.glob(removedPattern)
        self.log.info("Removing remnants of deleted images %s",
                      removedImages)
        for imageDir in removedImages:
            self.oop.fileUtils.cleanupdir(imageDir)

    def templateRelink(self, imgUUID, volUUID):
        allVols = self.getAllVolumes()
        tImgs = allVols[volUUID].imgs
        if len(tImgs) < 2:
            self.log.debug("Volume %s is an unused template or a regular "
                           "volume. Found  in images: %s allVols: %s", volUUID,
                           tImgs, allVols)
            return
        templateImage = tImgs[0]
        relinkImgs = tuple(tImgs[1:])
        repoPath = self._getRepoPath()
        basePath = os.path.join(repoPath, self.sdUUID, sd.DOMAIN_IMAGES)
        volFiles = [volUUID, volUUID + fileVolume.META_FILEEXT]
        if self.hasVolumeLeases():
            volFiles.append(volUUID + LEASE_FILEEXT)
        for rImg in relinkImgs:
            for volFile in volFiles:
                tLink = os.path.join(basePath, rImg, volFile)
                tVol = os.path.join(basePath, templateImage, volFile)
                self.log.info("Force linking %s to %s", tVol, tLink)
                self.oop.utils.forceLink(tVol, tLink)

    def getVolumeClass(self):
        return fileVolume.FileVolume


    def create_external_leases(self):
        proc = oop.getProcessPool(self.sdUUID)
        path = self.external_leases_path()
        size = FILE_SPECIAL_VOLUME_SIZES_MIB[sd.XLEASES] * constants.MEGAB
        self.log.info("Creating external leases volume %s", path)
        try:
            proc.truncateFile(path, size, METADATA_PERMISSIONS, creatExcl=True)
        except OSError as e:
            if e.errno == errno.EEXIST:
                self.log.info("Reusing external leases volume %s", path)


    def convert_volumes_metadata(self, target_version):
        current_version = self.getVersion()

        if not (current_version == 4 and target_version == 5):
            raise RuntimeError(
                "Cannot convert domain {} volumes metadata from version {} "
                "to version {}"
                .format(self.sdUUID, current_version, target_version))

        self.log.info(
            "Converting domain %s volumes metadata from version %s to "
            "version %s",
            self.sdUUID, current_version, target_version)

        for vol in self.iter_volumes():
            self.log.debug("Converting volume %s metadata", vol.volUUID)
            try:
                vol_md = vol.getMetadata()
            except se.MetaDataKeyNotFoundError as e:
                self.log.warning(
                    "Skipping volume %s with invalid metadata: %s",
                    vol.volUUID, e)
                continue
            vol.setMetadata(vol_md, CAP=vol_md.capacity)

    def finalize_volumes_metadata(self, target_version):
        current_version = self.getVersion()

        if current_version != target_version:
            raise RuntimeError(
                "Cannot finalize domain {} volumes metadata: current version "
                "{} != target version {}"
                .format(self.sdUUID, current_version, target_version))

        self.log.info("Finalizing domain %s volumes metadata version %s",
                      self.sdUUID, target_version)

        for vol in self.iter_volumes():
            self.log.debug("Finalizing volume %s metadata", vol.volUUID)
            try:
                vol_md = vol.getMetadata()
            except se.MetaDataKeyNotFoundError:
                continue
            vol.setMetadata(vol_md)


    @classmethod
    def _detect_block_size(cls, sd_id, mountpoint):
        log = logging.getLogger("storage.fileSD")
        iop = oop.getProcessPool(sd_id)
        try:
            block_size = iop.probe_block_size(mountpoint)
        except OSError as e:
            if e.errno != errno.EINVAL:
                raise
            raise se.StorageDomainTargetUnsupported(
                "Failed to probe block size on {}: {}".format(mountpoint, e))

        log.debug("Detected domain %s block size %s", sd_id, block_size)
        return block_size


def _getMountsList(pattern="*"):
    fileDomPattern = os.path.join(sc.REPO_MOUNT_DIR, pattern)

    mntList = [mnt for mnt in glob.iglob(fileDomPattern)
               if not mnt.endswith(_MOUNTLIST_IGNORE)]

    glusterDomPattern = os.path.join(
        sc.REPO_MOUNT_DIR, sd.GLUSTERSD_DIR, pattern)

    mntList.extend(glob.glob(glusterDomPattern))

    return mntList


def scanDomains(pattern="*"):
    log = logging.getLogger("storage.scanDomains")

    mntList = _getMountsList(pattern)

    def collectMetaFiles(mountPoint):
        try:
            if mountPoint.startswith(sc.REPO_MOUNT_DIR):
                client_name = mountPoint[len(sc.REPO_MOUNT_DIR):]

            mdPattern = os.path.join(
                glob_escape(mountPoint),
                UUID_GLOB_PATTERN,
                sd.DOMAIN_META_DATA)

            metaFiles = oop.getProcessPool(client_name).glob.glob(mdPattern)

            for metaFile in metaFiles:
                if (os.path.basename(os.path.dirname(metaFile)) !=
                        sd.MASTER_FS_DIR):
                    sdUUID = os.path.basename(os.path.dirname(metaFile))

                    return (sdUUID, os.path.dirname(metaFile))

        except Exception:
            log.warn("Could not collect metadata file for domain path %s",
                     mountPoint, exc_info=True)

    for res in concurrent.tmap(
            collectMetaFiles, mntList, max_workers=oop.HELPERS_PER_DOMAIN):
        if res.value is None:
            continue

        yield res.value


def getStorageDomainsList():
    return [item[0] for item in scanDomains()]
