diff --git a/bitelab/__init__.py b/bitelab/__init__.py index a4453b8..39c4892 100644 --- a/bitelab/__init__.py +++ b/bitelab/__init__.py @@ -39,7 +39,7 @@ from starlette.status import HTTP_200_OK from starlette.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, \ HTTP_403_FORBIDDEN, HTTP_404_NOT_FOUND, HTTP_409_CONFLICT from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR -from unittest.mock import patch, AsyncMock, Mock, PropertyMock +from unittest.mock import create_autospec, patch, AsyncMock, Mock, PropertyMock from . import config from .data import * @@ -76,6 +76,18 @@ tcp_server.parse_socket_addr = new_parse_socket_addr class SerialConsole(DefROAttribute): defattrname = 'console' + async def activate(self, brd): + cmd = ('devfs', '-m', brd.attrs['devfspath'], 'rule', 'apply', + 'path', os.path.basename(self._value), 'unhide', ) + sub = await asyncio.create_subprocess_exec(*cmd, + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + + ret = await sub.wait() + + if ret: + raise RuntimeError('activate failed: %d' % ret) + class BoardImpl: def __init__(self, name, brdclass, options): self.name = name @@ -110,6 +122,14 @@ class BoardImpl: for i in self.attrmap: self.attrcache[i] = await self.attrmap[i].getvalue() + async def activate(self): + for i in self.options: + await i.activate(self) + + async def deactivate(self): + for i in self.options: + await i.deactivate(self) + def add_info(self, d): self.attrcache.update(d) @@ -353,8 +373,6 @@ async def reserve_board(board_id_or_class, stdout, stderr = await sub.communicate() if sub.returncode: raise RuntimeError(sub.returncode, stderr) - - brd.add_info(json.loads(stdout)) except Exception as e: await brdreq.delete() await brd.release() @@ -369,6 +387,10 @@ async def reserve_board(board_id_or_class, ) raise + brd.add_info(json.loads(stdout)) + + await brd.activate() + await brd.update() return brd @@ -615,9 +637,10 @@ class TestBiteLab(unittest.IsolatedAsyncioTestCase): # and that the error got logged le.assert_called_with('release script failure: board: \'cora-1\', ret: 1, stderr: b\'error\'') + @patch('bitelab.BoardImpl.activate') @patch('asyncio.create_subprocess_exec') @patch('bitelab.snmp.snmpget') - async def test_board_reserve_release(self, sg, cse): + async def test_board_reserve_release(self, sg, cse, biact): # that when releasing a board that is not yet reserved res = await self.client.post('/board/cora-1/release', auth=BiteAuth('anotherlongapikey')) @@ -685,6 +708,9 @@ class TestBiteLab(unittest.IsolatedAsyncioTestCase): cse.assert_called_with(self.settings.setup_script, 'reserve', 'cora-1', 'foo', 'pubsshkey', stdout=subprocess.PIPE, stderr=subprocess.PIPE) + # and that the board was activated + biact.assert_called() + # that another user reserving the board res = await self.client.post('/board/cora-1/reserve', auth=BiteAuth('anotherlongapikey')) @@ -875,8 +901,28 @@ class TestBiteLab(unittest.IsolatedAsyncioTestCase): } self.assertEqual(res.json(), info) +class TestBoardImpl(unittest.IsolatedAsyncioTestCase): + async def test_activate(self): + # that a board impl + opt = create_autospec(Attribute) + brd = BoardImpl('foo', 'bar', [ opt ]) + + await brd.activate() + + opt.activate.assert_called_with(brd) + + async def test_deactivate(self): + # that a board impl + opt = create_autospec(Attribute) + brd = BoardImpl('foo', 'bar', [ opt ]) + + await brd.deactivate() + + opt.deactivate.assert_called_with(brd) + class TestAttrs(unittest.IsolatedAsyncioTestCase): - async def test_serialconsole(self): + @patch('asyncio.create_subprocess_exec') + async def test_serialconsole(self, cse): data = 'somepath' sc = SerialConsole(data) @@ -886,3 +932,22 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase): with self.assertRaises(TypeError): await sc.setvalue(data) + + devfspath = 'eifd' + + brd = BoardImpl('foo', 'bar', [ sc ]) + brd.add_info(dict(devfspath=devfspath)) + + wrap_subprocess_exec(cse, retcode=0) + + await sc.activate(brd) + + cse.assert_called_with('devfs', '-m', devfspath, 'rule', + 'apply', 'path', os.path.basename(await sc.getvalue()), + 'unhide', + stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL) + + wrap_subprocess_exec(cse, retcode=1) + with self.assertRaises(RuntimeError): + await sc.activate(brd) diff --git a/bitelab/abstract.py b/bitelab/abstract.py index 7c01837..a2d158c 100644 --- a/bitelab/abstract.py +++ b/bitelab/abstract.py @@ -36,11 +36,29 @@ class Attribute: defattrname = None async def getvalue(self): # pragma: no cover + '''Get the value for this attribute.''' + raise NotImplementedError async def setvalue(self, v): # pragma: no cover + '''Set the value for this attribute to v.''' + raise NotImplementedError + async def activate(self, brd): # pragma: no cover + '''This activates the attribute on brd. brd is an + isntance of BoardImpl. This is called after the jail and + other resources have been allocated.''' + + pass + + async def deactivate(self, brd): # pragma: no cover + '''This deactivates the attribute on brd. brd is an + isntance of BoardImpl. This is called just before the jail + other resources will be deallocated.''' + + pass + class ROAttribute(Attribute): '''A read-only attribute class. This implements setvalue, but raises a TypeError when setvalue is called.''' diff --git a/bitelab/testing.py b/bitelab/testing.py index f4601f7..22b9b04 100644 --- a/bitelab/testing.py +++ b/bitelab/testing.py @@ -30,5 +30,5 @@ from .snmp import TestSNMPPower, TestSNMPWrapper from .data import TestDatabase -from . import TestBiteLab, TestUnhashLRU, TestAttrs +from . import TestBiteLab, TestUnhashLRU, TestAttrs, TestBoardImpl from .__main__ import TestClient