感谢朋友支持本博客,欢迎共同探讨交流,由于能力和时间有限,错误之处在所难免,欢迎指正!
如果转载,请保留作者信息。
博客地址:http://blog.csdn.net/gaoxingnengjisuan
邮箱地址:[email protected]
这里就不写什么开头语了,直接继续上一篇博客!
2.构建并返回用于建立卷的flow(继续)
我们先来关注,这里是如何实现Flow类的初始化的,来看语句:
api_flow = linear_flow.Flow(flow_name)
来看看任务流基类中都定义了哪些方法,这也有助于我们理解任务流的整体概念;
class Flow(object):
"""
The base abstract class of all flow implementations.
Flow抽象类;
"""
def __init__(self, name, parents=None, uuid=None):
super(Flow, self).__init__(name, parents, uuid)
# 获取回滚方法累加器类的实例化对象;
self._accumulator = utils.RollbackAccumulator()
self.results = {}
self._leftoff_at = None
# 所有要运行的任务集合;
self._runners = []
self._connected = False
# 确定所要使用的恢复策略;
self.resumer = None
def name(self)flow可读的非唯一的名称;
def uuid(self)flow唯一的标识;
def state(self)为flow提供了一个只读的状态信息;
def _change_state(self, context, new_state)改变目前的flow状态为新的状态new_state,并执行通知操作;
def add(self, task)添加一个给定的task到工作流中;
def add_many(self, tasks)添加给定的若干的task到工作流中;
def interrupt(self)尝试中断当前的flow和当前没有在flow中执行的task;
def reset(self)完全重置flow的内部的状态,并允许flow再次运行;
def soft_reset(self)部分地重置flow的内部状态,并允许flow从中止的状态再次运行;
def run(self, context, *args, **kwargs)工作流(workflow)的执行操作;
def rollback(self, context, cause)执行workflow和其父workflow的回滚操作;
再来看一下这里是如何实现task的添加的,比如语句:
api_flow.add(base.InjectTask(create_what, addons=[ACTION]))
所以来看看方法add的源码实现:
@decorators.locked def add(self, task): """ Adds a given task to this flow. 添加一个给定的task到flow; """ assert isinstance(task, collections.Callable) r = utils.Runner(task) r.runs_before = list(reversed(self._runners)) self._runners.append(r) self._reset_internals() return r.uuid所实现的功能就是添加一个给定的task到flow中,具体实现就是添加到self._runners中;其中每一个task都包装成了一个Runner类的对象;其中runs_before中存储了除了当前task的以前的task;这里的变量信息后面的flow运行实现过程中都要用到的;
我们继续来关注用于卷的建立的flow中都添加了哪些task?
2.1 api_flow.add(base.InjectTask(create_what, addons=[ACTION]))
这个类实现了注入字典信息create_what到flow中,create_what是建立卷所要遵守的规范信息(字典);
来看类InjectTask的具体代码:
class InjectTask(CinderTask): """ 这个类实现了注入字典信息到flow中; """ def __init__(self, inject_what, addons=None): super(InjectTask, self).__init__(addons=addons) self.provides.update(inject_what.keys()) self._inject = inject_what def __call__(self, context): return dict(self._inject)这个类以task的形式添加到flow中,从这个类的__call__方法我们可以看出,这个类实际上就是实现了对create_what的字典化处理;
在这个task类中并没有具体实现revert方法,因为这个task中不需要回滚操作;
2.2 api_flow.add(ExtractVolumeRequestTask(image_service, az_check_functor))
这个task类所实现的功能就是对输入的请求信息中的相关参数进行验证,并将这些参数转换成有效的集合,并返回这些经过验证和转换的参数,这些参数会应用于后续的task的实现过程之中的;
来看类ExtractVolumeRequestTask的具体代码:
class ExtractVolumeRequestTask(base.CinderTask): """ 实现提取并验证处理卷的请求信息任务类; 这个task的主要任务是提取和验证输入的值,这些输入的值将形成一个潜在的卷的请求信息; 并且实现根据一组条件对这些输入值进行验证,并将这些输入值转换成有效的集合; 并返回这些经过验证和转换的输入值,这些输入值将会应用于其他task中。 """ # image_service:获取默认的镜像服务类 # az_check_functor:验证availability_zone是否是可用的(即是否包含在可用zone的列表中); def __init__(self, image_service, az_check_functor=None): super(ExtractVolumeRequestTask, self).__init__(addons=[ACTION]) self.provides.update(['availability_zone', 'size', 'snapshot_id', 'source_volid', 'volume_type', 'volume_type_id', 'encryption_key_id']) self.requires.update(['availability_zone', 'image_id', 'metadata', 'size', 'snapshot', 'source_volume', 'volume_type', 'key_manager', 'backup_source_volume']) self.image_service = image_service self.az_check_functor = az_check_functor if not self.az_check_functor: self.az_check_functor = lambda az: True def __call__(self, context, size, snapshot, image_id, source_volume, availability_zone, volume_type, metadata, key_manager, backup_source_volume): """ 这个task的主要任务是提取和验证输入的值,这些输入的值将形成一个潜在的卷的请求信息; 并且实现根据一组条件对这些输入值进行验证,并将这些输入值转换成有效的集合; 并返回这些经过验证和转换的输入值,这些输入值将会应用于其他task中。 """ # 实现对所提供的输入参数进行是否为空的验证操作; utils.check_exclusive_options(snapshot=snapshot, imageRef=image_id, source_volume=source_volume) # 验证在指定的上下文环境中ACTION是有效的; # ACTION = 'volume:create'; policy.enforce_action(context, ACTION) # 从给定的快照中提取快照的id信息; snapshot_id = self._extract_snapshot(snapshot) # 从给定的卷中提取卷的id信息; source_volid = self._extract_source_volume(source_volume) # 提取并验证卷的大小; size = self._extract_size(size, source_volume, snapshot) # 检测镜像的存在性,并验证镜像的元数据中镜像大小的属性信息; self._check_image_metadata(context, image_id, size) # 提取并返回一个经过验证的可用的zone; availability_zone = self._extract_availability_zone(availability_zone, snapshot, source_volume) # 如果没有定义volume_type,则获取默认的卷的类型; if not volume_type and not source_volume and not snapshot: # 获取默认的卷的类型; volume_type = volume_types.get_default_volume_type() # 获取卷类型信息的id值; volume_type_id = self._get_volume_type_id(volume_type, source_volume, snapshot, backup_source_volume) # 获取加密密钥信息的id值; encryption_key_id = self._get_encryption_key_id(key_manager, context, volume_type_id, snapshot, source_volume, backup_source_volume) specs = {} # 根据给定的卷类型获取所有QOS功能相关的信息; if volume_type_id: qos_specs = volume_types.get_volume_type_qos_specs(volume_type_id) specs = qos_specs['qos_specs'] if not specs: specs = None # 检测卷的元数据属性是有效的; self._check_metadata_properties(metadata) # 返回经过验证的参数信息; # 将会用于其他task的操作; return { 'size': size, 'snapshot_id': snapshot_id, 'source_volid': source_volid, 'availability_zone': availability_zone, 'volume_type': volume_type, 'volume_type_id': volume_type_id, 'encryption_key_id': encryption_key_id, 'qos_specs': specs, }具体功能的实现过程可以看我在代码中的注释信息,这里不再赘述,这个task类中也是不需要回滚操作的;
2.3 api_flow.add(QuotaReserveTask())
在这个task类中主要实现了几个步骤的内容,也就是说:
根据给定的建立新卷的大小和类型,对资源配额信息进行检测,检测建立卷的可行性;
根据给定的建立新卷的大小和类型,实现对数据库中资源配额信息的更新;
保留建立新卷之前的相关资源配额信息,用于一旦卷建立的执行出现异常时,调用逆转回滚方法实现资源配额信息的恢复;
来看类QuotaReserveTask的具体代码:
class QuotaReserveTask(base.CinderTask): """ 根据给定的大小值和给定的卷类型信息实现保存单一的卷; """ def __init__(self): super(QuotaReserveTask, self).__init__(addons=[ACTION]) self.requires.update(['size', 'volume_type_id']) self.provides.update(['reservations']) def __call__(self, context, size, volume_type_id): """ 根据给定的大小值和给定的卷类型信息实现保存单一的卷; 根据给定的建立新卷的大小和类型,对资源配额信息进行检测,检测建立卷的可行性; 根据给定的建立新卷的大小和类型,实现对数据库中资源配额信息的更新; 保留建立新卷之前的相关资源配额信息,用于一旦卷建立的执行出现异常时,调用逆转回滚方法实现资源配额信息的恢复; """ try: reserve_opts = {'volumes': 1, 'gigabytes': size} # 添加卷的类型信息到reserve_opts,reserve_opts表示保留选项信息; QUOTAS.add_volume_type_opts(context, reserve_opts, volume_type_id) # 检测配额信息和并建立相应的资源配额预留资源; reservations = QUOTAS.reserve(context, **reserve_opts) return { 'reservations': reservations, } except exception.OverQuota as e: overs = e.kwargs['overs'] quotas = e.kwargs['quotas'] usages = e.kwargs['usages'] def _consumed(name): return (usages[name]['reserved'] + usages[name]['in_use']) def _is_over(name): for over in overs: if name in over: return True return False if _is_over('gigabytes'): msg = _("Quota exceeded for %(s_pid)s, tried to create " "%(s_size)sG volume (%(d_consu:med)dG " "of %(d_quota)dG already consumed)") LOG.warn(msg % {'s_pid': context.project_id, 's_size': size, 'd_consumed': _consumed('gigabytes'), 'd_quota': quotas['gigabytes']}) raise exception.VolumeSizeExceedsAvailableQuota() elif _is_over('volumes'): msg = _("Quota exceeded for %(s_pid)s, tried to create " "volume (%(d_consumed)d volumes " "already consumed)") LOG.warn(msg % {'s_pid': context.project_id, 'd_consumed': _consumed('volumes')}) allowed = quotas['volumes'] raise exception.VolumeLimitExceeded(allowed=quotas['volumes']) else: # If nothing was reraised, ensure we reraise the initial error raise def revert(self, context, result, cause): """ 根据result中的reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态; """ if not result: return if context.quota_committed: return reservations = result['reservations'] # 根据reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态; try: QUOTAS.rollback(context, reservations) except exception.CinderException: LOG.exception(_("Failed rolling back quota for" " %s reservations"), reservations)具体功能的实现过程可以看我在代码中的注释信息,这里不再赘述;在这个类中实现了revert方法,用于当建立卷的过程中出现异常时,实现根据result中的reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态;在方法revert中,具体调用了方法QUOTAS.rollback,我们简单来看一下代码的实现过程:
def rollback(self, context, reservations, project_id=None): """ 回调配额预留资源; 根据reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态; """ # 回调配额预留资源; # 根据reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态; try: self._driver.rollback(context, reservations, project_id=project_id) except Exception: LOG.exception(_("Failed to roll back reservations " "%s") % reservations) def rollback(self, context, reservations, project_id=None): """ 回调配额预留资源; 根据reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态; """ if project_id is None: project_id = context.project_id db.reservation_rollback(context, reservations, project_id=project_id) def reservation_rollback(context, reservations, project_id=None): """ 回调配额预留资源; 根据reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态; """ return IMPL.reservation_rollback(context, reservations, project_id=project_id) @require_context def reservation_rollback(context, reservations, project_id=None): """ 回调配额预留资源; 根据reservations保留的信息,恢复数据库中卷的配额信息到建立新卷之前的状态; """ session = get_session() with session.begin(): usages = _get_quota_usages(context, session, project_id) for reservation in _quota_reservations(session, context, reservations): usage = usages[reservation.resource] if reservation.delta >= 0: usage.reserved -= reservation.delta reservation.delete(session=session) for usage in usages.values(): usage.save(session=session) def _quota_reservations(session, context, reservations): """Return the relevant reservations.""" # Get the listed reservations return model_query(context, models.Reservation, read_deleted="no", session=session).\ filter(models.Reservation.uuid.in_(reservations)).\ with_lockmode('update').\ all()我们可以看到,到最后方法根据reservations在数据表Reservation中查询到对应的保留的资源配额信息,来实现恢复数据库中卷的资源配额信息到建立新卷之前的状态。
2.4 v_uuid = api_flow.add(EntryCreateTask(db))
这个task类所实现的功能是在数据库中为给定的要建立的卷来建立相关条目;
来看类EntryCreateTask的具体代码:
class EntryCreateTask(base.CinderTask): """ 在数据库中为给定的卷建立条目; 逆转操作:从数据库中删除volume_id建立的条目; """ def __init__(self, db): super(EntryCreateTask, self).__init__(addons=[ACTION]) self.db = db self.requires.update(['availability_zone', 'description', 'metadata', 'name', 'reservations', 'size', 'snapshot_id', 'source_volid', 'volume_type_id', 'encryption_key_id']) self.provides.update(['volume_properties', 'volume_id']) def __call__(self, context, **kwargs): """ 为给定的输入在数据库中建立数据条目,并返回详细信息; 从kwargs中获取卷的相关属性值,并根据卷的相关属性值在数据库中实现新卷的建立; """ volume_properties = { 'size': kwargs.pop('size'), 'user_id': context.user_id, 'project_id': context.project_id, 'status': 'creating', 'attach_status': 'detached', 'encryption_key_id': kwargs.pop('encryption_key_id'), # Rename these to the internal name. 'display_description': kwargs.pop('description'), 'display_name': kwargs.pop('name'), } volume_properties.update(kwargs) # 根据卷的相关属性volume_properties的值来建立新卷; volume = self.db.volume_create(context, volume_properties) return { 'volume_id': volume['id'], 'volume_properties': volume_properties, 'volume': volume, } def revert(self, context, result, cause): """ 删除指定的卷在数据库中的数据条目信息,实现逆转回滚操作; """ # 如果result为none,说明从来没有产生任何结果,因此不能删除任何数据信息; if not result: return # quota_committed说明不能执行回滚操作,说明此时卷已经建立; if context.quota_committed: return vol_id = result['volume_id'] # 删除指定的卷在数据库中的数据条目信息; try: self.db.volume_destroy(context.elevated(), vol_id) except exception.CinderException: LOG.exception(_("Failed destroying volume entry %s"), vol_id)这个task类实现的功能很简单,就是在数据库中为新建立的卷添加相关的条目信息,如果建立卷的操作出现异常,可以调用方法revert来实现删除卷在数据库中新建立的数据条目信息,从而实现逆转回滚操作;
2.5 api_flow.add(QuotaCommitTask())
这个task类所实现的功能就是,暂时假设卷的建立是成功的,此时需要改变资源配额信息,这里就是提交新的资源配额信息到数据库中;如果卷的建立出现异常,这个task中也实现了revert方法,这个方法就是根据新建立的卷的大小等信息,从改变后的资源配额预留信息中减去新建立卷的大小等信息,就可以实现恢复卷的资源配额预留信息到新卷的建立之前的状态;
来看类QuotaCommitTask的具体代码:
class QuotaCommitTask(base.CinderTask): """ 提交新的资源配额的预留信息到数据库中; """ def __init__(self): super(QuotaCommitTask, self).__init__(addons=[ACTION]) self.requires.update(['reservations', 'volume_properties']) def __call__(self, context, reservations, volume_properties): # 提交新的资源配额的预留信息到数据库中; QUOTAS.commit(context, reservations) context.quota_committed = True return {'volume_properties': volume_properties} def revert(self, context, result, cause): """ 如果建立卷出现异常,则执行回滚操作,实现恢复数据库中原有的资源配额预留信息; """ if not result: return volume = result['volume_properties'] try: reserve_opts = {'volumes': -1, 'gigabytes': -volume['size']} # 添加卷的类型选项到reserve_opts; QUOTAS.add_volume_type_opts(context, reserve_opts, volume['volume_type_id']) # 检测配额信息和并建立相应的资源配额预留资源; reservations = QUOTAS.reserve(context, project_id=context.project_id, **reserve_opts) # 提交资源配额的预留信息到数据库中; if reservations: QUOTAS.commit(context, reservations, project_id=context.project_id) except Exception: LOG.exception(_("Failed to update quota for deleting volume: %s"), volume['id'])上述功能的实现是很好理解的,也就是数据库相关的操作;
2.6 api_flow.add(OnFailureChangeStatusTask(db))
这个task类所实现的功能是当出现错误异常时,设置指定id的卷的状态为ERROR;
来看类OnFailureChangeStatusTask的具体代码:
class OnFailureChangeStatusTask(base.CinderTask): """ 这个task实现了当出现错误时,设置指定id的卷的状态为ERROR; """ def __init__(self, db): super(OnFailureChangeStatusTask, self).__init__(addons=[ACTION]) self.db = db self.requires.update(['volume_id']) self.optional.update(['volume_spec']) def __call__(self, context, volume_id, volume_spec=None): return { 'volume_id': volume_id, 'volume_spec': volume_spec, } def revert(self, context, result, cause): volume_spec = result.get('volume_spec') if not volume_spec: volume_spec = _find_result_spec(cause.flow) volume_id = result['volume_id'] _restore_source_status(context, self.db, volume_spec) _error_out_volume(context, self.db, volume_id, reason=cause.exc) LOG.error(_("Volume %s: create failed"), volume_id) exc_info = False if all(cause.exc_info): exc_info = cause.exc_info LOG.error(_('Unexpected build error:'), exc_info=exc_info)2.7 api_flow.add(VolumeCastTask(scheduler_rpcapi, volume_rpcapi, db))
这个task类所实现的功能就是根据具体需求调用相关的方法实现卷的建立。这里我先不做具体的分析,后面我会专门写一篇新的博客来对这里的实现进行详细的解析。当然,这个task类中就没有revert方法啦。
OK!上面我对添加到flow中的相关task进行了简单的解析,我们发现这些task类都继承自父类CinderTask,以及祖父类(哈哈哈)Task,我们来看看它们的相关实现:
class CinderTask(task.Task): """ 所有cinder任务的基类; """ def __init__(self, addons=None): # _make_task_name:获取任务类的名称; super(CinderTask, self).__init__(_make_task_name(self.__class__, addons))这里我们看一个输出实例,是添加第一个task之后进行测试输出的:
_make_task_name(self.__class__, addons) = cinder.volume.flows.base.InjectTask;volume:create
可见,实现的功能就是获取了当前任务类的类名和任务;
class Task(object): """ 这里定义了task的抽象的概念,可以用于恢复进程到没有执行操作的状态; """ __metaclass__ = abc.ABCMeta def __init__(self, name): self.name = name self.requires = set() self.optional = set() self.provides = set() self.version = (1, 0) def __str__(self): return "%s==%s" % (self.name, utils.join(self.version, with_what=".")) @abc.abstractmethod def __call__(self, context, *args, **kwargs): raise NotImplementedError() def revert(self, context, result, cause): pass
好了,具体如何实现用于建立新卷的flow的构建的过程已经简单的进行了解析,在下一篇博客中,我会进行如何执行构建好的flow的解析工作,也就是上一篇博客中的第3步骤!
好晚了啊,明天继续吧,加油!