Ceilometer API调用流程分析

https://xiaofandh12.github.io/Ceilometer-Meter-List-API 


Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

从这三种方式的描述来看,我们知道从Ceilometer获取数据的方式归根结底还是通过RESTful API来实现的。


Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

ceilometer命令来获取数据的全过程,也就是命令ceilometer --debug meter-list来获取监控项列表的过程,也即上图中蓝色部分。

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

下面这些基础知识在上面文档部分都可以找到相应的文档作为参考,理解了这些基础知识对理解ceilometer API的调用流程非常有帮助,我对这些也是处于一知半解的状态:)

  • pymongo --> 用来连接MongoDB的python包
  • oslo.config --> OpenStack通用库之一,用来解析命令行和配置文件中的配置选项
  • pecan --> 具体实现WSGI的一个python框架
  • wsme --> Web Service Made Easy,专门用于实现REST服务的typing库
  • argparse --> python标准库中推荐用来编写命令行程序的工具,python-ceilometerclient使用的正是argparse
  • stevedore --> 用于运行时动态载入代码的python包
  • setuptools and pbr
  • python decorator --> python装饰器相关知识,python内置装饰器:@staticmethod, @classmethod, @property
  • python built-in function --> python内置函数相关知识

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

REST的全称是Representational State Transfer(表征状态转移),是Roy Fielding在他的博士论文Architectural Styles and the Design of Network-based Software Architecture中提出的一种软件架构风格,而我们一般把满足这种设计风格的API称为RESTful API。

具体到使用Python来提供RESTful API时,又提出了一个WSGI的规范。

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

WSGI

WSGI的全称是Web Server Gateway Interface(Web服务器网关接口),是python语言中所定义的Web服务器和Web应用程序或框架之间的通用接口标准,它对应于Java中的Servelet。



Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

下面是一些学习资源:

在OpenStack的项目中实现RESTful API的Web框架主要有两种方式:

  • Paste + PasteDeploy + Routes + WebOb
  • Pecan

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

在OpenStack早期的项目中(Nova, Nutron, Keystone)都是使用的Paste + PasteDeploy + Routes + WebOb,这样的框架好处在于灵活性,但后来它的灵活性并没有抵消它的复杂性,于是在OpenStack后来的项目中也就不再使用这个框架了,但对于理解这些早期项目仍很有必要好好学习这种框架,尤其是这些早期项目都是OpenStack中最重要的一些项目。

Pecan是一个轻量级的Python的Web框架,OpenStack中的新项目全面的使用了此框架(如magnum),Pecan还可以和PasteDeploy一起使用,Ceilometer就是如此。

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

  • python-ceilometerclient

    ceilometer --debug meter-list

    curl GET http://172.31.2.51:8777/v2/meters

  • Python Application的创建

  • HTTP请求的解析

    RootController

    V2Controller


Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

  • 实际去获取数据的两个函数

    pecan.request.storage_conn.get_meters的执行

    Meter.from_db_model的执行

python-ceilometerclient为我们提供了ceilometer命令,通过该命令我们可以很方面的调用ceilometer提供的API。 在搭建好的环境中执行ceilometer --debug meter-list,我们除了可以得到数据库中meter列表外,还可以看到如下的curl命令: curl -i -X GET -H 'X-Auth-Token: 968b7f741482416899f477a8e3aafba7' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'User-Agent: python-ceilometerclient' http://172.31.2.51:8777/v2/meters ,在这条命令中比较关键的是:

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

http://172.31.2.51:8777/v2/meters ,下面我们就分析ceilometer收到这个HTTP请求后是如何解析,如何使用Python Application去查询MongoDB数据库的

讲解过程会先给出一段文字解说,然后再给出相应的源码,看文字解说一定要阅读相应的源码方能更好理解。

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

需要使用Python Application来接收HTTP请求,因此需要先创建Python Application。下面我们介绍使用Pecan+PasetDeploy创建Python Application的过程。

在配置文件api_pasete.ini中我们可以看到,PasteDeploy会调用ceilometer.api.app:app_factory。

  • ../etc/ceilometer/api_paste.ini: [app:api-server]

        
        
    1. [pipeline:main]
    2. pipeline = request_id authtoken api-server
    3. [app:api-server]
    4. paste.app_factory = ceilometer.api.app:app_factory

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

