web_alert.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  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 time
  19. import subprocess
  20. import os
  21. from alerts.base_alert import BaseAlert
  22. from collections import namedtuple
  23. from resource_management.libraries.functions.get_port_from_url import get_port_from_url
  24. from ambari_commons import OSCheck
  25. from ambari_commons.inet_utils import resolve_address
  26. logger = logging.getLogger()
  27. CURL_CONNECTION_TIMEOUT = '20'
  28. class WebAlert(BaseAlert):
  29. def __init__(self, alert_meta, alert_source_meta):
  30. super(WebAlert, self).__init__(alert_meta, alert_source_meta)
  31. # extract any lookup keys from the URI structure
  32. self.uri_property_keys = None
  33. if 'uri' in alert_source_meta:
  34. uri = alert_source_meta['uri']
  35. self.uri_property_keys = self._lookup_uri_property_keys(uri)
  36. def _collect(self):
  37. if self.uri_property_keys is None:
  38. raise Exception("Could not determine result. URL(s) were not defined.")
  39. # use the URI lookup keys to get a final URI value to query
  40. alert_uri = self._get_uri_from_structure(self.uri_property_keys)
  41. logger.debug("[Alert][{0}] Calculated web URI to be {1} (ssl={2})".format(
  42. self.get_name(), alert_uri.uri, str(alert_uri.is_ssl_enabled)))
  43. url = self._build_web_query(alert_uri)
  44. web_response = self._make_web_request(url)
  45. status_code = web_response.status_code
  46. time_seconds = web_response.time_millis / 1000
  47. error_message = web_response.error_msg
  48. if status_code == 0:
  49. return (self.RESULT_CRITICAL, [status_code, url, time_seconds, error_message])
  50. if status_code < 400:
  51. return (self.RESULT_OK, [status_code, url, time_seconds])
  52. return (self.RESULT_WARNING, [status_code, url, time_seconds])
  53. def _build_web_query(self, alert_uri):
  54. """
  55. Builds a URL out of the URI structure. If the URI is already a URL of
  56. the form http[s]:// then this will return the URI as the URL; otherwise,
  57. it will build the URL from the URI structure's elements
  58. """
  59. # shortcut if the supplied URI starts with the information needed
  60. string_uri = str(alert_uri.uri)
  61. if string_uri.startswith('http://') or string_uri.startswith('https://'):
  62. return alert_uri.uri
  63. # start building the URL manually
  64. host = BaseAlert.get_host_from_url(alert_uri.uri)
  65. if host is None:
  66. host = self.host_name
  67. # maybe slightly realistic
  68. port = 80
  69. if alert_uri.is_ssl_enabled is True:
  70. port = 443
  71. # extract the port
  72. try:
  73. port = int(get_port_from_url(alert_uri.uri))
  74. except:
  75. pass
  76. scheme = 'http'
  77. if alert_uri.is_ssl_enabled is True:
  78. scheme = 'https'
  79. if OSCheck.is_windows_family():
  80. # on windows 0.0.0.0 is invalid address to connect but on linux it resolved to 127.0.0.1
  81. host = resolve_address(host)
  82. return "{0}://{1}:{2}".format(scheme, host, str(port))
  83. def _make_web_request(self, url):
  84. """
  85. Makes an http(s) request to a web resource and returns the http code. If
  86. there was an error making the request, return 0 for the status code.
  87. """
  88. WebResponse = namedtuple('WebResponse', 'status_code time_millis error_msg')
  89. time_millis = 0
  90. try:
  91. kerberos_keytab = None
  92. kerberos_principal = None
  93. if self.uri_property_keys.kerberos_principal is not None:
  94. kerberos_principal = self._get_configuration_value(
  95. self.uri_property_keys.kerberos_principal)
  96. if kerberos_principal is not None:
  97. # substitute _HOST in kerberos principal with actual fqdn
  98. kerberos_principal = kerberos_principal.replace('_HOST', self.host_name)
  99. if self.uri_property_keys.kerberos_keytab is not None:
  100. kerberos_keytab = self._get_configuration_value(self.uri_property_keys.kerberos_keytab)
  101. if kerberos_principal is not None and kerberos_keytab is not None:
  102. os.system("kinit -kt {0} {1} > /dev/null".format(kerberos_keytab, kerberos_principal))
  103. # substitute 0.0.0.0 in url with actual fqdn
  104. url = url.replace('0.0.0.0', self.host_name)
  105. start_time = time.time()
  106. curl = subprocess.Popen(['curl', '--negotiate', '-u', ':', '-sL', '-w',
  107. '%{http_code}', url, '--connect-timeout', CURL_CONNECTION_TIMEOUT,
  108. '-o', '/dev/null'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
  109. out, err = curl.communicate()
  110. if err != '':
  111. raise Exception(err)
  112. response_code = int(out)
  113. time_millis = time.time() - start_time
  114. except Exception, exc:
  115. if logger.isEnabledFor(logging.DEBUG):
  116. logger.exception("[Alert][{0}] Unable to make a web request.".format(self.get_name()))
  117. return WebResponse(status_code=0, time_millis=0, error_msg=str(exc))
  118. return WebResponse(status_code=response_code, time_millis=time_millis, error_msg=None)
  119. def _get_reporting_text(self, state):
  120. '''
  121. Gets the default reporting text to use when the alert definition does not
  122. contain any.
  123. :param state: the state of the alert in uppercase (such as OK, WARNING, etc)
  124. :return: the parameterized text
  125. '''
  126. if state == self.RESULT_CRITICAL:
  127. return 'Connection failed to {1}'
  128. return 'HTTP {0} response in {2:.4f} seconds'