1. 程式人生 > >Ceilometer 20、prometheus-openstack-exporter原始碼分析

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