Adding new expectation (Regex) and creating unit tests.

This commit is contained in:
Mitsuo Takaki
2016-04-29 08:48:53 -07:00
parent 2f3106da60
commit c5520450ba
8 changed files with 167 additions and 7 deletions

3
.gitignore vendored
View File

@@ -5,3 +5,6 @@ share/
.Python .Python
*.swp *.swp
*.pyc *.pyc
.cache
.coverage
*.egg-info

View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
import abc import abc
import logging import logging
import re
import requests import requests
import timeit import timeit
from yaml import load from yaml import load
@@ -20,10 +21,14 @@ class Configuration(object):
in self.data['endpoint']['expectation']] in self.data['endpoint']['expectation']]
def evaluate(self): def evaluate(self):
#TODO(mtakaki|2016-04-27): Add support to configurable timeout. """Sends the request to the URL set in the configuration and executes
each one of the expectations, one by one. The status will be updated
according to the expectation results.
"""
try: try:
self.request = requests.request(self.data['endpoint']['method'], self.request = requests.request(self.data['endpoint']['method'],
self.data['endpoint']['url']) self.data['endpoint']['url'],
timeout=self.data['endpoint']['timeout'])
except requests.ConnectionError: except requests.ConnectionError:
logging.warning('The URL is unreachable: %s %s' % logging.warning('The URL is unreachable: %s %s' %
(self.data['endpoint']['method'], (self.data['endpoint']['method'],
@@ -39,9 +44,8 @@ class Configuration(object):
self.status = 3 self.status = 3
return return
# We, by default, assume the API is healthy. # We initially assume the API is healthy.
self.status = 1 self.status = 1
self.message = ''
for expectation in self.expectations: for expectation in self.expectations:
status = expectation.get_status(self.request) status = expectation.get_status(self.request)
@@ -66,11 +70,18 @@ class Configuration(object):
' status: [%d]' % (component_request.status_code, self.status)) ' status: [%d]' % (component_request.status_code, self.status))
class Expectaction(object): class Expectaction(object):
"""Base class for URL result expectations. Any new excpectation should extend
this class and the name added to create() method.
"""
@staticmethod @staticmethod
def create(configuration): def create(configuration):
"""Creates a list of expectations based on the configuration types
list.
"""
expectations = { expectations = {
'HTTP_STATUS': HttpStatus, 'HTTP_STATUS': HttpStatus,
'LATENCY': Latency 'LATENCY': Latency,
'REGEX': Regex
} }
return expectations.get(configuration['type'])(configuration) return expectations.get(configuration['type'])(configuration)
@@ -110,4 +121,18 @@ class Latency(Expectaction):
return 2 return 2
def get_message(self, response): def get_message(self, response):
return 'Latency above threshold: %d' % (response.elapsed.total_seconds(),) return 'Latency above threshold: %.4f' % (response.elapsed.total_seconds(),)
class Regex(Expectaction):
def __init__(self, configuration):
self.regex = re.compile(configuration['regex'])
def get_status(self, response):
if self.regex.match(response.text):
return 1
else:
return 3
def get_message(self, response):
return 'Regex did not match anything in the body'

View File

@@ -7,14 +7,21 @@ import time
class Agent(object): class Agent(object):
"""Monitor agent that will be constantly verifying if the URL is healthy
and updating the component.
"""
def __init__(self, configuration): def __init__(self, configuration):
self.configuration = configuration self.configuration = configuration
def execute(self): def execute(self):
"""Will verify the API status and push the status and metrics to the
cachet server.
"""
self.configuration.evaluate() self.configuration.evaluate()
self.configuration.push_status_and_metrics() self.configuration.push_status_and_metrics()
def start(self): def start(self):
"""Sets up the schedule based on the configuration file."""
schedule.every(self.configuration.data['frequency']).seconds.do(self.execute) schedule.every(self.configuration.data['frequency']).seconds.do(self.execute)

View File

@@ -1,6 +1,7 @@
endpoint: endpoint:
url: http://www.google.com url: http://localhost:8080/swagger
method: GET method: GET
timeout: 0.010
expectation: expectation:
- type: HTTP_STATUS - type: HTTP_STATUS
status: 200 status: 200

View File

@@ -1,5 +1,8 @@
PyYAML==3.11 PyYAML==3.11
ipython==4.2.0 ipython==4.2.0
mock==2.0.0
pudb==2016.1 pudb==2016.1
pytest==2.9.1
pytest-cov==2.2.1
requests==2.9.1 requests==2.9.1
schedule==0.3.2 schedule==0.3.2

11
setup.py Normal file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
from distutils.core import setup
setup(name='cachet-url-monitor',
version='0.1',
description='Cachet URL monitor plugin',
author='Mitsuo Takaki',
author_email='mitsuotakaki@gmail.com',
url='https://github.com/mtakaki/cachet-url-monitor',
packages=['cachet_url_monitor'],
)

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
import unittest
from cachet_url_monitor.configuration import Configuration
class ConfigurationTest(unittest.TestCase):
def test_init(self):
configuration = Configuration('config.yml')
assert len(configuration.data) == 3
assert len(configuration.expectations) == 2

99
tests/test_expectation.py Normal file
View File

@@ -0,0 +1,99 @@
#!/usr/bin/env python
import mock
import re
import unittest
from cachet_url_monitor.configuration import Expectaction,Latency
from cachet_url_monitor.configuration import HttpStatus,Regex
class LatencyTest(unittest.TestCase):
def setUp(self):
self.expectation = Latency({'type': 'LATENCY', 'threshold': 1})
def test_init(self):
assert self.expectation.threshold == 1
def test_get_status_healthy(self):
def total_seconds():
return 0.1
request = mock.Mock()
elapsed = mock.Mock()
request.elapsed = elapsed
elapsed.total_seconds = total_seconds
assert self.expectation.get_status(request) == 1
def test_get_status_unhealthy(self):
def total_seconds():
return 2
request = mock.Mock()
elapsed = mock.Mock()
request.elapsed = elapsed
elapsed.total_seconds = total_seconds
assert self.expectation.get_status(request) == 2
def test_get_message(self):
def total_seconds():
return 0.1
request = mock.Mock()
elapsed = mock.Mock()
request.elapsed = elapsed
elapsed.total_seconds = total_seconds
assert self.expectation.get_message(request) == ('Latency above '
'threshold: 0.1000')
class HttpStatusTest(unittest.TestCase):
def setUp(self):
self.expectation = HttpStatus({'type': 'HTTP_STATUS', 'status': 200})
def test_init(self):
assert self.expectation.status == 200
def test_get_status_healthy(self):
request = mock.Mock()
request.status_code = 200
assert self.expectation.get_status(request) == 1
def test_get_status_unhealthy(self):
request = mock.Mock()
request.status_code = 400
assert self.expectation.get_status(request) == 3
def test_get_message(self):
request = mock.Mock()
request.status_code = 400
assert self.expectation.get_message(request) == ('Unexpected HTTP '
'status (400)')
class RegexTest(unittest.TestCase):
def setUp(self):
self.expectation = Regex({'type': 'REGEX', 'regex': '.*(found stuff).*'})
def test_init(self):
assert self.expectation.regex == re.compile('.*(found stuff).*')
def test_get_status_healthy(self):
request = mock.Mock()
request.text = 'We cound found stuff in this body.'
assert self.expectation.get_status(request) == 1
def test_get_status_unhealthy(self):
request = mock.Mock()
request.text = 'We will not find here'
assert self.expectation.get_status(request) == 3
def test_get_message(self):
request = mock.Mock()
request.text = 'We will not find here'
assert self.expectation.get_message(request) == ('Regex did not match '
'anything in the body')