下面我们再到../ceilometer/api/app.py中查看app_factory函数,可以看到app_factory函数返回的是一个VersionSelectorApplication类,VersionSelectorApplication是为了创建不同版本的Python Application,其中v1版本是不能用的,如果是v2版本则会去调用setup_app函数。

setup_app函数就是真正创建Python Application的函数,在此函数中最重要的就是调用了pecan.make_app函数,在此函数中最重要的就是指定了解析HTTP Request的RootController,是通过pecan_config.app.root参数指定的;另外一个重要的地方就是hoooks.DBHook,在这里面初始化了数据库的链接,关于这一点后面再做介绍。

  • ../ceilometer/api/app.py: app_factory --> VersionSelectorApplication --> setup_app

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard



  •     
        
    1. ...
    2. def setup_app(pecan_config=None, extra_hooks=None):
    3. app_hooks = [hooks.ConfigHook(),hooks.DBHook(),hooks.NotifierHook(),hooks.TranslationHook()]
    4. if extra_hooks:
    5. app_hooks.extend(extra_hooks)
    6. if not pecan_config:
    7. pecan_config = get_pecan_config()
    8. pecan.configuration.set_config(dict(pecan_config), overwrite=True)
    9. pecan_debug = CONF.api.pecan_debug
    10. if CONF.api.workers and CONF.api.works != 1 and pecan_debug:
    11. pecan_debug = False
    12. LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, the value is overrided with False'))
    13. app = pecan.make_app(
    14. pecan_config.app.root,
    15. debug=pecan_debug,
    16. force_cannonical=getattr(pecan_config.app, 'force_canonical',True)
    17. hooks=app_hooks,
    18. wrap_app=middleware.ParsableErrorMiddleware,
    19. guess_content_type_from_ext=False
    20. )
    21. return app
    22. ...
    23. class VersionSelectorApplication(object):
    24. def __init__(self):
    25. pc = get_pecan_config()
    26. def not_found(environ, start_response):
    27. start_response('404 Not Found', [])
    28. return []
    29. self.v1 = not_found
    30. self.v2 = setup_app(pecan_config=pc)
    31. def __call__(self, environ, start_response):
    32. if environ['PATH_INFO'].startswith('/v1/'):
    33. return self.v1(environ, start_response)
    34. return self.v2(environ, start_response)
    35. ...
    36. def app_factory(global_config, **local_conf):
    37. return VersionSelectorApplication()

在上面我们提到解析HTTP请求的RootController是通过pecan_config.app.root参数指定的,阅读代码可知pecan_config.app.root就是config.py中指定的,也就是ceilometer.api.controllers.root.RootController。这样Python Application就创建完成了,并且指定好了解析HTTP Request的RootController,也就是说/v2/meters/从RootController开始处理。

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

  •     
        
    1. ...
    2. def setup_app(pecan_config=None, extra_hooks=None):
    3. app_hooks = [hooks.ConfigHook(),hooks.DBHook(),hooks.NotifierHook(),hooks.TranslationHook()]
    4. if extra_hooks:
    5. app_hooks.extend(extra_hooks)
    6. if not pecan_config:
    7. pecan_config = get_pecan_config()
    8. pecan.configuration.set_config(dict(pecan_config), overwrite=True)
    9. pecan_debug = CONF.api.pecan_debug
    10. if CONF.api.workers and CONF.api.works != 1 and pecan_debug:
    11. pecan_debug = False
    12. LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, the value is overrided with False'))
    13. app = pecan.make_app(
    14. pecan_config.app.root,
    15. debug=pecan_debug,
    16. force_cannonical=getattr(pecan_config.app, 'force_canonical',True)
    17. hooks=app_hooks,
    18. wrap_app=middleware.ParsableErrorMiddleware,
    19. guess_content_type_from_ext=False
    20. )
    21. return app
    22. ...
    23. class VersionSelectorApplication(object):
    24. def __init__(self):
    25. pc = get_pecan_config()
    26. def not_found(environ, start_response):
    27. start_response('404 Not Found', [])
    28. return []
    29. self.v1 = not_found
    30. self.v2 = setup_app(pecan_config=pc)
    31. def __call__(self, environ, start_response):
    32. if environ['PATH_INFO'].startswith('/v1/'):
    33. return self.v1(environ, start_response)
    34. return self.v2(environ, start_response)
    35. ...
    36. def app_factory(global_config, **local_conf):
    37. return VersionSelectorApplication()

