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