Ceilometer 20、prometheus-openstack-exporter源码分析

1 引言


prometheus-openstack-exporter是用于提供openstack各组件服务状态信息给prometheus的项目。
该项目主要分为两部分:
1) 默认每隔30秒向openstack各组件发送请求,获取各个组件服务的状态并写入到缓存中。
2) 开启一个tcp服务器,prometheus默认每隔1分钟向该tcp服务器发送请求,该服务器会将缓存中的各组件服务状态信息
   以字符串的形式返回给prometheus。

2 源码分析


2.1 项目总入口

总入口是prometheus-openstack-exporter/exporter/main.py
具体代码如下:
if __name__ == '__main__':
    parser = argparse.ArgumentParser(
        usage=__doc__, description='Prometheus OpenStack exporter',
        formatter_class=argparse.RawTextHelpFormatter)
    parser.add_argument('--config-file', nargs='?',
                        help='Configuration file path',
                        type=argparse.FileType('r'),
                        required=False)
    args = parser.parse_args()
    config = {}
    if args.config_file:
        config = yaml.safe_load(args.config_file.read())

    os_keystone_url = config.get('OS_AUTH_URL', os.getenv('OS_AUTH_URL'))
    os_password = config.get('OS_PASSWORD', os.getenv('OS_PASSWORD'))
    os_tenant_name = config.get('OS_PROJECT_NAME',
                                os.getenv('OS_PROJECT_NAME'))
    os_username = config.get('OS_USERNAME', os.getenv('OS_USERNAME'))
    os_user_domain = config.get('OS_USER_DOMAIN_ID',
                                os.getenv('OS_USER_DOMAIN_ID'))
    os_region = config.get('OS_REGION_NAME', os.getenv('OS_REGION_NAME'))
    os_timeout = config.get('TIMEOUT_SECONDS',
                            int(os.getenv('TIMEOUT_SECONDS', 10)))
    os_polling_interval = config.get(
        'OS_POLLING_INTERVAL', int(os.getenv('OS_POLLING_INTERVAL', 900)))
    os_retries = config.get('OS_RETRIES', int(os.getenv('OS_RETRIES', 1)))
    os_cpu_overcomit_ratio = config.get('OS_CPU_OC_RATIO',
                                        float(os.getenv('OS_CPU_OC_RATIO', 1)))
    os_ram_overcomit_ratio = config.get('OS_RAM_OC_RATIO',
                                        float(os.getenv('OS_RAM_OC_RATIO', 1)))

    osclient = OSClient(os_keystone_url, os_password,
                        os_tenant_name, os_username, os_user_domain,
                        os_region, os_timeout, os_retries)
    oscache = OSCache(os_polling_interval, os_region)
    collectors.append(oscache)

    check_os_api = CheckOSApi(oscache, osclient)
    collectors.append(check_os_api)
    neutron_agent_stats = NeutronAgentStats(oscache, osclient)
    collectors.append(neutron_agent_stats)
    cinder_service_stats = CinderServiceStats(oscache, osclient)
    collectors.append(cinder_service_stats)
    nova_service_stats = NovaServiceStats(oscache, osclient)
    collectors.append(nova_service_stats)
    hypervisor_stats = HypervisorStats(
        oscache, osclient, os_cpu_overcomit_ratio, os_ram_overcomit_ratio)
    collectors.append(hypervisor_stats)

    oscache.start()

    listen_port = config.get('LISTEN_PORT',
                             int(os.getenv('LISTEN_PORT', 19103)))
    server = ForkingHTTPServer(('', listen_port), handler)
    server.serve_forever()

分析:
1) prometheus-openstack-exporter逻辑流程
步骤1:    开启一个线程默认每隔30秒轮询:
    步骤1.1: openstack各组件api服务的状态,
    步骤1.2: 获取nova/neutron/cinder组件下面在每个host上具体服务的状态
    步骤1.3: 获取nova的hypervisor信息
    获取上述的信息,分别建立<缓存名称,缓存结果>存放在字典中
