目录

配额Quota

一个系统中资源的总量是有限的,因此,这个系统在将资源分配给其组织单元的资源量也是有限额的——也就是配额。在openstack中,配额管理的单位是一个工程(Project),管理的对象是资源,包括cpu、内存、存储空间、网络和实例等。例如,我们可以设定,在某个Project中允许创建的虚拟机实例只能是10个,内存总量为30G,磁盘的限制总容量是100TB等,这些就是这个Project分配的资源的配额。

manila配额管理模块

和cinder类似,manila模块的代码也在~/manila/quota.py中,配额模块有三个主要的抽象:资源Resource、驱动Driver和引擎Engine,模块的模型图(来源)见下图所示。
Quota数据模型图

QuotaEngine
QuotaEngine是配额模块对外提供的接口,其主要作用是判断和初始化资源类型,然后初始化相应的驱动Driver,通过调用Driver进行资源配额的相关操作(reserve、commit、rollback和expire等),以及对外提供配额资源的查询的接口。

DbQuotaDriver
DbQuotaDriver是manila默认配额管理的具体实现类,包括配额管理以及DB配额数据增删改查的的相关操作,其中比较重要的是reserve(), commit(), rollback()这三个方法,这三个方法共同构成了配额管理一个很重要的特性:事务性——即当资源分配失败或者已达到配额上限等异常情况发生时,对已经修改过的数据库,可以回滚到分配之前的状态。

关于配额的这三个方法,会在后面进行详细描述。

资源Resource
资源在配额Quota中有一个基础类BaseResource,定义配额资源的基本,如下:

class BaseResource(object):  
    def __init__(self, name, flag=None):  
    def quota(self, driver, context, **kwargs):  
    def default(self):  

其中,quota()方法为获取当前资源的Quota配额对象,default()方法返回资源的配额信息的标识位,或者在配置项中的默认值。例如,对于文件共享share个数配额的资源,其资源对象的name是"shares", 通过default()获取的值便是默认值或者是配置文件CONF中的配置值。

资源中有两类,都继承自基类BaseResource,一种是ReservableResource,用于project绑定的资源,比如文件共享shares的数量,或者文件共享的容量,同时,会定义一个相应的同步sync的方法,供驱动Driver在需要的时候调用sync方法进行同步,这些同步方法实现的功能是实时的从数据库中查询出正在使用的资源的数量。

另外一种就是AbsoluteResource,其子类是CountableResource,这种类型的资源同样保存到数据中,但是这种类型的资源在统计配额时,不能仅仅基于project ID来统计,同时,定义的不在是同步sync方法,而是count方法

资源中有两类,都继承自基类BaseResource,一种是ReservableResource,这种类型的资源直接和数据库中的资源对象关联起来,比如文件共享shares的数量,或者文件共享的容量,同时ReservableResource资源也会初始化相应的同步方法。另外一种就是AbsoluteResource,其子类是CountableResource,这种类型的资源也会和保存到数据中,但是这种类型的资源在统计配额时,不会仅仅基于project ID来统计。

在manila中,仅仅有ReservableResource,其初始化方法如下:

resources = [
    ReservableResource('shares', '_sync_shares', 'quota_shares'),
    ReservableResource('snapshots', '_sync_snapshots', 'quota_snapshots'),
    ReservableResource('gigabytes', '_sync_gigabytes', 'quota_gigabytes'),
    ReservableResource('snapshot_gigabytes', '_sync_snapshot_gigabytes',
                       'quota_snapshot_gigabytes'),
    ReservableResource('share_networks', '_sync_share_networks',
                       'quota_share_networks'),
]

上述,表明初始化了shares、gigabytes等几个ReservableResource,并指定了相应的同步方法和默认的配置项CONF标识位。

配额的数据模型

配额在数据库中主要涉及到如下quotas、project_user_quotas、quota_class、quota_usage和reservation五个表,详细定义参考db/sqlalchemy/models.py。在Quota和quota_class表记录了分配给每个工程的各种资源的最大限额,其中hard_limit值就是保存最大限额的。重点关注的是quota_usage和reservation连个表.

quota_usages;  
+---------------+--------------+------+
| Field         | Type         | Null |
+---------------+--------------+------+
| created_at    | datetime     | YES  |
| updated_at    | datetime     | YES  |
| deleted_at    | datetime     | YES  |
| deleted       | int          | YES  |
| id            | int          | NO   |
| project_id    | varchar(255) | YES  |
| resource      | varchar(255) | YES  |
| in_use        | int          | NO   |
| reserved      | int          | NO   |
| until_refresh | int          | YES  |
+---------------+--------------+------+
  
reservations;  
+------------+--------------+------+
| Field      | Type         | Null |
+------------+--------------+------+
| created_at | datetime     | YES  |
| updated_at | datetime     | YES  |
| deleted_at | datetime     | YES  |
| deleted    | int          | YES  |
| id         | int          | NO   |
| uuid       | varchar(36)  | NO   |
| usage_id   | int          | NO   |
| project_id | varchar(255) | YES  |
| resource   | varchar(255) | YES  |
| delta      | int          | NO   |
| expire     | datetime     | YES  |
+------------+--------------+------+

