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