步骤2:    开启一个TCPServer服务器,监听9103端口,
    prometheus默认每隔60秒向prometheus-openstack-exporter服务发送请求,
    该请求会被上述TCPServer服务器处理。请求处理见步骤3
步骤3:    遍历缓存结果,获取每个缓存名称对应的结果列表(是数组),
    步骤3.1: 对该缓存结果列表遍历,对每个缓存结果(是字典),
        调用prometheus_client的方法设置监控项名称,监控项对应的值,以及标签列表
    步骤3.2: 最后调用prometheus_client.generate_latest(registry)方法产生最终结果(是一个字符串)并返回
    对上述每个缓存结果产生的字符串进行拼接,最终做为一个大字符串返回给prometheus。

2) 重要代码分析之OSClient
上述代码中有重要一行内容如下:
    osclient = OSClient(
        os_keystone_url,
        os_password,
        os_tenant_name,
        os_username,
        os_user_domain,
        os_region,
        os_timeout,
        os_retries)
具体参见2.2的分析

3) 重要代码分析之CheckOSApi
上述代码中有重要一行内容如下:
check_os_api = CheckOSApi(oscache, osclient)
具体参见2.3的分析

4) 重要代码分析之OSCache
上述代码中有重要一行内容如下:
oscache = OSCache(os_polling_interval, os_region)
具体参见2.4的分析

5) 重要代码分析之ForkingHTTPServer
上述代码中有重要内容如下:
    server = ForkingHTTPServer(('', listen_port), handler)
    server.serve_forever()
具体参见2.6的分析

2.2 OSClient分析


在exporter/osclient.py中有如下内容:
class OSClient(object):
    """ Base class for querying the OpenStack API endpoints.

    It uses the Keystone service catalog to discover the API endpoints.
    """
    EXPIRATION_TOKEN_DELTA = datetime.timedelta(0, 30)
    states = {'up': 1, 'down': 0, 'disabled': 2}

    def __init__(
            self,
            keystone_url,
            password,
            tenant_name,
            username,
            user_domain,
            region,
            timeout,
            retries):
        self.keystone_url = keystone_url
        self.password = password
        self.tenant_name = tenant_name
        self.username = username
        self.user_domain = user_domain
        self.region = region
        self.timeout = timeout
        self.retries = retries
        self.token = None
        self.valid_until = None
        self.session = requests.Session()
        self.session.mount(
            'http://', requests.adapters.HTTPAdapter(max_retries=retries))
        self.session.mount(
            'https://', requests.adapters.HTTPAdapter(max_retries=retries))
        self._service_catalog = []

    def get_token(self):
        self.clear_token()
        data = json.dumps({
            "auth": {
                "identity": {
                    "methods": ["password"],
                    "password": {
                        "user": {
                            "name": self.username,
                            "domain": {"id": self.user_domain},
                            "password": self.password
                        }
                    }
                },
                "scope": {
                    "project": {
                        "name": self.tenant_name,
                        "domain": {"id": self.user_domain}
                    }
                }
            }
        })
        logger.info("Trying to get token from '%s'" % self.keystone_url)
        r = self.make_request('post',
                              '%s/auth/tokens' % self.keystone_url, data=data,
                              token_required=False)
        if not r:
            logger.error(
                "Cannot get a valid token from {}".format(
                    self.keystone_url))

        if r.status_code < 200 or r.status_code > 299:
            logger.error(
                "{} responded with code {}".format(
                    self.keystone_url,
                    r.status_code))

        data = r.json()
        self.token = r.headers.get("X-Subject-Token")
        self.tenant_id = data['token']['project']['id']
        self.valid_until = dateutil.parser.parse(
            data['token']['expires_at']) - self.EXPIRATION_TOKEN_DELTA
        self._service_catalog = []
        for item in data['token']['catalog']:
            internalURL = None
            publicURL = None
            adminURL = None
            for endpoint in item['endpoints']:
                if endpoint['region'] == self.region or self.region is None:
                    if endpoint['interface'] == 'internal':
                        internalURL = endpoint['url']
                    elif endpoint['interface'] == 'public':
                        publicURL = endpoint['url']
                    elif endpoint['interface'] == 'admin':
                        adminURL = endpoint['url']

            if internalURL is None and publicURL is None:
                logger.warning(
                    "Service '{}' skipped because no URL can be found".format(
                        item['name']))
                continue
            self._service_catalog.append({
                'name': item['name'],
                'region': self.region,
                'service_type': item['type'],
                'url': internalURL if internalURL is not None else publicURL,
                'admin_url': adminURL,
            })

        logger.debug("Got token '%s'" % self.token)
        return self.token

    @property
    def service_catalog(self):
        if not self._service_catalog:
            self.get_token()
        return self._service_catalog
    ......
