感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
邮箱地址:dong.liu@siat.ac.cn
继续看方法quota_reserve:
def quota_reserve(context, resources, quotas, deltas, expire, until_refresh, max_age, project_id=None):
elevated = context.elevated()
# 获取db_session的session;
# get_session:返回一个SQLAlchemy session,若没有定义,则新建一个SQLAlchemy session;
session = get_session()
with session.begin():
# 获取context中的project_id;
if project_id is None:
project_id = context.project_id
# 从quota_usages表中获得当前工程的各种资源的使用情况;
usages = _get_quota_usages(context, session, project_id)
# Handle usage refresh
work = set(deltas.keys())
while work:
# 任意地从集合work中取出一个元素;
resource = work.pop()
refresh = False
if resource not in usages:
usages[resource] = _quota_usage_create(elevated,project_id,resource,0, 0,until_refresh or None,session=session)
refresh = True
elif usages[resource].in_use < 0:
refresh = True
elif usages[resource].until_refresh is not None:
usages[resource].until_refresh -= 1
if usages[resource].until_refresh <= 0:
refresh = True
elif max_age and (usages[resource].updated_at - timeutils.utcnow()).seconds >= max_age:
refresh = True
# 执行更新(同步)usage;
if refresh:
# Grab the sync routine
# 获取同步方法,_sync_*(),这些方法定义在quota模块中,不同的资源有不同的同步方法;
sync = resources[resource].sync
# 查询当前正在使用的实时的资源的数据信息;
updates = sync(elevated, project_id, session)
for res, in_use in updates.items():
# 如果试实时使用的资源没有在usages中,那么把它添加进去;
if res not in usages:
usages[res] = _quota_usage_create(elevated,project_id,res,0, 0,until_refresh or None,session=session)
# 更新usage中的in_use数据信息;
usages[res].in_use = in_use
# 更新usage中的until_refresh数据信息;
usages[res].until_refresh = until_refresh or None
work.discard(res)
# 检测资源数据中in_use加上delta之后,可能小于0的情况;
unders = [resource for resource, delta in deltas.items()
if delta < 0 and
delta + usages[resource].in_use < 0]
# Now, let's check the quotas
# 检测这个resource的hard_limit是否小于in_use+resourced+delta之和;
overs = [resource for resource, delta in deltas.items()
if quotas[resource] >= 0 and delta >= 0 and
quotas[resource] < delta + usages[resource].total]
# Create the reservations
# 如果没有超过的话,更新reservations表,再次更新usages
if not overs:
reservations = []
for resource, delta in deltas.items():
# str(uuid.uuid4()):随机获取uuid值,是唯一的;
# usages[resource]:更新过的当前资源在quota_usages表中的使用情况;
reservation = reservation_create(elevated,str(uuid.uuid4()),usages[resource],project_id,resource, delta, expire,session=session)
reservations.append(reservation.uuid)
# 更新usages中的reserved值,加上变化值;
if delta > 0:
usages[resource].reserved += delta
# Apply updates to the usages table
# 更新quota_usages表;
for usage_ref in usages.values():
usage_ref.save(session=session)
if unders:
LOG.warning(_("Change will make usage less than 0 for the following resources: %(unders)s") % locals())
if overs:
usages = dict((k, dict(in_use=v['in_use'], reserved=v['reserved']))
for k, v in usages.items())
raise exception.OverQuota(overs=sorted(overs), quotas=quotas,usages=usages)
return reservations
继续来看代码,看看是如何执行refresh更新(同步)操作的;
# 执行更新(同步)usage;
if refresh:
# Grab the sync routine
# 获取同步方法,_sync_*(),这些方法定义在quota模块中,不同的资源有不同的同步方法;
sync = resources[resource].sync
# 查询当前正在使用的实时的资源的数据信息;
updates = sync(elevated, project_id, session)
for res, in_use in updates.items():
# 如果试实时使用的资源没有在usages中,那么把它添加进去;
if res not in usages:
usages[res] = _quota_usage_create(elevated,project_id,res,0, 0,until_refresh or None,session=session)
# 更新usage中的in_use数据信息;
usages[res].in_use = in_use
# 更新usage中的until_refresh数据信息;
usages[res].until_refresh = until_refresh or None
work.discard(res)
回顾一下,在quota.py中resources的定义:
resources = [
ReservableResource('instances', _sync_instances, 'quota_instances'),
ReservableResource('cores', _sync_instances, 'quota_cores'),
ReservableResource('ram', _sync_instances, 'quota_ram'),
ReservableResource('floating_ips', _sync_floating_ips, 'quota_floating_ips'),
ReservableResource('fixed_ips', _sync_fixed_ips, 'quota_fixed_ips'),
ReservableResource('security_groups', _sync_security_groups, 'quota_security_groups'),
AbsoluteResource('metadata_items', 'quota_metadata_items'),
AbsoluteResource('injected_files', 'quota_injected_files'),
AbsoluteResource('injected_file_content_bytes', 'quota_injected_file_content_bytes'),
AbsoluteResource('injected_file_path_bytes', 'quota_injected_file_path_bytes'),
CountableResource('security_group_rules', db.security_group_rule_count_by_group, 'quota_security_group_rules'),
CountableResource('key_pairs', db.key_pair_count_by_user, 'quota_key_pairs'),
]
这些_sync_*就是同步方法,也就是这里通过语句:sync = resources[resource].sync来获取的方法。通过这些方法能够实时查询到工程当前所使用资源的情况,也就能够用于刷新(同步)资源使用信息的操作。
阅读代码我们可以知道:
deltas.keys() = ['instances', 'ram', 'cores']
work = set(deltas.keys())
resource = work.pop()
sync = resources[resource].sync
所以sync只能从'instances', 'ram', 'cores'三个资源中选取对应的同步方法,而这三个资源对应的同步方法都是_sync_instances,进而我们来看看_sync_instances这个方法:
def _sync_instances(context, project_id, session):
return dict(zip(('instances', 'cores', 'ram'),
db.instance_data_get_for_project(
context, project_id, session=session)))
再看方法instance_data_get_for_project:
def instance_data_get_for_project(context, project_id, session=None):
result = model_query(context,
func.count(models.Instance.id),
func.sum(models.Instance.vcpus),
func.sum(models.Instance.memory_mb),
base_model=models.Instance,
session=session).\
filter_by(project_id=project_id).\
first()
# NOTE(vish): convert None to 0
return (result[0] or 0, result[1] or 0, result[2] or 0)
在来看看类Instance,这个类定义了虚拟机实例信息的数据库表;
class Instance(BASE, NovaBase):
"""
表示一个来宾VM的类;
构建数据表instances的数据结构;
"""
# 表名instances;
__tablename__ = 'instances'
injected_files = []
# 定义数据表instances的字段id;
id = Column(Integer, primary_key=True, autoincrement=True)
@property
def name(self):
try:
base_name = CONF.instance_name_template % self.id
except TypeError:
# Support templates like "uuid-%(uuid)s", etc.
info = {}
# NOTE(russellb): Don't use self.iteritems() here, as it will
# result in infinite recursion on the name property.
for column in iter(object_mapper(self).columns):
key = column.name
# prevent recursion if someone specifies %(name)s
# %(name)s will not be valid.
if key == 'name':
continue
info[key] = self[key]
try:
base_name = CONF.instance_name_template % info
except KeyError:
base_name = self.uuid
return base_name
def _extra_keys(self):
return ['name']
user_id = Column(String(255)) # 定义数据表instances的字段user_id;
project_id = Column(String(255)) # 定义数据表instances的字段project_id;
image_ref = Column(String(255)) # 定义数据表instances的字段image_ref;
kernel_id = Column(String(255)) # 定义数据表instances的字段kernel_id;
ramdisk_id = Column(String(255)) # 定义数据表instances的字段ramdisk_id;
hostname = Column(String(255)) # 定义数据表instances的字段hostname;
launch_index = Column(Integer) # 定义数据表instances的字段lauch_index;
key_name = Column(String(255)) # 定义数据表instances的字段key_name;
key_data = Column(Text) # 定义数据表instances的字段key_data;
power_state = Column(Integer) # 定义数据表instances的字段power_state;
vm_state = Column(String(255)) # 定义数据表instances的字段vm_state;
task_state = Column(String(255)) # 定义数据表instances的字段task_state;
memory_mb = Column(Integer) # 定义数据表instances的字段memory_mb;
vcpus = Column(Integer) # 定义数据表instances的字段vcpus;
root_gb = Column(Integer) # 定义数据表instances的字段root_gb;
ephemeral_gb = Column(Integer) # 定义数据表instances的字段ephemeral_gb;
# 定义数据表instances的字段host;
# 注意这个字段不是与hostname相关的,而是指nova的节点;
host = Column(String(255)) # , ForeignKey('hosts.id'))
# 定义数据表instances的字段node;
# 识别实例所在的"ComputeNode" ;
# 这与ComputeNode.hypervisor_hostname.
node = Column(String(255))
instance_type_id = Column(Integer) # 定义数据表instances的字段instance_type_id;
user_data = Column(Text) # 定义数据表instances的字段user_data;
reservation_id = Column(String(255)) # 定义数据表instances的字段reservation_id;
scheduled_at = Column(DateTime) # 定义数据表instances的字段scheduled_at;
launched_at = Column(DateTime) # 定义数据表instances的字段launched_at;
terminated_at = Column(DateTime) # 定义数据表instances的字段terminated_at;
availability_zone = Column(String(255)) # 定义数据表instances的字段availability_zone;
# User editable field for display in user-facing UIs
display_name = Column(String(255)) # 定义数据表instances的字段display_name;
display_description = Column(String(255)) # 定义数据表instances的字段display_description;
# To remember on which host an instance booted.
# An instance may have moved to another host by live migration.
# 记住在哪个主机上启动了一个实例;
# 实例可能已经通过实时迁移到了另一个主机上;
launched_on = Column(Text) # 定义数据表instances的字段launched_on;
locked = Column(Boolean) # 定义数据表instances的字段locked;
os_type = Column(String(255)) # 定义数据表instances的字段os_type;
architecture = Column(String(255)) # 定义数据表instances的字段architecture;
vm_mode = Column(String(255)) # 定义数据表instances的字段vm_mode;
uuid = Column(String(36)) # 定义数据表instances的字段uuid;
root_device_name = Column(String(255)) # 定义数据表instances的字段root_device_name;
default_ephemeral_device = Column(String(255), nullable=True) # 定义数据表instances的字段default_ephemeral_device;
default_swap_device = Column(String(255), nullable=True) # 定义数据表instances的字段default_swap_device;
config_drive = Column(String(255)) # 定义数据表instances的字段config_drive;
# User editable field meant to represent what ip should be used to connect to the instance
# 用户可编辑字段,代表着哪一个IP能够被用来连接实例;
access_ip_v4 = Column(types.IPAddress()) # 定义数据表instances的字段access_ip_v4;
access_ip_v6 = Column(types.IPAddress()) # 定义数据表instances的字段access_ip_v6;
auto_disk_config = Column(Boolean()) # 定义数据表instances的字段auto_disk_config;
progress = Column(Integer) # 定义数据表instances的字段progress;
# EC2 instance_initiated_shutdown_terminate
# True: -> 'terminate'
# False: -> 'stop'
# Note(maoy): currently Nova will always stop instead of terminate
# no matter what the flag says. So we set the default to False.
# 当前的nova应该是stop而不是terminate,所以我们默认设置标志为False;
shutdown_terminate = Column(Boolean(), default=False, nullable=False) # 定义数据表instances的字段shutdown_terminate;
# EC2 disable_api_termination
disable_terminate = Column(Boolean(), default=False, nullable=False) # 定义数据表instances的字段disable_terminate;
# OpenStack compute cell name. This will only be set at the top of
# the cells tree and it'll be a full cell name such as 'api!hop1!hop2'
# OpenStack的计算单元名;
cell_name = Column(String(255)) # 定义数据表instances的字段cell_name;
由此我们可以知道,语句updates = sync(elevated, project_id, session)实现的就是定位到资源的同步方法,实时查询所需要更新的资源使用信息;比如这里调用的同步方法就是方法_sync_instances,它实现了从数据库中查询到匹配的数据表'instances',进而获取其id、vcpus和memory_mb三种资源的实时的使用情况,分别赋值给'instances','cores'和 'ram',以字典的形式返回给updates;
这里我调试运行了一下,得到了获取的实时资源使用数据信息:
当建立了10个实例的时候:
updates = {instances:10, ram:20480, cores:10}
删除了1个实例之后:
updates = {instances:9, ram:18432, cores:9}
所以在这里我们就能够理解,为什么应用语句:resource = work.pop() 来随机获取work集合中的一个资源,原因就是针对'instances', 'ram', 'cores'无论哪一个资源,都会调用同样的同步方法_sync_instances来获取实时的工程资源使用情况,都会得到这三个资源的实时的使用数据信息。
继续看下面的代码:
for res, in_use in updates.items():
# 如果试实时使用的资源没有在usages中,那么把它添加进去;
if res not in usages:
usages[res] = _quota_usage_create(elevated,project_id,res,0, 0,until_refresh or None,session=session)
# 更新usage中的in_use数据信息;
usages[res].in_use = in_use
# 更新usage中的until_refresh数据信息;
usages[res].until_refresh = until_refresh or None
work.discard(res)
这段代码完成的功能就是如果获取的实时使用的资源没有在usages中,那么就把它添加进去,并更新它的in_use和until_refresh数据信息。
继续看代码:
unders = [resource for resource, delta in deltas.items()
if delta < 0 and delta + usages[resource].in_use < 0]
检测资源数据中in_use加上delta之后,可能小于0的情况,如果存在这种情况,后面将会引发异常;
overs = [resource for resource, delta in deltas.items()
if quotas[resource] >= 0 and delta >= 0 and
quotas[resource] < delta + usages[resource].total]
检测这个resource的hard_limit是否小于in_use+resourced+delta之和,如果over值为真,说明delta + usages[resource].total的值已经大于系统限定的资源配额的数值,后面将会引发异常;
继续看下一段代码:
if not overs:
reservations = []
for resource, delta in deltas.items():
# str(uuid.uuid4()):随机获取uuid值,是唯一的;
# usages[resource]:更新过的当前资源在quota_usages表中的使用情况;
reservation = reservation_create(elevated,str(uuid.uuid4()),usages[resource],project_id,resource, delta, expire,session=session)
reservations.append(reservation.uuid)
# 更新usages中的reserved值,加上变化值;
if delta > 0:
usages[resource].reserved += delta
# 更新quota_usages表;
for usage_ref in usages.values():
usage_ref.save(session=session)
这段代码是当没有发生资源配额超出限制值的时候,建立更新reservations数据表,并再次更新usages。reservations表记录的是每次分配的各种资源的变化值,即delta保存的值,它和quota_usages通过usage_id外键关联。这个表中的记录是不更新的,每次分配都会相应的增加记录。
最后返回reservations。
至此,方法quota_reserve解析完成,这里引用一片博文http://blog.csdn.net/hackerain/article/details/8223125中的话对这个方法进行总结:
这个函数执行的过程主要分为以下几步:
(1)同步
为什么要同步呢?这里同步的是什么呢?因为在为工程分配资源时,可能有各种特殊情况导致quota_usages表中记录的in_use不准确,需要得到当前实际使用的资源的情况,更新一下in_use,得到真实的资源使用情况。这里的特殊情况有一下4个:
1)当前申请的资源没有在quota_usages表中记录
2)当前申请的资源在quota_usages表中的in_use值小于0
3)当前申请的资源的until_refresh值不为空,减1之后小于0
4)当前申请的资源在quota_usages表中更新的时间减去现在的时间大于max_age
如果符合这四种情况之一,就执行同步。同步时,是调用当前资源的_sync_*()函数,去相关的表中查询实时的使用情况,比如_sync_instances()就是去instances表中查询出当前工程的instances数量,vcpu之和,ram之和,然后以字典的方式返回。然后根据这些实时的数据,更新in_use值。
(2)检查
根据各种资源的配额,和变化的情况(delta),来检查两种极端的情况:under和over。under是检查delta为负数的情况,即执行了删除等操作,使delta为负,in_use减少,导致in_use值可能小于0。over是检查delta为正数时的情况,in_use+delta就有可能大于最大的限额了。这里只对over的情况进行处理,即它只关心上限,不关心下限。如果没有over的话,就有下面的第(3)步了。如果over的话,就直接进行第(4)步。
(3)向reservations表中增加记录,记录申请资源的delta值。
(4)把in_use值写入到quota_usages表中保存。(不论under还是over都执行)
(5)如果over,即超出最大限额,则报出OverQuota异常。
至此,方法_check_num_instances_quota也全部解析结束,这个方法实现了根据配额资源限制所要建立实例的数目。方法返回两个参数max_count和reservations,返回值max_count表示建立实例的最大数目,返回值reservations表示建立的预定(分配)的资源的UUID的列表。
下一篇博文中,我们将回到方法_validate_and_provision_instance,前面分析我们知道,这个方法主要完成的是对建立实例所调用的方法_create_instance中输入的各个参数进行检查验证,其中完成了一个非常重要的任务,即资源的配额管理。