Skip to content

Commit

Permalink
Adding Excalibur detector infrastructure to plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
timcnicholls committed May 24, 2016
1 parent 46644e6 commit 0abf558
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 47 deletions.
67 changes: 44 additions & 23 deletions plugins/excalibur/excalibur/adapter.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,67 @@
"""
adapter.py - EXCALIBUR API adapter for the ODIN server
adapter.py - EXCALIBUR API adapter for the ODIN server.
Tim Nicholls, STFC Application Engineering Group
"""

import logging
from odin.adapters.adapter import ApiAdapter, ApiAdapterResponse, request_types, response_types

from excalibur.fem import ExcaliburFem, ExcaliburFemError
from excalibur.detector import ExcaliburDetector, ExcaliburDetectorError

class ExcaliburAdapter(ApiAdapter):

"""
ExcaliburAdapter class
def require_valid_detector(func):
"""Decorator method for request handler methods to check that adapter has valid detector."""
def wrapper(_self, path, request):
if _self.detector is None:
return ApiAdapterResponse(
'Invalid detector configuration', status_code=500
)
return func(_self, path, request)
return wrapper


class ExcaliburAdapter(ApiAdapter):
"""ExcaliburAdapter class.
This class provides the adapter interface between the ODIN server and the EXCALIBUR detector system,
transforming the REST-like API HTTP verbs into the appropriate EXCALIBUR detector control actions
This class provides the adapter interface between the ODIN server and the EXCALIBUR detector
system, transforming the REST-like API HTTP verbs into the appropriate EXCALIBUR detector
control actions
"""

def __init__(self, **kwargs):
"""Initialise the ExcaliburAdapter object.
"""Initialise the ExcaliburAdapter object"""

:param kwargs: keyword arguments passed to ApiAdapter as options.
"""
super(ExcaliburAdapter, self).__init__(**kwargs)

self.fem = ExcaliburFem(123)
logging.debug('ExcaliburAdapter loaded')

# Parse the FEM connection information out of the adapter options and initialise the
# detector object
self.detector = None
if 'detector_fems' in self.options:
fems = [tuple(fem.split(':')) for fem in self.options['detector_fems'].split(',')]
try:
self.detector = ExcaliburDetector(fems)
logging.debug('ExcaliburAdapter loaded')
except ExcaliburDetectorError as e:
logging.error('ExcaliburAdapter failed to initialise detector: %s', e)
else:
logging.warning('No detector FEM option specified in configuration')

@request_types('application/json')
@response_types('application/json', default='application/json')
@require_valid_detector
def get(self, path, request):
"""Handle an HTTP GET request.
"""
Implementation of the HTTP GET verb for ExcaliburAdapter
This method is the implementation of the HTTP GET handler for ExcaliburAdapter.
:param path: URI path of the GET request
:param request: Tornado HTTP request object
:return: ApiAdapterResponse object to be returned to the client
"""

response = {'response' : '{}: GET on path {}'.format(self.name, path)}
response = {'response': '{}: GET on path {}'.format(self.name, path)}
status_code = 200

logging.debug(response)
Expand All @@ -49,35 +70,35 @@ def get(self, path, request):

@request_types('application/json')
@response_types('application/json', default='application/json')
@require_valid_detector
def put(self, path, request):
"""Handle an HTTP PUT request.
"""
Implementation of the HTTP PUT verb for ExcaliburAdapter
This method is the implementation of the HTTP PUT handler for ExcaliburAdapter/
:param path: URI path of the PUT request
:param request: Tornado HTTP request object
:return: ApiAdapterResponse object to be returned to the client
"""

response = {'response': '{}: PUT on path {}'.format(self.name, path)}
status_code = 200

logging.debug(response)

return ApiAdapterResponse(response, status_code=status_code)


@request_types('application/json')
@response_types('application/json', default='application/json')
@require_valid_detector
def delete(self, path, request):
"""
Implementation of the HTTP DELETE verb for ExcaliburAdapter
"""Handle an HTTP DELETE request.
This method is the implementation of the HTTP DELETE verb for ExcaliburAdapter.
:param path: URI path of the DELETE request
:param request: Tornado HTTP request object
:return: ApiAdapterResponse object to be returned to the client
"""

response = {'response': '{}: DELETE on path {}'.format(self.name, path)}
status_code = 200

Expand Down
39 changes: 32 additions & 7 deletions plugins/excalibur/excalibur/detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ class ExcaliburDetectorError(Exception):
pass


class ExcaliburDetectorFemConnection(object):
"""Internally used container class describing FEM connection."""

STATE_DISCONNECTED = 0
STATE_CONNECTED = 1

def __init__(self, id, host, port, fem=None, state=STATE_DISCONNECTED):

self.id = id
self.host = host
self.port = port
self.fem = fem
self.state = self.STATE_DISCONNECTED


class ExcaliburDetector(object):
"""EXCALIBUR detector class.
Expand All @@ -25,12 +40,22 @@ def __init__(self, fem_connections):
:param fem_connections: list of (address, port) FEM connections to make
"""
if not isinstance(fem_connections, list):
self.fems = []
if not isinstance(fem_connections, (list, tuple)):
fem_connections = [fem_connections]
if isinstance(fem_connections, tuple) and len(fem_connections) == 2:
fem_connections = [fem_connections]
print fem_connections, type(fem_connections)

idx = 1
for (host, port) in fem_connections:
logging.info("%d: %s:%d", idx, host, port)
print(idx, host, port)
idx += 1
try:
id = 1
for (host, port) in fem_connections:
self.fems.append(ExcaliburDetectorFemConnection(id, host, int(port)))
id += 1
except Exception as e:
raise ExcaliburDetectorError('Failed to initialise detector FEM list: {}'.format(e))

