| @@ -154,19 +154,19 @@ async def log_event(tag, board=None, user=None, extra={}): | |||
| class TimeOut(Attribute): | |||
| ''' | |||
| Implement a TimeOut functionality. The argument val (first and | |||
| Implement timeout functionality. The argument val (first and | |||
| only) to __init__ is a number of seconds for the timeout to | |||
| last. This will start the time ticking on activation. If it | |||
| is not deactivated before the timer expires, it will deactivate | |||
| last. This will start the timer on activation. If it is not | |||
| deactivated before the timer expires, it will release | |||
| the board itself. | |||
| Not that this uses the asyncio loop timescale and NOT UTC. This | |||
| means that over large durations, the clock will drift. This means | |||
| that over time, the "expired" time will change. | |||
| Note that this uses the asyncio loop timescale and NOT UTC. This | |||
| means that over large durations, the clock likely will drift, | |||
| such that the returned "expired" time will change. | |||
| While the board is not activated, it will display the timeout in | |||
| seconds. When the board is activated, the getvalue will return | |||
| the time the board will be deactivated. | |||
| the time the board will be deactivated in (approximate) UTC. | |||
| ''' | |||
| defattrname = 'timeout' | |||
| @@ -257,6 +257,11 @@ class TimeOut(Attribute): | |||
| self._task = asyncio.create_task(self.timeout_coro()) | |||
| class EtherIface(DefROAttribute): | |||
| '''Add the specified ethernet interface to the board when | |||
| activated. This is for adding specific interfaces, such as | |||
| the vlan interface that maps to the board. | |||
| ''' | |||
| defattrname = 'eiface' | |||
| async def activate(self, brd): | |||
| @@ -271,6 +276,11 @@ class EtherIface(DefROAttribute): | |||
| raise RuntimeError('activate failed: %d' % ret) | |||
| class SerialConsole(DefROAttribute): | |||
| '''Specify a device to add to the board jail. This is primarily | |||
| designed for use with a hard wired serial device, one that will | |||
| not change on board restart, nor system reboot. | |||
| ''' | |||
| defattrname = 'console' | |||
| async def activate(self, brd): | |||
| @@ -328,7 +338,13 @@ class BoardImpl: | |||
| return repr(Board.from_orm(self)) | |||
| async def reserve(self, user, sshpubkey=None): | |||
| '''Reserve the board.''' | |||
| '''Reserve the board. It requires the user to be | |||
| specified and optionally the ssh public key that will be | |||
| installed for the freebsd user in the jail. | |||
| The user and optional sshpubkey will be passed to the | |||
| setup script. | |||
| ''' | |||
| if not self.lock.locked(): | |||
| raise RuntimeError('code error, board not locked') | |||
| @@ -405,10 +421,12 @@ class BoardImpl: | |||
| self.attrcache[i] = await self.attrmap[i].getvalue() | |||
| async def activate(self): | |||
| '''Activate the attributes. This means that the board | |||
| '''Activate the attributes. This requires that the board | |||
| has been reserved. This is for things like adding an | |||
| ethernet interface to the jail's vnet, or starting the | |||
| timeout.''' | |||
| timeout. | |||
| This is called by the reserve method.''' | |||
| if not self.lock.locked(): | |||
| raise RuntimeError('code error, board not locked') | |||
| @@ -420,9 +438,11 @@ class BoardImpl: | |||
| await i.activate(self) | |||
| async def deactivate(self): | |||
| '''Deactivate the attributes. This should be called | |||
| just before release. This is to clean up anything like | |||
| scheduled tasks.''' | |||
| '''Deactivate the attributes. This is called just before | |||
| release. This is to clean up anything like scheduled | |||
| tasks. | |||
| This is called by the release method.''' | |||
| if not self.lock.locked(): | |||
| raise RuntimeError('code error, board not locked') | |||
| @@ -456,10 +476,20 @@ class BoardImpl: | |||
| @dataclass | |||
| class BITEError(Exception): | |||
| '''Object representing an error that occured during processing | |||
| an API request.''' | |||
| errobj: Error | |||
| status_code: int | |||
| class BoardManager(object): | |||
| '''Object that acts as a repository for the boards and their | |||
| classes. | |||
| This will normally be instantiated via the from_settings or | |||
| from_ucl methods. | |||
| ''' | |||
| _option_map = dict( | |||
| etheriface=EtherIface, | |||
| serialconsole=SerialConsole, | |||
| @@ -497,6 +527,16 @@ class BoardManager(object): | |||
| return self.board_class_info | |||
| def unhashable_lru(): | |||
| '''A decorator to implement an LRU (least recently used) | |||
| cache. Currently, there is no limit. | |||
| It implements handling unhashable arguments via checking | |||
| if two objects are the same with is. It also uses the id | |||
| property to more quickly look up the arguments. This | |||
| does mean that this keeps a copy to any objects passed in | |||
| as arguments, so do not expect them to be GC'd. | |||
| ''' | |||
| def newwrapper(fun): | |||
| cache = {} | |||
| @@ -523,6 +563,14 @@ def unhashable_lru(): | |||
| return newwrapper | |||
| class BiteAuth(Auth): | |||
| '''Implement a bearer token authentication for use with the | |||
| httpx client. | |||
| Example usage: | |||
| client = AsyncClient(base_url='https://example.com') | |||
| res = await client.get('/board/classes', auth=BiteAuth('anapikey')) | |||
| ''' | |||
| def __init__(self, token): | |||
| self.token = token | |||
| @@ -587,6 +635,10 @@ async def validate_board_params(board_id, data, brdmgr, user=None, token=None): | |||
| '''This context manager checks to see if the request is authorized | |||
| for the board_id. This requires that the board is reserved by | |||
| the user, or the connection came from the board's jail (TBI). | |||
| This should only be used for operations on a board that do not | |||
| involve reserve or release, that is setting attributes and other | |||
| operations. | |||
| ''' | |||
| brd = brdmgr.boards[board_id] | |||
| @@ -613,6 +665,8 @@ async def validate_board_params(board_id, data, brdmgr, user=None, token=None): | |||
| async def lookup_user(token: str = Depends(oauth2_scheme), | |||
| data: data.DataWrapper = Depends(get_data)): | |||
| '''Using the token, look up the user that the token authorizes.''' | |||
| try: | |||
| return (await data.APIKey.objects.get(key=token)).user | |||
| except orm.exceptions.NoMatch: | |||
| @@ -629,6 +683,20 @@ def board_priority(request: Request): | |||
| scope = request.scope | |||
| return scope['server'] | |||
| @router.get('/') | |||
| async def root_test(board_prio: dict = Depends(board_priority), | |||
| settings: config.Settings = Depends(get_settings)): | |||
| return { 'foo': 'bar', 'board': board_prio } | |||
| @router.get('/board/',response_model=Dict[str, Board]) | |||
| async def get_boards(user: str = Depends(lookup_user), | |||
| brdmgr: BoardManager = Depends(get_boardmanager)): | |||
| brds = brdmgr.boards | |||
| for i in brds: | |||
| await brds[i].update() | |||
| return brds | |||
| @router.get('/board/classes', response_model=Dict[str, BoardClassInfo]) | |||
| async def get_board_classes(user: str = Depends(lookup_user), | |||
| brdmgr: BoardManager = Depends(get_boardmanager)): | |||
| @@ -862,20 +930,6 @@ async def set_board_attrs( | |||
| return brd | |||
| @router.get('/board/',response_model=Dict[str, Board]) | |||
| async def get_boards(user: str = Depends(lookup_user), | |||
| brdmgr: BoardManager = Depends(get_boardmanager)): | |||
| brds = brdmgr.boards | |||
| for i in brds: | |||
| await brds[i].update() | |||
| return brds | |||
| @router.get('/') | |||
| async def root_test(board_prio: dict = Depends(board_priority), | |||
| settings: config.Settings = Depends(get_settings)): | |||
| return { 'foo': 'bar', 'board': board_prio } | |||
| def getApp(): | |||
| app = FastAPI() | |||
| app.include_router(router) | |||