class ClusterDiscovery(Resource):
    """
    Cluster and Node Discovery Resource Class for flask_restful
    """
    def __init__(self, statobjs):
        self.logger = logging.getLogger('esz.ClusterDiscovery')
        self.statobjs = statobjs
        self.statobj = statobjs['nodeinfo']
        self.nodeinfo = self.statobj.cached_read('nodeinfo')['nodes']

    def get(self, value):
        """GET method"""
        return self.post(value)

    def post(self, value):
        """POST method"""
        self.logger.debug('request.data contents = {}'.format(request.data))
        flag = None
        if request.data != b'':
            # Must decode to 'utf-8' for older versions of Python
            json_data = json.loads(request.data.decode('utf-8'))
            flag = json_data['flag'] if 'flag' in json_data else None
        if flag:
            # Placeholder if needed.
            pass
        macros = []
        if value == 'cluster':
            nodeid = list(self.nodeinfo.keys())[0]
            self.logger.debug('Value is "cluster."  Returning LLD data for the cluster...')
            self.logger.debug('Using nodeid {0} for cluster data'.format(nodeid))
            macros.append(get_cluster_macros(self.statobj, nodeid))
        elif value == 'nodes':
            self.logger.debug('Value is "nodes."  Returning LLD data for all discovered nodes...')
            for nodeid in self.nodeinfo:
                macros.append(get_cluster_macros(self.statobj, nodeid))
        return {'data': macros} class Discovery(Resource):
    """
    Endpoint Discovery Resource Class for flask_restful
    """
    def __init__(self, statobjs, do_not_discover, endpoints):
        self.statobjs = statobjs
        self.dnd = do_not_discover
        self.raw_endpoints = endpoints
        self.logger = logging.getLogger('esz.Discovery')

    def get(self):
        """GET method"""
        return self.post()

    def post(self):
        """POST method"""
        endpoints = extract_endpoints(self.raw_endpoints)
        node = None
        show_all = False
        self.logger.debug('request.data contents = {}'.format(request.data))
        if request.data != b'':
            # Must decode to 'utf-8' for older versions of Python
            json_data = json.loads(request.data.decode('utf-8'))
            node = json_data['node'] if 'node' in json_data else None
            show_all = json_data['show_all'] if 'show_all' in json_data else False
        results = macrogen(self.statobjs, self.dnd, node=node,
                           included=None if show_all else endpoints)
        llddata = lldoutput(results)
        return {'data': llddata} class DisplayEndpoints(Resource):
    """
    Endpoint Display Resource Class for flask_restful
    """
    def __init__(self, statobjs):
        self.statobjs = statobjs
        self.logger = logging.getLogger('esz.DisplayEndpoints')

    def get(self):
        """GET method"""
        return self.post()

    def post(self):
        """POST method"""
        node = None
        self.logger.debug('request.data contents = {}'.format(request.data))
        if request.data != b'':
            # Must decode to 'utf-8' for older versions of Python
            json_data = json.loads(request.data.decode('utf-8'))
            node = json_data['node'] if 'node' in json_data else None
        results = {}
        node = node if node else self.statobjs['health'].local_name
        for api in APIS:
            results[api] = get_endpoints(self.statobjs, api, node=node)[api]
        self.logger.debug('RESULTS = {0}'.format(results))
        return results class NodeDiscovery(Resource):
    """
    Node Discovery Resource Class for flask_restful
    """
    def __init__(self, statobjs):
        self.logger = logging.getLogger('esz.NodeDiscovery')
        self.statobjs = statobjs
        self.statobj = statobjs['nodeinfo']
        self.nodeinfo = self.statobj.cached_read('nodeinfo')['nodes']

    def get(self, node):
        """GET method"""
        return self.post(node)

    def post(self, node):
        """POST method"""
        self.logger.debug('request.data contents = {}'.format(request.data))
        flag = None
        if request.data != b'':
            # Must decode to 'utf-8' for older versions of Python
            json_data = json.loads(request.data.decode('utf-8'))
            flag = json_data['flag'] if 'flag' in json_data else None
        try:
            nodeid = get_nodeid(self.statobjs, node)
        except NotFound:
            return {'data': []}
        if flag:
            pass # Placeholder to quiet pylint
        macros = get_node_macros(self.statobj, nodeid)
        return {'data': macros} class RequestLogger(Resource):
    """
    RequestLogger Resource class for flask_restful
    """
    def __init__(self):
        self.logger = logging.getLogger('esz.RequestLogger')
        self.keymap = {
            'DEBUG': self.logger.debug,
            'INFO': self.logger.info,
            'WARNING': self.logger.warning,
            'ERROR': self.logger.error,
            'CRITICAL': self.logger.critical,
        }

    def post(self, loglevel):
        """POST method"""
        if request.data != b'':
            # Must decode to 'utf-8' for older versions of Python
            json_data = json.loads(request.data.decode('utf-8'))
            # If multiple keys were posted, log them all as specified
            for k in json_data:
                self.keymap[loglevel.upper()]('{0}: {1}'.format(k, json_data[k]))
        else:
            self.keymap[loglevel.upper()]('Received no POST data') class Stat(Resource):
    """Stat Resource class for flask_restful"""
    def __init__(self, statobj):
        self.statobj = statobj
        # Turn <class 'es_stats.classes.ClusterHealth'> into just "ClusterHealth"
        statclass = str(type(statobj)).split('.')[-1].split("'")[0]
        self.logger = logging.getLogger('es_stats_zabbix.Stat.{0}'.format(statclass))

    def get(self, key):
        """GET method"""
        return self.post(key)

    def post(self, key):
        """POST method"""
        self.logger.debug('request.data contents = {}'.format(request.data))
        node = None
        if request.data != b'':
            # Must decode to 'utf-8' for older versions of Python
            json_data = json.loads(request.data.decode('utf-8'))
            node = json_data['node'] if 'node' in json_data else None
        self.logger.debug('Node = {0} -- key = {1}'.format(node, key))
        result = self.statobj.get(key, name=node)
        # Remap for `status`
        if key == 'status':
            result = status_map(result)
        if result == DotMap():
            return {'message':'ZBX_NOTFOUND'}, 404
        if str(result).strip().lower() == 'false':
            return 0, 200
        if str(result).strip().lower() == 'true':
            return 1, 200
        if result == '':
            self.logger.error('Empty value')
            raise EmptyResult('No result received.')
        return result, 200 class TrapperDiscovery(Resource):
    """TrapperDiscovery Resource class for flask_restful"""
    def __init__(self, statobjs, zabbix, endpoints, do_not_discover):
        self.logger = logging.getLogger('esz.TrapperDiscovery')
        self.statobjs = statobjs
        self.zabbix = zabbix
        self.raw_endpoints = endpoints
        self.dnd = do_not_discover

    def get(self, zbxhost):
        """GET method"""
        return self.post(zbxhost)

    def post(self, zbxhost):
        """POST method"""
        node, endpoints = get_node_endpoints(zbxhost, self.raw_endpoints, self.statobjs)
        endpoints = extract_endpoints(endpoints)
        results = macrogen(
            self.statobjs, self.dnd, node=node, included=endpoints)
        llddata = lldoutput(results)
        exit_code, http_code = shipit(
            self.zabbix, zbxhost, llddata, data_type='lld')
        return exit_code, http_code class TrapperStats(Resource):
    """TrapperStats Resource class for flask_restful"""
    def __init__(self, statobjs, zabbix, endpoints):
        self.statobjs = statobjs
        self.raw_endpoints = endpoints
        self.zabbix = zabbix
        self.client = statobjs['health'].client
        self.logger = logging.getLogger('esz.TrapperStats')
        self.nodeinfo = self.client.nodes.info()['nodes']
        self.debug = False
        if logging.getLogger().getEffectiveLevel() == 10:
            self.debug = True

    def _get_kv(self, api, entry, node):
        value = self.statobjs[api].get(entry, name=node)
        self.logger.debug('Value: {0}'.format(value))
        # Map cluster health status to Zabbix expected values
        if entry == 'status':
            value = status_map(value)
        # Do nothing for nested or DotMap values
        if value == DotMap():
            self.logger.debug('Nested or value for {0}:{1}'.format(api, entry))
            return None, None
        # Set boolean and bool-ish values to firm 0/1
        if str(value).strip().lower() == 'false':
            value = 0
        if str(value).strip().lower() == 'true':
            value = 1
        # Do nothing for empty values
        if value == '':
            self.logger.debug('Empty value for {0}:{1}'.format(api, entry))
            return None, None
        # Truncate floating point values at 3 places
        if isinstance(value, float):
            value = float("{0:.3f}".format(value))
        key = 'es_stats[{0},{1},]'.format(api, entry)
        return key, value

    def get(self, zbxhost):
        """GET method"""
        self.logger.debug('We are doing a GET, but shouldn\'t be')
        return self.post(zbxhost)

    def post(self, zbxhost):
        """POST method"""
        self.logger.debug('request.data contents = {}'.format(request.data))
        node, endpoints = get_node_endpoints(zbxhost, self.raw_endpoints, self.statobjs)
        if request.data != b'':
            # Must decode to 'utf-8' for older versions of Python
            json_data = json.loads(request.data.decode('utf-8'))
            nodetype = json_data['nodetype'] if 'nodetype' in json_data else None
            interval = json_data['interval'] if 'interval' in json_data else '60s'
        # Get the result here.
        # We need all the results specified in the YAML file.
        # endpoints:
        #   nodetype:
        #     interval:
        #       api:
        #         - array of keys
        stats = {}
        if nodetype in endpoints:
            if interval in endpoints[nodetype]:
                for api in APIS:
                    if api in endpoints[nodetype][interval]:
                        for entry in endpoints[nodetype][interval][api]:
                            key, value = self._get_kv(api, entry, node)
                            if key:
                                stats[key] = value
        exit_code, http_code = shipit(self.zabbix, zbxhost, stats)
        return exit_code, http_code class ConfigurationError(ESZException):
    """
    Exception raised when a misconfiguration is detected
    """ def configure_logging(cfg):
    """
    Read logging configuration and initiate logging.
    """
    set_logging(check_schema(cfg, 'logging')['logging']) def get_client(cfg):
    """
    Return an Elasticsearch client object from a configuration dictionary
    """
    try:
        return Builder(cfg).client
    except Exception as err:
        msg = 'Failed to connect to Elasticsearch: Exception: {0}'.format(err)
        LOGGER.error(msg)
        raise ConfigurationError(msg) def get_config(cfg, key):
    """
    Wrapper to get schema-validated values from :func:~`es_stats_zabbix.helpers.config.check_schema`
    """
    return check_schema(cfg, key)[key]