分析:
1) OSClient
作用是:实际就是获取keystone的endpoints列表,token等
成员变量:
主要包含_service_catalog(是一个列表),用来存储catalog信息
成员函数:
1 __init__: 用keystone_url, password等参数进行赋值
2 get_token(self): 获取token信息,解析出catalog信息,遍历每个服务的catalog,记录各服务的admin,public,internal等url信息
    处理过程: 
    步骤1: 向http://keystone-api.openstack.svc.cluster.local:80/v3/auth/tokens 发送请求,
      解析出返回结果中 token字典中的catalog数组
    步骤2: 遍历catalog数组,对每个服务的catalog,记录各服务的admin,public,internal等url信息,
      封装为如下形式的字典,样例如下:
      {'service_type': u'volume', 'url': u'http://cinder-api.openstack.svc.cluster.local:8776/v1/88bde1c34a1f4d7db93b5d2d73fa05be', 'region': 'RegionOne', 'admin_url': u'http://cinder-api.openstack.svc.cluster.local:8776/v1/88bde1c34a1f4d7db93b5d2d73fa05be', 'name': u'cinder'}
      将该字典写入OSClient类的成员变量self._service_catalog中
    步骤3: 最后返回,此时self._service_catalog中已经包含了所有openstack组件的catalog信息
3 service_catalog():如果self._service_catalog为空,就调用get_token获取catalog信息,否则,直接返回已经缓存的catalog信息

2.3 CheckOSApi分析


2.3.1 CheckOSApi定义如下
class CheckOSApi(OSBase):
    """Class to check the status of OpenStack API services."""
    ......

分析:
1) 可以看到CheckOSApi继承自OSBase类

2.3.2 OSBase分析
OSBase的具体内容如下:
class OSBase(object):
    FAIL = 0
    OK = 1
    UNKNOWN = 2
    GAUGE_NAME_FORMAT = "openstack_{}"

    def __init__(self, oscache, osclient):
        self.oscache = oscache
        self.osclient = osclient
        self.oscache.cache_me(self)

    def get_cache_data(self):
        return self.oscache.get_cache_data(self.get_cache_key())

    def build_cache_data(self):
        """ build a hash to store in cache """
        raise NotImplemented("Must be implemented by the subclass!")

    def get_cache_key(self):
        """ cache key """
        raise NotImplemented("Must be implemented by the subclass!")
    ......

分析:
1) OSBase:
作用: 做为CheckOSApi,NovaServiceStats等子类的对象,包含osclients和oscache对象,osclients是一个数组,oscache 是一个线程对象,
      定义了子类需要实现的build_cache_data和get_cache_key方法,每次实例化
成员变量:
包含osclients和oscache对象,osclients是一个数组,oscache 是一个线程对象
成员函数:
1 __init__(oscache, osclient): 对oscache,osclient赋值,并向oscache的osclient数组中加入当前子类对象,例如CheckOSApi对象
2 build_cache_data(): 抽象方法,由各个子类去实现构建缓存数据的方法
3 get_cache_key(): 抽象方法,由各个子类去实现获取缓存的名称的方法


