From 4a66c896f254e5911d41ebfdeffd1b34e8fdb8aa Mon Sep 17 00:00:00 2001 From: Megan Henning <meganhenning@flywheel.io> Date: Mon, 28 Nov 2016 17:19:15 -0600 Subject: [PATCH] Add device handler --- api/api.py | 8 +++ api/base.py | 5 +- api/handlers/devicehandler.py | 124 ++++++++++++++++++++++++++++++++++ 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 api/handlers/devicehandler.py diff --git a/api/api.py b/api/api.py index 721415be..52d9a2f5 100644 --- a/api/api.py +++ b/api/api.py @@ -25,6 +25,7 @@ from .handlers import resolvehandler from .handlers import searchhandler from .handlers import schemahandler from .handlers import reporthandler +from .handlers import devicehandler from .request import SciTranRequest log = config.log @@ -115,6 +116,13 @@ routes = [ webapp2.Route(_format(r'/<uid:{user_id_re}>/groups'), grouphandler.GroupHandler, handler_method='get_all', methods=['GET'], name='groups'), webapp2.Route(_format(r'/<uid:{user_id_re}>/avatar'), userhandler.UserHandler, handler_method='avatar', methods=['GET'], name='avatar'), ]), + webapp2.Route(r'/api/devices', devicehandler.DeviceHandler, name='device_list', handler_method='get_all', methods=['GET']), + webapp2.Route(r'/api/devices', devicehandler.DeviceHandler, methods=['POST']), + webapp2_extras.routes.PathPrefixRoute(r'/api/devices', [ + webapp2.Route(r'/status', devicehandler.DeviceHandler, handler_method='get_status', methods=['GET']), + webapp2.Route(r'/self', devicehandler.DeviceHandler, handler_method='get_self', methods=['GET']), + webapp2.Route(_format(r'/<device_id:[^/]+>'), devicehandler.DeviceHandler, name='device_details', methods=['GET']), + ]), webapp2.Route(r'/api/jobs', JobsHandler), webapp2_extras.routes.PathPrefixRoute(r'/api/jobs', [ webapp2.Route(r'/next', JobsHandler, handler_method='next', methods=['GET']), diff --git a/api/base.py b/api/base.py index 7412d634..1ff301d0 100644 --- a/api/base.py +++ b/api/base.py @@ -241,9 +241,10 @@ class RequestHandler(webapp2.RequestHandler): }, { '$set': { '_id': self.origin['id'], - 'last-seen': datetime.datetime.utcnow(), + 'last_seen': datetime.datetime.utcnow(), 'method': self.origin['method'], - 'name': self.origin['name'] + 'name': self.origin['name'], + 'errors': None # Reset errors list if device checks in } }, upsert=True, diff --git a/api/handlers/devicehandler.py b/api/handlers/devicehandler.py new file mode 100644 index 00000000..1890abc8 --- /dev/null +++ b/api/handlers/devicehandler.py @@ -0,0 +1,124 @@ +import datetime as dt + +from .. import base +from .. import config +from .. import util +from ..dao import containerstorage, APIPermissionException, APINotFoundException +from ..types import Origin + +log = config.log + +Status = util.Enum('Origin', { + 'ok': 'ok', # Device's last seen time is shorter than expected interval for checkin, no errors listed. + 'missing': 'missing', # Device's last seen time is longer than the expected interval for checkin, but no errors listed. + 'error': 'error' , # Device has errors listed. + 'unknown': 'unknown' # Device did not set an expected checkin interval. +}) + + +def require_login(handler_method): + """ + A decorator to ensure the request is not a public request. + + Accepts superuser and non-superuser requests. + Accepts drone and user requests. + """ + def check_login(self, *args, **kwargs): + if self.public_request: + raise APIPermissionException('Login required.') + return handler_method(self, *args, **kwargs) + return check_login + +def require_superuser(handler_method): + """ + A decorator to ensure the request is made as superuser. + + Accepts drone and user requests. + """ + def check_superuser(self, *args, **kwargs): + if not self.superuser_request: + raise APIPermissionException('Superuser required.') + return handler_method(self, *args, **kwargs) + return check_superuser + +def require_drone(handler_method): + """ + A decorator to ensure the request is made as a drone. + + Will also ensure superuser, which is implied with a drone request. + """ + def check_drone(self, *args, **kwargs): + if self.origin.get('type', '') != Origin.device: + raise APIPermissionException('Drone request required.') + if not self.superuser_request: + raise APIPermissionException('Superuser required.') + return handler_method(self, *args, **kwargs) + return check_drone + + +class DeviceHandler(base.RequestHandler): + + def __init__(self, request=None, response=None): + super(DeviceHandler, self).__init__(request, response) + self.storage = containerstorage.ContainerStorage('devices', use_object_id=False) + + @require_login + def get(self, device_id): + return self.storage.get_container(device_id) + + @require_login + def get_all(self): + return self.storage.get_all_el(None, None, None) + + @require_drone + def get_self(self): + device_id = self.origin.get('id', '') + return self.storage.get_container(device_id) + + @require_drone + def post(self): + device_id = self.origin.get('id', '') + payload = self.request.json_body + #validators.validate_data(payload, 'device.json', 'input', 'PUT', optional=True) + + result = self.storage.update_el(device_id, payload) + if result.modified_count == 1: + return {'modified': result.modified_count} + else: + raise APINotFoundException('Device with id {} not found, state not updated'.format(device_id)) + + @require_superuser + def delete(self, device_id): + raise NotImplementedError() + + def get_status(self): + devices = self.storage.get_all_el(None, None, None) + response = {} + now = dt.datetime.now() + for d in devices: + d_obj = {} + d_obj['last_seen'] = d.get('last_seen') + + if d.get('errors'): + d_obj['status'] = str(Status.error) + d_obj['errors'] = d.get('errors') + response[d.get('_id')] = d_obj.copy() + + elif not d.get('interval'): + d_obj['status'] = str(Status.unknown) + response[d.get('_id')] = d_obj.copy() + + elif (now-d.get('last_seen', now)).seconds > d.get('interval'): + d_obj['status'] = str(Status.missing) + response[d.get('_id')] = d_obj.copy() + + else: + d_obj['status'] = str(Status.ok) + response[d.get('_id')] = d_obj.copy() + + return response + + + + + -- GitLab