def connect(self):
"""Establish connection to the detectors FEMs."""
for idx in range(len(self.fems)):
the_fem = ExcaliburFem(self.fems[idx].id)
self.fems[idx].fem = the_fem
2 changes: 1 addition & 1 deletion plugins/excalibur/excalibur/testing/ecalibur_test.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ logging = debug

[adapter.excalibur]
module = excalibur.adapter.ExcaliburAdapter
test_param = 13.46
detector_fems = 192.168.0.1:6969, 192.168.0.2:6969, 192.168.0.3:6969
51 changes: 43 additions & 8 deletions plugins/excalibur/excalibur/testing/test_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,40 @@

from excalibur.adapter import ExcaliburAdapter

class TestExcaliburAdapter():
class ExcaliburAdapterFixture(object):

@classmethod
def setup_class(cls):

cls.adapter = ExcaliburAdapter()
cls.path = '/excalibur/test/path'
def setup_class(cls, **adapter_params):
cls.adapter = ExcaliburAdapter(**adapter_params)
cls.path = '/test/path'
cls.request = Mock()
cls.request.headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}


class TestExcaliburAdapter(ExcaliburAdapterFixture):

@classmethod
def setup_class(cls):

adapter_params = {
'detector_fems': '192.168.0.1:6969, 192.168.0.2:6969, 192.168.0.3:6969',
}
super(TestExcaliburAdapter, cls).setup_class(**adapter_params)

def test_adapter_name(self):

assert_equal(self.adapter.name, 'ExcaliburAdapter')

def test_adapter_single_fem(self):
adapter_params = {'detector_fems': '192.168.0.1:6969'}
adapter = ExcaliburAdapter(**adapter_params)
assert_equal(len(adapter.detector.fems), 1)

def test_adapter_bad_fem_config(self):
adapter_params = {'detector_fems': '192.168.0.1 6969, 192.168.0.2:6969'}
adapter = ExcaliburAdapter(**adapter_params)
assert_equal(adapter.detector, None)

def test_adapter_get(self):
expected_response = {'response': '{}: GET on path {}'.format(self.adapter.name, self.path)}
response = self.adapter.get(self.path, self.request)
Expand All @@ -40,9 +60,24 @@ def test_adapter_put(self):
assert_equal(response.data, expected_response)
assert_equal(response.status_code, 200)


def test_adapter_delete(self):
expected_response = {'response': '{}: DELETE on path {}'.format(self.adapter.name, self.path)}
expected_response = {
'response': '{}: DELETE on path {}'.format(self.adapter.name, self.path)
}
response = self.adapter.delete(self.path, self.request)
assert_equal(response.data, expected_response)
assert_equal(response.status_code, 200)
assert_equal(response.status_code, 200)


class TestExcaliburAdapterNoFems(ExcaliburAdapterFixture):

@classmethod
def setup_class(cls):
super(TestExcaliburAdapterNoFems, cls).setup_class()

def test_adapter_no_fems(self):
assert_equal(self.adapter.detector, None)

def test_adapter_no_fems_get(self):
response = self.adapter.get(self.path, self.request)
assert_equal(response.status_code, 500)
30 changes: 24 additions & 6 deletions plugins/excalibur/excalibur/testing/test_detector.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

from nose.tools import *
import logging

from excalibur.detector import ExcaliburDetector, ExcaliburDetectorError

Expand All @@ -14,18 +15,35 @@ class TestExcaliburDetector():
@classmethod
def setup_class(cls):
cls.detector_fems = [('192.168.0.1', 6969), ('192.168.0.2', 6969), ('192.168.0.3', 6969)]
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG)

def test_detector_simple_init(self):

detector = ExcaliburDetector(self.detector_fems)
print detector
assert_equal(len(detector.fems), len(self.detector_fems))

def test_detector_single_fem(self):

detector = ExcaliburDetector(self.detector_fems[0])
print detector
assert_equal(len(detector.fems), 1)

# def test_detector_bad_fem_spec(self):
#
# detector = ExcaliburDetector([1, 2, 3])
# print detector
def test_detector_bad_fem_spec(self):

with assert_raises_regexp(ExcaliburDetectorError, "Failed to initialise detector FEM list"):
detector = ExcaliburDetector([1, 2, 3])

with assert_raises_regexp(ExcaliburDetectorError, "Failed to initialise detector FEM list"):
detector = ExcaliburDetector('nonsense')

def test_detector_bad_fem_port(self):
bad_detector_fems = self.detector_fems[:]
bad_detector_fems[0] = ('192.168.0.1', 'bad_port')

with assert_raises_regexp(ExcaliburDetectorError, "Failed to initialise detector FEM list"):
detector = ExcaliburDetector(bad_detector_fems)

def test_detector_connect_fems(self):

detector = ExcaliburDetector(self.detector_fems)
detector.connect()
4 changes: 2 additions & 2 deletions plugins/excalibur/excalibur/testing/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def setup_class(cls):
adapter_config = {
'excalibur': {
'module': 'excalibur.adapter.ExcaliburAdapter',
'test': 123.4,
'detector_fems': '192.168.0.1:6969, 192.168.0.2:6969, 192.168.0.3:6969',
}
}
super(TestExcaliburPlugin, cls).setup_class(adapter_config)
Expand All @@ -36,4 +36,4 @@ def test_simple_get(self):
headers=headers
)
assert_equal(result.status_code, 200)
print(result.json())
assert_equal(result.json()['response'], 'ExcaliburAdapter: GET on path config/none')

0 comments on commit 0abf558

Please sign in to comment.