2.3.3 继续分析CheckOSApi
exporter/check_os_api.py中CheckOSAPI具体定义如下:

class CheckOSApi(OSBase):
    """Class to check the status of OpenStack API services."""

    def build_cache_data(self):
        """ Check the status of all the API services.

            Yields a list of dict items with 'service', 'status' (either OK,
            FAIL or UNKNOWN) and 'region' keys.
        """
        check_array = []
        catalog = self.osclient.service_catalog

        for service in catalog:
            name = service['name']
            if name == 'cinder':
                if service.get('service_type', '') != 'volumev2':
                    continue
            url = None
            status_code = 500
            if name not in self.CHECK_MAP:
                logger.info(
                    "No check found for service '%s', creating one" % name)
                self.CHECK_MAP[name] = {
                    'path': '/',
                    'expect': [200, 300, 302, 401, 404],
                    'name': name,
                }
            check = self.CHECK_MAP[name]
            url = self._service_url(service['url'], check['path'])
            r = self.osclient.raw_get(
                url, token_required=check.get(
                    'auth', False))

            if r is not None:
                status_code = r.status_code

            if r is None or status_code not in check['expect']:
                logger.info(
                    "Service %s check failed "
                    "(returned '%s' but expected '%s')" % (
                        name, status_code, check['expect'])
                )
                status = self.FAIL
            else:
                status = self.OK

            check_array.append({
                'service': name,
                'status': status,
                'url': url,
                'status_code': status_code,
                'region': self.osclient.region,
            })
        return check_array

    def get_cache_key(self):
        return "check_os_api"

    def get_stats(self):
        registry = CollectorRegistry()
        labels = ['region', 'url', 'service']
        check_api_data_cache = self.get_cache_data()
        for check_api_data in check_api_data_cache:
            label_values = [check_api_data['region'], check_api_data['url'], check_api_data['service']]
            gague_name = self.gauge_name_sanitize("check_{}_api".format(check_api_data['service']))
            check_gauge = Gauge(gague_name,
                         'Openstack API check. fail = 0, ok = 1 and unknown = 2',
                         labels, registry=registry)
            check_gauge.labels(*label_values).set(check_api_data['status'])
        return generate_latest(registry)