在上面我们提到解析HTTP请求的RootController是通过pecan_config.app.root参数指定的,阅读代码可知pecan_config.app.root就是config.py中指定的,也就是ceilometer.api.controllers.root.RootController。这样Python Application就创建完成了,并且指定好了解析HTTP Request的RootController,也就是说/v2/meters/从RootController开始处理。


  • ../ceilometer/api/config.py: app

        
        
    1. server = {
    2. 'port': '8777',
    3. 'host': '0.0.0.0'
    4. }
    5. app = {
    6. 'root': 'ceilometer.api.controllers.root.RootController',
    7. 'modules': ['ceilometer.api'],
    8. }

在RootController我们可以看到它先创建了一个类属性:v2,于是所有的以/v2开头的HTTP Request都会由V2Controller来处理,/v2/meters/当然也不例外。

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

  •     
        
    1. ...
    2. def setup_app(pecan_config=None, extra_hooks=None):
    3. app_hooks = [hooks.ConfigHook(),hooks.DBHook(),hooks.NotifierHook(),hooks.TranslationHook()]
    4. if extra_hooks:
    5. app_hooks.extend(extra_hooks)
    6. if not pecan_config:
    7. pecan_config = get_pecan_config()
    8. pecan.configuration.set_config(dict(pecan_config), overwrite=True)
    9. pecan_debug = CONF.api.pecan_debug
    10. if CONF.api.workers and CONF.api.works != 1 and pecan_debug:
    11. pecan_debug = False
    12. LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, the value is overrided with False'))
    13. app = pecan.make_app(
    14. pecan_config.app.root,
    15. debug=pecan_debug,
    16. force_cannonical=getattr(pecan_config.app, 'force_canonical',True)
    17. hooks=app_hooks,
    18. wrap_app=middleware.ParsableErrorMiddleware,
    19. guess_content_type_from_ext=False
    20. )
    21. return app
    22. ...
    23. class VersionSelectorApplication(object):
    24. def __init__(self):
    25. pc = get_pecan_config()
    26. def not_found(environ, start_response):
    27. start_response('404 Not Found', [])
    28. return []
    29. self.v1 = not_found
    30. self.v2 = setup_app(pecan_config=pc)
    31. def __call__(self, environ, start_response):
    32. if environ['PATH_INFO'].startswith('/v1/'):
    33. return self.v1(environ, start_response)
    34. return self.v2(environ, start_response)
    35. ...
    36. def app_factory(global_config, **local_conf):
    37. return VersionSelectorApplication()

在上面我们提到解析HTTP请求的RootController是通过pecan_config.app.root参数指定的,阅读代码可知pecan_config.app.root就是config.py中指定的,也就是ceilometer.api.controllers.root.RootController。这样Python Application就创建完成了,并且指定好了解析HTTP Request的RootController,也就是说/v2/meters/从RootController开始处理。


  • ../ceilometer/api/controllers/root.py: RootController

        
        
    1. ...
    2. class RootController(object):
    3. def __init__(self):
    4. self.v2 = v2.V2Controller()
    5. def index(self):
    6. base_url = pecan.request.application_url
    7. available = [{'tag': 'v2', 'date': '2013-02-13T00:00:00Z',}]
    8. collected = [version_descriptor(base_url, v['tag'], v['date']) for v in available]
    9. versions = {'versions': {'values': collected}}
    10. return versions

下面我们再去看V2Controller,我们可以看到它有类属性:event_types,events,capabilities,也就是说/v2/event_types会由EventTypesController来处理,/v2/events/会由EventsController来处理,/v2/capabilities/会由CapabilitiesController来处理,那/v2/meters/呢?往下看,

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

  •     
        
    1. ...
    2. def setup_app(pecan_config=None, extra_hooks=None):
    3. app_hooks = [hooks.ConfigHook(),hooks.DBHook(),hooks.NotifierHook(),hooks.TranslationHook()]
    4. if extra_hooks:
    5. app_hooks.extend(extra_hooks)
    6. if not pecan_config:
    7. pecan_config = get_pecan_config()
    8. pecan.configuration.set_config(dict(pecan_config), overwrite=True)
    9. pecan_debug = CONF.api.pecan_debug
    10. if CONF.api.workers and CONF.api.works != 1 and pecan_debug:
    11. pecan_debug = False
    12. LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, the value is overrided with False'))
    13. app = pecan.make_app(
    14. pecan_config.app.root,
    15. debug=pecan_debug,
    16. force_cannonical=getattr(pecan_config.app, 'force_canonical',True)
    17. hooks=app_hooks,
    18. wrap_app=middleware.ParsableErrorMiddleware,
    19. guess_content_type_from_ext=False
    20. )
    21. return app
    22. ...
    23. class VersionSelectorApplication(object):
    24. def __init__(self):
    25. pc = get_pecan_config()
    26. def not_found(environ, start_response):
    27. start_response('404 Not Found', [])
    28. return []
    29. self.v1 = not_found
    30. self.v2 = setup_app(pecan_config=pc)
    31. def __call__(self, environ, start_response):
    32. if environ['PATH_INFO'].startswith('/v1/'):
    33. return self.v1(environ, start_response)
    34. return self.v2(environ, start_response)
    35. ...
    36. def app_factory(global_config, **local_conf):
    37. return VersionSelectorApplication()