import json
import logging
from time import sleep
from flask import Flask
from flask_restful import Api
from es_stats.classes import ClusterHealth, ClusterState, ClusterStats, NodeInfo, NodeStats
from es_stats_zabbix.backend import (
    ClusterDiscovery, Discovery, DisplayEndpoints, NodeDiscovery, RequestLogger, Stat,
    TrapperDiscovery, TrapperStats
)
from es_stats_zabbix.exceptions import ConfigurationError
from es_stats_zabbix.helpers.config import configure_logging, get_client, get_config
"""
Actually run the flask backend, and spin up all Resource classes and necessary configuration data
"""


def retry_es_connect(config):
    """
    On initial connection, to prevent a failure of the flask backend,
    continually retry connection to Elasticsearch until able to establish a connection.
    """
    logger = logging.getLogger('esz.backend.runner')
    connected = False
    while not connected:
        try:
            client = get_client(config)
            info = client.info()
            logger.info(
                'Connected to cluster: "{0}" through (initial) node: "{1}".  '
                'Elasticsearch version: "{2}"'.format(
                    info['cluster_name'], info['name'], info['version']['number']
                )
            )
            connected = True
        except ConfigurationError:
            msg = 'Unable to connect to Elasticsearch cluster. Retry in 5 seconds...'
            logger.error(msg)
            sleep(5)
    return client

