封裝抽象
quota相關(guān)功能實現(xiàn)在cinder\quota.py實現(xiàn),包含了引擎、資源、驅(qū)動三個大類抽象封裝??雌饋韖uota.py用的是設(shè)計模式“抽象工廠模式”,引擎選擇驅(qū)動、定義資源,調(diào)用驅(qū)動管理資源。
一、資源
資源其實就是對配額的封裝,封裝了資源名、默認(rèn)值、數(shù)據(jù)庫統(tǒng)計函數(shù)dbapi等。
資源分類列表:
| 類名 | 描述 | 屬性 |
|---|---|---|
| cinder.quota.BaseResource | 定義單個配額資源 | name(資源名)、flag(控制默認(rèn)值)、parent_project_id(當(dāng)前租戶的附租戶)、quota方法用于獲取資源的使用量、default方法用戶獲取默認(rèn)值 |
| cinder.quota.AbsoluteResource | 無預(yù)留的資源 | pass無定義 |
| cinder.quota.ReservableResource | 可預(yù)留的資源 | sync (dbapi 方法名,統(tǒng)計配額使用信息。如范例) |
| cinder.quota.CountableResource | 可統(tǒng)計的資源,cinder代碼里沒看到使用 | count(統(tǒng)計函數(shù)) |
| cinder.quota.VolumeTypeResource | 為卷類型定義的資源,繼承ReservableResource | volume_type_name(卷類型名)、volume_type_id(卷類型id) |
注意:
ReservableResource:相比BaseResource,多了sync方法,sync會被驅(qū)動調(diào)用,用于在計算配額之前,先同步配額信息(到本地和數(shù)據(jù)庫)。ReservableResource只能用于project綁定的資源。
CountableResource:相比BaseResource,多了count方法,count方法必須給出一個函數(shù),自己計算配額,其返回值里會包含配額實際使用值。

范例:ReservableResource資源'volume'的sync:cinder.db.sqlalchemy.api._sync_volumes
def _sync_volumes(context, project_id, session, volume_type_id=None,
volume_type_name=None):
# 根據(jù)volume_type_id和project_id統(tǒng)計卷數(shù)量和卷空間使用量
(volumes, _gigs) = _volume_data_get_for_project(
context, project_id, volume_type_id=volume_type_id, session=session)
key = 'volumes'
if volume_type_name:
key += '_' + volume_type_name
return {key: volumes}
二、引擎
定義了資源集。調(diào)用驅(qū)動來實現(xiàn)查詢統(tǒng)計功能。
引擎列表:
| 類名 | 描述 | resources |
|---|---|---|
| cinder.quota.QuotaEngine | 配額引擎,基類 | |
| cinder.quota.VolumeTypeQuotaEngine | 卷類型配額引擎 | 'volumes', 'per_volume_gigabytes','snapshots','gigabytes','backups','backup_gigabytes' |
| cinder.quota.CGQuotaEngine | 一致性組的配額的引擎 | consistencygroups |
| cinder.quota.GroupQuotaEngine | 組配額的引擎 | groups |
三、驅(qū)動
驅(qū)動是可配置的,對應(yīng)配置項quota_driver,默認(rèn)值是cinder.quota.DbQuotaDriver
cfg.StrOpt('quota_driver',
default="cinder.quota.DbQuotaDriver",
help='Default driver to use for quota checks')
驅(qū)動列表:
| 名稱 | 描述 |
|---|---|
| cinder.quota.DbQuotaDriver | Number of volume gigabytes allowed per tenant |
| cinder.quota.NestedDbQuotaDriver | Number of Block Storage snapshots allowed per tenant. |
quota主要操作四張數(shù)據(jù)表:
- reservations表,定義每個項目配額的增量。

- quota_usage表,定義每個項目配額的已使用量和預(yù)留量。

- quota_classes表,定義了配額類的配額。操作界面上的默認(rèn)配額就是保存在這個表里。