分析:
1) CheckOSApi.build_cache_data(self):
    作用: 检查各个openstack组件api服务的状态是否正常,返回各api服务的统计结果
    处理过程:
    1 获取keystone的catalog(是一个数组,每个元素是一个字典,包含服务类型,服务名称,adminUrl, interUrl,
    publicUrl等,endpoint实际是某个服务具体的url),如果为空,就调用get_token获取catalog
    2 遍历catalog,对每个catalog
        2.1 如果该组件的api服务已经被配置需要处理,
        则根据该服务的internalUrl + 该服务的访问api服务需要的路径后缀 拼接成的url,
        发送请求,获取该服务的api状态;
        2.2 如果该服务的api响应的status_code在期望的正确的状态码列表中,就认为该组件的api服务
        正常,否则认为异常;
        2.3 将记录的该组件api服务的信息,具体是一个字典,样例如下所示:
           {
                'service': name,
                'status': status,
                'url': url,
                'status_code': status_code,
                'region': self.osclient.region,
           }
           添加到最终的统计结果数组中
    3 返回组件api服务统计结果数组
    
    返回结果样例如下:
    [{'status': 2, 'url': None, 'region': 'RegionOne', 'service': u'placement', 'status_code': 500}, {'status': 1, 'url': u'http://glance-api.openstack.svc.cluster.local:9292', 'region': 'RegionOne', 'service': u'glance', 'status_code': 300}, {'status': 1, 'url': u'http://murano-api.openstack.svc.cluster.local:8082', 'region': 'RegionOne', 'service': u'murano', 'status_code': 300}, {'status': 1, 'url': u'http://heat-api.openstack.svc.cluster.local:8004', 'region': 'RegionOne', 'service': u'heat', 'status_code': 300}, {'status': 1, 'url': u'http://heat-cfn.openstack.svc.cluster.local:8000', 'region': 'RegionOne', 'service': u'heat-cfn', 'status_code': 300}, {'status': 1, 'url': u'http://cinder-api.openstack.svc.cluster.local:8776', 'region': 'RegionOne', 'service': u'cinder', 'status_code': 300}, {'status': 1, 'url': u'http://ceph-rgw.ceph.svc.cluster.local:8088', 'region': 'RegionOne', 'service': u'swift', 'status_code': 200}, {'status': 2, 'url': None, 'region': 'RegionOne', 'service': u'coaster', 'status_code': 500}, {'status': 1, 'url': u'http://aodh-api.openstack.svc.cluster.local:8042', 'region': 'RegionOne', 'service': u'aodh', 'status_code': 200}, {'status': 1, 'url': u'http://ceilometer-api.openstack.svc.cluster.local:8777/v2/capabilities', 'region': 'RegionOne', 'service': u'ceilometer', 'status_code': 200}, {'status': 1, 'url': u'http://neutron-server.openstack.svc.cluster.local:9696', 'region': 'RegionOne', 'service': u'neutron', 'status_code': 200}, {'status': 1, 'url': u'http://nova-api.openstack.svc.cluster.local:8774', 'region': 'RegionOne', 'service': u'nova', 'status_code': 200}, {'status': 1, 'url': u'http://keystone-api.openstack.svc.cluster.local:80', 'region': 'RegionOne', 'service': u'keystone', 'status_code': 300}, {'status': 1, 'url': u'http://gnocchi-api.openstack.svc.cluster.local:8041', 'region': 'RegionOne', 'service': u'gnocchi', 'status_code': 200}]

2) 其中CHECK_MAP内容如下

class CheckOSApi(OSBase):

    CHECK_MAP = {
        'keystone': {'path': '/', 'expect': [300],
                     'name': 'keystone-public-api'},
        'glance': {'path': '/', 'expect': [300], 'name': 'glance-api'},
        'cinder': {'path': '/', 'expect': [200, 300], 'name': 'cinder-api'},
        'cinderv2': {
            'path': '/', 'expect': [200, 300], 'name': 'cinder-v2-api'},
        'neutron': {'path': '/', 'expect': [200], 'name': 'neutron-api'},
        'nova': {'path': '/', 'expect': [200], 'name': 'nova-api'},
        'ceilometer': {
            'path': 'v2/capabilities', 'expect': [200], 'auth': True,
            'name': 'ceilometer-api'},
        ......
    }

解释:
CHECK_MAP实际是一个字典,定义了各个组件检测api服务的请求的url路径,以及期待的状态码等信息。

3) CheckOSApi.get_stats(self):
作用: 获取缓存中的openstack各组件api服务的统计状态,最后拼接成一个字符串返回
处理过程:
1 获取当前对象在缓存(实际是字典)中对应的openstack组件api的缓存结果
2 遍历该缓存结果(实际是一个数组),对每个组件api服务的缓存数据(实际是一个字典):
2.0 一份组件api服务缓存数据样例如下:
  {
    'status': 1,
    'url': u 'http://cinder-api.openstack.svc.cluster.local:8776',
    'region': 'RegionOne',
    'service': u 'cinder',
    'status_code': 300
  }
2.1 获取region, url, service等键的值组成的列表做为labels
2.2 以监控项名称(例如check_cinder_api),labels,registry来构建一个
  prometheus_client.Gauge(
        gague_name,
        'Openstack API check. fail = 0, ok = 1 and unknown = 2',
        labels, registry=registry)
