|
@@ -154,19 +154,19 @@ async def log_event(tag, board=None, user=None, extra={}): |
|
|
|
|
|
|
|
|
class TimeOut(Attribute): |
|
|
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 |
|
|
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. |
|
|
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 |
|
|
While the board is not activated, it will display the timeout in |
|
|
seconds. When the board is activated, the getvalue will return |
|
|
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' |
|
|
defattrname = 'timeout' |
|
@@ -257,6 +257,11 @@ class TimeOut(Attribute): |
|
|
self._task = asyncio.create_task(self.timeout_coro()) |
|
|
self._task = asyncio.create_task(self.timeout_coro()) |
|
|
|
|
|
|
|
|
class EtherIface(DefROAttribute): |
|
|
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' |
|
|
defattrname = 'eiface' |
|
|
|
|
|
|
|
|
async def activate(self, brd): |
|
|
async def activate(self, brd): |
|
@@ -271,6 +276,11 @@ class EtherIface(DefROAttribute): |
|
|
raise RuntimeError('activate failed: %d' % ret) |
|
|
raise RuntimeError('activate failed: %d' % ret) |
|
|
|
|
|
|
|
|
class SerialConsole(DefROAttribute): |
|
|
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' |
|
|
defattrname = 'console' |
|
|
|
|
|
|
|
|
async def activate(self, brd): |
|
|
async def activate(self, brd): |
|
@@ -328,7 +338,13 @@ class BoardImpl: |
|
|
return repr(Board.from_orm(self)) |
|
|
return repr(Board.from_orm(self)) |
|
|
|
|
|
|
|
|
async def reserve(self, user, sshpubkey=None): |
|
|
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(): |
|
|
if not self.lock.locked(): |
|
|
raise RuntimeError('code error, board not locked') |
|
|
raise RuntimeError('code error, board not locked') |
|
@@ -405,10 +421,12 @@ class BoardImpl: |
|
|
self.attrcache[i] = await self.attrmap[i].getvalue() |
|
|
self.attrcache[i] = await self.attrmap[i].getvalue() |
|
|
|
|
|
|
|
|
async def activate(self): |
|
|
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 |
|
|
has been reserved. This is for things like adding an |
|
|
ethernet interface to the jail's vnet, or starting the |
|
|
ethernet interface to the jail's vnet, or starting the |
|
|
timeout.''' |
|
|
|
|
|
|
|
|
timeout. |
|
|
|
|
|
|
|
|
|
|
|
This is called by the reserve method.''' |
|
|
|
|
|
|
|
|
if not self.lock.locked(): |
|
|
if not self.lock.locked(): |
|
|
raise RuntimeError('code error, board not locked') |
|
|
raise RuntimeError('code error, board not locked') |
|
@@ -420,9 +438,11 @@ class BoardImpl: |
|
|
await i.activate(self) |
|
|
await i.activate(self) |
|
|
|
|
|
|
|
|
async def deactivate(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(): |
|
|
if not self.lock.locked(): |
|
|
raise RuntimeError('code error, board not locked') |
|
|
raise RuntimeError('code error, board not locked') |
|
@@ -456,10 +476,20 @@ class BoardImpl: |
|
|
|
|
|
|
|
|
@dataclass |
|
|
@dataclass |
|
|
class BITEError(Exception): |
|
|
class BITEError(Exception): |
|
|
|
|
|
'''Object representing an error that occured during processing |
|
|
|
|
|
an API request.''' |
|
|
|
|
|
|
|
|
errobj: Error |
|
|
errobj: Error |
|
|
status_code: int |
|
|
status_code: int |
|
|
|
|
|
|
|
|
class BoardManager(object): |
|
|
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( |
|
|
_option_map = dict( |
|
|
etheriface=EtherIface, |
|
|
etheriface=EtherIface, |
|
|
serialconsole=SerialConsole, |
|
|
serialconsole=SerialConsole, |
|
@@ -497,6 +527,16 @@ class BoardManager(object): |
|
|
return self.board_class_info |
|
|
return self.board_class_info |
|
|
|
|
|
|
|
|
def unhashable_lru(): |
|
|
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): |
|
|
def newwrapper(fun): |
|
|
cache = {} |
|
|
cache = {} |
|
|
|
|
|
|
|
@@ -523,6 +563,14 @@ def unhashable_lru(): |
|
|
return newwrapper |
|
|
return newwrapper |
|
|
|
|
|
|
|
|
class BiteAuth(Auth): |
|
|
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): |
|
|
def __init__(self, token): |
|
|
self.token = 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 |
|
|
'''This context manager checks to see if the request is authorized |
|
|
for the board_id. This requires that the board is reserved by |
|
|
for the board_id. This requires that the board is reserved by |
|
|
the user, or the connection came from the board's jail (TBI). |
|
|
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] |
|
|
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), |
|
|
async def lookup_user(token: str = Depends(oauth2_scheme), |
|
|
data: data.DataWrapper = Depends(get_data)): |
|
|
data: data.DataWrapper = Depends(get_data)): |
|
|
|
|
|
'''Using the token, look up the user that the token authorizes.''' |
|
|
|
|
|
|
|
|
try: |
|
|
try: |
|
|
return (await data.APIKey.objects.get(key=token)).user |
|
|
return (await data.APIKey.objects.get(key=token)).user |
|
|
except orm.exceptions.NoMatch: |
|
|
except orm.exceptions.NoMatch: |
|
@@ -629,6 +683,20 @@ def board_priority(request: Request): |
|
|
scope = request.scope |
|
|
scope = request.scope |
|
|
return scope['server'] |
|
|
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]) |
|
|
@router.get('/board/classes', response_model=Dict[str, BoardClassInfo]) |
|
|
async def get_board_classes(user: str = Depends(lookup_user), |
|
|
async def get_board_classes(user: str = Depends(lookup_user), |
|
|
brdmgr: BoardManager = Depends(get_boardmanager)): |
|
|
brdmgr: BoardManager = Depends(get_boardmanager)): |
|
@@ -862,20 +930,6 @@ async def set_board_attrs( |
|
|
|
|
|
|
|
|
return brd |
|
|
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(): |
|
|
def getApp(): |
|
|
app = FastAPI() |
|
|
app = FastAPI() |
|
|
app.include_router(router) |
|
|
app.include_router(router) |
|
|