base_alert.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  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 logging
  18. import re
  19. import time
  20. from collections import namedtuple
  21. logger = logging.getLogger()
  22. class BaseAlert(object):
  23. RESULT_OK = 'OK'
  24. RESULT_WARNING = 'WARNING'
  25. RESULT_CRITICAL = 'CRITICAL'
  26. RESULT_UNKNOWN = 'UNKNOWN'
  27. def __init__(self, alert_meta, alert_source_meta):
  28. self.alert_meta = alert_meta
  29. self.alert_source_meta = alert_source_meta
  30. self.cluster = ''
  31. self.host_name = ''
  32. self._lookup_keys = []
  33. def interval(self):
  34. """ gets the defined interval this check should run """
  35. if not self.alert_meta.has_key('interval'):
  36. return 1
  37. else:
  38. interval = self.alert_meta['interval']
  39. return 1 if interval < 1 else interval
  40. def is_enabled(self):
  41. """
  42. gets whether the definition is enabled
  43. """
  44. return self.alert_meta['enabled']
  45. def get_name(self):
  46. """
  47. gets the unique name of the alert definition
  48. """
  49. return self.alert_meta['name']
  50. def get_uuid(self):
  51. """
  52. gets the unique has of the alert definition
  53. """
  54. return self.alert_meta['uuid']
  55. def set_helpers(self, collector, value_dict):
  56. """ sets helper objects for alerts without having to use them in a constructor """
  57. self.collector = collector
  58. self.config_value_dict = value_dict
  59. def set_cluster(self, cluster, host):
  60. """ sets cluster information for the alert """
  61. self.cluster = cluster
  62. self.host_name = host
  63. def collect(self):
  64. """ method used for collection. defers to _collect() """
  65. res = (BaseAlert.RESULT_UNKNOWN, [])
  66. res_base_text = "{0}"
  67. try:
  68. res = self._collect()
  69. res_base_text = self.alert_source_meta['reporting'][res[0].lower()]['text']
  70. except Exception as e:
  71. message = "Unable to run alert {0}".format(str(self.alert_meta['name']))
  72. # print the exception if in DEBUG, otherwise just log the warning
  73. if logger.isEnabledFor(logging.DEBUG):
  74. logger.exception(message)
  75. else:
  76. logger.warning(message)
  77. res = (BaseAlert.RESULT_UNKNOWN, [str(e)])
  78. res_base_text = "{0}"
  79. if logger.isEnabledFor(logging.DEBUG):
  80. logger.debug("debug alert result: {0}".format(str(res)))
  81. data = {}
  82. data['name'] = self._find_value('name')
  83. data['label'] = self._find_value('label')
  84. data['state'] = res[0]
  85. data['text'] = res_base_text.format(*res[1])
  86. data['cluster'] = self.cluster
  87. data['service'] = self._find_value('serviceName')
  88. data['component'] = self._find_value('componentName')
  89. data['timestamp'] = long(time.time() * 1000)
  90. data['uuid'] = self._find_value('uuid')
  91. data['enabled'] = self._find_value('enabled')
  92. if logger.isEnabledFor(logging.DEBUG):
  93. logger.debug("debug alert text: {0}".format(data['text']))
  94. self.collector.put(self.cluster, data)
  95. def _find_value(self, meta_key):
  96. """ safe way to get a value when outputting result json. will not throw an exception """
  97. if self.alert_meta.has_key(meta_key):
  98. return self.alert_meta[meta_key]
  99. else:
  100. return None
  101. def get_lookup_keys(self):
  102. """ returns a list of lookup keys found for this alert """
  103. return self._lookup_keys
  104. def _find_lookup_property(self, key):
  105. """
  106. check if the supplied key is parameterized and appends the extracted key
  107. to the array of keys
  108. """
  109. keys = re.findall("{{([\S]+)}}", key)
  110. if len(keys) > 0:
  111. logger.debug("Found parameterized key {0} for {1}".format(str(keys), str(self)))
  112. self._lookup_keys.append(keys[0])
  113. return keys[0]
  114. return key
  115. def _lookup_property_value(self, key):
  116. """
  117. in the case of specifying a configuration path, lookup that path's value
  118. """
  119. if not key in self._lookup_keys:
  120. return key
  121. if key in self.config_value_dict:
  122. return self.config_value_dict[key]
  123. else:
  124. return None
  125. def _lookup_uri_property_keys(self, uri_structure):
  126. """
  127. Loads the configuration lookup keys that the URI structure needs. This
  128. will return a named tuple that contains the keys needed to lookup
  129. parameterized URI values from the URI structure. The URI structure looks
  130. something like:
  131. "uri":{
  132. "http": foo,
  133. "https": bar,
  134. ...
  135. }
  136. """
  137. if uri_structure is None:
  138. return None
  139. http_key = None
  140. https_key = None
  141. https_property_key = None
  142. https_property_value_key = None
  143. default_port = None
  144. if 'http' in uri_structure:
  145. http_key = self._find_lookup_property(uri_structure['http'])
  146. if 'https' in uri_structure:
  147. https_key = self._find_lookup_property(uri_structure['https'])
  148. if 'https_property' in uri_structure:
  149. https_property_key = self._find_lookup_property(uri_structure['https_property'])
  150. if 'https_property_value' in uri_structure:
  151. https_property_value_key = uri_structure['https_property_value']
  152. if 'default_port' in uri_structure:
  153. default_port = uri_structure['default_port']
  154. AlertUriLookupKeys = namedtuple('AlertUriLookupKeys',
  155. 'http https https_property https_property_value default_port')
  156. alert_uri_lookup_keys = AlertUriLookupKeys(http=http_key, https=https_key,
  157. https_property=https_property_key,
  158. https_property_value=https_property_value_key, default_port=default_port)
  159. return alert_uri_lookup_keys
  160. def _get_uri_from_structure(self, alert_uri_lookup_keys):
  161. """
  162. Gets the URI to use by examining the URI structure from the definition.
  163. This will return a named tuple that has the uri and the SSL flag. The
  164. URI structure looks something like:
  165. "uri":{
  166. "http": foo,
  167. "https": bar,
  168. ...
  169. }
  170. """
  171. if alert_uri_lookup_keys is None:
  172. return None
  173. http_uri = None
  174. https_uri = None
  175. https_property = None
  176. https_property_value = None
  177. # create a named tuple to return both the concrete URI and SSL flag
  178. AlertUri = namedtuple('AlertUri', 'uri is_ssl_enabled')
  179. # attempt to parse and parameterize the various URIs; properties that
  180. # do not exist int he lookup map are returned as None
  181. if alert_uri_lookup_keys.http is not None:
  182. http_uri = self._lookup_property_value(alert_uri_lookup_keys.http)
  183. if alert_uri_lookup_keys.https is not None:
  184. https_uri = self._lookup_property_value(alert_uri_lookup_keys.https)
  185. if alert_uri_lookup_keys.https_property is not None:
  186. https_property = self._lookup_property_value(alert_uri_lookup_keys.https_property)
  187. if alert_uri_lookup_keys.https_property_value is not None:
  188. https_property_value = self._lookup_property_value(alert_uri_lookup_keys.https_property_value)
  189. # without a URI, there's no way to create the structure we need - return
  190. # the default port if specified, otherwise throw an exception
  191. if http_uri is None and https_uri is None:
  192. if alert_uri_lookup_keys.default_port is not None:
  193. alert_uri = AlertUri(uri=alert_uri_lookup_keys.default_port, is_ssl_enabled=False)
  194. return alert_uri
  195. else:
  196. raise Exception("Could not determine result. Either the http or https URI must be specified.")
  197. # start out assuming plaintext
  198. uri = http_uri
  199. is_ssl_enabled = False
  200. if https_uri is not None:
  201. # https without http implies SSL
  202. if http_uri is None:
  203. is_ssl_enabled = True
  204. uri = https_uri
  205. elif https_property is not None and https_property == https_property_value:
  206. is_ssl_enabled = True
  207. uri = https_uri
  208. alert_uri = AlertUri(uri=uri, is_ssl_enabled=is_ssl_enabled)
  209. return alert_uri
  210. def _collect(self):
  211. """
  212. Low level function to collect alert data. The result is a tuple as:
  213. res[0] = the result code
  214. res[1] = the list of arguments supplied to the reporting text for the result code
  215. """
  216. raise NotImplementedError
  217. """
  218. See RFC3986, Appendix B
  219. Tested on the following cases:
  220. "192.168.54.1"
  221. "192.168.54.2:7661
  222. "hdfs://192.168.54.3/foo/bar"
  223. "ftp://192.168.54.4:7842/foo/bar"
  224. Returns None if only a port is passsed in
  225. """
  226. @staticmethod
  227. def get_host_from_url(uri):
  228. if uri is None:
  229. return None
  230. # if not a string, return None
  231. if not isinstance(uri, basestring):
  232. return None
  233. # RFC3986, Appendix B
  234. parts = re.findall('^(([^:/?#]+):)?(//([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?', uri)
  235. # index of parts
  236. # scheme = 1
  237. # authority = 3
  238. # path = 4
  239. # query = 6
  240. # fragment = 8
  241. host_and_port = uri
  242. if 0 == len(parts[0][1]):
  243. host_and_port = parts[0][4]
  244. elif 0 == len(parts[0][2]):
  245. host_and_port = parts[0][1]
  246. elif parts[0][2].startswith("//"):
  247. host_and_port = parts[0][3]
  248. if -1 == host_and_port.find(':'):
  249. if host_and_port.isdigit():
  250. return None
  251. return host_and_port
  252. else:
  253. return host_and_port.split(':')[0]