Browse Source

push down code for reserve/release into BoardImpl...

This makes the separation between the implementation and the API
more clean, as before the API was doing too much work..
main
John-Mark Gurney 3 years ago
parent
commit
d7927f1130
3 changed files with 259 additions and 89 deletions
  1. +257
    -88
      bitelab/__init__.py
  2. +0
    -1
      bitelab/config.py
  3. +2
    -0
      fixtures/board_conf.ucl

+ 257
- 88
bitelab/__init__.py View File

@@ -73,6 +73,11 @@ import unittest
import urllib import urllib
import websockets import websockets


# Silence warnings with a sledge hammer, since I'm tired of them, and
# I can't get warnings.filterwarnings to work.
import warnings
warnings.warn = lambda *args, **kwargs: None

epsilon = sys.float_info.epsilon epsilon = sys.float_info.epsilon


# fix up parse_socket_addr for hypercorn # fix up parse_socket_addr for hypercorn
@@ -206,7 +211,8 @@ class TimeOut(Attribute):
self._exp = self._cb.when() self._exp = self._cb.when()


async def activate(self, brd): async def activate(self, brd):
assert brd.lock.locked()
if not brd.lock.locked():
raise RuntimeError('code error, board not locked')


loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
self._brd = brd self._brd = brd
@@ -215,7 +221,8 @@ class TimeOut(Attribute):
self._task = None self._task = None


async def deactivate(self, brd): async def deactivate(self, brd):
assert brd.lock.locked()
if not brd.lock.locked():
raise RuntimeError('code error, board not locked')


if self._cb is not None: if self._cb is not None:
self._cb.cancel() self._cb.cancel()
@@ -289,7 +296,7 @@ class BoardImpl:
be destroyed before the operation completes. be destroyed before the operation completes.
''' '''


def __init__(self, name: str, brdclass: str, options):
def __init__(self, name: str, brdclass: str, setupscript: str, options):
''' '''
name: name of the board. name: name of the board.
brdclass: class that the board belongs to. brdclass: class that the board belongs to.
@@ -300,8 +307,10 @@ class BoardImpl:


self.name = name self.name = name
self.brdclass = brdclass self.brdclass = brdclass
self.setupscript = setupscript
self.options = options self.options = options
self.reserved = False self.reserved = False
self.user = None
self.attrmap = {} self.attrmap = {}
self.lock = asyncio.Lock() self.lock = asyncio.Lock()
for i in options: for i in options:
@@ -318,11 +327,28 @@ class BoardImpl:
def __repr__(self): #pragma: no cover def __repr__(self): #pragma: no cover
return repr(Board.from_orm(self)) return repr(Board.from_orm(self))


async def reserve(self):
async def reserve(self, user, sshpubkey=None):
'''Reserve the board.''' '''Reserve the board.'''


assert self.lock.locked() and not self.reserved
if not self.lock.locked():
raise RuntimeError('code error, board not locked')

if self.reserved:
raise RuntimeError('Board currently reserved.')

