|
|
@@ -48,6 +48,7 @@ from .snmp import * |
|
|
|
from .mocks import * |
|
|
|
|
|
|
|
import asyncio |
|
|
|
import contextlib |
|
|
|
import json |
|
|
|
import orm |
|
|
|
import os |
|
|
@@ -96,6 +97,10 @@ class BoardImpl: |
|
|
|
|
|
|
|
self.reserved = False |
|
|
|
|
|
|
|
async def update_attrs(self, **attrs): |
|
|
|
for i in attrs: |
|
|
|
self.attrcache[i] = await self.attrmap[i].setvalue(attrs[i]) |
|
|
|
|
|
|
|
async def update(self): |
|
|
|
for i in self.attrmap: |
|
|
|
self.attrcache[i] = await self.attrmap[i].getvalue() |
|
|
@@ -179,13 +184,6 @@ class BiteAuth(Auth): |
|
|
|
request.headers['Authorization'] = 'Bearer ' + self.token |
|
|
|
yield request |
|
|
|
|
|
|
|
@lru_cache() |
|
|
|
def sync_get_board_lock(): |
|
|
|
return asyncio.Lock() |
|
|
|
|
|
|
|
async def get_board_lock(): |
|
|
|
return sync_get_board_lock() |
|
|
|
|
|
|
|
# how to get coverage for this? |
|
|
|
@lru_cache() |
|
|
|
def get_settings(): # pragma: no cover |
|
|
@@ -208,6 +206,42 @@ async def get_boardmanager(settings: config.Settings = Depends(get_settings)): |
|
|
|
|
|
|
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/nonexistent') |
|
|
|
|
|
|
|
def get_authorized_board_parms(board_id, token: str = Depends(oauth2_scheme), |
|
|
|
data: data.DataWrapper = Depends(get_data), |
|
|
|
brdmgr: BoardManager = Depends(get_boardmanager)): |
|
|
|
'''This dependancy is used to collect the parameters needed for |
|
|
|
the validate_board_params context manager.''' |
|
|
|
|
|
|
|
return dict(board_id=board_id, token=token, data=data, brdmgr=brdmgr) |
|
|
|
|
|
|
|
@contextlib.asynccontextmanager |
|
|
|
async def validate_board_params(board_id, token, data, brdmgr): |
|
|
|
'''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). |
|
|
|
''' |
|
|
|
|
|
|
|
brd = brdmgr.boards[board_id] |
|
|
|
|
|
|
|
async with brd.lock: |
|
|
|
user = await lookup_user(token, data) |
|
|
|
|
|
|
|
try: |
|
|
|
brduser = await data.BoardStatus.objects.get(board=board_id) |
|
|
|
except orm.exceptions.NoMatch: |
|
|
|
raise BITEError( |
|
|
|
status_code=HTTP_403_FORBIDDEN, |
|
|
|
errobj=Error(error='Board not reserved.', |
|
|
|
board=Board.from_orm(brd))) |
|
|
|
|
|
|
|
if user != brduser.user: |
|
|
|
raise BITEError( |
|
|
|
status_code=HTTP_403_FORBIDDEN, |
|
|
|
errobj=Error(error='Board reserved by %s.' % repr(brduser.user), |
|
|
|
board=Board.from_orm(brd))) |
|
|
|
|
|
|
|
yield brd |
|
|
|
|
|
|
|
async def lookup_user(token: str = Depends(oauth2_scheme), |
|
|
|
data: data.DataWrapper = Depends(get_data)): |
|
|
|
try: |
|
|
@@ -242,7 +276,6 @@ async def get_board_info(board_id, user: str = Depends(lookup_user), |
|
|
|
@router.post('/board/{board_id_or_class}/reserve', response_model=Union[Board, Error]) |
|
|
|
async def reserve_board(board_id_or_class, user: str = Depends(lookup_user), |
|
|
|
brdmgr: BoardManager = Depends(get_boardmanager), |
|
|
|
brdlck: asyncio.Lock = Depends(get_board_lock), |
|
|
|
settings: config.Settings = Depends(get_settings), |
|
|
|
data: data.DataWrapper = Depends(get_data)): |
|
|
|
board_id = board_id_or_class |
|
|
@@ -299,7 +332,6 @@ async def reserve_board(board_id_or_class, user: str = Depends(lookup_user), |
|
|
|
@router.post('/board/{board_id}/release', response_model=Union[Board, Error]) |
|
|
|
async def release_board(board_id, user: str = Depends(lookup_user), |
|
|
|
brdmgr: BoardManager = Depends(get_boardmanager), |
|
|
|
brdlck: asyncio.Lock = Depends(get_board_lock), |
|
|
|
settings: config.Settings = Depends(get_settings), |
|
|
|
data: data.DataWrapper = Depends(get_data)): |
|
|
|
brd = brdmgr.boards[board_id] |
|
|
@@ -337,10 +369,14 @@ async def release_board(board_id, user: str = Depends(lookup_user), |
|
|
|
return brd |
|
|
|
|
|
|
|
@router.post('/board/{board_id}/attrs', response_model=Union[Board, Error]) |
|
|
|
async def set_board_attrs(board_id, |
|
|
|
async def set_board_attrs( |
|
|
|
attrs: Dict[str, Any], |
|
|
|
brdmgr: BoardManager = Depends(get_boardmanager)): |
|
|
|
pass |
|
|
|
brdparams: dict = Depends(get_authorized_board_parms)): |
|
|
|
|
|
|
|
async with validate_board_params(**brdparams) as brd: |
|
|
|
await brd.update_attrs(**attrs) |
|
|
|
|
|
|
|
return brd |
|
|
|
|
|
|
|
@router.get('/board/',response_model=Dict[str, Board]) |
|
|
|
async def get_boards(user: str = Depends(lookup_user), |
|
|
@@ -410,6 +446,9 @@ class TestBiteLab(unittest.IsolatedAsyncioTestCase): |
|
|
|
def get_data_override(self): |
|
|
|
return self.data |
|
|
|
|
|
|
|
def get_boardmanager_override(self): |
|
|
|
return self.brdmgr |
|
|
|
|
|
|
|
async def asyncSetUp(self): |
|
|
|
self.app = getApp() |
|
|
|
|
|
|
@@ -426,9 +465,12 @@ class TestBiteLab(unittest.IsolatedAsyncioTestCase): |
|
|
|
setup_script='somesetupscript', |
|
|
|
) |
|
|
|
|
|
|
|
self.brdmgr = BoardManager(self.settings) |
|
|
|
|
|
|
|
self.app.dependency_overrides[get_settings] = \ |
|
|
|
self.get_settings_override |
|
|
|
self.app.dependency_overrides[get_data] = self.get_data_override |
|
|
|
self.app.dependency_overrides[get_boardmanager] = self.get_boardmanager_override |
|
|
|
|
|
|
|
self.client = AsyncClient(app=self.app, |
|
|
|
base_url='http://testserver') |
|
|
@@ -633,3 +675,82 @@ class TestBiteLab(unittest.IsolatedAsyncioTestCase): |
|
|
|
'attrs': { 'power': True }, |
|
|
|
} |
|
|
|
self.assertEqual(res.json(), info) |
|
|
|
|
|
|
|
@patch('bitelab.snmp.snmpset') |
|
|
|
async def test_board_attrs(self, ss): |
|
|
|
data = self.data |
|
|
|
|
|
|
|
# that when snmpset returns False |
|
|
|
ss.return_value = False |
|
|
|
|
|
|
|
attrs = dict(power=False) |
|
|
|
|
|
|
|
# that setting the board attributes requires auth |
|
|
|
res = await self.client.post('/board/cora-1/attrs', |
|
|
|
auth=BiteAuth('badapi'), |
|
|
|
json=attrs) |
|
|
|
|
|
|
|
# that it fails auth |
|
|
|
self.assertEqual(res.status_code, HTTP_401_UNAUTHORIZED) |
|
|
|
|
|
|
|
# that when properly authorized, but board is not reserved |
|
|
|
res = await self.client.post('/board/cora-1/attrs', |
|
|
|
auth=BiteAuth('thisisanapikey'), |
|
|
|
json=attrs) |
|
|
|
|
|
|
|
# that it is forbidden |
|
|
|
self.assertEqual(res.status_code, HTTP_403_FORBIDDEN) |
|
|
|
|
|
|
|
# that the cora-1 board is reserved |
|
|
|
brd = self.brdmgr.boards['cora-1'] |
|
|
|
async with brd.lock: |
|
|
|
await brd.reserve() |
|
|
|
obrdreq = await data.BoardStatus.objects.create( |
|
|
|
board='cora-1', user='foo') |
|
|
|
|
|
|
|
# that setting the board attributes |
|
|
|
res = await self.client.post('/board/cora-1/attrs', |
|
|
|
auth=BiteAuth('thisisanapikey'), |
|
|
|
json=attrs) |
|
|
|
|
|
|
|
# that it is successful |
|
|
|
self.assertEqual(res.status_code, HTTP_200_OK) |
|
|
|
|
|
|
|
# calls snmpset w/ the correct args |
|
|
|
ss.assert_called_with('poe', 'pethPsePortAdminEnable.1.2', |
|
|
|
'bool', False) |
|
|
|
|
|
|
|
# and returns the correct data |
|
|
|
info = { |
|
|
|
'name': 'cora-1', |
|
|
|
'brdclass': 'cora-z7s', |
|
|
|
'reserved': True, |
|
|
|
'attrs': { 'power': False }, |
|
|
|
} |
|
|
|
self.assertEqual(res.json(), info) |
|
|
|
|
|
|
|
# that when snmpset returns True |
|
|
|
ss.return_value = True |
|
|
|
|
|
|
|
attrs = dict(power=True) |
|
|
|
|
|
|
|
# that setting the board attributes |
|
|
|
res = await self.client.post('/board/cora-1/attrs', |
|
|
|
auth=BiteAuth('thisisanapikey'), |
|
|
|
json=attrs) |
|
|
|
|
|
|
|
# calls snmpget w/ the correct args |
|
|
|
ss.assert_called_with('poe', 'pethPsePortAdminEnable.1.2', |
|
|
|
'bool', True) |
|
|
|
|
|
|
|
# that it is successful |
|
|
|
self.assertEqual(res.status_code, HTTP_200_OK) |
|
|
|
|
|
|
|
# and returns the correct data |
|
|
|
info = { |
|
|
|
'name': 'cora-1', |
|
|
|
'brdclass': 'cora-z7s', |
|
|
|
'reserved': True, |
|
|
|
'attrs': { 'power': True }, |
|
|
|
} |
|
|
|
self.assertEqual(res.json(), info) |