OpenStack:glance_store:_drivers/filesystem.py文件源码学习-02

来源:互联网 发布:微纳制造技术 知乎 编辑:程序博客网 时间:2024/06/14 12:10

继续01部分的学习。

方法_check_directory_paths(self, datadir_path, directory_paths, priority_paths)

检查传入的路径datadir_path是否已经在集合directory_paths中存在。

    def _check_directory_paths(self, datadir_path, directory_paths,                               priority_paths):        if datadir_path in directory_paths:            msg = (_("Directory %(datadir_path)s specified "                     "multiple times in filesystem_store_datadirs "                     "option of filesystem configuration") %                   {'datadir_path': datadir_path})            # If present with different priority it's a bad configuration            if datadir_path not in priority_paths:                LOG.exception(msg)                raise exceptions.BadStoreConfiguration(                    store_name="filesystem", reason=msg)            # Present with same prio (exact duplicate) only deserves a warning            LOG.warning(msg)

01 若路径datadir_path不在集合directory_paths中,什么都不做,方法结束。

02 若存在,继续判断传入的路径是否在列表priority_paths中,若不存在,则异常。这一步的判断其实是对配置进行了一种保护,因为第一步能通过,说明之前相同的路径已经存在了,第二步判断优先级列表中又没有,说明同一路径配置了不同的优先级,这是不正确的。

03 若未抛出异常并完成了上述判断,这里打印日志。

方法_get_datadir_path_and_priority(self, datadir)

从glance-api.conf配置文件的filesystem_store_datadirs选项中,获取路径及对应的优先级。

这里得说明一下,传入的参数是单条路径,而非一个列表或是其它集合。

    def _get_datadir_path_and_priority(self, datadir):        priority = 0        parts = [part.strip() for part in datadir.rsplit(":", 1)]        datadir_path = parts[0]        if len(parts) == 2 and parts[1]:            priority = parts[1]            if not priority.isdigit():                msg = (_("Invalid priority value %(priority)s in "                         "filesystem configuration") % {'priority': priority})                LOG.exception(msg)                raise exceptions.BadStoreConfiguration(                    store_name="filesystem", reason=msg)        if not datadir_path:            msg = _("Invalid directory specified in filesystem configuration")            LOG.exception(msg)            raise exceptions.BadStoreConfiguration(                store_name="filesystem", reason=msg)        return datadir_path, priority

01 将传入的datadir按冒号分割,获取路径和优先级信息。这里注意语句:parts = [part.strip() for part in datadir.rsplit(":", 1)],我自己试了一下如下:

>>> datadir = "wo:1:shi:2:shui:3">>> datadir.rsplit(":", 1)['wo:1:shi:2:shui', '3']

不过因为传入的都是单条路径,结构都是<path>:<priority>,所以解析出来的都是一个只包含路径和对应信息的列表。其实就是转换了信息形式方便处理。

02 如果传入的datadir能取到两部分信息(此时还不能判断路径是否是None),那么判断配置的优先级的值是否是数字,不是则异常。

03 判断获取到的路径信息是否为真,不为真异常。

04 返回获取的路径信息和对应的优先级信息。

方法_resolve_location(location)

该方法为静态方法,它获取指定位置的文件路径和文件大小。

@staticmethoddef _resolve_location(location):    filepath = location.store_location.path    if not os.path.exists(filepath):        raise exceptions.NotFound(image=filepath)    filesize = os.path.getsize(filepath)    return filepath, filesize

01 若指定位置中的文件路径不存在,异常。方法是Python的os.path.exists(filepath)

02 获取路径下文件的大小。方法是os.path.getsize(filepath),注意返回值是byte为单位的,不是Kb或其它单位。

03 返回文件路径和大小。

方法_get_metadata(self, filepath)

该方法返回元数据目录。

如果元数据作为字典的列表提供,那么会返回一个包含了“id”和“mountpoint”的字典。

def _get_metadata(self, filepath):    if self.FILESYSTEM_STORE_METADATA:        for image_meta in self.FILESYSTEM_STORE_METADATA:            if filepath.startswith(image_meta['mountpoint']):                return image_meta        reason = (_LE("The image path %(path)s does not match with "                      "any of the mountpoint defined in "                      "metadata: %(metadata)s. An empty dictionary "                      "will be returned to the client.")                  % dict(path=filepath,                         metadata=self.FILESYSTEM_STORE_METADATA))        LOG.error(reason)    return {}

01 若当前调用者中FILESYSTEM_STORE_METADATA存在值,则进一步处理,否则直接返回空字典。

查看代码,在模块中,只有在当前类的方法_validate_metadata(self, metadata_file)中对其进行了赋值,其来源就是JSON文件。

