在openstack上测试批量创建thin lun时,发现部分创建失败,分析代码发现,根本原因是主机的能力是周期性刷新(默认是1分钟),而不是即时刷新。现在来详细分析openstack的资源刷新机制,主要以cinder为例。
在cinder源代码中, 可以在很多函数上看到@periodic_task
这样的修饰符, 我们根据_publish_service_capabilities这个函数知道这是cinder的定时任务,可以让这个函数周期性执行, 但是不了解这个修饰符产生作用的原理和用法, 这里将进行详细分析。
关于元类:就像对象是类的实例一样,类是它的元类的实例。
在manager.py中更新函数的定义如下:@periodic_task.periodic_task说明该函数被装饰器装饰。被此装饰器修饰后,函数新增了这个属性:f._periodic_task = True。
@periodic_task.periodic_task def _publish_service_capabilities(self, context): """Pass data back to the scheduler at a periodic interval.""" if self.last_capabilities: LOG.debug('Notifying Schedulers of capabilities ...') self.scheduler_rpcapi.update_service_capabilities( context, self.service_name, self.host, self.last_capabilities)
def periodic_task(*args, **kwargs): """Decorator to indicate that a method is a periodic task. This decorator can be used in two ways: 1. Without arguments '@periodic_task', this will be run on the default interval of 60 seconds. 2. With arguments: @periodic_task(spacing=N [, run_immediately=[True|False]] [, name=[None|"string"]) this will be run on approximately every N seconds. If this number is negative the periodic task will be disabled. If the run_immediately argument is provided and has a value of 'True', the first run of the task will be shortly after task scheduler starts. If run_immediately is omitted or set to 'False', the first time the task runs will be approximately N seconds after the task scheduler starts. If name is not provided, __name__ of function is used. """ def decorator(f): # Test for old style invocation if 'ticks_between_runs' in kwargs: raise InvalidPeriodicTaskArg(arg='ticks_between_runs') # Control if run at all f._periodic_task = True f._periodic_external_ok = kwargs.pop('external_process_ok', False) f._periodic_enabled = kwargs.pop('enabled', True) f._periodic_name = kwargs.pop('name', f.__name__) # Control frequency f._periodic_spacing = kwargs.pop('spacing', 0) f._periodic_immediate = kwargs.pop('run_immediately', False) if f._periodic_immediate: f._periodic_last_run = None else: f._periodic_last_run = now() return f # NOTE(sirp): The `if` is necessary to allow the decorator to be used with # and without parenthesis. # # In the 'with-parenthesis' case (with kwargs present), this function needs # to return a decorator function since the interpreter will invoke it like: # # periodic_task(*args, **kwargs)(f) # # In the 'without-parenthesis' case, the original function will be passed # in as the first argument, like: # # periodic_task(f) if kwargs: return decorator else: return decorator(args[0])
periodic_task.py中的类_PeriodicTasksMeta是PeriodicTasks类的元类,在PeriodicTasks初始化时会调用_PeriodicTasksMeta的init方法,这三个语句将有_periodic_task这个属性的函数对象加入到Manager._periodic_tasks中。
for value in cls.__dict__.values(): if getattr(value, '_periodic_task', False): cls._add_periodic_task(value)
class _PeriodicTasksMeta(type): def _add_periodic_task(cls, task): """Add a periodic task to the list of periodic tasks. The task should already be decorated by @periodic_task. :return: whether task was actually enabled """ name = task._periodic_name if task._periodic_spacing < 0: LOG.info(_LI('Skipping periodic task %(task)s because ' 'its interval is negative'), {'task': name}) return False if not task._periodic_enabled: LOG.info(_LI('Skipping periodic task %(task)s because ' 'it is disabled'), {'task': name}) return False # A periodic spacing of zero indicates that this task should # be run on the default interval to avoid running too # frequently. if task._periodic_spacing == 0: task._periodic_spacing = DEFAULT_INTERVAL cls._periodic_tasks.append((name, task)) cls._periodic_spacing[name] = task._periodic_spacing return True def __init__(cls, names, bases, dict_): """Metaclass that allows us to collect decorated periodic tasks.""" super(_PeriodicTasksMeta, cls).__init__(names, bases, dict_) # NOTE(sirp): if the attribute is not present then we must be the base # class, so, go ahead an initialize it. If the attribute is present, # then we're a subclass so make a copy of it so we don't step on our # parent's toes. try: cls._periodic_tasks = cls._periodic_tasks[:] except AttributeError: cls._periodic_tasks = [] try: cls._periodic_spacing = cls._periodic_spacing.copy() except AttributeError: cls._periodic_spacing = {} for value in cls.__dict__.values(): if getattr(value, '_periodic_task', False): cls._add_periodic_task(value)
Manager的这个类有一个函数periodic_tasks,阅读函数代码可以发现这个函数实际上的作用就是把所有在self._periodic_tasks
中的函数对象(也就是所有用@periodic_task
修饰符修饰过的函数)全部遍历并调用一遍. 如果能定期调用这个函数的话, 就能实现类似linux中crontab的定时任务功能.
class Manager(base.Base, PeriodicTasks): # Set RPC API version to 1.0 by default. RPC_API_VERSION = '1.0' target = messaging.Target(version=RPC_API_VERSION) def __init__(self, host=None, db_driver=None): if not host: host = CONF.host self.host = host self.additional_endpoints = [] super(Manager, self).__init__(db_driver) def periodic_tasks(self, context, raise_on_error=False): """Tasks to be run at a periodic interval.""" return self.run_periodic_tasks(context, raise_on_error=raise_on_error)
@six.add_metaclass(_PeriodicTasksMeta) class PeriodicTasks(object): def __init__(self, conf): super(PeriodicTasks, self).__init__() self.conf = conf self.conf.register_opts(_options.periodic_opts) self._periodic_last_run = {} for name, task in self._periodic_tasks: self._periodic_last_run[name] = task._periodic_last_run def add_periodic_task(self, task): """Add a periodic task to the list of periodic tasks. The task should already be decorated by @periodic_task. """ if self.__class__._add_periodic_task(task): self._periodic_last_run[task._periodic_name] = ( task._periodic_last_run) def run_periodic_tasks(self, context, raise_on_error=False): """Tasks to be run at a periodic interval.""" idle_for = DEFAULT_INTERVAL for task_name, task in self._periodic_tasks: if (task._periodic_external_ok and not self.conf.run_external_periodic_tasks): continue full_task_name = '.'.join([self.__class__.__name__, task_name]) spacing = self._periodic_spacing[task_name] last_run = self._periodic_last_run[task_name] # Check if due, if not skip idle_for = min(idle_for, spacing) if last_run is not None: delta = last_run + spacing - now() if delta > 0: idle_for = min(idle_for, delta) continue LOG.debug("Running periodic task %(full_task_name)s", {"full_task_name": full_task_name}) self._periodic_last_run[task_name] = _nearest_boundary( last_run, spacing) try: task(self, context) except Exception: if raise_on_error: raise LOG.exception(_LE("Error during %(full_task_name)s"), {"full_task_name": full_task_name}) time.sleep(0) return idle_for
service.py 中server类RPC server服务启动时,调用start函数,该函数中会周期性的调用Manager的periodic_tasks函数。即这样实现了host的资源刷新。
if self.periodic_interval: if self.periodic_fuzzy_delay: initial_delay = random.randint(0, self.periodic_fuzzy_delay) else: initial_delay = None periodic = loopingcall.FixedIntervalLoopingCall( self.periodic_tasks) periodic.start(interval=self.periodic_interval, initial_delay=initial_delay) self.timers.append(periodic)
def periodic_tasks(self, raise_on_error=False): """Tasks to be run at a periodic interval.""" ctxt = context.get_admin_context() self.manager.periodic_tasks(ctxt, raise_on_error=raise_on_error)