2.3 调用该prometheus_client.Gauge 的labels(*label_values).set方法设定
 监控项的值(具体对应到openstack-api也就是一个数字:0或1或2,其中
 0表示该组件api服务正常,1表示该组件api服务异常,2表示该组件api服务状态未知)
3 最后调用prometheus_client.generate_latest(registry)方法产生最终结果(是一个字符串)并返回
结果样例如下:
'# HELP check_ceilometer_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_ceilometer_api gauge\ncheck_ceilometer_api{region="RegionOne",service="ceilometer",url="http://ceilometer-api.openstack.svc.cluster.local:8777/v2/capabilities"} 1.0\n# HELP check_aodh_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_aodh_api gauge\ncheck_aodh_api{region="RegionOne",service="aodh",url="http://aodh-api.openstack.svc.cluster.local:8042"} 1.0\n# HELP check_heat_cfn_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_heat_cfn_api gauge\ncheck_heat_cfn_api{region="RegionOne",service="heat-cfn",url="http://heat-cfn.openstack.svc.cluster.local:8000"} 1.0\n# HELP check_cinder_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_cinder_api gauge\ncheck_cinder_api{region="RegionOne",service="cinder",url="http://cinder-api.openstack.svc.cluster.local:8776"} 1.0\n# HELP check_murano_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_murano_api gauge\ncheck_murano_api{region="RegionOne",service="murano",url="http://murano-api.openstack.svc.cluster.local:8082"} 1.0\n# HELP check_glance_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_glance_api gauge\ncheck_glance_api{region="RegionOne",service="glance",url="http://glance-api.openstack.svc.cluster.local:9292"} 1.0\n# HELP check_nova_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_nova_api gauge\ncheck_nova_api{region="RegionOne",service="nova",url="http://nova-api.openstack.svc.cluster.local:8774"} 1.0\n# HELP check_coaster_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_coaster_api gauge\ncheck_coaster_api{region="RegionOne",service="coaster",url="None"} 2.0\n# HELP check_placement_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_placement_api gauge\ncheck_placement_api{region="RegionOne",service="placement",url="None"} 2.0\n# HELP check_swift_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_swift_api gauge\ncheck_swift_api{region="RegionOne",service="swift",url="http://ceph-rgw.ceph.svc.cluster.local:8088"} 1.0\n# HELP check_keystone_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_keystone_api gauge\ncheck_keystone_api{region="RegionOne",service="keystone",url="http://keystone-api.openstack.svc.cluster.local:80"} 1.0\n# HELP check_heat_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_heat_api gauge\ncheck_heat_api{region="RegionOne",service="heat",url="http://heat-api.openstack.svc.cluster.local:8004"} 1.0\n# HELP check_neutron_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_neutron_api gauge\ncheck_neutron_api{region="RegionOne",service="neutron",url="http://neutron-server.openstack.svc.cluster.local:9696"} 1.0\n# HELP check_gnocchi_api Openstack API check. fail = 0, ok = 1 and unknown = 2\n# TYPE check_gnocchi_api gauge\ncheck_gnocchi_api{region="RegionOne",service="gnocchi",url="http://gnocchi-api.openstack.svc.cluster.local:8041"} 1.0\n'
    


2.4 OSCache分析


exporter/oscache.py中定义OSCache的主要内容如下:

class OSCache(Thread):

    def __init__(self, refresh_interval, region):
        Thread.__init__(self)
        self.daemon = True
        self.duration = 0
        self.refresh_interval = refresh_interval
        self.cache = ThreadSafeDict()
        self.region = region
        self.osclients = []

    def cache_me(self, osclient):
        self.osclients.append(osclient)
        logger.debug("new osclient added to cache")

    def run(self):
        while True:
            start_time = time()
            '''
            这个self.osclients经过多次实例化:
            CheckOSApi, NeutronAgentStats, CinderServiceStats等后,不断将继承OSBase类的子类对象添加到
            OSCache对象的self.osclients对象中,变成了相互引用:
            OSBase引用了OSCache,OSCache的成员变量osclients又包含了继承OSBase类的子类对象
            '''
            for osclient in self.osclients:
                try:
                    self.cache[osclient.get_cache_key()] = \
                        osclient.build_cache_data()
                except Exception as e:
                    logger.error(str(e))
                    logger.error("failed to get data for cache"
                                 "key {}".format(osclient.get_cache_key()))
            self.duration = time() - start_time
            sleep(self.refresh_interval)
    ......