02 遍历FILESYSTEM_STORE_METADATA,在每次循环中判断指定的文件路径,是否以当前循环中镜像元数据的挂载点开始,若是,则直接返回本次循环中的镜像元数据。

03 若遍历所有都未找到,记录日志。

04 返回空字典。

方法get(self, location, offset=0, chunk_size=None, context=None)

入参中携带了一个glance_store.location.Location类型对象,指明在哪里找到镜像文件,并返回一个生成器(用于读取镜像文件)及镜像大小image_size

@capabilities.checkdef get(self, location, offset=0, chunk_size=None, context=None):    filepath, filesize = self._resolve_location(location)    msg = _("Found image at %s. Returning in ChunkedFile.") % filepath    LOG.debug(msg)    return (ChunkedFile(filepath,                        offset=offset,                        chunk_size=self.READ_CHUNKSIZE,                        partial_length=chunk_size),            chunk_size or filesize)

01 调用类中的_resolve_location(location)方法获取镜像文件路径和镜像文件大小。

02 调用ChunkedFile()类构造方法生成实例对象,并将对象与镜像大小/块大小组合成元组返回。

方法get_size(self, location, context=None)

入参中携带一个glance_store.location.Location类型的对象,用于指明镜像位置并返回镜像大小。

def get_size(self, location, context=None):    filepath, filesize = self._resolve_location(location)    msg = _("Found image at %s.") % filepath    LOG.debug(msg)    return filesize

01 调用类中的_resolve_location(location)方法获取镜像文件路径和镜像文件大小。

02 返回镜像大小。

方法delete(self, location, context=None)

入参携带一个glance_store.location.Location类型的对象,用于指明从哪里找到对应镜像去删除。

@capabilities.checkdef delete(self, location, context=None):    """    Takes a `glance_store.location.Location` object that indicates    where to find the image file to delete    :param location: `glance_store.location.Location` object, supplied              from glance_store.location.get_location_from_uri()    :raises: NotFound if image does not exist    :raises: Forbidden if cannot delete because of permissions    """    loc = location.store_location    fn = loc.path    if os.path.exists(fn):        try:            LOG.debug(_("Deleting image at %(fn)s"), {'fn': fn})            os.unlink(fn)        except OSError:            raise exceptions.Forbidden(                message=(_("You cannot delete file %s") % fn))    else:        raise exceptions.NotFound(image=fn)

01 从入参location中获取存储镜像的位置。

02 调用os.path.exists()方法判断指定的位置是否存在,不存在异常。存在则继续处理。

03 调用os.unlink()方法进行删除指定的镜像目录。

方法_get_capacity_info(self, mount_point)

计算给定挂载点(其实就是某个目录)的所有可用空间大小。挂载点是Glance数据目录的路径。

def _get_capacity_info(self, mount_point):    # Calculate total available space    stvfs_result = os.statvfs(mount_point)    total_available_space = stvfs_result.f_bavail * stvfs_result.f_bsize    return max(0, total_available_space)

01 调用os.statvfs(mount_point)方法获取指定路径的系统信息。主要包括了该目录下存储相关的信息,比如文件系统块大小、可用块数等等。

02 将非超级用户可获取的块数(f_bavail)与文件系统块大小(f_bsize)相乘作为结果。

03 为保证万一,返回该结果与0比较的最大值。

方法_find_best_datadir(self, image_size)

根据路径优先级和剩余空间大小,选择最优的镜像路径。

遍历整个目录,并根据路径优先级,返回第一个有足够空间的路径。如果有两个合适的路径含有相同的优先级,选择剩余空间最多的那个。

def _find_best_datadir(self, image_size):    if not self.multiple_datadirs:        return self.datadir    best_datadir = None    max_free_space = 0    for priority in self.priority_list:        for datadir in self.priority_data_map.get(priority):            free_space = self._get_capacity_info(datadir)            if free_space >= image_size and free_space > max_free_space:                max_free_space = free_space                best_datadir = datadir        # If datadir is found which can accommodate image and has maximum        # free space for the given priority then break the loop,        # else continue to lookup further.        if best_datadir:            break    else:        msg = (_("There is no enough disk space left on the image "                 "storage media. requested=%s") % image_size)        LOG.exception(msg)        raise exceptions.StorageFull(message=msg)    return best_datadir

01 判断调用者自身是否是有多条路径,若没有,直接返回调用者自身的datadir变量值。

02 若调用者已获取各路径的优先级列表priority_list及路径和优先级构成的字典priority_data_map,则进行遍历处理。外层循环为优先级列表,内层为根据该优先级获取的路径。

​ (1) 通过调用_get_capacity_info(datadir)方法,获取当前循环中目录的剩余空间大小。