args = ( self.setupscript, 'reserve',
self.name, user, )
if sshpubkey is not None:
args += (sshpubkey, )
sub = await asyncio.create_subprocess_exec(*args,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = await sub.communicate()
if sub.returncode:
raise RuntimeError(sub.returncode, stderr)

self.add_info(json.loads(stdout))


self.user = user
self.reserved = True self.reserved = True


await self.activate() await self.activate()
@@ -330,16 +356,44 @@ class BoardImpl:
async def release(self): async def release(self):
'''Release the board.''' '''Release the board.'''


assert self.lock.locked() and self.reserved
if not self.lock.locked():
raise RuntimeError('code error, board not locked')

if not self.reserved:
raise RuntimeError('Board not reserved.')


await self.deactivate() await self.deactivate()


env = os.environ.copy()
addkeys = { 'iface', 'ip', 'devfsrule', 'devfspath' }
env.update((k, self.attrs[k]) for k in addkeys if k in self.attrs)

sub = await asyncio.create_subprocess_exec(
self.setupscript, 'release', self.name, self.user, env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = await sub.communicate()
retcode = sub.returncode
if retcode:
logging.error('release script failure: ' +
'board: %s, ret: %s, stderr: %s' % (repr(self.name),
retcode, repr(stderr)))
raise RuntimeError(
'Failed to release board, ret: %d, stderr: %s' %
(retcode, repr(stderr))
)

self.clean_info()

self.reserved = False self.reserved = False


async def update_attrs(self, **attrs): async def update_attrs(self, **attrs):
'''Set the various attrs that are specified.''' '''Set the various attrs that are specified.'''


assert self.lock.locked() and self.reserved
if not self.lock.locked():
raise RuntimeError('code error, board not locked')

if not self.reserved:
raise RuntimeError('code error, board not reserved')


for i in attrs: for i in attrs:
self.attrcache[i] = await self.attrmap[i].setvalue(attrs[i]) self.attrcache[i] = await self.attrmap[i].setvalue(attrs[i])
@@ -356,7 +410,11 @@ class BoardImpl:
ethernet interface to the jail's vnet, or starting the ethernet interface to the jail's vnet, or starting the
timeout.''' timeout.'''


assert self.lock.locked() and self.reserved
if not self.lock.locked():
raise RuntimeError('code error, board not locked')

if not self.reserved:
raise RuntimeError('code error, board not reserved')


for i in self.attrmap.values(): for i in self.attrmap.values():
await i.activate(self) await i.activate(self)
@@ -366,7 +424,11 @@ class BoardImpl:
just before release. This is to clean up anything like just before release. This is to clean up anything like
scheduled tasks.''' scheduled tasks.'''


assert self.lock.locked() and self.reserved
if not self.lock.locked():
raise RuntimeError('code error, board not locked')

if not self.reserved:
raise RuntimeError('code error, board not reserved')


for i in self.attrmap.values(): for i in self.attrmap.values():
await i.deactivate(self) await i.deactivate(self)
@@ -404,13 +466,13 @@ class BoardManager(object):
snmppower=SNMPPower, snmppower=SNMPPower,
) )


def __init__(self, cls_info, boards):
def __init__(self, cls_info, boards, setup_script):
# add the name to the classes # add the name to the classes
classes = { k: dict(clsname=k, **cls_info[k]) for k in cls_info } classes = { k: dict(clsname=k, **cls_info[k]) for k in cls_info }
self.board_class_info = classes self.board_class_info = classes


self.boards = dict(**{ x.name: x for x in self.boards = dict(**{ x.name: x for x in
(BoardImpl(**y) for y in boards)})
(BoardImpl(setupscript=setup_script, **y) for y in boards)})


@classmethod @classmethod
def from_settings(cls, settings): def from_settings(cls, settings):
@@ -429,7 +491,7 @@ class BoardManager(object):
opt = i['options'] opt = i['options']
opt[:] = [ makeopt(x) for x in opt ] opt[:] = [ makeopt(x) for x in opt ]


return cls(classes, brds)
return cls(classes, brds, setup_script=conf['setup_script'])


def classes(self): def classes(self):
return self.board_class_info return self.board_class_info
@@ -605,7 +667,6 @@ async def reserve_board(board_id_or_class,
# obrdreq.user == user # obrdreq.user == user
brdreq = await data.BoardStatus.objects.get(board=board_id, brdreq = await data.BoardStatus.objects.get(board=board_id,
user=user) user=user)
await brd.reserve()
# XXX - orm isn't doing it's job here # XXX - orm isn't doing it's job here
except sqlite3.IntegrityError: except sqlite3.IntegrityError:
raise BITEError( raise BITEError(
@@ -614,20 +675,11 @@ async def reserve_board(board_id_or_class,
board=Board.from_orm(brd)), board=Board.from_orm(brd)),
) )