def run_backend(config_dict):
    """
    Actually launch the backend.
    """
    # Enable logging first
    configure_logging(config_dict)
    logger = logging.getLogger('esz.backend.run_backend')
    dnd = get_config(config_dict, 'do_not_discover')
    zabbix = get_config(config_dict, 'zabbix')
    endpoints = get_config(config_dict, 'endpoints')
    backend = get_config(config_dict, 'backend')
    c_t = backend['cache_timeout']

    # Now that logging is enabled and we have the rest of the configuration,
    # let's attempt an Elasticsearch Client connection:
    client = retry_es_connect(config_dict)

    app = Flask(__name__)
    api = Api(app)
    statobjs = {
        'health': ClusterHealth(client, cache_timeout=c_t),
        'clusterstate': ClusterState(client, cache_timeout=c_t),
        'clusterstats': ClusterStats(client, cache_timeout=c_t),
        'nodeinfo': NodeInfo(client, cache_timeout=c_t),
        'nodestats': NodeStats(client, cache_timeout=c_t),
    }
    api.add_resource(Stat, '/api/health/<key>', endpoint='/health/',
                     resource_class_kwargs={'statobj': statobjs['health']})
    api.add_resource(Stat, '/api/clusterstate/<key>', endpoint='/clusterstate/',
                     resource_class_kwargs={'statobj': statobjs['clusterstate']})
    api.add_resource(Stat, '/api/clusterstats/<key>', endpoint='/clusterstats/',
                     resource_class_kwargs={'statobj': statobjs['clusterstats']})
    api.add_resource(Stat, '/api/nodeinfo/<key>', endpoint='/nodeinfo/',
                     resource_class_kwargs={'statobj': statobjs['nodeinfo']})
    api.add_resource(Stat, '/api/nodestats/<key>', endpoint='/nodestats/',
                     resource_class_kwargs={'statobj': statobjs['nodestats']})
    api.add_resource(DisplayEndpoints, '/api/display/', endpoint='/display/',
                     resource_class_kwargs={'statobjs': statobjs})
    api.add_resource(Discovery, '/api/discovery/', endpoint='/discovery/',
                     resource_class_kwargs={
                         'statobjs': statobjs,
                         'endpoints': endpoints,
                         'do_not_discover': dnd})
    api.add_resource(ClusterDiscovery, '/api/clusterdiscovery/<value>',
                     endpoint='/clusterdiscovery/',
                     resource_class_kwargs={'statobjs': statobjs})

    api.add_resource(NodeDiscovery, '/api/nodediscovery/<node>', endpoint='/nodediscovery/',