metric_alert.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208
  1. #!/usr/bin/env python
  2. """
  3. Licensed to the Apache Software Foundation (ASF) under one
  4. or more contributor license agreements. See the NOTICE file
  5. distributed with this work for additional information
  6. regarding copyright ownership. The ASF licenses this file
  7. to you under the Apache License, Version 2.0 (the
  8. "License"); you may not use this file except in compliance
  9. with the License. You may obtain a copy of the License at
  10. http://www.apache.org/licenses/LICENSE-2.0
  11. Unless required by applicable law or agreed to in writing, software
  12. distributed under the License is distributed on an "AS IS" BASIS,
  13. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. See the License for the specific language governing permissions and
  15. limitations under the License.
  16. """
  17. import imp
  18. import json
  19. import logging
  20. import re
  21. import urllib2
  22. import uuid
  23. from alerts.base_alert import BaseAlert
  24. from ambari_commons.urllib_handlers import RefreshHeaderProcessor
  25. from resource_management.libraries.functions.get_port_from_url import get_port_from_url
  26. logger = logging.getLogger()
  27. class MetricAlert(BaseAlert):
  28. def __init__(self, alert_meta, alert_source_meta):
  29. super(MetricAlert, self).__init__(alert_meta, alert_source_meta)
  30. self.metric_info = None
  31. if 'jmx' in alert_source_meta:
  32. self.metric_info = JmxMetric(alert_source_meta['jmx'])
  33. # extract any lookup keys from the URI structure
  34. self.uri_property_keys = None
  35. if 'uri' in alert_source_meta:
  36. uri = alert_source_meta['uri']
  37. self.uri_property_keys = self._lookup_uri_property_keys(uri)
  38. def _collect(self):
  39. if self.metric_info is None:
  40. raise Exception("Could not determine result. Specific metric collector is not defined.")
  41. if self.uri_property_keys is None:
  42. raise Exception("Could not determine result. URL(s) were not defined.")
  43. # use the URI lookup keys to get a final URI value to query
  44. alert_uri = self._get_uri_from_structure(self.uri_property_keys)
  45. logger.debug("[Alert][{0}] Calculated metric URI to be {1} (ssl={2})".format(
  46. self.get_name(), alert_uri.uri, str(alert_uri.is_ssl_enabled)))
  47. host = BaseAlert.get_host_from_url(alert_uri.uri)
  48. if host is None:
  49. host = self.host_name
  50. port = 80 # probably not very realistic
  51. try:
  52. port = int(get_port_from_url(alert_uri.uri))
  53. except:
  54. pass
  55. collect_result = None
  56. value_list = []
  57. if isinstance(self.metric_info, JmxMetric):
  58. value_list.extend(self._load_jmx(alert_uri.is_ssl_enabled, host, port, self.metric_info))
  59. check_value = self.metric_info.calculate(value_list)
  60. value_list.append(check_value)
  61. collect_result = self.__get_result(value_list[0] if check_value is None else check_value)
  62. logger.debug("[Alert][{0}] Resolved values = {1}".format(
  63. self.get_name(), str(value_list)))
  64. return (collect_result, value_list)
  65. def __get_result(self, value):
  66. ok_value = self.__find_threshold('ok')
  67. warn_value = self.__find_threshold('warning')
  68. crit_value = self.__find_threshold('critical')
  69. # critical values are higher
  70. critical_direction_up = crit_value >= warn_value
  71. if critical_direction_up:
  72. # critical values are higher
  73. if value >= crit_value:
  74. return self.RESULT_CRITICAL
  75. elif value >= warn_value:
  76. return self.RESULT_WARNING
  77. else:
  78. if ok_value is not None:
  79. if value >= ok_value and value < warn_value:
  80. return self.RESULT_OK
  81. else:
  82. return self.RESULT_UNKNOWN
  83. else:
  84. return self.RESULT_OK
  85. else:
  86. # critical values are lower
  87. if value <= crit_value:
  88. return self.RESULT_CRITICAL
  89. elif value <= warn_value:
  90. return self.RESULT_WARNING
  91. else:
  92. if ok_value is not None:
  93. if value <= ok_value and value > warn_value:
  94. return self.RESULT_OK
  95. else:
  96. return self.RESULT_UNKNOWN
  97. else:
  98. return self.RESULT_OK
  99. return None
  100. def __find_threshold(self, reporting_type):
  101. """ find the defined thresholds for alert values """
  102. if not 'reporting' in self.alert_source_meta:
  103. return None
  104. if not reporting_type in self.alert_source_meta['reporting']:
  105. return None
  106. if not 'value' in self.alert_source_meta['reporting'][reporting_type]:
  107. return None
  108. return self.alert_source_meta['reporting'][reporting_type]['value']
  109. def _load_jmx(self, ssl, host, port, jmx_metric):
  110. """ creates a JmxMetric object that holds info about jmx-based metrics """
  111. value_list = []
  112. if logger.isEnabledFor(logging.DEBUG):
  113. logger.debug(str(jmx_metric.property_map))
  114. for jmx_property_key, jmx_property_value in jmx_metric.property_map.iteritems():
  115. url = "{0}://{1}:{2}/jmx?qry={3}".format(
  116. "https" if ssl else "http", host, str(port), jmx_property_key)
  117. # use a customer header processor that will look for the non-standard
  118. # "Refresh" header and attempt to follow the redirect
  119. url_opener = urllib2.build_opener(RefreshHeaderProcessor())
  120. response = url_opener.open(url)
  121. content = response.read()
  122. json_response = json.loads(content)
  123. json_data = json_response['beans'][0]
  124. for attr in jmx_property_value:
  125. if attr not in json_data:
  126. raise Exception("Unable to find {0} in JSON from {1} ".format(attr, url))
  127. value_list.append(json_data[attr])
  128. return value_list
  129. def _get_reporting_text(self, state):
  130. '''
  131. Always returns {0} since the result of the script alert is a rendered string.
  132. This will ensure that the base class takes the result string and just uses
  133. it directly.
  134. :param state: the state of the alert in uppercase (such as OK, WARNING, etc)
  135. :return: the parameterized text
  136. '''
  137. return '{0}'
  138. class JmxMetric:
  139. def __init__(self, jmx_info):
  140. self.custom_module = None
  141. self.property_list = jmx_info['property_list']
  142. self.property_map = {}
  143. if 'value' in jmx_info:
  144. realcode = re.sub('(\{(\d+)\})', 'args[\g<2>]', jmx_info['value'])
  145. self.custom_module = imp.new_module(str(uuid.uuid4()))
  146. code = 'def f(args):\n return ' + realcode
  147. exec code in self.custom_module.__dict__
  148. for p in self.property_list:
  149. parts = p.split('/')
  150. if not parts[0] in self.property_map:
  151. self.property_map[parts[0]] = []
  152. self.property_map[parts[0]].append(parts[1])
  153. def calculate(self, args):
  154. if self.custom_module is not None:
  155. return self.custom_module.f(args)
  156. return None