Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
E
ecommerce
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
edx
ecommerce
Commits
a5aba4fb
Commit
a5aba4fb
authored
Mar 22, 2015
by
Renzo Lucioni
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Gracefully handle LMS connection errors
parent
912c168a
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
74 additions
and
48 deletions
+74
-48
ecommerce/health/constants.py
+13
-0
ecommerce/health/tests/test_views.py
+41
-34
ecommerce/health/views.py
+20
-14
No files found.
ecommerce/health/constants.py
0 → 100644
View file @
a5aba4fb
"""Health check constants."""
class
Status
(
object
):
"""Health statuses."""
OK
=
u"OK"
UNAVAILABLE
=
u"UNAVAILABLE"
class
UnavailabilityMessage
(
object
):
"""Messages to be logged when services are unavailable."""
DATABASE
=
u"Unable to connect to database"
LMS
=
u"Unable to connect to LMS"
ecommerce/health/tests/test_views.py
View file @
a5aba4fb
...
...
@@ -4,74 +4,81 @@ import logging
import
mock
from
requests
import
Response
from
requests.exceptions
import
RequestException
from
rest_framework
import
status
from
django.test
import
TestCase
from
django.db
import
DatabaseError
from
django.core.urlresolvers
import
reverse
from
ecommerce.health.
views
import
OK
,
UNAVAILABLE
from
ecommerce.health.
constants
import
Status
@mock.patch
(
'requests.get'
)
class
HealthViewTests
(
TestCase
):
class
HealthTests
(
TestCase
):
"""Tests of the health endpoint."""
def
setUp
(
self
):
self
.
fake_lms_response
=
Response
()
# Override all loggers, suppressing logging calls of severity CRITICAL and below
logging
.
disable
(
logging
.
CRITICAL
)
def
tearDown
(
self
):
# Remove logger override
logging
.
disable
(
logging
.
NOTSET
)
self
.
addCleanup
(
logging
.
disable
,
logging
.
NOTSET
)
def
test_healthy
(
self
,
mock_lms_request
):
def
test_all_services_available
(
self
,
mock_lms_request
):
"""Test that the endpoint reports when all services are healthy."""
self
.
fake_lms_response
.
status_code
=
status
.
HTTP_200_OK
mock_lms_request
.
return_value
=
self
.
fake_lms_response
response
=
self
.
client
.
get
(
reverse
(
'health'
))
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_200_OK
)
self
.
assertEqual
(
response
[
'content-type'
],
'application/json'
)
expected_data
=
{
'overall_status'
:
OK
,
'detailed_status'
:
{
'database_status'
:
OK
,
'lms_status'
:
OK
}
}
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
expected_data
)
self
.
_assert_health
(
status
.
HTTP_200_OK
,
Status
.
OK
,
Status
.
OK
,
Status
.
OK
)
@mock.patch
(
'django.db.backends.BaseDatabaseWrapper.cursor'
,
mock
.
Mock
(
side_effect
=
DatabaseError
))
def
test_database_outage
(
self
,
mock_lms_request
):
"""Test that the endpoint reports when the database is unavailable."""
self
.
fake_lms_response
.
status_code
=
status
.
HTTP_200_OK
mock_lms_request
.
return_value
=
self
.
fake_lms_response
response
=
self
.
client
.
get
(
reverse
(
'health'
))
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_503_SERVICE_UNAVAILABLE
)
self
.
assertEqual
(
response
[
'content-type'
],
'application/json'
)
self
.
_assert_health
(
status
.
HTTP_503_SERVICE_UNAVAILABLE
,
Status
.
UNAVAILABLE
,
Status
.
UNAVAILABLE
,
Status
.
OK
)
expected_data
=
{
'overall_status'
:
UNAVAILABLE
,
'detailed_status'
:
{
'database_status'
:
UNAVAILABLE
,
'lms_status'
:
OK
}
}
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
expected_data
)
def
test_health_lms_outage
(
self
,
mock_lms_request
):
def
test_lms_outage
(
self
,
mock_lms_request
):
"""Test that the endpoint reports when the LMS is experiencing difficulties."""
self
.
fake_lms_response
.
status_code
=
status
.
HTTP_503_SERVICE_UNAVAILABLE
mock_lms_request
.
return_value
=
self
.
fake_lms_response
self
.
_assert_health
(
status
.
HTTP_503_SERVICE_UNAVAILABLE
,
Status
.
UNAVAILABLE
,
Status
.
OK
,
Status
.
UNAVAILABLE
)
def
test_lms_connection_failure
(
self
,
mock_lms_request
):
"""Test that the endpoint reports when it cannot contact the LMS."""
mock_lms_request
.
side_effect
=
RequestException
self
.
_assert_health
(
status
.
HTTP_503_SERVICE_UNAVAILABLE
,
Status
.
UNAVAILABLE
,
Status
.
OK
,
Status
.
UNAVAILABLE
)
def
_assert_health
(
self
,
status_code
,
overall_status
,
database_status
,
lms_status
):
"""Verify that the response matches expectations."""
response
=
self
.
client
.
get
(
reverse
(
'health'
))
self
.
assertEqual
(
response
.
status_code
,
status
.
HTTP_503_SERVICE_UNAVAILABLE
)
self
.
assertEqual
(
response
.
status_code
,
status
_code
)
self
.
assertEqual
(
response
[
'content-type'
],
'application/json'
)
expected_data
=
{
'overall_status'
:
UNAVAILABLE
,
'overall_status'
:
overall_status
,
'detailed_status'
:
{
'database_status'
:
OK
,
'lms_status'
:
UNAVAILABLE
'database_status'
:
database_status
,
'lms_status'
:
lms_status
}
}
self
.
assertDictEqual
(
json
.
loads
(
response
.
content
),
expected_data
)
ecommerce/health/views.py
View file @
a5aba4fb
...
...
@@ -2,16 +2,17 @@
import
logging
import
requests
from
requests.exceptions
import
RequestException
from
rest_framework
import
status
from
django.conf
import
settings
from
django.db
import
connection
,
DatabaseError
from
django.http
import
JsonResponse
from
ecommerce.health.constants
import
Status
,
UnavailabilityMessage
logger
=
logging
.
getLogger
(
__name__
)
OK
=
u'OK'
UNAVAILABLE
=
u'UNAVAILABLE'
LMS_HEALTH_PAGE
=
getattr
(
settings
,
'LMS_HEARTBEAT_URL'
)
...
...
@@ -34,26 +35,31 @@ def health(_):
>>> response.content
'{"overall_status": "OK", "detailed_status": {"database_status": "OK", "lms_status": "OK"}}'
"""
overall_status
=
database_status
=
lms_status
=
UNAVAILABLE
overall_status
=
database_status
=
lms_status
=
Status
.
UNAVAILABLE
try
:
cursor
=
connection
.
cursor
()
cursor
.
execute
(
"SELECT 1"
)
cursor
.
fetchone
()
cursor
.
close
()
database_status
=
OK
database_status
=
Status
.
OK
except
DatabaseError
:
logger
.
critical
(
'Unable to connect to database'
)
database_status
=
UNAVAILABLE
response
=
requests
.
get
(
LMS_HEALTH_PAGE
)
if
response
.
status_code
==
status
.
HTTP_200_OK
:
lms_status
=
OK
else
:
logger
.
critical
(
'Unable to connect to LMS'
)
lms_status
=
UNAVAILABLE
database_status
=
Status
.
UNAVAILABLE
try
:
response
=
requests
.
get
(
LMS_HEALTH_PAGE
)
if
response
.
status_code
==
status
.
HTTP_200_OK
:
lms_status
=
Status
.
OK
else
:
logger
.
critical
(
UnavailabilityMessage
.
LMS
)
lms_status
=
Status
.
UNAVAILABLE
except
RequestException
:
logger
.
critical
(
UnavailabilityMessage
.
LMS
)
lms_status
=
Status
.
UNAVAILABLE
overall_status
=
OK
if
(
database_status
==
lms_status
==
OK
)
else
UNAVAILABLE
overall_status
=
Status
.
OK
if
(
database_status
==
lms_status
==
Status
.
OK
)
else
Status
.
UNAVAILABLE
data
=
{
'overall_status'
:
overall_status
,
...
...
@@ -63,7 +69,7 @@ def health(_):
},
}
if
overall_status
==
OK
:
if
overall_status
==
Status
.
OK
:
return
JsonResponse
(
data
)
else
:
return
JsonResponse
(
data
,
status
=
status
.
HTTP_503_SERVICE_UNAVAILABLE
)
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment