#
# Copyright 2015 Intel Corp.
#
# 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.
import itertools
import pkg_resources
from oslo_config import cfg
from oslo_log import log
from oslo_utils import netutils
import six
from ceilometer.agent import plugin_base
from ceilometer import declarative
from ceilometer.hardware import inspector as insloader
from ceilometer.hardware.pollsters import util
from ceilometer.i18n import _LE, _LW
from ceilometer import sample
OPTS = [
cfg.StrOpt('meter_definitions_file',
default="snmp.yaml",
help="Configuration file for defining hardware snmp meters."
),
]
cfg.CONF.register_opts(OPTS, group='hardware')
LOG = log.getLogger(__name__)
[docs]class MeterDefinitionException(Exception):
def __init__(self, message, definition_cfg):
super(MeterDefinitionException, self).__init__(message)
self.message = message
self.definition_cfg = definition_cfg
def __str__(self):
return '%s %s: %s' % (self.__class__.__name__,
self.definition_cfg, self.message)
[docs]class MeterDefinition(object):
required_fields = ['name', 'unit', 'type']
def __init__(self, definition_cfg):
self.cfg = definition_cfg
for fname, fval in self.cfg.items():
if (isinstance(fname, six.string_types) and
(fname in self.required_fields or
fname.endswith('_inspector'))):
setattr(self, fname, fval)
else:
LOG.warning(_LW("Ignore unrecognized field %s"), fname)
for fname in self.required_fields:
if not getattr(self, fname, None):
raise MeterDefinitionException(
_LE("Missing field %s") % fname, self.cfg)
if self.type not in sample.TYPES:
raise MeterDefinitionException(
_LE("Unrecognized type value %s") % self.type, self.cfg)
[docs]class GenericHardwareDeclarativePollster(plugin_base.PollsterBase):
CACHE_KEY = 'hardware.generic'
mapping = None
def __init__(self):
super(GenericHardwareDeclarativePollster, self).__init__()
self.inspectors = {}
def _update_meter_definition(self, definition):
self.meter_definition = definition
self.cached_inspector_params = {}
@property
def default_discovery(self):
return 'tripleo_overcloud_nodes'
@staticmethod
def _parse_resource(res):
"""Parse resource from discovery.
Either URL can be given or dict. Dict has to contain at least
keys 'resource_id' and 'resource_url', all the dict keys will be stored
as metadata.
:param res: URL or dict containing all resource info.
:return parsed_url, resource_id, metadata: Returns parsed URL used for
SNMP query, unique identifier of the resource and metadata
of the resource.
"""
parsed_url, resource_id, metadata = (None, None, None)
if isinstance(res, dict):
if 'resource_url' not in res or 'resource_id' not in res:
LOG.error(_LE('Passed resource dict must contain keys '
'resource_id and resource_url.'))
else:
metadata = res
parsed_url = netutils.urlsplit(res['resource_url'])
resource_id = res['resource_id']
else:
metadata = {}
parsed_url = netutils.urlsplit(res)
resource_id = res
return parsed_url, resource_id, metadata
def _get_inspector(self, parsed_url):
if parsed_url.scheme not in self.inspectors:
try:
driver = insloader.get_inspector(parsed_url)
self.inspectors[parsed_url.scheme] = driver
except Exception as err:
LOG.exception(_LE("Cannot load inspector %(name)s: %(err)s"),
dict(name=parsed_url.scheme,
err=err))
raise err
return self.inspectors[parsed_url.scheme]
[docs] def get_samples(self, manager, cache, resources=None):
"""Return an iterable of Sample instances from polling the resources.
:param manager: The service manager invoking the plugin
:param cache: A dictionary for passing data between plugins
:param resources: end point to poll data from
"""
resources = resources or []
h_cache = cache.setdefault(self.CACHE_KEY, {})
sample_iters = []
# Get the meter identifiers to poll
identifier = self.meter_definition.name
for resource in resources:
parsed_url, res, extra_metadata = self._parse_resource(resource)
if parsed_url is None:
LOG.error(_LE("Skip invalid resource %s"), resource)
continue
ins = self._get_inspector(parsed_url)
try:
# Call hardware inspector to poll for the data
i_cache = h_cache.setdefault(res, {})
# Prepare inspector parameters and cache it for performance
param_key = parsed_url.scheme + '.' + identifier
inspector_param = self.cached_inspector_params.get(param_key)
if not inspector_param:
param = getattr(self.meter_definition,
parsed_url.scheme + '_inspector', {})
inspector_param = ins.prepare_params(param)
self.cached_inspector_params[param_key] = inspector_param
if identifier not in i_cache:
i_cache[identifier] = list(ins.inspect_generic(
host=parsed_url,
cache=i_cache,
extra_metadata=extra_metadata,
param=inspector_param))
# Generate samples
if i_cache[identifier]:
sample_iters.append(self.generate_samples(
parsed_url,
i_cache[identifier]))
except Exception as err:
LOG.exception(_LE('inspector call failed for %(ident)s '
'host %(host)s: %(err)s'),
dict(ident=identifier,
host=parsed_url.hostname,
err=err))
return itertools.chain(*sample_iters)
[docs] def generate_samples(self, host_url, data):
"""Generate a list of Sample from the data returned by inspector
:param host_url: host url of the endpoint
:param data: list of data returned by the corresponding inspector
"""
samples = []
definition = self.meter_definition
for (value, metadata, extra) in data:
s = util.make_sample_from_host(host_url,
name=definition.name,
sample_type=definition.type,
unit=definition.unit,
volume=value,
res_metadata=metadata,
extra=extra,
name_prefix=None)
samples.append(s)
return samples
@classmethod
[docs] def build_pollsters(cls):
if not cls.mapping:
definition_cfg = declarative.load_definitions(
{}, cfg.CONF.hardware.meter_definitions_file,
pkg_resources.resource_filename(__name__, "data/snmp.yaml"))
cls.mapping = load_definition(definition_cfg)
pollsters = []
for name in cls.mapping:
pollster = cls()
pollster._update_meter_definition(cls.mapping[name])
pollsters.append((name, pollster))
return pollsters
def load_definition(config_def):
mappings = {}
for meter_def in config_def.get('metric', []):
try:
meter = MeterDefinition(meter_def)
mappings[meter.name] = meter
except MeterDefinitionException as me:
errmsg = (_LE("Error loading meter definition : %(err)s")
% dict(err=me.message))
LOG.error(errmsg)
return mappings