# Initialize board
# Reserve board
try: try:
args = ( settings.setup_script, 'reserve',
brd.name, user, )
if sshpubkey is not None:
args += (sshpubkey, )
sub = await asyncio.create_subprocess_exec(*args,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = await sub.communicate()
if sub.returncode:
raise RuntimeError(sub.returncode, stderr)
await brd.reserve(user, sshpubkey)
except Exception as e: except Exception as e:
await brdreq.delete() await brdreq.delete()
await brd.release()
if isinstance(e, RuntimeError): if isinstance(e, RuntimeError):
retcode, stderr = e.args retcode, stderr = e.args
raise BITEError( raise BITEError(
@@ -639,10 +691,6 @@ async def reserve_board(board_id_or_class,
) )
raise raise


brd.add_info(json.loads(stdout))

await brd.activate()

await log_event('reserve', user=user, board=brd) await log_event('reserve', user=user, board=brd)


await brd.update() await brd.update()
@@ -789,33 +837,16 @@ async def release_board(board_id, user: str = Depends(lookup_user),
board=Board.from_orm(brd)), board=Board.from_orm(brd)),
) )


await brd.deactivate()

env = os.environ.copy()
addkeys = { 'iface', 'ip', 'devfsrule', 'devfspath' }
env.update((k, brd.attrs[k]) for k in addkeys if k in brd.attrs)

sub = await asyncio.create_subprocess_exec(
settings.setup_script, 'release', brd.name, user, env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = await sub.communicate()
retcode = sub.returncode
if retcode:
logging.error('release script failure: ' +
'board: %s, ret: %s, stderr: %s' % (repr(brd.name),
retcode, repr(stderr)))
try:
await brd.release()
except RuntimeError as e:
raise BITEError( raise BITEError(
status_code=HTTP_500_INTERNAL_SERVER_ERROR, status_code=HTTP_500_INTERNAL_SERVER_ERROR,
errobj=Error(error=
'Failed to release board, ret: %d, stderr: %s' %
(retcode, repr(stderr)),
board=Board.from_orm(brd)),
errobj=Error(error=e.args[0],
board=Board.from_orm(brd)),
) )


await data.BoardStatus.delete(brduser) await data.BoardStatus.delete(brduser)
await brd.release()

brd.clean_info()


await brd.update() await brd.update()


@@ -906,7 +937,6 @@ class TestCommon(unittest.IsolatedAsyncioTestCase):


# setup settings # setup settings
self.settings = config.Settings(db_file=self.dbtempfile.name, self.settings = config.Settings(db_file=self.dbtempfile.name,
setup_script='somesetupscript',
board_conf = os.path.join('fixtures', 'board_conf.ucl') board_conf = os.path.join('fixtures', 'board_conf.ucl')
) )


@@ -991,11 +1021,12 @@ class TestWebSocket(TestCommon):
stdout=2) stdout=2)


# that when the board is reserved by the wrong user # that when the board is reserved by the wrong user
wrap_subprocess_exec(cse, stdout=b'{}', retcode=0)
brd = self.brdmgr.boards['cora-1'] brd = self.brdmgr.boards['cora-1']
obrdreq = await self.data.BoardStatus.objects.create( obrdreq = await self.data.BoardStatus.objects.create(
board='cora-1', user='bar') board='cora-1', user='bar')
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.reserve('bar')


# that it fails # that it fails
with self.assertRaisesRegex(RuntimeError, 'Board reserved by \'bar\'.'): with self.assertRaisesRegex(RuntimeError, 'Board reserved by \'bar\'.'):
@@ -1103,19 +1134,22 @@ class TestBiteLabAPI(TestCommon):
# that when snmpget returns False # that when snmpget returns False
sg.return_value = False sg.return_value = False


# that when the setup script will fail
wrap_subprocess_exec(cse, stderr=b'error', retcode=1)
# that when the script is successful
wrap_subprocess_exec(cse, stdout=b'{}', retcode=0)