quota_usages表
quota_usages表记录了每个工程当前使用的各种资源的数量。
in_use值就是保存正在使用的资源数量的,程序中就是通过更新这个in_use值来实现配额管理。reserved值,用于保存资源的预占值。

reservations表
reservations表记录的是每次分配的各种资源的变化值,即其中delta字段保存的值,用于资源配额恢复,这个表中的记录是不更新的,每次分配都会相应的增加记录。如果是使用资源例如新建一个share,那么delta值便是正数;如果是删除一个资源,例如删除share,那么delta字段的值便是负数。

配额的事务性

前面说过配额管理有事务性——即当资源分配失败或者已达到配额上限等异常情况发生时,对已经修改过的数据库,可以回滚到分配之前的状态。这主要就是通过DbQuotaDriver的reserve(), commit()和rollback()这三个方法,以及quota_usages表和reservations表中的in_use、reserved和delta三个值来实现的。

1. reserve()
reserve()函数的作用是预判配额是否超出限额,同时判断是否需要执行资源同步。如果分配给请求的资源的数量超出工程的限额,就报异常,如果没有超出,就更新一下数据库中quota_usages表的in_use值以及在reservations表中创建一条相应的reservation记录。

函数主要分为以下几步:
(1)同步
在为工程分配资源时,可能有各种特殊情况导致quota_usages表中记录的in_use不准确,需要得到当前实际使用的资源的情况,更新in_use值。
当前申请的资源为如下情况:

  • 没有在quota_usages表中记录

  • quota_usages表中的in_use值小于0,表示数据已经不一致。

  • 当前申请的资源的until_refresh值不为空,减1之后小于0时,表明需要手动执行分配资源实际使用情况。

  • 当前时间减去申请的资源在quota_usages表中的更新时间大于max_age时,配置项max_age的值为连续两次配额更新同步的时间,如果距离上次更新后过了max_age的时间,就执行同步操作。

如果符合这四种情况之一,就执行同步。同步时,是调用当前资源的_sync_*()函数,去相关的表中查询实时的使用情况,然后根据这些实时的数据,更新in_use值。

(2)检查

根据各种资源的配额,和变化的情况(delta),来检查两种极端的情况:under和over。

  • under是检查delta为负数的情况,即执行了删除等操作,使delta为负,in_use减少,导致in_use值可能小于0。

  • over是检查delta为正数时的情况,in_use+delta就有可能大于最大的限额了。
    这里只对over的情况进行处理,即它只关心上限,不关心下限。

(3)处理

如果没有over的话,向reservations表中增加一条记录,记录申请资源的delta值, 并把in_use值写入到quota_usages表中保存。同时,如果是对于配额增加的操作,即delta大于0的情况,还需要将delta值记录到quota_usages表的reserved字段中保持。然后把in_use值写入到quota_usages表中保存(不论under还是over都执行)。
如果over,即超出最大限额,则报出OverQuota异常。

2. commit()
当reserve()函数执行完之后,没有报OverQuota异常,即本次请求的资源加上当前工程中已使用的资源数量没有超过限额时,它

  • 如果reservations表中delta是正值,即需要消耗配额时,更新quota_usages表reserved字段的值,将其减去delta变化值。

  • 将in_use加上变化值delta(无论delta是否为正数还是fushu),然后更新到quota_usages表中

  • 从reservations表中删除这个reservation的记录,标记为deleted。

@require_context
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
def reservation_commit(context, reservations, project_id=None, user_id=None):
    session = get_session()
    with session.begin():
        usages = _get_user_quota_usages(context, session, project_id, user_id)
        reservation_query = _quota_reservations_query(session, context,
                                                      reservations)
        for reservation in reservation_query.all():
            usage = usages[reservation.resource]
            if reservation.delta >= 0:
                usage.reserved -= reservation.delta
            usage.in_use += reservation.delta
        reservation_query.soft_delete(synchronize_session=False)

3. rollback()
如果reserve()执行正常,但是commit()执行失败,需要回滚在reserve()函数中更改的数据。在reserve()中,如果没有over的话,reserved值会执行reserved+=delta,在这里,便会执行反向的操作,执行reserved-=delta,同时从reservations表中删除这个reservation的记录。

@require_context
@oslo_db_api.wrap_db_retry(max_retries=5, retry_on_deadlock=True)
def reservation_rollback(context, reservations, project_id=None, user_id=None):
    session = get_session()
    with session.begin():
        usages = _get_user_quota_usages(context, session, project_id, user_id)
        reservation_query = _quota_reservations_query(session, context,
                                                      reservations)
        for reservation in reservation_query.all():
            usage = usages[reservation.resource]
            if reservation.delta >= 0:
                usage.reserved -= reservation.delta
        reservation_query.soft_delete(synchronize_session=False)

dinghb
71 声望1 粉丝

引用和评论

0 条评论