分析:
1) OSCache
作用: 继承自Thread类,默认每隔30秒,遍历所有待处理的openstack服务统计对象列表,对每个对象,调用其自身的build_cache_data()方法,获取该服务的统计结果,调用其自身的get_cache_key()方法,获取该服务的缓存名称,建立<缓存名称,该服务的统计结果>的映射并将结果写入到缓存(本质是一个字典字典中)中
成员变量:
主要包含: self.osclients是一个数组,self.cache 是一个线程安全的字典对象
成员函数:
1 __init__(self, refresh_interval, region):设置线程安全字典对象self.cache和数组self.osclients
2 cache_me(self, osclient): 向数组self.osclients中加入check_os_api.CheckOSApi, nova_services.NovaServiceStats等对象
3 run(): 遍历所有待处理的openstack服务统计对象列表,对每个对象,调用其自身的
     build_cache_data()方法,获取该服务的统计结果,
     调用其自身的get_cache_key()方法,获取该服务的缓存名称,
     建立<缓存名称,该服务的统计结果>的映射
     并将结果写入到缓存(本质是一个字典字典中)中


2.5 回到对main.py的分析


在exporter/main.py中

if __name__ == '__main__':
    ......
    osclient = OSClient(
        os_keystone_url,
        os_password,
        os_tenant_name,
        os_username,
        os_user_domain,
        os_region,
        os_timeout,
        os_retries)
    oscache = OSCache(os_polling_interval, os_region)
    collectors.append(oscache)

    check_os_api = CheckOSApi(oscache, osclient)
    collectors.append(check_os_api)
    neutron_agent_stats = NeutronAgentStats(oscache, osclient)
    collectors.append(neutron_agent_stats)
    cinder_service_stats = CinderServiceStats(oscache, osclient)
    collectors.append(cinder_service_stats)
    nova_service_stats = NovaServiceStats(oscache, osclient)
    collectors.append(nova_service_stats)
    hypervisor_stats = HypervisorStats(
        oscache,
        osclient,
        os_cpu_overcomit_ratio,
        os_ram_overcomit_ratio)
    collectors.append(hypervisor_stats)

    oscache.start()

    listen_port = config.get(
        'LISTEN_PORT', int(
            os.getenv(
                'LISTEN_PORT', 9103)))
    server = ForkingHTTPServer(('', listen_port), handler)
    server.serve_forever()
分析:
根据对2.2中OSClient的分析,2.3中CheckOSApi的分析,2.4中OSCache的分析可知,
OSClient: 实际就是获取keystone的endpoints列表,token等

OSBase: 做为CheckOSApi,NovaServiceStats等子类的对象,包含osclients和oscache对象,osclients是一个数组,oscache 是一个线程对象,定义了子类需要实现的build_cache_data和get_cache_key方法。

CheckOSApi: OSBase的子类,检查各个openstack组件api服务的状态是否正常,返回各api服务的统计结果

OSCache: 继承自Thread类,默认每隔30秒,遍历所有待处理的openstack服务统计对象列表,对每个对象,调用其自身的build_cache_data()方法,获取该服务的统计结果,调用其自身的get_cache_key()方法,获取该服务的缓存名称,建立<缓存名称,该服务的统计结果>的映射并将结果写入到缓存(本质是一个字典字典中)中

