From dec1bb5edc73cc455d7b2c759c77c1dd4ca77ff3 Mon Sep 17 00:00:00 2001 From: Benjamin Diaz Date: Wed, 10 Oct 2018 16:50:44 -0300 Subject: [PATCH] Adds MON Prometheus exporter Adds a new MON component called mon-exporter, which collects vdu infra metrics and exposes them through a webservice by using prometheus_client. This webservice follows the Prometheus exporter format so it can be integrated as a target so that it polls metrics from there. Signed-off-by: Benjamin Diaz Change-Id: Iaf2073879d884d0597aa8341f7b6fbbbc3d86e7e --- debian/python3-osm-mon.postinst | 1 + docker/Dockerfile | 2 +- docker/scripts/runInstall.sh | 3 +- osm_mon/cmd/__init__.py | 23 ++++ osm_mon/cmd/exporter.py | 52 ++++++++ osm_mon/exporter/__init__.py | 23 ++++ osm_mon/exporter/exporter.py | 125 +++++++++++++++++++ osm_mon/plugins/vRealiseOps/vrops_config.xml | 2 +- requirements.txt | 1 + setup.py | 5 + 10 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 osm_mon/cmd/__init__.py create mode 100644 osm_mon/cmd/exporter.py create mode 100644 osm_mon/exporter/__init__.py create mode 100644 osm_mon/exporter/exporter.py diff --git a/debian/python3-osm-mon.postinst b/debian/python3-osm-mon.postinst index 3fa26dc..89cd2b3 100644 --- a/debian/python3-osm-mon.postinst +++ b/debian/python3-osm-mon.postinst @@ -16,4 +16,5 @@ pip3 install six==1.11.* pip3 install bottle==0.12.* pip3 install peewee==3.1.* pip3 install pyyaml==3.* +pip3 install prometheus_client==0.4.* echo "Installation of python dependencies finished" \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile index 6f7a829..5e32f9e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -45,6 +45,6 @@ ENV REQUEST_TIMEOUT 10 ENV OSMMON_LOG_LEVEL INFO ENV OSMMON_KAFKA_LOG_LEVEL INFO -EXPOSE 8662 +EXPOSE 8662 8000 CMD /bin/bash mon/docker/scripts/runInstall.sh diff --git a/docker/scripts/runInstall.sh b/docker/scripts/runInstall.sh index a5231ba..ccfc2f3 100755 --- a/docker/scripts/runInstall.sh +++ b/docker/scripts/runInstall.sh @@ -22,5 +22,6 @@ ## /bin/bash /mon/osm_mon/plugins/vRealiseOps/vROPs_Webservice/install.sh python3 /mon/osm_mon/plugins/OpenStack/Aodh/notifier.py & -python3 /mon/osm_mon/core/message_bus/common_consumer.py +python3 /mon/osm_mon/core/message_bus/common_consumer.py & +osm-mon-exporter diff --git a/osm_mon/cmd/__init__.py b/osm_mon/cmd/__init__.py new file mode 100644 index 0000000..d81308a --- /dev/null +++ b/osm_mon/cmd/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018 Whitestack, LLC +# ************************************************************* + +# This file is part of OSM Monitoring module +# All Rights Reserved to Whitestack, LLC + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# For those usages not covered by the Apache License, Version 2.0 please +# contact: bdiaz@whitestack.com or glavado@whitestack.com +## diff --git a/osm_mon/cmd/exporter.py b/osm_mon/cmd/exporter.py new file mode 100644 index 0000000..a3545a3 --- /dev/null +++ b/osm_mon/cmd/exporter.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018 Whitestack, LLC +# ************************************************************* + +# This file is part of OSM Monitoring module +# All Rights Reserved to Whitestack, LLC + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# For those usages not covered by the Apache License, Version 2.0 please +# contact: bdiaz@whitestack.com or glavado@whitestack.com +## +import logging +import sys + +from osm_mon.core.settings import Config +from osm_mon.exporter.exporter import MonExporter + + +def main(): + cfg = Config.instance() + log_formatter_str = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + logging.basicConfig(stream=sys.stdout, + format=log_formatter_str, + datefmt='%m/%d/%Y %I:%M:%S %p', + level=logging.getLevelName(cfg.OSMMON_LOG_LEVEL)) + kafka_logger = logging.getLogger('kafka') + kafka_logger.setLevel(logging.getLevelName(cfg.OSMMON_KAFKA_LOG_LEVEL)) + kafka_formatter = logging.Formatter(log_formatter_str) + kafka_handler = logging.StreamHandler(sys.stdout) + kafka_handler.setFormatter(kafka_formatter) + kafka_logger.addHandler(kafka_handler) + log = logging.getLogger(__name__) + log.info("Starting MON Exporter...") + log.info("Config: %s", vars(cfg)) + exporter = MonExporter() + exporter.run() + + +if __name__ == '__main__': + main() diff --git a/osm_mon/exporter/__init__.py b/osm_mon/exporter/__init__.py new file mode 100644 index 0000000..8fc00af --- /dev/null +++ b/osm_mon/exporter/__init__.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018 Whitestack, LLC +# ************************************************************* + +# This file is part of OSM Monitoring module +# All Rights Reserved to Whitestack, LLC + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# For those usages not covered by the Apache License, Version 2.0 please +# contact: bdiaz@whitestack.com or glavado@whitestack.com +## \ No newline at end of file diff --git a/osm_mon/exporter/exporter.py b/osm_mon/exporter/exporter.py new file mode 100644 index 0000000..b7893e0 --- /dev/null +++ b/osm_mon/exporter/exporter.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- + +# Copyright 2018 Whitestack, LLC +# ************************************************************* + +# This file is part of OSM Monitoring module +# All Rights Reserved to Whitestack, LLC + +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# For those usages not covered by the Apache License, Version 2.0 please +# contact: bdiaz@whitestack.com or glavado@whitestack.com +## +import json +import logging +import random +import threading +import time +import uuid + +from kafka import KafkaProducer, KafkaConsumer +from osm_common import dbmongo +from prometheus_client import start_http_server, Gauge + +from osm_mon.core.settings import Config + +log = logging.getLogger(__name__) + + +class MonExporter: + + def __init__(self): + cfg = Config.instance() + self.kafka_server = cfg.BROKER_URI + self.common_db_host = cfg.MONGO_URI.split(':')[0] + self.common_db_port = cfg.MONGO_URI.split(':')[1] + self.collector_interval = 5 + self.metrics = {} + + def _run_exporter(self): + start_http_server(8000) + + def _run_collector(self): + producer = KafkaProducer(bootstrap_servers=self.kafka_server, + key_serializer=str.encode, + value_serializer=str.encode) + consumer = KafkaConsumer(bootstrap_servers=self.kafka_server, + key_deserializer=bytes.decode, + value_deserializer=bytes.decode, + consumer_timeout_ms=10000, + group_id='mon-collector-' + str(uuid.uuid4())) + consumer.subscribe(['metric_response']) + common_db = dbmongo.DbMongo() + common_db.db_connect({'host': self.common_db_host, 'port': int(self.common_db_port), 'name': 'osm'}) + + while True: + try: + time.sleep(self.collector_interval) + vnfrs = common_db.get_list('vnfrs') + for vnfr in vnfrs: + vnfd = common_db.get_one('vnfds', {"_id": vnfr['vnfd-id']}) + for vdur in vnfr['vdur']: + vdu = next( + filter(lambda vdu: vdu['id'] == vdur['vdu-id-ref'], vnfd['vdu']) + ) + if 'monitoring-param' in vdu: + for param in vdu['monitoring-param']: + metric_name = param['nfvi-metric'] + nsr_id = vnfr['nsr-id-ref'] + vnf_member_index = vnfr['member-vnf-index-ref'] + vdu_name = vdur['name'] + cor_id = random.randint(1, 10e7) + payload = { + 'correlation_id': cor_id, + 'metric_name': metric_name, + 'ns_id': nsr_id, + 'vnf_member_index': vnf_member_index, + 'vdu_name': vdu_name, + 'collection_period': 1, + 'collection_unit': 'DAY', + } + producer.send(topic='metric_request', key='read_metric_data_request', + value=json.dumps(payload)) + producer.flush() + for message in consumer: + if message.key == 'read_metric_data_response': + content = json.loads(message.value) + if content['correlation_id'] == cor_id and len( + content['metrics_data']['metrics_series']): + metric_reading = content['metrics_data']['metrics_series'][-1] + if metric_name not in self.metrics.keys(): + self.metrics[metric_name] = Gauge(metric_name, + 'Metric generated by MON collector', + ['ns_id', + 'vnf_member_index', + 'vdu_name']) + self.metrics[metric_name].labels( + ns_id=nsr_id, + vnf_member_index=vnf_member_index, + vdu_name=vdu_name + ).set(metric_reading) + break + + + except Exception: + log.exception("Error collecting metrics: ") + + def run(self): + t1 = threading.Thread(target=self._run_exporter) + t1.start() + t2 = threading.Thread(target=self._run_collector) + t2.start() + + +if __name__ == '__main__': + MonExporter().run() diff --git a/osm_mon/plugins/vRealiseOps/vrops_config.xml b/osm_mon/plugins/vRealiseOps/vrops_config.xml index 8165903..c2bf38c 100644 --- a/osm_mon/plugins/vRealiseOps/vrops_config.xml +++ b/osm_mon/plugins/vRealiseOps/vrops_config.xml @@ -141,4 +141,4 @@ Org2 Org2 - + \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index fd9c838..a1582fa 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,4 +33,5 @@ six==1.11.* bottle==0.12.* peewee==3.1.* pyyaml==3.* +prometheus_client==0.4.* git+https://osm.etsi.org/gerrit/osm/common.git#egg=osm-common \ No newline at end of file diff --git a/setup.py b/setup.py index 16c056b..dc5812f 100644 --- a/setup.py +++ b/setup.py @@ -71,6 +71,11 @@ setup( "osm-common" ], include_package_data=True, + entry_points={ + "console_scripts": [ + "osm-mon-exporter = osm_mon.cmd.exporter:main", + ] + }, dependency_links=[ 'git+https://osm.etsi.org/gerrit/osm/common.git#egg=osm-common' ], -- 2.25.1