176 lines
6.3 KiB
Python
176 lines
6.3 KiB
Python
#
|
|
# 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 os
|
|
|
|
from jsonpath_rw_ext import parser
|
|
from oslo_config import cfg
|
|
from oslo_log import log
|
|
import six
|
|
import yaml
|
|
|
|
from ceilometer.i18n import _, _LI
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class DefinitionException(Exception):
|
|
def __init__(self, message, definition_cfg):
|
|
super(DefinitionException, self).__init__(message)
|
|
self.definition_cfg = definition_cfg
|
|
|
|
|
|
class Definition(object):
|
|
JSONPATH_RW_PARSER = parser.ExtentedJsonPathParser()
|
|
GETTERS_CACHE = {}
|
|
|
|
def __init__(self, name, cfg, plugin_manager):
|
|
self.cfg = cfg
|
|
self.name = name
|
|
self.plugin = None
|
|
if isinstance(cfg, dict):
|
|
if 'fields' not in cfg:
|
|
raise DefinitionException(
|
|
_("The field 'fields' is required for %s") % name,
|
|
self.cfg)
|
|
|
|
if 'plugin' in cfg:
|
|
plugin_cfg = cfg['plugin']
|
|
if isinstance(plugin_cfg, six.string_types):
|
|
plugin_name = plugin_cfg
|
|
plugin_params = {}
|
|
else:
|
|
try:
|
|
plugin_name = plugin_cfg['name']
|
|
except KeyError:
|
|
raise DefinitionException(
|
|
_('Plugin specified, but no plugin name supplied '
|
|
'for %s') % name, self.cfg)
|
|
plugin_params = plugin_cfg.get('parameters')
|
|
if plugin_params is None:
|
|
plugin_params = {}
|
|
try:
|
|
plugin_ext = plugin_manager[plugin_name]
|
|
except KeyError:
|
|
raise DefinitionException(
|
|
_('No plugin named %(plugin)s available for '
|
|
'%(name)s') % dict(
|
|
plugin=plugin_name,
|
|
name=name), self.cfg)
|
|
plugin_class = plugin_ext.plugin
|
|
self.plugin = plugin_class(**plugin_params)
|
|
|
|
fields = cfg['fields']
|
|
else:
|
|
# Simple definition "foobar: jsonpath"
|
|
fields = cfg
|
|
|
|
if isinstance(fields, list):
|
|
# NOTE(mdragon): if not a string, we assume a list.
|
|
if len(fields) == 1:
|
|
fields = fields[0]
|
|
else:
|
|
fields = '|'.join('(%s)' % path for path in fields)
|
|
|
|
if isinstance(fields, six.integer_types):
|
|
self.getter = fields
|
|
else:
|
|
try:
|
|
self.getter = self.make_getter(fields)
|
|
except Exception as e:
|
|
raise DefinitionException(
|
|
_("Parse error in JSONPath specification "
|
|
"'%(jsonpath)s' for %(name)s: %(err)s")
|
|
% dict(jsonpath=fields, name=name, err=e), self.cfg)
|
|
|
|
def _get_path(self, match):
|
|
if match.context is not None:
|
|
for path_element in self._get_path(match.context):
|
|
yield path_element
|
|
yield str(match.path)
|
|
|
|
def parse(self, obj, return_all_values=False):
|
|
if callable(self.getter):
|
|
values = self.getter(obj)
|
|
else:
|
|
return self.getter
|
|
|
|
values = [match for match in values
|
|
if return_all_values or match.value is not None]
|
|
|
|
if self.plugin is not None:
|
|
if return_all_values and not self.plugin.support_return_all_values:
|
|
raise DefinitionException("Plugin %s don't allows to "
|
|
"return multiple values" %
|
|
self.cfg["plugin"]["name"], self.cfg)
|
|
values_map = [('.'.join(self._get_path(match)), match.value) for
|
|
match in values]
|
|
values = [v for v in self.plugin.trait_values(values_map)
|
|
if v is not None]
|
|
else:
|
|
values = [match.value for match in values if match is not None]
|
|
if return_all_values:
|
|
return values
|
|
else:
|
|
return values[0] if values else None
|
|
|
|
def make_getter(self, fields):
|
|
if fields in self.GETTERS_CACHE:
|
|
return self.GETTERS_CACHE[fields]
|
|
else:
|
|
getter = self.JSONPATH_RW_PARSER.parse(fields).find
|
|
self.GETTERS_CACHE[fields] = getter
|
|
return getter
|
|
|
|
|
|
def load_definitions(defaults, config_file, fallback_file=None):
|
|
"""Setup a definitions from yaml config file."""
|
|
|
|
if not os.path.exists(config_file):
|
|
config_file = cfg.CONF.find_file(config_file)
|
|
if not config_file and fallback_file is not None:
|
|
LOG.debug("No Definitions configuration file found!"
|
|
"Using default config.")
|
|
config_file = fallback_file
|
|
|
|
if config_file is not None:
|
|
LOG.debug("Loading definitions configuration file: %s", config_file)
|
|
|
|
with open(config_file) as cf:
|
|
config = cf.read()
|
|
|
|
try:
|
|
definition_cfg = yaml.safe_load(config)
|
|
except yaml.YAMLError as err:
|
|
if hasattr(err, 'problem_mark'):
|
|
mark = err.problem_mark
|
|
errmsg = (_("Invalid YAML syntax in Definitions file "
|
|
"%(file)s at line: %(line)s, column: %(column)s.")
|
|
% dict(file=config_file,
|
|
line=mark.line + 1,
|
|
column=mark.column + 1))
|
|
else:
|
|
errmsg = (_("YAML error reading Definitions file "
|
|
"%(file)s")
|
|
% dict(file=config_file))
|
|
LOG.error(errmsg)
|
|
raise
|
|
|
|
else:
|
|
LOG.debug("No Definitions configuration file found!"
|
|
"Using default config.")
|
|
definition_cfg = defaults
|
|
|
|
LOG.info(_LI("Definitions: %s"), definition_cfg)
|
|
return definition_cfg
|