# and that the cora-1 board is reserved
# that the cora-1 board is reserved
data = self.data data = self.data
brd = self.brdmgr.boards['cora-1'] brd = self.brdmgr.boards['cora-1']
attrs = dict(iface='a', ip='b', devfsrule='c') attrs = dict(iface='a', ip='b', devfsrule='c')
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.reserve('foo')
obrdreq = await data.BoardStatus.objects.create( obrdreq = await data.BoardStatus.objects.create(
board='cora-1', user='foo') board='cora-1', user='foo')
brd.attrcache.update(attrs) brd.attrcache.update(attrs)


# that when the script fails
wrap_subprocess_exec(cse, stderr=b'error', retcode=1)

# that when the correct user releases the board # that when the correct user releases the board
res = await self.client.post('/board/cora-1/release', res = await self.client.post('/board/cora-1/release',
auth=BiteAuth('thisisanapikey')) auth=BiteAuth('thisisanapikey'))
@@ -1136,7 +1170,7 @@ class TestBiteLabAPI(TestCommon):
# and that it called the release script # and that it called the release script
env = os.environ.copy() env = os.environ.copy()
env.update(attrs) env.update(attrs)
cse.assert_called_with(self.settings.setup_script,
cse.assert_called_with('somesetupscript',
'release', 'cora-1', 'foo', env=env, 'release', 'cora-1', 'foo', env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)


@@ -1179,7 +1213,7 @@ class TestBiteLabAPI(TestCommon):
self.assertEqual(res.json(), info) self.assertEqual(res.json(), info)


# and that it called the start script # and that it called the start script
cse.assert_called_with(self.settings.setup_script, 'reserve',
cse.assert_called_with('somesetupscript', 'reserve',
'cora-1', 'foo', stdout=subprocess.PIPE, 'cora-1', 'foo', stdout=subprocess.PIPE,
stderr=subprocess.PIPE) stderr=subprocess.PIPE)


@@ -1215,7 +1249,7 @@ class TestBiteLabAPI(TestCommon):
self.assertEqual(res.json(), brdinfo) self.assertEqual(res.json(), brdinfo)


# and that it called the start script # and that it called the start script
cse.assert_called_with(self.settings.setup_script, 'reserve',
cse.assert_called_with('somesetupscript', 'reserve',
'cora-1', 'foo', 'pubsshkey', stdout=subprocess.PIPE, stderr=subprocess.PIPE) 'cora-1', 'foo', 'pubsshkey', stdout=subprocess.PIPE, stderr=subprocess.PIPE)


# and that the board was activated # and that the board was activated
@@ -1280,7 +1314,7 @@ class TestBiteLabAPI(TestCommon):
env['devfspath'] = brdinfo['attrs']['devfspath'] env['devfspath'] = brdinfo['attrs']['devfspath']


# and that it called the release script # and that it called the release script
cse.assert_called_with(self.settings.setup_script, 'release',
cse.assert_called_with('somesetupscript', 'release',
'cora-1', 'foo', env=env, 'cora-1', 'foo', env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout=subprocess.PIPE, stderr=subprocess.PIPE)


@@ -1344,13 +1378,17 @@ class TestBiteLabAPI(TestCommon):
} }
self.assertEqual(res.json(), info) self.assertEqual(res.json(), info)


@patch('asyncio.create_subprocess_exec')
@patch('bitelab.snmp.snmpset') @patch('bitelab.snmp.snmpset')
async def test_board_attrs(self, ss):
async def test_board_attrs(self, ss, cse):
data = self.data data = self.data


# that when snmpset returns False # that when snmpset returns False
ss.return_value = False ss.return_value = False


# that when the setup script passed
wrap_subprocess_exec(cse, stdout=b'{}', retcode=0)

attrs = dict(power=False) attrs = dict(power=False)


