# # Copyright 2013 Intel Corp. # Copyright 2014 Red Hat, Inc # # 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 hashlib import os import pkg_resources from oslo_log import log from oslo_utils import fnmatch import six import yaml LOG = log.getLogger(__name__) class ConfigException(Exception): def __init__(self, cfg_type, message, cfg): self.cfg_type = cfg_type self.msg = message self.cfg = cfg def __str__(self): return '%s %s: %s' % (self.cfg_type, self.cfg, self.msg) class SourceException(Exception): def __init__(self, message, cfg): self.msg = message self.cfg = cfg def __str__(self): return 'Source definition invalid: %s (%s)' % (self.msg, self.cfg) class ConfigManagerBase(object): """Base class for managing configuration file refresh""" def __init__(self, conf): self.conf = conf self.cfg_loc = None def load_config(self, cfg_file, fallback_cfg_prefix='pipeline/data/'): """Load a configuration file and set its refresh values.""" if os.path.exists(cfg_file): self.cfg_loc = cfg_file else: self.cfg_loc = self.conf.find_file(cfg_file) if not self.cfg_loc and fallback_cfg_prefix is not None: LOG.debug("No pipeline definitions configuration file found! " "Using default config.") self.cfg_loc = pkg_resources.resource_filename( __name__, fallback_cfg_prefix + cfg_file) with open(self.cfg_loc) as fap: data = fap.read() conf = yaml.safe_load(data) self.cfg_mtime = self.get_cfg_mtime() self.cfg_hash = self.get_cfg_hash() LOG.info("Config file: %s", conf) return conf def get_cfg_mtime(self): """Return modification time of cfg file""" return os.path.getmtime(self.cfg_loc) if self.cfg_loc else None def get_cfg_hash(self): """Return hash of configuration file""" if not self.cfg_loc: return None with open(self.cfg_loc) as fap: data = fap.read() if six.PY3: data = data.encode('utf-8') file_hash = hashlib.md5(data).hexdigest() return file_hash def cfg_changed(self): """Returns hash of changed cfg else False.""" mtime = self.get_cfg_mtime() if mtime > self.cfg_mtime: LOG.info('Configuration file has been updated.') self.cfg_mtime = mtime _hash = self.get_cfg_hash() if _hash != self.cfg_hash: LOG.info("Detected change in configuration.") return _hash return False class Source(object): """Represents a generic source""" def __init__(self, cfg): self.cfg = cfg try: self.name = cfg['name'] except KeyError as err: raise SourceException( "Required field %s not specified" % err.args[0], cfg) def __str__(self): return self.name def check_source_filtering(self, data, d_type): """Source data rules checking - At least one meaningful datapoint exist - Included type and excluded type can't co-exist on the same pipeline - Included type meter and wildcard can't co-exist at same pipeline """ if not data: raise SourceException('No %s specified' % d_type, self.cfg) if ([x for x in data if x[0] not in '!*'] and [x for x in data if x[0] == '!']): raise SourceException( 'Both included and excluded %s specified' % d_type, self.cfg) if '*' in data and [x for x in data if x[0] not in '!*']: raise SourceException( 'Included %s specified with wildcard' % d_type, self.cfg) @staticmethod def is_supported(dataset, data_name): # Support wildcard like storage.* and !disk.* # Start with negation, we consider that the order is deny, allow if any(fnmatch.fnmatch(data_name, datapoint[1:]) for datapoint in dataset if datapoint[0] == '!'): return False if any(fnmatch.fnmatch(data_name, datapoint) for datapoint in dataset if datapoint[0] != '!'): return True # if we only have negation, we suppose the default is allow return all(datapoint.startswith('!') for datapoint in dataset)