在上面我们提到解析HTTP请求的RootController是通过pecan_config.app.root参数指定的,阅读代码可知pecan_config.app.root就是config.py中指定的,也就是ceilometer.api.controllers.root.RootController。这样Python Application就创建完成了,并且指定好了解析HTTP Request的RootController,也就是说/v2/meters/从RootController开始处理。


_lookup函数中我们可以看到/v2/meters/会由MetersController来处理,关于__lookup可以参看官方文档。

  • ../ceilometer/api/controllers/v2/root.py: V2Controller

        
        
    1. ...
    2. class V2Controller(object):
    3. event_types = events.EventTypesController()
    4. events = events.EventsController()
    5. capabilities = capabilities.CapabilitiesController()
    6. def __init__(self):
    7. self._gnocchi_is_enabled = None
    8. self._aodh_is_enabled = None
    9. self._aodh_url = None
    10. ...
    11. def __lookup(self, kind, *remainder):
    12. if (kind in ['meters', 'resources', 'samples'] and self.gnocchi_is_enabled):
    13. gnocchi_abort()
    14. elif kind == 'meters':
    15. return meters.MetersController(), remainder
    16. elif kind == 'resources':
    17. return resources.ResourcesController(), remainder
    18. elif kind == 'samples':
    19. return samples.SamplesController(), remainder
    20. elif kind == 'query':
    21. return QueryController(gnocchi_is_enabled=self.gnocchi_is_enabled, aodh_url=self.aodh_url,), remainder
    22. elif kind == 'alarms' and self.aodu_url:
    23. aodh_redirect(self.aodh_url)
    24. elif kind == 'alarms':
    25. return alarms.AlarmsController(), reaminder
    26. else:
    27. pecan.abort(404)

终于到了MetersController,这里就是最终处理HTTP请求:/v2/meters/的地方,貌似要结束了的样子。在这里最重要的就是最后那一行代码:return [Meter.from_db_model(m) for m in pecan.request.storage_conn.get_meters(limit=limit,**kwargs)],在这行代码中有两个重要的函数调用:pecan.request.storage_conn.get_meters 以及 Meter.from_db_model。所以还没结束,下面还得分析这两个函数是如何执行的。

Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard

  •     
        
    1. ...
    2. def setup_app(pecan_config=None, extra_hooks=None):
    3. app_hooks = [hooks.ConfigHook(),hooks.DBHook(),hooks.NotifierHook(),hooks.TranslationHook()]
    4. if extra_hooks:
    5. app_hooks.extend(extra_hooks)
    6. if not pecan_config:
    7. pecan_config = get_pecan_config()
    8. pecan.configuration.set_config(dict(pecan_config), overwrite=True)
    9. pecan_debug = CONF.api.pecan_debug
    10. if CONF.api.workers and CONF.api.works != 1 and pecan_debug:
    11. pecan_debug = False
    12. LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, the value is overrided with False'))
    13. app = pecan.make_app(
    14. pecan_config.app.root,
    15. debug=pecan_debug,
    16. force_cannonical=getattr(pecan_config.app, 'force_canonical',True)
    17. hooks=app_hooks,
    18. wrap_app=middleware.ParsableErrorMiddleware,
    19. guess_content_type_from_ext=False
    20. )
    21. return app
    22. ...
    23. class VersionSelectorApplication(object):
    24. def __init__(self):
    25. pc = get_pecan_config()
    26. def not_found(environ, start_response):
    27. start_response('404 Not Found', [])
    28. return []
    29. self.v1 = not_found
    30. self.v2 = setup_app(pecan_config=pc)
    31. def __call__(self, environ, start_response):
    32. if environ['PATH_INFO'].startswith('/v1/'):
    33. return self.v1(environ, start_response)
    34. return self.v2(environ, start_response)
    35. ...
    36. def app_factory(global_config, **local_conf):
    37. return VersionSelectorApplication()