# that setting the board attributes requires auth # that setting the board attributes requires auth
@@ -1372,7 +1410,7 @@ class TestBiteLabAPI(TestCommon):
# that the cora-1 board is reserved # that the cora-1 board is reserved
brd = self.brdmgr.boards['cora-1'] brd = self.brdmgr.boards['cora-1']
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.reserve('foo')
obrdreq = await data.BoardStatus.objects.create( obrdreq = await data.BoardStatus.objects.create(
board='cora-1', user='foo') board='cora-1', user='foo')


@@ -1424,34 +1462,154 @@ class TestBiteLabAPI(TestCommon):
self.assertEqual(res.json(), info) self.assertEqual(res.json(), info)


class TestBoardImpl(unittest.IsolatedAsyncioTestCase): class TestBoardImpl(unittest.IsolatedAsyncioTestCase):
async def xtest_reserve(self):
async def asyncSetUp(self):
opttup = create_autospec, dict(spec=Attribute)
brd = BoardImpl('aboard', 'aclass', 'ascript', [ opttup ])
(opt,) = tuple(brd.attrmap.values())

self.brd = brd
self.opt = opt

async def asyncTearDown(self):
await asyncio.sleep(0)
await asyncio.sleep(0)

sys.stdout.flush()
sys.stderr.flush()

async def test_errors(self):
# that reserve raises an error when not locked
with self.assertRaises(RuntimeError):
await self.brd.reserve(None)

# that release raises an error when not locked
with self.assertRaises(RuntimeError):
await self.brd.release()

# that update_attrs raises an error when not locked
with self.assertRaises(RuntimeError):
await self.brd.update_attrs()

# that update_attrs raises an error when not reserved
async with self.brd.lock:
with self.assertRaises(RuntimeError):
await self.brd.update_attrs()

# that activate raises an error when not reserved
async with self.brd.lock:
with self.assertRaises(RuntimeError):
await self.brd.activate()

# that deactivate raises an error when not reserved
async with self.brd.lock:
with self.assertRaises(RuntimeError):
await self.brd.deactivate()

@patch('bitelab.BoardImpl.deactivate')
@patch('bitelab.BoardImpl.activate')
@patch('logging.error')
@patch('asyncio.create_subprocess_exec')
async def test_reserve_release(self, cse, le, act, deact):
brd = self.brd

# that when the setup script will fail
wrap_subprocess_exec(cse, stderr=b'error', retcode=1)

# that attempting to reserve the board
async with brd.lock:
with self.assertRaises(RuntimeError):
await brd.reserve(None)

# that the setup script will pass
attrs = dict(iface='a', ip='b', devfsrule='c')
wrap_subprocess_exec(cse, stdout=json.dumps(attrs).encode(), retcode=0)

# that when the board is reserved # that when the board is reserved
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.reserve('auser')


