%PDF- %PDF-
Direktori : /lib/python2.7/site-packages/salt/modules/ |
Current File : //lib/python2.7/site-packages/salt/modules/telemetry.py |
# -*- coding: utf-8 -*- ''' Connection module for Telemetry .. versionadded:: 2016.3.0 https://github.com/mongolab/mongolab-telemetry-api-docs/blob/master/alerts.md :configuration: This module accepts explicit telemetry credentials or can also read api key credentials from a pillar. More Information available here__. .. __: https://github.com/mongolab/mongolab-telemetry-api-docs/blob/master/alerts.md In the minion's config file: .. code-block:: yaml telemetry.telemetry_api_keys: - abc123 # Key 1 - efg321 # Backup Key 1 telemetry_api_base_url: https://telemetry-api.mongolab.com/v0 :depends: requests ''' # Import Python libs from __future__ import absolute_import, unicode_literals, print_function import logging # Import Salt libs import salt.utils.json import salt.utils.stringutils # Import 3rd-party libs from salt.ext import six log = logging.getLogger(__name__) # Import third party libs try: import requests HAS_REQUESTS = True except ImportError: HAS_REQUESTS = False __virtualname__ = 'telemetry' def __virtual__(): # Only load if imports exist. if not HAS_REQUESTS: return False return __virtualname__ def _get_telemetry_base(profile): config = __salt__['config.option'](profile) return config.get('telemetry_api_base_url') def _auth(api_key=None, profile='telemetry'): # return telemetry api key in the header if api_key is None and profile is None: raise Exception("Missing api_key and profile") if profile: if isinstance(profile, six.string_types): _profile = __salt__['config.option'](profile) elif isinstance(profile, dict): _profile = profile if _profile: api_key = _profile.get('telemetry_api_keys')[0] else: raise Exception("Missing api_key") return {'Telemetry-API-Key': api_key, 'content-type': 'application/json'} def _update_cache(deployment_id, metric_name, alert): key = "telemetry.{0}.alerts".format(deployment_id) if key in __context__: alerts = __context__[key] alerts[metric_name] = alert __context__[key] = alerts return __context__.get(key, []) def _retrieve_channel_id(email, profile='telemetry'): ''' Given an email address, checks the local cache if corresponding email address: channel_id mapping exists email Email escalation policy profile A dict of telemetry config information. ''' key = "telemetry.channels" auth = _auth(profile=profile) if key not in __context__: get_url = _get_telemetry_base(profile) + "/notification-channels?_type=EmailNotificationChannel" response = requests.get(get_url, headers=auth) if response.status_code == 200: cache_result = {} for index, alert in enumerate(response.json()): cache_result[alert.get('email')] = alert.get('_id', 'false') __context__[key] = cache_result return __context__[key].get(email, False) def get_alert_config(deployment_id, metric_name=None, api_key=None, profile="telemetry"): ''' Get all alert definitions associated with a given deployment or if metric_name is specified, obtain the specific alert config Returns dictionary or list of dictionaries. CLI Example: salt myminion telemetry.get_alert_config rs-ds033197 currentConnections profile=telemetry salt myminion telemetry.get_alert_config rs-ds033197 profile=telemetry ''' auth = _auth(profile=profile) alert = False key = "telemetry.{0}.alerts".format(deployment_id) if key not in __context__: try: get_url = _get_telemetry_base(profile) + "/alerts?deployment={0}".format(deployment_id) response = requests.get(get_url, headers=auth) except requests.exceptions.RequestException as e: log.error(six.text_type(e)) return False http_result = {} if response.status_code == 200: for alert in response.json(): http_result[alert.get('condition', {}).get('metric')] = alert __context__[key] = http_result if not __context__.get(key): return [] alerts = __context__[key].values() if metric_name: return __context__[key].get(metric_name) return [alert['_id'] for alert in alerts if '_id' in alert] def get_notification_channel_id(notify_channel, profile="telemetry"): ''' Given an email address, creates a notification-channels if one is not found and also returns the corresponding notification channel id. notify_channel Email escalation policy profile A dict of telemetry config information. CLI Example: salt myminion telemetry.get_notification_channel_id userx@company.com profile=telemetry ''' # This helper is used to procure the channel ids # used to notify when the alarm threshold is violated auth = _auth(profile=profile) notification_channel_id = _retrieve_channel_id(notify_channel) if not notification_channel_id: log.info("%s channel does not exist, creating.", notify_channel) # create the notification channel and cache the id post_url = _get_telemetry_base(profile) + "/notification-channels" data = { "_type": "EmailNotificationChannel", "name": notify_channel[:notify_channel.find('@')] + 'EscalationPolicy', "email": notify_channel } response = requests.post(post_url, data=salt.utils.json.dumps(data), headers=auth) if response.status_code == 200: log.info("Successfully created EscalationPolicy %s with EmailNotificationChannel %s", data.get('name'), notify_channel) notification_channel_id = response.json().get('_id') __context__["telemetry.channels"][notify_channel] = notification_channel_id else: raise Exception("Failed to created notification channel {0}".format(notify_channel)) return notification_channel_id def get_alarms(deployment_id, profile="telemetry"): ''' get all the alarms set up against the current deployment Returns dictionary of alarm information CLI Example: salt myminion telemetry.get_alarms rs-ds033197 profile=telemetry ''' auth = _auth(profile=profile) try: response = requests.get(_get_telemetry_base(profile) + "/alerts?deployment={0}".format(deployment_id), headers=auth) except requests.exceptions.RequestException as e: log.error(six.text_type(e)) return False if response.status_code == 200: alarms = response.json() if len(alarms) > 0: return alarms return 'No alarms defined for deployment: {0}'.format(deployment_id) else: # Non 200 response, sent back the error response' return {'err_code': response.status_code, 'err_msg': salt.utils.json.loads(response.text).get('err', '')} def create_alarm(deployment_id, metric_name, data, api_key=None, profile="telemetry"): ''' create an telemetry alarms. data is a dict of alert configuration data. Returns (bool success, str message) tuple. CLI Example: salt myminion telemetry.create_alarm rs-ds033197 {} profile=telemetry ''' auth = _auth(api_key, profile) request_uri = _get_telemetry_base(profile) + "/alerts" key = "telemetry.{0}.alerts".format(deployment_id) # set the notification channels if not already set post_body = { "deployment": deployment_id, "filter": data.get('filter'), "notificationChannel": get_notification_channel_id(data.get('escalate_to')).split(), "condition": { "metric": metric_name, "max": data.get('max'), "min": data.get('min') } } try: response = requests.post(request_uri, data=salt.utils.json.dumps(post_body), headers=auth) except requests.exceptions.RequestException as e: # TODO: May be we should retry? log.error(six.text_type(e)) if response.status_code >= 200 and response.status_code < 300: # update cache log.info('Created alarm on metric: %s in deployment: %s', metric_name, deployment_id) log.debug('Updating cache for metric %s in deployment %s: %s', metric_name, deployment_id, response.json()) _update_cache(deployment_id, metric_name, response.json()) else: log.error( 'Failed to create alarm on metric: %s in ' 'deployment %s: payload: %s', metric_name, deployment_id, salt.utils.json.dumps(post_body) ) return response.status_code >= 200 and response.status_code < 300, response.json() def update_alarm(deployment_id, metric_name, data, api_key=None, profile="telemetry"): ''' update an telemetry alarms. data is a dict of alert configuration data. Returns (bool success, str message) tuple. CLI Example: salt myminion telemetry.update_alarm rs-ds033197 {} profile=telemetry ''' auth = _auth(api_key, profile) alert = get_alert_config(deployment_id, metric_name, api_key, profile) if not alert: return False, "No entity found matching deployment {0} and alarms {1}".format(deployment_id, metric_name) request_uri = _get_telemetry_base(profile) + '/alerts/' + alert['_id'] # set the notification channels if not already set post_body = { "deployment": deployment_id, "filter": data.get('filter'), "notificationChannel": get_notification_channel_id(data.get('escalate_to')).split(), "condition": { "metric": metric_name, "max": data.get('max'), "min": data.get('min') } } try: response = requests.put(request_uri, data=salt.utils.json.dumps(post_body), headers=auth) except requests.exceptions.RequestException as e: log.error('Update failed: %s', e) return False, six.text_type(e) if response.status_code >= 200 and response.status_code < 300: # Also update cache log.debug('Updating cache for metric %s in deployment %s: %s', metric_name, deployment_id, response.json()) _update_cache(deployment_id, metric_name, response.json()) log.info('Updated alarm on metric: %s in deployment: %s', metric_name, deployment_id) return True, response.json() err_msg = six.text_type( # future lint: disable=blacklisted-function 'Failed to create alarm on metric: {0} in deployment: {1} ' 'payload: {2}').format( salt.utils.stringutils.to_unicode(metric_name), salt.utils.stringutils.to_unicode(deployment_id), salt.utils.json.dumps(post_body) ) log.error(err_msg) return False, err_msg def delete_alarms(deployment_id, alert_id=None, metric_name=None, api_key=None, profile='telemetry'): '''delete an alert specified by alert_id or if not specified blows away all the alerts in the current deployment. Returns (bool success, str message) tuple. CLI Example: salt myminion telemetry.delete_alarms rs-ds033197 profile=telemetry ''' auth = _auth(profile=profile) if alert_id is None: # Delete all the alarms associated with this deployment alert_ids = get_alert_config(deployment_id, api_key=api_key, profile=profile) else: alert_ids = [alert_id] if len(alert_ids) == 0: return False, "failed to find alert associated with deployment: {0}".format(deployment_id) failed_to_delete = [] for id in alert_ids: delete_url = _get_telemetry_base(profile) + "/alerts/{0}".format(id) try: response = requests.delete(delete_url, headers=auth) if metric_name: log.debug("updating cache and delete %s key from %s", metric_name, deployment_id) _update_cache(deployment_id, metric_name, None) except requests.exceptions.RequestException as e: log.error('Delete failed: %s', e) if response.status_code != 200: failed_to_delete.append(id) if len(failed_to_delete) > 0: return False, "Failed to delete {0} alarms in deployment: {1}" .format(', '.join(failed_to_delete), deployment_id) return True, "Successfully deleted {0} alerts in deployment: {1}".format(', '.join(alert_ids), deployment_id)