在上面我们提到解析HTTP请求的RootController是通过pecan_config.app.root参数指定的,阅读代码可知pecan_config.app.root就是config.py中指定的,也就是ceilometer.api.controllers.root.RootController。这样Python Application就创建完成了,并且指定好了解析HTTP Request的RootController,也就是说/v2/meters/从RootController开始处理。


  • ../ceilometer/api/controllers/v2/meters.py: MetersController

        
        
    1. ...
    2. class MetersController(rest.RestController):
    3. @pecan.expose()
    4. def __lookup(self, meter_name, *remainder):
    5. return MeterController(meter_name), remainder
    6. @wsme_pecan.wsexpose([Meter], [base.Query], int)
    7. def get_all(self, q=None, limit=None):
    8. rbac.enforce('get_meters', pecan.request)
    9. q = q or []
    10. limit = v2_utils.enforce_limit(limit)
    11. kwargs = v2_utils.query_to_kwargs(q, pecan.request.storage_conn.get_meters,['limit'],allow_timestamps=False)
    12. return [Meter.from_db_model(m) for m in pecan.request.storage_conn.get_meters(limit=limit,**kwargs)]

要分析pecan.request.storage_conn.get_meters(limit=limit, **kwargs)的执行过程我们分两步:一是storage_conn是如何获得的,二是get_meters是如何执行的 。

stroage_conn的获得过程