async def test_activate(self):
# that a board impl
opttup = create_autospec, dict(spec=Attribute)
brd = BoardImpl('foo', 'bar', [ opttup ])
(opt,) = tuple(brd.attrmap.values())
# that the script was called properly
cse.assert_called_with('ascript',
'reserve', 'aboard', 'auser',
stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# that the returned attribute is there:
# XXX - I can't silenct the deprecation warning about
# assertDictContainsSubset
self.assertEqual(attrs, { k: v for k, v in
brd.attrs.items() if k in set(attrs.keys()) })

# activate is called
act.assert_called()

# that if it tries to be reserved again
async with brd.lock:

# that it raises a RuntimeError
with self.assertRaisesRegex(RuntimeError,
'Board currently reserved.'):
await brd.reserve('auser')


# that it can be released:
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.release()

# that the script was called properly
env = os.environ.copy()
env.update(attrs)
self.assertEqual(env, cse.mock_calls[-2].kwargs['env'])
cse.assert_called_with('ascript',
'release', 'aboard', 'auser', env=env,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)

# deactivate is called
deact.assert_called()

# that the attrs are no longer present
# (clear_info was called)
self.assertFalse(set(brd.attrs.keys()) & set(attrs.keys()))

# that if released w/o being reserved
# that it raises a RuntimeError
with self.assertRaisesRegex(RuntimeError,
'Board not reserved.'):
await brd.release()

@patch('asyncio.create_subprocess_exec')
async def test_activate(self, cse):
brd, opt = self.brd, self.opt

wrap_subprocess_exec(cse, stdout=b'{}', retcode=0)

# that a board impl
async with brd.lock:
await brd.reserve(None)


opt.activate.assert_called_with(brd) opt.activate.assert_called_with(brd)


async def test_deactivate(self):
# that raises runtime when not locked
with self.assertRaises(RuntimeError):
await brd.activate()

@patch('asyncio.create_subprocess_exec')
async def test_deactivate(self, cse):
# that a board impl # that a board impl
opttup = create_autospec, dict(spec=Attribute) opttup = create_autospec, dict(spec=Attribute)
brd = BoardImpl('foo', 'bar', [ opttup ])
brd = BoardImpl('foo', 'bar', None, [ opttup ])
(opt,) = tuple(brd.attrmap.values()) (opt,) = tuple(brd.attrmap.values())


wrap_subprocess_exec(cse, stdout=b'{}', retcode=0)

async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.reserve(None)
await brd.release() await brd.release()


opt.deactivate.assert_called_with(brd) opt.deactivate.assert_called_with(brd)


# that raises runtime when not locked
with self.assertRaises(RuntimeError):
await brd.deactivate()

class TestLogEvent(unittest.IsolatedAsyncioTestCase): class TestLogEvent(unittest.IsolatedAsyncioTestCase):
@patch('time.time') @patch('time.time')
@patch('logging.info') @patch('logging.info')
@@ -1460,7 +1618,7 @@ class TestLogEvent(unittest.IsolatedAsyncioTestCase):
user = 'weoijsdfkj' user = 'weoijsdfkj'
brdname = 'woied' brdname = 'woied'
extra = dict(something=2323, someelse='asdlfkj') extra = dict(something=2323, someelse='asdlfkj')
brd = BoardImpl(brdname, {}, [])
brd = BoardImpl(brdname, None, None, [])


tt.return_value = 1607650392.384 tt.return_value = 1607650392.384


@@ -1502,7 +1660,7 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):


sctup = (SerialConsole, dict(val=data)) sctup = (SerialConsole, dict(val=data))


brd = BoardImpl('foo', 'bar', [ sctup ])
brd = BoardImpl('foo', 'bar', None, [ sctup ])
sc = brd.attrmap['console'] sc = brd.attrmap['console']


self.assertEqual(sc.defattrname, 'console') self.assertEqual(sc.defattrname, 'console')
@@ -1542,7 +1700,7 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):


eitup = EtherIface, dict(val=eiface) eitup = EtherIface, dict(val=eiface)


brd = BoardImpl('foo', 'bar', [ eitup ])
brd = BoardImpl('foo', 'bar', None, [ eitup ])


ei = brd.attrmap['eiface'] ei = brd.attrmap['eiface']


@@ -1570,7 +1728,7 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):


# That multiple attributes w/ same name raises ValueError # That multiple attributes w/ same name raises ValueError
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
BoardImpl('foo', 'bar', attrs)
BoardImpl('foo', 'bar', None, attrs)


# Enough of this code depends upon the event loop using the # Enough of this code depends upon the event loop using the
# code in BaseEventLoop wrt scheduling that this is not a # code in BaseEventLoop wrt scheduling that this is not a
@@ -1606,14 +1764,17 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):
self.assertAlmostEqual(looptoutc(utctoloop(sometime)), sometime) self.assertAlmostEqual(looptoutc(utctoloop(sometime)), sometime)


