# For those usages not covered by the Apache License, Version 2.0 please
# contact: bdiaz@whitestack.com or glavado@whitestack.com
##
-import logging
from enum import Enum
+import logging
+import time
from typing import List
+from ceilometerclient import client as ceilometer_client
+from ceilometerclient.exc import HTTPException
import gnocchiclient.exceptions
-from ceilometerclient.v2 import client as ceilometer_client
from gnocchiclient.v1 import client as gnocchi_client
-from keystoneauth1 import session
from keystoneauth1.exceptions.catalog import EndpointNotFound
-from keystoneauth1.identity import v3
from keystoneclient.v3 import client as keystone_client
from neutronclient.v2_0 import client as neutron_client
from osm_mon.collector.metric import Metric
-from osm_mon.collector.utils import CollectorUtils
+from osm_mon.collector.utils.openstack import OpenstackUtils
from osm_mon.collector.vnf_collectors.base_vim import BaseVimCollector
from osm_mon.collector.vnf_metric import VnfMetric
from osm_mon.core.common_db import CommonDbClient
from osm_mon.core.config import Config
+
log = logging.getLogger(__name__)
METRIC_MAPPINGS = {
"packets_out_dropped": "network.incoming.packets.drop",
"packets_received": "network.incoming.packets.rate",
"packets_sent": "network.outgoing.packets.rate",
- "cpu_utilization": "cpu_util",
+ "cpu_utilization": "cpu",
+}
+
+METRIC_MULTIPLIERS = {
+ "cpu": 0.0000001
+}
+
+METRIC_AGGREGATORS = {
+ "cpu": "rate:mean"
}
INTERFACE_METRICS = ['packets_in_dropped', 'packets_out_dropped', 'packets_received', 'packets_sent']
class OpenstackCollector(BaseVimCollector):
def __init__(self, config: Config, vim_account_id: str):
super().__init__(config, vim_account_id)
- self.conf = config
self.common_db = CommonDbClient(config)
- self.backend = self._get_backend(vim_account_id)
+ vim_account = self.common_db.get_vim_account(vim_account_id)
+ self.backend = self._get_backend(vim_account)
- def _build_keystone_client(self, vim_account_id: str) -> keystone_client.Client:
- sess = OpenstackBackend.get_session(vim_account_id)
+ def _build_keystone_client(self, vim_account: dict) -> keystone_client.Client:
+ sess = OpenstackUtils.get_session(vim_account)
return keystone_client.Client(session=sess)
def _get_resource_uuid(self, nsr_id: str, vnf_member_index: str, vdur_name: str) -> str:
nsr_id = vnfr['nsr-id-ref']
vnf_member_index = vnfr['member-vnf-index-ref']
vnfd = self.common_db.get_vnfd(vnfr['vnfd-id'])
+ # Populate extra tags for metrics
+ tags = {}
+ tags['ns_name'] = self.common_db.get_nsr(nsr_id)['name']
+ if vnfr['_admin']['projects_read']:
+ tags['project_id'] = vnfr['_admin']['projects_read'][0]
+ else:
+ tags['project_id'] = ''
+
metrics = []
+
for vdur in vnfr['vdur']:
# This avoids errors when vdur records have not been completely filled
if 'name' not in 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']
- interface_name = param['interface-name-ref'] if 'interface-name-ref' in param else None
+ if 'monitoring-parameter' in vdu:
+ for param in vdu['monitoring-parameter']:
+ metric_name = param['performance-metric']
openstack_metric_name = METRIC_MAPPINGS[metric_name]
- metric_type = self._get_metric_type(metric_name, interface_name)
+ metric_type = self._get_metric_type(metric_name)
try:
resource_id = self._get_resource_uuid(nsr_id, vnf_member_index, vdur['name'])
except ValueError:
vdur['name'], vnf_member_index, nsr_id)
continue
try:
- value = self.backend.collect_metric(metric_type, openstack_metric_name, resource_id,
- interface_name)
+ log.info(
+ "Collecting metric type: %s and metric_name: %s and resource_id %s and ",
+ metric_type,
+ metric_name,
+ resource_id)
+ value = self.backend.collect_metric(metric_type, openstack_metric_name, resource_id)
if value is not None:
- tags = {}
- if interface_name:
- tags['interface'] = interface_name
+ log.info("value: %s", value)
metric = VnfMetric(nsr_id, vnf_member_index, vdur['name'], metric_name, value, tags)
metrics.append(metric)
- except Exception:
+ else:
+ log.info("metric value is empty")
+ except Exception as e:
log.exception("Error collecting metric %s for vdu %s" % (metric_name, vdur['name']))
+ log.info("Error in metric collection: %s" % e)
return metrics
- def _get_backend(self, vim_account_id: str):
+ def _get_backend(self, vim_account: dict):
try:
- ceilometer = CeilometerBackend(vim_account_id)
+ gnocchi = GnocchiBackend(vim_account)
+ gnocchi.client.metric.list(limit=1)
+ log.info("Using gnocchi backend to collect metric")
+ return gnocchi
+ except (HTTPException, EndpointNotFound):
+ ceilometer = CeilometerBackend(vim_account)
ceilometer.client.capabilities.get()
+ log.info("Using ceilometer backend to collect metric")
return ceilometer
- except EndpointNotFound:
- gnocchi = GnocchiBackend(vim_account_id)
- gnocchi.client.status.get()
- return gnocchi
- def _get_metric_type(self, metric_name: str, interface_name: str) -> MetricType:
+ def _get_metric_type(self, metric_name: str) -> MetricType:
if metric_name not in INTERFACE_METRICS:
return MetricType.INSTANCE
else:
- if interface_name:
- return MetricType.INTERFACE_ONE
return MetricType.INTERFACE_ALL
class OpenstackBackend:
- def collect_metric(self, metric_type: MetricType, metric_name: str, resource_id: str, interface_name: str):
+ def collect_metric(self, metric_type: MetricType, metric_name: str, resource_id: str):
pass
- @staticmethod
- def get_session(vim_account_id: str):
- creds = CollectorUtils.get_credentials(vim_account_id)
- verify_ssl = CollectorUtils.is_verify_ssl(creds)
- auth = v3.Password(auth_url=creds.url,
- username=creds.user,
- password=creds.password,
- project_name=creds.tenant_name,
- project_domain_id='default',
- user_domain_id='default')
- return session.Session(auth=auth, verify=verify_ssl)
-
class GnocchiBackend(OpenstackBackend):
- def __init__(self, vim_account_id: str):
- self.client = self._build_gnocchi_client(vim_account_id)
- self.neutron = self._build_neutron_client(vim_account_id)
+ def __init__(self, vim_account: dict):
+ self.client = self._build_gnocchi_client(vim_account)
+ self.neutron = self._build_neutron_client(vim_account)
- def _build_gnocchi_client(self, vim_account_id: str) -> gnocchi_client.Client:
- sess = OpenstackBackend.get_session(vim_account_id)
+ def _build_gnocchi_client(self, vim_account: dict) -> gnocchi_client.Client:
+ sess = OpenstackUtils.get_session(vim_account)
return gnocchi_client.Client(session=sess)
- def _build_neutron_client(self, vim_account_id: str) -> neutron_client.Client:
- sess = OpenstackBackend.get_session(vim_account_id)
+ def _build_neutron_client(self, vim_account: dict) -> neutron_client.Client:
+ sess = OpenstackUtils.get_session(vim_account)
return neutron_client.Client(session=sess)
- def collect_metric(self, metric_type: MetricType, metric_name: str, resource_id: str, interface_name: str):
- if metric_type == MetricType.INTERFACE_ONE:
- return self._collect_interface_one_metric(metric_name, resource_id, interface_name)
-
+ def collect_metric(self, metric_type: MetricType, metric_name: str, resource_id: str):
if metric_type == MetricType.INTERFACE_ALL:
return self._collect_interface_all_metric(metric_name, resource_id)
else:
raise Exception('Unknown metric type %s' % metric_type.value)
- def _collect_interface_one_metric(self, metric_name, resource_id, interface_name):
- ports = self.neutron.list_ports(name=interface_name, device_id=resource_id)
- if not ports or not ports['ports']:
- raise Exception(
- 'Port not found for interface %s on instance %s' % (interface_name, resource_id))
- port = ports['ports'][0]
- port_uuid = port['id'][:11]
- tap_name = 'tap' + port_uuid
- interfaces = self.client.resource.search(resource_type='instance_network_interface',
- query={'=': {'name': tap_name}})
- measures = self.client.metric.get_measures(metric_name,
- resource_id=interfaces[0]['id'],
- limit=1)
- return measures[-1][2] if measures else None
-
def _collect_interface_all_metric(self, openstack_metric_name, resource_id):
total_measure = None
interfaces = self.client.resource.search(resource_type='instance_network_interface',
total_measure = 0.0
total_measure += measures[-1][2]
- except gnocchiclient.exceptions.NotFound as e:
+ except (gnocchiclient.exceptions.NotFound, TypeError) as e:
+ # Gnocchi in some Openstack versions raise TypeError instead of NotFound
log.debug("No metric %s found for interface %s: %s", openstack_metric_name,
interface['id'], e)
return total_measure
def _collect_instance_metric(self, openstack_metric_name, resource_id):
value = None
try:
- measures = self.client.metric.get_measures(openstack_metric_name,
- resource_id=resource_id,
- limit=1)
- if measures:
- value = measures[-1][2]
+ aggregation = METRIC_AGGREGATORS.get(openstack_metric_name)
+
+ try:
+ measures = self.client.metric.get_measures(openstack_metric_name,
+ aggregation=aggregation,
+ start=time.time() - 1200,
+ resource_id=resource_id)
+ if measures:
+ value = measures[-1][2]
+ except (gnocchiclient.exceptions.NotFound, TypeError) as e:
+ # CPU metric in previous Openstack versions do not support rate:mean aggregation method
+ # Gnocchi in some Openstack versions raise TypeError instead of NotFound
+ if openstack_metric_name == "cpu":
+ log.debug("No metric %s found for instance %s: %s", openstack_metric_name, resource_id, e)
+ log.info("Retrying to get metric %s for instance %s without aggregation",
+ openstack_metric_name, resource_id)
+ measures = self.client.metric.get_measures(openstack_metric_name,
+ resource_id=resource_id,
+ limit=1)
+ else:
+ raise e
+ # measures[-1] is the last measure
+ # measures[-2] is the previous measure
+ # measures[x][2] is the value of the metric
+ if measures and len(measures) >= 2:
+ value = measures[-1][2] - measures[-2][2]
+ if value:
+ # measures[-1][0] is the time of the reporting interval
+ # measures[-1][1] is the duration of the reporting interval
+ if aggregation:
+ # If this is an aggregate, we need to divide the total over the reported time period.
+ # Even if the aggregation method is not supported by Openstack, the code will execute it
+ # because aggregation is specified in METRIC_AGGREGATORS
+ value = value / measures[-1][1]
+ if openstack_metric_name in METRIC_MULTIPLIERS:
+ value = value * METRIC_MULTIPLIERS[openstack_metric_name]
except gnocchiclient.exceptions.NotFound as e:
log.debug("No metric %s found for instance %s: %s", openstack_metric_name, resource_id,
e)
class CeilometerBackend(OpenstackBackend):
- def __init__(self, vim_account_id: str):
- self.client = self._build_ceilometer_client(vim_account_id)
+ def __init__(self, vim_account: dict):
+ self.client = self._build_ceilometer_client(vim_account)
- def _build_ceilometer_client(self, vim_account_id: str) -> ceilometer_client.Client:
- sess = OpenstackBackend.get_session(vim_account_id)
- return ceilometer_client.Client(session=sess)
+ def _build_ceilometer_client(self, vim_account: dict) -> ceilometer_client.Client:
+ sess = OpenstackUtils.get_session(vim_account)
+ return ceilometer_client.Client("2", session=sess)
- def collect_metric(self, metric_type: MetricType, metric_name: str, resource_id: str, interface_name: str):
+ def collect_metric(self, metric_type: MetricType, metric_name: str, resource_id: str):
if metric_type != MetricType.INSTANCE:
raise NotImplementedError('Ceilometer backend only support instance metrics')
measures = self.client.samples.list(meter_name=metric_name, limit=1, q=[