- quotas表,定義了項目的配額。是如果僅僅是調(diào)用API接口或者client指令
openstack project create pro3創(chuàng)建項目,是不會同時創(chuàng)建項目對應(yīng)的專用配額的。但是如果在管理界面上創(chuàng)建項目,horizon會同時調(diào)用cinder的quota接口創(chuàng)建三個“gigabytes”、“volumes”、“snapshots” cinder專用配額,另外還會調(diào)neutron、nova的配額接口創(chuàng)建它們專用的配額。


指令功能介紹
quotas相關(guān)的指令:
quota-class-show Lists quotas for a quota class.
quota-class-update Updates quotas for a quota class.
quota-defaults Lists default quotas for a tenant.
quota-delete Delete the quotas for a tenant.
quota-show Lists quotas for a tenant.
quota-update Updates quotas for a tenant.
quota-usage Lists quota usage for a tenant.
quota-class-show、quota-class-update、quota-defaults是對quota_classes表操作,quota-delete、quota-show、quota-update 主要對quota表操作,quota-usage是對quota_classes、quota_usage操作。
作為管理員用戶,我們可以修改塊存儲租戶的配額,也可以對新租戶定義默認(rèn)配額。
1. 列出默認(rèn)List all default quotas for all tenants, as follows:
$ cinder quota-defaults tenantID
[root@node1 ~]# cinder quota-defaults admin
+------------------------------+-------+
| Property | Value |
+------------------------------+-------+
| backup_gigabytes | 1000 |
| backups | 10 |
| gigabytes | 1000 |
| gigabytes_netapp_volume_type | -1 |
| gigabytes_nfs_common | -1 |
| gigabytes_vmware | -1 |
| gigabytes_vmware-type | -1 |
| per_volume_gigabytes | -1 |
| snapshots | 10 |
| snapshots_netapp_volume_type | -1 |
| snapshots_nfs_common | -1 |
| snapshots_vmware | -1 |
| snapshots_vmware-type | -1 |
| volumes | 10 |
| volumes_netapp_volume_type | -1 |
| volumes_nfs_common | -1 |
| volumes_vmware | -1 |
| volumes_vmware-type | -1 |
+------------------------------+-------+
quota-defaults 代碼分析:
cinder.api.contrib.quotas.QuotaSetsController#defaults:
return self._format_quota_set(id, QUOTAS.get_defaults(context, project_id=id))
# QUOTAS 是類對象 cinder.quota.VolumeTypeQuotaEngine()
cinder.quota.QuotaEngine#get_defaults:
def get_defaults(self, context, project_id=None):
return self._driver.get_defaults(context, self.resources,
project_id)
_driver 是 cinder.quota.QuotaEngine#_driver,代碼可得cinder.quota.DbQuotaDriver
@property
def _driver(self):
# _driver_class是__init__構(gòu)造函數(shù)里傳入設(shè)置的,沒傳為None
if self._driver_class:
return self._driver_class
if not self._quota_driver_class:
# 讀配置項cfg.StrOpt('quota_driver',default="cinder.quota.DbQuotaDriver",
self._quota_driver_class = CONF.quota_driver
if isinstance(self._quota_driver_class, six.string_types):
# 動態(tài)導(dǎo)入類對象
self._quota_driver_class = importutils.import_object(
self._quota_driver_class)
self._driver_class = self._quota_driver_class
return self._driver_class
cinder.quota.DbQuotaDriver#get_defaults:
def get_defaults(self, context, resources, project_id=None):
quotas = {}
default_quotas = {}
# cfg.BoolOpt('use_default_quota_class',default=True,
if CONF.use_default_quota_class:
# 查詢'quota_classes'表,過濾出class_name = 'defualt'的記錄,
default_quotas = db.quota_class_get_defaults(context)
# resources 來自cinder.quota.VolumeTypeQuotaEngine的@property 方法
for resource in resources.values():
if default_quotas:
if resource.name not in default_quotas:
versionutils.report_deprecated_feature(LOG, _(
"Default quota for resource: %(res)s is set "
"by the default quota flag: quota_%(res)s, "
"it is now deprecated. Please use the "
"default quota class for default "
"quota.") % {'res': resource.name})
# default_quotas的值 復(fù)寫 resources ,如果default_quotas里不包含resource,則使用resource的default屬性。default屬性說明見下文!
quotas[resource.name] = default_quotas.get(resource.name,
resource.default)
return quotas
代碼可知cinder.quota.DbQuotaDriver的get_defaults方法不區(qū)分租戶project_id,所以如果配置項'quota_driver'使用"cinder.quota.DbQuotaDriver",調(diào)用 cinder quota-defaults ${project_id} 的結(jié)果都是一樣的。
cinder.quota.VolumeTypeQuotaEngine#resources:
@property
def resources(self):
"""Fetches all possible quota resources."""
result = {}
# Global quotas.
argses = [('volumes', '_sync_volumes', 'quota_volumes'),
('per_volume_gigabytes', None, 'per_volume_size_limit'),
('snapshots', '_sync_snapshots', 'quota_snapshots'),
('gigabytes', '_sync_gigabytes', 'quota_gigabytes'),
('backups', '_sync_backups', 'quota_backups'),
('backup_gigabytes', '_sync_backup_gigabytes',
'quota_backup_gigabytes')]
# 根據(jù)上面定義的argses獲得ReservableResource列表,
for args in argses:
resource = ReservableResource(*args)
result[resource.name] = resource
# 查詢得volume_type列表
volume_types = db.volume_type_get_all(context.get_admin_context(),
False)
for volume_type in volume_types.values():
for part_name in ('volumes', 'gigabytes', 'snapshots'):
# 對每一個volume_type,按照規(guī)則 name = "%s_%s" % (part_name, self.volume_type_name)設(shè)置resource.name
resource = VolumeTypeResource(part_name, volume_type)
result[resource.name] = resource
# 返回ReservableResource和VolumeTypeResource組合的resoure列表
return result
#### (Pdb) p result
{'per_volume_gigabytes': <cinder.quota.ReservableResource object at 0x98e7090>, 'gigabytes': <cinder.quota.ReservableResource object at 0x98e70d0>, 'backup_gigabytes': <cinder.quota.ReservableResource object at 0x98e7150>, 'snapshots': <cinder.quota.ReservableResource object at 0x98e7050>, 'volumes': <cinder.quota.ReservableResource object at 0x9b8ffd0>, 'backups': <cinder.quota.ReservableResource object at 0x98e7110>}
看下ReservableResource和VolumeTypeResource 類的繼承關(guān)系:
ReservableResource和VolumeTypeResource 對象的default屬性,都繼承自cinder.quota.BaseResource#default:
@property
def default(self):
"""Return the default value of the quota."""
if self.parent_project_id:
return 0
# 如果self.flag不是空,則返回CONF[self.flag],否則返回-1
return CONF[self.flag] if self.flag else -1
總結(jié):
- 如果配置項'quota_driver'使用"cinder.quota.DbQuotaDriver"或者不配置,cinder quota-defaults 指令打印結(jié)果和租戶id無關(guān)。
- cinder quota-defaults 指令打印列表,包含了'volumes', 'per_volume_gigabytes','snapshots','gigabytes','backups','backup_gigabytes' 六個元素,另外還包含'volumes_${volume_type}', 'gigabytes_${volume_type}', 'snapshots_${volume_type}' 多個元素,元素的值由表 quota_classes 里 class_name='default' 的記錄設(shè)置,如果表里沒有,則由cinder.quota.BaseResource#default得到。
2. 根據(jù)quota-class列出quota列表:
cinder quota-class-show <class>
quota-defaults 代碼分析:
如果配置項'quota_driver'使用"cinder.quota.DbQuotaDriver"或者不配置,走的是cinder.quota.DbQuotaDriver#get_class_quotas:
def get_class_quotas(self, context, resources, quota_class,
defaults=True):
"""Given list of resources, retrieve the quotas for given quota class.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resources.
:param quota_class: The name of the quota class to return
quotas for.
:param defaults: If True, the default value will be reported
if there is no specific value for the
resource.
"""
quotas = {}
default_quotas = {}
# 根據(jù)class_name在數(shù)據(jù)庫表quota_classes查詢出歸屬某類的配額屬性
class_quotas = db.quota_class_get_all_by_name(context, quota_class)
if defaults:
# 數(shù)據(jù)庫表quota_classes查出默認(rèn)類的配額屬性
default_quotas = db.quota_class_get_defaults(context)
for resource in resources.values():
# 檢查cinder.quota.VolumeTypeQuotaEngine#resources的元素是否有跟class_quotas交集,
# 如果有,則用class_quotas的元素值覆蓋
if resource.name in class_quotas:
quotas[resource.name] = class_quotas[resource.name]
continue
# 檢查cinder.quota.VolumeTypeQuotaEngine#resources的元素是否有跟default_quotas交集,
# 如果有,則用default_quotas的元素值覆蓋
if defaults:
quotas[resource.name] = default_quotas.get(resource.name,
resource.default)
return quotas
總結(jié):
- 根據(jù)class_name在數(shù)據(jù)庫表quota_classes表查出記錄集class_quotas,再從class_name='default'在數(shù)據(jù)庫表quota_classes表查出記錄集default_quotas。
- 用class_quotas的元素值覆蓋cinder.quota.VolumeTypeQuotaEngine#resources的同名的元素值。
- 用default_quotas的元素值覆蓋cinder.quota.VolumeTypeQuotaEngine#resources的同名的元素值。
3. 根據(jù)quota-class列出quota列表:
cinder quota-show <tenant_id>
cinder.quota.DbQuotaDriver#get_project_quotas:
def get_project_quotas(self, context, resources, project_id,
quota_class=None, defaults=True,
usages=True):
"""Retrieve quotas for a project.
Given a list of resources, retrieve the quotas for the given
project.
:param context: The request context, for access checks.
:param resources: A dictionary of the registered resources.
:param project_id: The ID of the project to return quotas for.
:param quota_class: If project_id != context.project_id, the
quota class cannot be determined. This
parameter allows it to be specified. It
will be ignored if project_id ==
context.project_id.
:param defaults: If True, the quota class value (or the
default value, if there is no value from the
quota class) will be reported if there is no
specific value for the resource.
:param usages: If True, the current in_use, reserved and allocated
counts will also be returned.
"""
quotas = {}
# 在數(shù)據(jù)庫表 quotas 根據(jù) project_id 查詢字段 hard_limit
project_quotas = db.quota_get_all_by_project(context, project_id)
allocated_quotas = None
default_quotas = None
# API調(diào)用的時候傳入的usages=False
if usages:
# 根據(jù) project_id 查詢數(shù)據(jù)庫表 quota_usages ,獲得各quota resource的 in_use和 reserved
project_usages = db.quota_usage_get_all_by_project(context,
project_id)
# 在數(shù)據(jù)庫表 quotas 根據(jù) project_id 查詢字段 allocated
allocated_quotas = db.quota_allocated_get_all_by_project(
context, project_id)
allocated_quotas.pop('project_id')
# Get the quotas for the appropriate class. If the project ID
# matches the one in the context, we use the quota_class from
# the context, otherwise, we use the provided quota_class (if
# any)
# 如果context有quota_class,用context.quota_class查詢class_quotas
if project_id == context.project_id:
quota_class = context.quota_class
if quota_class:
class_quotas = db.quota_class_get_all_by_name(context, quota_class)
else:
class_quotas = {}
for resource in resources.values():
# Omit default/quota class values
# defaults = True
if not defaults and resource.name not in project_quotas:
continue
quota_val = project_quotas.get(resource.name)
# 如果 project_quota 是空,用 class_quota 的值賦值
if quota_val is None:
quota_val = class_quotas.get(resource.name)
# 如果 class_quota 賦的值也是空,用 default_quotas 的值賦值
if quota_val is None:
# Lazy load the default quotas
if default_quotas is None:
default_quotas = self.get_defaults(
context, resources, project_id)
quota_val = default_quotas[resource.name]
quotas[resource.name] = {'limit': quota_val}
# Include usages if desired. This is optional because one
# internal consumer of this interface wants to access the
# usages directly from inside a transaction.
if usages: # False
usage = project_usages.get(resource.name, {})
quotas[resource.name].update(
in_use=usage.get('in_use', 0),
reserved=usage.get('reserved', 0), )
if allocated_quotas: # None
quotas[resource.name].update(
allocated=allocated_quotas.get(resource.name, 0), )
return quotas
總結(jié):
- 用project_quotas的元素值覆蓋cinder.quota.VolumeTypeQuotaEngine#resources的同名的元素值
- 如果project_quotas的元素值為空,用class_quotas的元素值覆蓋cinder.quota.VolumeTypeQuotaEngine#resources的同名的元素值。
- 如果class_quotas的元素值為空,用default_quotas的元素值覆蓋cinder.quota.VolumeTypeQuotaEngine#resources的同名的元素值。
3. quota-usage 根據(jù)租戶列出配額的使用量
cinder quota-usage <tenant_id>
核心代碼跟 cinder quota-show 一樣,走的是cinder.quota.DbQuotaDriver#get_project_quotas
cinder.quota.DbQuotaDriver#get_project_quotas:
def get_project_quotas(self, context, resources, project_id,
quota_class=None, defaults=True,
usages=True):
quotas = {}
# 在數(shù)據(jù)庫表 quotas 根據(jù) project_id 查詢字段 hard_limit
project_quotas = db.quota_get_all_by_project(context, project_id)
allocated_quotas = None
default_quotas = None
# API調(diào)用的時候傳入的usages=True
if usages:
# 根據(jù) project_id 查詢數(shù)據(jù)庫表 quota_usages ,獲得各quota resource的 in_use和 reserved
project_usages = db.quota_usage_get_all_by_project(context,
project_id)
# 在數(shù)據(jù)庫表 quotas 根據(jù) project_id 查詢字段 allocated
allocated_quotas = db.quota_allocated_get_all_by_project(
context, project_id)
allocated_quotas.pop('project_id')
# Get the quotas for the appropriate class. If the project ID
# matches the one in the context, we use the quota_class from
# the context, otherwise, we use the provided quota_class (if
# any)
# 如果context有quota_class,用context.quota_class查詢class_quotas
if project_id == context.project_id:
quota_class = context.quota_class
if quota_class:
class_quotas = db.quota_class_get_all_by_name(context, quota_class)
else:
class_quotas = {}
for resource in resources.values():
# Omit default/quota class values
# defaults = True
if not defaults and resource.name not in project_quotas:
continue
quota_val = project_quotas.get(resource.name)
# 如果 project_quota 是空,用 class_quota 的值賦值
if quota_val is None:
quota_val = class_quotas.get(resource.name)
# 如果 class_quota 賦的值也是空,用 default_quotas 的值賦值
if quota_val is None:
# Lazy load the default quotas
if default_quotas is None:
default_quotas = self.get_defaults(
context, resources, project_id)
quota_val = default_quotas[resource.name]
quotas[resource.name] = {'limit': quota_val}
# Include usages if desired. This is optional because one
# internal consumer of this interface wants to access the
# usages directly from inside a transaction.
if usages: # True
# 設(shè)置usage
usage = project_usages.get(resource.name, {})
quotas[resource.name].update(
in_use=usage.get('in_use', 0),
reserved=usage.get('reserved', 0), )
if allocated_quotas: # None
quotas[resource.name].update(
allocated=allocated_quotas.get(resource.name, 0), )
return quotas