@timeout(2) @timeout(2)
@patch('asyncio.create_subprocess_exec')
@patch('asyncio.BaseEventLoop.time') @patch('asyncio.BaseEventLoop.time')
@patch('time.time') @patch('time.time')
async def test_timeout_vals(self, ttime, belt):
async def test_timeout_vals(self, ttime, belt, cse):
# that a TimeOut with args # that a TimeOut with args
totup = TimeOut, dict(val=10) totup = TimeOut, dict(val=10)


wrap_subprocess_exec(cse, stdout=b'{}', retcode=0)

# passed to a board w/ the totup # passed to a board w/ the totup
brd = BoardImpl('foo', 'bar', [ totup ])
brd = BoardImpl('foo', 'bar', None, [ totup ])


to = brd.attrmap['timeout'] to = brd.attrmap['timeout']


@@ -1638,8 +1799,7 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):


# that when reserved/activated # that when reserved/activated
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.activate()
await brd.reserve(None)


await brd.update() await brd.update()


@@ -1664,18 +1824,29 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):
self.assertEqual(brd.attrs, dict(timeout='2020-12-10T14:06:44.280Z')) self.assertEqual(brd.attrs, dict(timeout='2020-12-10T14:06:44.280Z'))


@timeout(2) @timeout(2)
async def test_timeout(self):
@patch('asyncio.create_subprocess_exec')
async def test_timeout(self, cse):
# that a TimeOut with args # that a TimeOut with args
totup = TimeOut, dict(val=.01) totup = TimeOut, dict(val=.01)


wrap_subprocess_exec(cse, stdout=b'{}', retcode=0)

# passed to a board w/ the totup # passed to a board w/ the totup
brd = BoardImpl('foo', 'bar', [ totup ])
brd = BoardImpl('foo', 'bar', None, [ totup ])


to = brd.attrmap['timeout'] to = brd.attrmap['timeout']


# that it raises a RuntimeError when not locked
with self.assertRaises(RuntimeError):
await to.activate(brd)

# that it raises a RuntimeError when not locked
with self.assertRaises(RuntimeError):
await to.deactivate(brd)

# that when reserved/activated # that when reserved/activated
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.reserve(None)


evt = asyncio.Event() evt = asyncio.Event()
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
@@ -1687,7 +1858,7 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):


# that when reserved/activated/deactivated/released # that when reserved/activated/deactivated/released
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.reserve(None)
exp = to._exp exp = to._exp
await brd.release() await brd.release()


@@ -1702,8 +1873,7 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):


# that when reserved/activated # that when reserved/activated
async with brd.lock: async with brd.lock:
await brd.reserve()
await brd.activate()
await brd.reserve(None)


# but the board is locked for some reason # but the board is locked for some reason
await brd.lock.acquire() await brd.lock.acquire()
@@ -1722,4 +1892,3 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):


# that the board was not released # that the board was not released
self.assertTrue(brd.reserved) self.assertTrue(brd.reserved)


+ 0
- 1
bitelab/config.py View File

@@ -36,7 +36,6 @@ __all__ = [ 'Settings' ]
# https://web.archive.org/web/20201113005838/https://github.com/samuelcolvin/pydantic/issues/655 # https://web.archive.org/web/20201113005838/https://github.com/samuelcolvin/pydantic/issues/655
class Settings(BaseSettings): class Settings(BaseSettings):
db_file: str = Field(description='path to SQLite3 database file') db_file: str = Field(description='path to SQLite3 database file')
setup_script: str = Field(description='script that will initalize an environment')
board_conf: str = Field(description='UCL configuration for the boards') board_conf: str = Field(description='UCL configuration for the boards')


class Config: class Config:


+ 2
- 0
fixtures/board_conf.ucl View File

@@ -1,3 +1,5 @@
setup_script = somesetupscript;

classes { classes {
cora-z7s = { cora-z7s = {
arch = arm-armv7; arch = arm-armv7;


Loading…
Cancel
Save