storage_conn指的是数据库的链接,在DBHook.__init__中可以看出它是通过调用函数get_connection来获得的,而函数get_connection又调用了函数storage.get_connection_from_config。

  • ../ceilometer/api/hooks.py: DBHook.__init__ --> get_connection

        
        
    1. ...
    2. class DBHook(hooks.PecanHook):
    3. def __init__(self):
    4. self.storage_connection = DBHook.get_connection('metering')
    5. self.event_storage_connection = DBHook.get_connection('event')
    6. self.alarm_stroage_connection = DBHook.get_connection('alarm')
    7. if (not self.storage_connection and not self.event_storage_connection and not self.alarm_storage_connection):
    8. raise Exception("Api failed to start. Failed to connect to database, purpose: %s" % ', '.join(['metering', 'event', 'alarm'])
    9. def before(self, state):
    10. state.request.storage_conn = self.storage_connection
    11. state.request.event_storage_conn = self.event_storage_connection
    12. state.request.alarm_storage_conn = self.alarm_storage_connection
    13. @staticmethod
    14. def get_connection(purpose):
    15. try:
    16. return storage.get_connection_from_config(cfg.CONF, purpose)
    17. except Exception as err:
    18. params = {"purpose": purpose, "err": err}
    19. LOG.exception(_LE("Failed to connect to db, purpose %(purpose)s retry later:%(err)s") % params)
    20. ...

下面再看storage.get_connection_from_config函数是如何执行的,函数storage.get_connection_from_config调用了函数storage.get_connection,而在函数storage.get_connection中重要的是: mgr = driver.DriverManger(namespace, engine_name),其中的driver为:from stevedore import driver,namespace为:ceilometer.meterings.storage,engine_name为:mongodb。于是stevedore会到setup.cfg中查找相应的设置。

  • ../ceilometer/storage/__init__.py: get_connection_from_config --> get_connection

        
        
    1. ...
    2. def get_connection_from_config(conf, purpose='metering'):
    3. retries = conf.database.max_retries
    4. @retrying.retry(wait_fixed=conf.database.retry_interval*1000,stop_max_attempt_number=retries if retries >= 0 else None)
    5. def __inner():
    6. if conf.database_connection:
    7. conf.set_override('connection', conf.database_connection,group='databse')
    8. namespace = 'ceilometer.%s.storage' % purpose
    9. url = (getattr(conf.database, '%s_connection' % purpose) or conf.database.connection)
    10. return get_connection(url, namespace)
    11. return __inner()
    12. def get_connection(url, namespace):
    13. connection_scheme = urlparse.urlparse(url).scheme
    14. engine_name = connection_scheme.split('+')[0]
    15. if engine_name == 'db2':
    16. import warnings
    17. warnings.simplefilter("always")
    18. import debtcollector
    19. debtcollector.deprecate("The DB2nosql driver is no longer supperted", version="Liberty", removal_version="N*-cycle")
    20. LOG.debug('looking for %(name)r driver in %(namespace)r', {'name': engine_name, 'namespace':namespace})
    21. mgr = driver.DriverManager(namespace, engine_name)
    22. return mgr.driver(url)

在../setup.cfg中能看到storage_conn会被赋值为ceilometer.storage.impl_monogodb:Connection,然后就会调用该Connection的__init__函数进行初始化。

  • ../setup.cfg: mongodb = ceilometer.storage.impl_mongodb:Connection

        
        
    1. ceilometer.metering.storage =
    2. log = ceilometer.storage.impl_log:Connection
    3. mongodb = ceilometer.storage.impl_mongodb:Connection
    4. mysql = ceilometer.storage.impl_sqlalchemy:Connection
    5. postgresql = ceilometer.storage.impl_sqlalchemy:Connection
    6. sqlite = ceilometer.storage.impl_sqlalchemy:Connection
    7. hbase = ceilometer.storage.impl_hbase:Connection
    8. db2 = ceilometer.storage.impl_db2:Connection

在ceilometer.storage.imple_mongodb.Connection类的初始化函数中会初始化数据库的连接,没有相应collection的情况下新建相应collection,设置ttl等。

  • ../ceilometer/storage/impl_mongodb.py: Connection.__init__

        
        
    1. class Connection(pymongo_base.Connection):
    2. ...
    3. def __init__(self, url):
    4. self.conn = self.CONNECTION_POOL.connect(url)
    5. self.version = self.conn.server_info()['versionArray']
    6. if self.version < pymongo_utils.MINMUM_COMPATIBLE_MONGODB_VERSION:
    7. raise storage.StorageBadVersion("Need at least MongoDB %s" % pymongo_utils.MINIMUM_COMPATIBLE_MONGODB_VERSION)
    8. connection_options = pymongo.uri_parser.parse_uri(url)
    9. self.db = getattr(self.conn, connection_options['database'])
    10. if connection_options.get('username'):
    11. self.db.authenticate(connection_options['username'],connection_options['password'])
    12. self.upgrade()
    13. @staticmethod
    14. def update_ttl(ttl, ttl_index_name, index_field, coll):
    15. indexes = coll.index_information()
    16. if ttl <= 0:
    17. if ttl_index_name in indexes:
    18. coll.drop_index(ttl_index_name)
    19. return
    20. if ttl_index_name in indexes:
    21. return coll.database.command('collMod', coll.name, index={'keyPattern': {index_field: pymongo.ASCENDING),'expireAfterSeconds': ttl})
    22. coll.create_index([(index_field, pymongo.ASCENDING)],expireAfterSeconds=ttl,name=ttl_index_name)
    23. def upgrade(self):
    24. if 'resource' not in self.db.conn.collection_names():
    25. self.db.conn.create_collection('resource')
    26. if 'meter' not in self.db.conn.collection_names():
    27. self.db.conn.create_collection('meter')
    28. name_qualifier = dict(user_id='', project_id='project_')
    29. backgroud = dict(user_id=False, project_id=True)
    30. for primary in ['user_id', 'project_id']:
    31. name = 'meter_%sidx' % name_qualifier[primary]
    32. self.db.meter.create_index([
    33. ('resource_id', pymongo.ASCENDING),
    34. (primary, pymongo.ASCENDING),
    35. ('counter_name', pymongo.ASCENDING),
    36. ('timestamp', pymongo.ASCENDING),
    37. ], name=name, background=background[primary])
    38. self.db.meter.create_index([('timestamp', pymongo.DESCENDING)], name='timestamp_idx')
    39. self.db.resource.create_index([('user_id',pymongo.DESCENDING),('project_id', pymongo.DESCENDING),('last_sample_timestamp',pymongo.DESCENDING)],name='resource_user_project_timestamp',)
    40. self.db.resource.create_index([('last_sample_timestamp',pymongo.DESCENDING)],name='last_sample_timestamp_idx')
    41. ttl = cfg.CONF.database.metering_time_to_live
    42. self.update_ttl(ttl, 'meter_ttl', 'timestamp', self.db.meter)
    43. self.update_ttl(ttl, 'resource_ttl', 'last_sample_timestamp',self.db.resource)
    44. ...
get_meters的执行过程

这里有一个类的继承关系:object <-- storage.base.Connection <-- storage.pymongo_base.Connection <-- storage.impl_mongodb.Connection,关于storage.impl_mongodb.Connection需要讲的都在上面提到了。

在storage.base.Connection中给出了一些函数定义,但都没有具体的实现。

  • ../ceilometer/storage/base.py

        
        
    1. class Connection(object):
    2. ...
    3. def __init__(self, url):
    4. pass
    5. @staticmethod
    6. def upgrade():
    7. ...

在storage.pymongo_base.Connection中给出了get_meters的定义,在这里我们就可以真正的看到查询数据库的语句了: self.db.resource.find(q),下面我们还要解释一下语句models.Meter。

  • ../ceilometer/storage/pymongo_base.py: Connection.get_meters

        
        
    1. ...
    2. class Connection(base.Connection):
    3. ...
    4. def get_meters(self, user=None, project=None, resource=None, source=None, metaquery=None, limit=None):
    5. if limit == 0:
    6. return
    7. metaquery = pymongo_utils.improve_keys(metaquery, metaquery=True) or {}
    8. q = {}
    9. if user is not None
    10. q['user_id'] = user
    11. if project is not None:
    12. q['project_id'] = project
    13. if resource is not None:
    14. q['_id'] = resource
    15. if source is not None:
    16. q['source'] = source
    17. q.update(metaquery)
    18. count = 0
    19. for r in self.db.resource.find(q):
    20. for r_meter in r['meter']:
    21. if limit and count >= limt:
    22. return
    23. else:
    24. count += 1
    25. yield models.Meter(
    26. name=r_meter['counter_name'],
    27. type=r_meter['counter_type']
    28. unit=r_meter.get('counter_unit', ''),
    29. resource_id=r['_id'],
    30. porject_id=r['project_id'],
    31. source=r['source'],
    32. user_id=r['user_id'],
    33. )
    34. ...

这里又有一个类的继承关系:object <-- storage.base.Model <-- storage.models.Meter。 这里其实就是把查询到的值和键做个对应的设置。

  • ../ceilometer/storage/base.py: Model

        
        
    1. class Model(object):
    2. def __init__(self, **kwds):
    3. self.fields = list(kwds)
    4. for k, v in six.iteritems(kwds):
    5. setattr(self, k, v)
    6. def as_dict(self):
    7. d = {}
    8. for f in self.fields:
    9. v = getattr(self, f)
    10. if isinstance(v, Model):
    11. v = v.as_dice()
    12. elif isinstance(v, list) and v and isinstance(v[0], Model):
    13. v = [sub.as_dict() for sub in v]
    14. d[f] = v
    15. return d
    16. ...
  • ../ceilometer/storage/models.py

        
        
    1. ...
    2. class Meter(base.Model):
    3. def __init__(self, name, type, unit, resource_id, project_id, source, user_id):
    4. base.Model.__init__(self,
    5. name=name,
    6. type=type,
    7. unit=unit,
    8. resource_id=resource_id,
    9. project_id=project_id,
    10. source=source,
    11. user_id=user_id,
    12. )
    13. ...

pecan.request.storage_conn.get_meters的执行过程讲解完毕,终于要到最后一部分了:Meter.from_db_model(m)。

这里有个类的继承关系:wsme.types.Base <-- wsme.types.DynamicBase <-- api.controllers.v2.base.Base <-- api.v2.meters.Meter

后面api.v2.meters.Meter的初始化函数调用了wsme.types.Base.__init__函数。 对于wsme和pecan有的时候看源码比看文档来得快而准确。

  • wsme.types

        
        
    1. ...
    2. class Base(six.with_metaclass(BaseMeta)):
    3. def __init__(self, **kw):
    4. for key, value in kw.items()
    5. if hasattr(self, key):
    6. setattr(self, key, value)
    7. ...
    8. class DynamicBase(Base):
    9. @classmethod
    10. def add_attributes(cls, **attrs):
    11. for n,t in attrs.items():
    12. setattr(cls, n, t)
    13. cls.__registry__.reregister(cls)
    14. ...

函数Meter.from_db_model定义在ceilometer.api.controllers.v2.base:Base中,函数Meter.from_db_model是把返回的ceilometer.storage.models.Meter实例, 进一步做一些简单的处理。

  • ../ceilometer/api/controllers/v2/base.py

        
        
    1. ...
    2. class Base(wtypes.DynamicBase):
    3. @classmethod
    4. def from_db_model(cls, m):
    5. return cls(**(m.as_dict()))
    6. @classmethod
    7. def from_db_and_links(cls, m, links):
    8. return cls(links=links, **(m.as_dict()))
    9. def as_dict(self, db_model):
    10. valid_keys = inspect.getargspec(db_model.__init__)[0]
    11. if 'self' in valid_keys:
    12. valid_keys.remove('self')
    13. return self.as_dict_from_keys(valid_keys)
    14. def as_dict_from_keys(self, keys):
    15. return dict((k, getattr(self, keys))for k in keys if hasattr(self, k) and getattr(self, k) != wsme.Unset)
    16. ...

Meter是在ceilometer.api.v2.meters中定义的,Meter中定义它的的类属性:name, type, unit, resource_id, project_id, user_id, source, meter_id;Meter中还定义了初始化函数__init__,该初始化函数主要是构造meter_id和调用wsme.types.Base的初始化函数__init__

  • ../ceilometer/api/v2/meters.py

        
        
    1. ...
    2. class Meter(base.Base):
    3. name = wtypes.text
    4. "The unique name for the meter"
    5. type = wtypes.Enum(str, *sample.TYPES)
    6. "The meter type (see :ref:`measurements`)"
    7. unit = wtypes.text
    8. "The unit of measure"
    9. resource_id = wtypes.text
    10. "The ID of the :class:`Resource` for which the measurements are taken"
    11. project_id = wtypes.text
    12. "The ID of the project or tenant that owns the resource"
    13. user_id = wtypes.text
    14. "The ID of the user who last triggered an update to the resource"
    15. source = wtypes.text
    16. "The ID of the source that identifies where the meter comes from"
    17. meter_id = wtypes.text
    18. "The unique identifier for the meter"
    19. def __init__(self, **kwargs):
    20. meter_id = '%s+%s' % (kwargs['resource_id'], kwargs['name'])
    21. meter_id = base64.b64encode(meter_id.encode('urg-8'))
    22. kwargs['meter_id'] = meter_id
    23. super(Meter, self).__init__(**kwargs)
    24. ...
    25. ...

还是有很多细节问题没有弄清,需要继续研究




Article About

在文章Ceilometer数据采集原理中曾提到(这也是来自于官方文档的说法)三种从Ceilometer获取数据的方式:

  • with the RESTful API
  • with the command line interface
  • with Metering tab on an openstack dashboard
  •     
        
    1. ...
    2. def setup_app(pecan_config=None, extra_hooks=None):
    3. app_hooks = [hooks.ConfigHook(),hooks.DBHook(),hooks.NotifierHook(),hooks.TranslationHook()]
    4. if extra_hooks:
    5. app_hooks.extend(extra_hooks)
    6. if not pecan_config:
    7. pecan_config = get_pecan_config()
    8. pecan.configuration.set_config(dict(pecan_config), overwrite=True)
    9. pecan_debug = CONF.api.pecan_debug
    10. if CONF.api.workers and CONF.api.works != 1 and pecan_debug:
    11. pecan_debug = False
    12. LOG.warning(_LW('pecan_debug cannot be enabled, if workers is > 1, the value is overrided with False'))
    13. app = pecan.make_app(
    14. pecan_config.app.root,
    15. debug=pecan_debug,
    16. force_cannonical=getattr(pecan_config.app, 'force_canonical',True)
    17. hooks=app_hooks,
    18. wrap_app=middleware.ParsableErrorMiddleware,
    19. guess_content_type_from_ext=False
    20. )
    21. return app
    22. ...
    23. class VersionSelectorApplication(object):
    24. def __init__(self):
    25. pc = get_pecan_config()
    26. def not_found(environ, start_response):
    27. start_response('404 Not Found', [])
    28. return []
    29. self.v1 = not_found
    30. self.v2 = setup_app(pecan_config=pc)
    31. def __call__(self, environ, start_response):
    32. if environ['PATH_INFO'].startswith('/v1/'):
    33. return self.v1(environ, start_response)
    34. return self.v2(environ, start_response)
    35. ...
    36. def app_factory(global_config, **local_conf):
    37. return VersionSelectorApplication()

在上面我们提到解析HTTP请求的RootController是通过pecan_config.app.root参数指定的,阅读代码可知pecan_config.app.root就是config.py中指定的,也就是ceilometer.api.controllers.root.RootController。这样Python Application就创建完成了,并且指定好了解析HTTP Request的RootController,也就是说/v2/meters/从RootController开始处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值