Skip to content

Commit

Permalink
Added adapter cleanup mechanism and associated unit testing.
Browse files Browse the repository at this point in the history
  • Loading branch information
timcnicholls committed Nov 8, 2016
1 parent 9430141 commit a56ac1c
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 9 deletions.
9 changes: 9 additions & 0 deletions odin/adapters/adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,15 @@ def delete(self, path, request):
response = "DELETE method not implemented by {}".format(self.name)
return ApiAdapterResponse(response, status_code=400)

def cleanup(self):
"""Clean up adapter state.
This is an abstract implementation of the cleanup mechanism provided to allow adapters
to clean up their state (e.g. disconnect cleanly from the device being controlled, set
some status message).
"""
pass


class ApiAdapterResponse(object):
"""
Expand Down
12 changes: 11 additions & 1 deletion odin/adapters/dummy.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Dummy adapter class for the ODIN server.
""" Dummy adapter class for the ODIN server.
This class implements a dummy adapter for the ODIN server, demonstrating the
basic adapter implementation and providing a loadable adapter for testing
Expand Down Expand Up @@ -125,3 +125,13 @@ def delete(self, path, request):
logging.debug(response)

return ApiAdapterResponse(response, status_code=status_code)

def cleanup(self):
"""Clean up the state of the adapter.
This method cleans up the state of the adapter, which in this case is
trivially setting the background task counter back to zero for test
purposes.
"""
logging.debug("DummyAdapter cleanup: resetting background test counter")
self.background_task_counter = 0
12 changes: 12 additions & 0 deletions odin/http/routes/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,3 +244,15 @@ def adapter(self, subsystem):
:return: adapter for subsystem
"""
return self.adapters[subsystem]

def cleanup_adapters(self):
"""Clean up state of registered adapters.
This calls the cleanup method present in any registered adapters, allowing them to
clean up their state (e.g. connected hardware) in a controlled fashion at shutdown.
"""
for adapter_name, adapter in self.adapters.items():
try:
getattr(adapter, 'cleanup')()
except AttributeError:
logging.debug("Adapter %s has no cleanup method", adapter_name)
11 changes: 8 additions & 3 deletions odin/http/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,13 @@ def __init__(self, debug_mode=False, static_path='./static', adapters=None):
}

# Create an API route
api_route = ApiRoute()
self.api_route = ApiRoute()

# Register adapters with the API route and get handlers
for adapter in adapters:
api_route.register_adapter(adapters[adapter])
self.api_route.register_adapter(adapters[adapter])

handlers = api_route.get_handlers()
handlers = self.api_route.get_handlers()

# Create a default route for static content and get handlers
default_route = DefaultRoute(static_path)
Expand All @@ -51,3 +51,8 @@ def listen(self, port, host=''):
:param host: host address to listen on
"""
self.application.listen(port, host)

def cleanup_adapters(self):
"""Clean up state of registered adapters.
"""
self.api_route.cleanup_adapters()
3 changes: 3 additions & 0 deletions odin/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ def main(argv=None):
# Enter IO processing loop
tornado.ioloop.IOLoop.instance().start()

# At shutdown, clean up the state of the loaded adapters
http_server.cleanup_adapters()

logging.info('ODIN server shutdown')

return 0
Expand Down
22 changes: 21 additions & 1 deletion odin/testing/adapters/test_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,15 @@ def test_api_adapter_has_options(self):
opts = self.adapter.options
assert_equal(opts, self.adapter_options)

def test_api_adapter_cleanup(self):

raised = False
try:
self.adapter.cleanup()
except:
raised = True
assert_false(raised)

class TestApiAdapterResponse():

def test_simple_response(self):
Expand Down Expand Up @@ -137,13 +146,24 @@ def test_decorated_method_plaintext(self):

plain_request = Mock()
plain_request.data = 'Simple plain text request'
plain_request.headers = {'Accept' : 'text/plain', 'Content-Type': 'text/plain'}
plain_request.headers = {'Accept': 'text/plain', 'Content-Type': 'text/plain'}

response = self.decorated_method(self.path, plain_request)
assert_equal(response.status_code, self.response_code)
assert_equal(response.content_type, self.response_type_plain)
assert_equal(response.data, self.response_data_plain)

def test_decorated_method_default(self):

json_request = Mock()
json_request.data = '{\'request\': 1234}'
json_request.headers = {'Accept': '*/*', 'Content-Type': 'application/json'}

response = self.decorated_method(self.path, json_request)
assert_equal(response.status_code, self.response_code)
assert_equal(response.content_type, self.response_type_json)
assert_equal(response.data, self.response_data_json)

def test_decorated_method_json(self):

json_request = Mock()
Expand Down
5 changes: 5 additions & 0 deletions odin/testing/adapters/test_dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,8 @@ def test_adapter_put_bad_accept_type(self):
response = self.adapter.put(self.path, bad_request)
assert_equal(response.data, 'Requested content types not supported')
assert_equal(response.status_code, 406)

def test_adapter_cleanup(self):
self.adapter.background_task_counter = 1000
self.adapter.cleanup()
assert_equal(self.adapter.background_task_counter, 0)
18 changes: 14 additions & 4 deletions odin/testing/routes/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,20 @@ def test_register_adapter_badclass(self):
with assert_raises_regexp(ApiError, 'has no attribute \'BadAdapter\''):
self.api_route.register_adapter(adapter_config, fail_ok=False)

def test_register_adapter_no_cleanup(self):

adapter_name = 'dummy_no_clean'
adapter_config = AdapterConfig(adapter_name, 'odin.adapters.dummy.DummyAdapter')
self.api_route.register_adapter(adapter_config)
self.api_route.adapters[adapter_name].cleanup = Mock(side_effect=AttributeError())

raised = False
try:
self.api_route.cleanup_adapters()
except:
raised = True

assert_false(raised)

class TestApiHandler():

Expand Down Expand Up @@ -141,7 +155,3 @@ def test_handler_response_json_bad_type(self):
with assert_raises_regexp(ApiError,
'A response with content type application/json must have str or dict data'):
self.handler.respond(bad_response)




0 comments on commit a56ac1c

Please sign in to comment.