各个类之间主要的关系就是:
OSCache(本质是线程)是总入口,调用各个继承自OSBase类的子类的对象(例如CheckOSApi对象,NovaServiceStats对象等)的获取缓存数据的方法,建立了<缓存名称,该服务的统计结果>的映射并将结果写入到缓存(本质是一个字典字典中)中。
OSCache的成员变量osclients是一个数组,包含了各个继承OSBase类的子类的对象(例如CheckOSApi对象,NovaServiceStats对象等),从这一点上说: OSCache聚合了OSBase的各个子类
但是OSBase的成员变量oscache实际就是OSCache对象,从这一点来看: 继承自OSBase的各个子类又聚合了OSCache
即OSCache和继承自OSBase的各个子类之间互相是聚合关系,这种类设计的结构很混乱。

2.6 ForkingHTTPServer分析


调用的代码如下:
    server = ForkingHTTPServer(('', listen_port), handler)
    server.serve_forever()

ForkingHTTPServer定义代码如下:
class ForkingHTTPServer(ForkingMixIn, HTTPServer):
    pass


class OpenstackExporterHandler(BaseHTTPRequestHandler):
    def __init__(self, *args, **kwargs):
        BaseHTTPRequestHandler.__init__(self, *args, **kwargs)

    def do_GET(self):
        url = urlparse.urlparse(self.path)
        if url.path == '/metrics':
            output = ''
            for collector in collectors:
                try:
                    stats = collector.get_stats()
                    if stats is not None:
                        output = output + stats
                except BaseException:
                    logger.warning(
                        "Could not get stats for collector {}".format(
                            collector.get_cache_key()))
            self.send_response(200)
            self.send_header('Content-Type', CONTENT_TYPE_LATEST)
            self.end_headers()
            self.wfile.write(output)
        elif url.path == '/':
            self.send_response(200)
            self.end_headers()
            self.wfile.write("""<html>
            <head><title>OpenStack Exporter</title></head>
            <body>
            <h1>OpenStack Exporter</h1>
            <p>Visit <code>/metrics</code> to use.</p>
            </body>
            </html>

分析:
1) BaseHTTPServer.HTTPServer(server_address, RequestHandlerClass)
含义: 是SocketServer.TCPServer子类
函数: 
TCPServer(BaseServer).__init__(self, server_address, RequestHandlerClass, bind_and_activate=True)
参数:
server_address:是一个元组,(host, port)
RequestHandlerClass: 请求处理类,可继承自BaseHTTPServer.BaseHTTPRequestHandler的对象
处理过程: 创建和监听HTTP的socket,分发请求到一个处理器
2) SocketServer.ForkingMixIn()
含义: SocketServer模块简化了网络服务器的写任务,ForkingMixIn可以创建
        一个单独的进程或线程来处理每个请求。
本质: 让单进程服务器变为多进程服务器。每次处理用户请求会开启新的进程。
      支持异步模型。实际是采用多进程(分叉)实现异步。
3) BaseHTTPServer.BaseHTTPRequestHandler(request, client_address, server)
含义: 这个类用于处理HTTP请求。它必须被子类继承来处理每隔请求方法。
     例如(GET,或者POST)。这个类提供了许多类和实例的变量,方法被子类使用。
处理过程: 
1)这个处理器将会解析请求和头部,接着调用指定请求类型的一个方法。该方法名称
 是从请求中被构建。
2)例如,请求方法是SPAM,那么do_SPAM()方法将会被无参形式调用。
3)所有相关的信息都被存储在该处理器的实例变量中。子类不需要覆盖或扩展__init__()方法
成员变量:
client_address: 包含一个元组(host, port)指向客户端地址
server: 包含server实例

4) do_GET方法
do_GET(self):这里遍历所有的继承自OSBase类的对象,对每个对象,调用其自身的get_stats方法,获取之前已经在缓存中的结果,最终将一个大字符串传递给prometheus

猜你喜欢

转载自blog.csdn.net/qingyuanluofeng/article/details/84779737