​ (2) 判断剩余空间大小是否大于等于指定的镜像大小,并且该剩余空间大小是否大于当前记录的最大剩余空间值max_free_space。若都满足,则将本次循环中得到的剩余空间大小赋值给max_free_space,并将当前目录赋值给变量best_datadir,作为临时的最佳目录。

​ (3) 若在当前内层循环后,得到了一个最优目录,那么跳出整个循环。

03 若内外两层遍历结束后仍未找到合适的目录,则异常。

04 返回找出的最优目录。

这个方法要注意遍历过程中内外层的顺序,为什么外层是优先级,内层是目录:
因为设置优先级就是为了方便处理,按照之前理解的,获取的优先级列表会进行一个反序重排。因此正常情况下,会先在低优先级的目录下进行查找,逐步上升。

方法add(self, image_id, image_file, image_size, context=None, verifier=None)

作为镜像上传的关键方法,需要重点了解。
该方法使用一个提供的标识符在后端存储系统上存储一个镜像文件,成功后返回一个包含这个已存储镜像的信息的元组。

@capabilities.checkdef add(self, image_id, image_file, image_size, context=None,        verifier=None):    datadir = self._find_best_datadir(image_size)    filepath = os.path.join(datadir, str(image_id))    if os.path.exists(filepath):        raise exceptions.Duplicate(image=filepath)    checksum = hashlib.md5()    bytes_written = 0    try:        with open(filepath, 'wb') as f:            for buf in utils.chunkreadable(image_file,                                           self.WRITE_CHUNKSIZE):                bytes_written += len(buf)                checksum.update(buf)                if verifier:                    verifier.update(buf)                f.write(buf)    except IOError as e:        if e.errno != errno.EACCES:            self._delete_partial(filepath, image_id)        errors = {errno.EFBIG: exceptions.StorageFull(),                  errno.ENOSPC: exceptions.StorageFull(),                  errno.EACCES: exceptions.StorageWriteDenied()}        raise errors.get(e.errno, e)    except Exception:        with excutils.save_and_reraise_exception():            self._delete_partial(filepath, image_id)    checksum_hex = checksum.hexdigest()    metadata = self._get_metadata(filepath)    LOG.debug(_("Wrote %(bytes_written)d bytes to %(filepath)s with "                "checksum %(checksum_hex)s"),              {'bytes_written': bytes_written,               'filepath': filepath,               'checksum_hex': checksum_hex})    if self.conf.glance_store.filesystem_store_file_perm > 0:        perm = int(str(self.conf.glance_store.filesystem_store_file_perm),                   8)        try:            os.chmod(filepath, perm)        except (IOError, OSError):            LOG.warning(_LW("Unable to set permission to image: %s") %                        filepath)    return ('file://%s' % filepath, bytes_written, checksum_hex, metadata)

01 调用_find_best_datadir(image_size)方法,获取一个最优存放目录。

02 调用os.path.join(datadir, str(image_id)),拼接待存放文件的路径。

03 如果该路径已存在,则异常,说明路径重复。

04 通过方法hashlib.md5()生成校验需要的对象checksum

05 在指定路径下,写入镜像文件。

​ (1) 首先将镜像文件封装成一个产生最佳块大小的读取器,这里块大小是64Kb。

​ (2) 对镜像文件进行遍历。

​ (3) 遍历中一次取一个块,累计块大小,并对每一个块调用checksum.update()方法。为了是后面得到镜像文件的MD5值:checksum_hex = checksum.hexdigest()

​ (4) 遍历中判断是否传入了verifier,它是用于验证镜像签名的一个对象。若传入还得继续处理当前数据块。

​ (5) 写入当前数据库。

06 得到镜像文件的MD5值。通过语句checksum_hex = checksum.hexdigest()来获取。

07 调用方法_get_metadata(filepath)方法获取镜像文件的元数据。

08 判断配置中filesystem_store_file_perm选项值是否大于0(表示需要设置权限),若是,则调用os.chmod(filepath, perm)方法设置镜像文件的权限。

09 返回镜像文件的信息元组:('file://%s' % filepath, bytes_written, checksum_hex, metadata)

方法_delete_partial(filepath, iid)

通过调用os.unlink(filepath)方法删除指定的目录及内容。

@staticmethoddef _delete_partial(filepath, iid):    try:        os.unlink(filepath)    except Exception as e:        msg = _('Unable to remove partial image '                'data for image %(iid)s: %(e)s')        LOG.error(msg % dict(iid=iid,                             e=encodeutils.exception_to_unicode(e)))

该方法实现比较简单,可参加其调用的os.unlink(filepath)

0 0