@@ -26,10 +26,10 @@  
		
	
		
			
			# SUCH DAMAGE.  
		
	
		
			
			#  
		
	
		
			
			 
		
	
		
			
			from typing import Optional, Union, Dict, Any  
		
	
		
			
			from dataclasses import dataclass  
		
	
		
			
			from functools import lru_cache, wraps  
		
	
		
			
			from io import StringIO  
		
	
		
			
			from typing import Optional, Union, Dict, Any  
		
	
		
			
			 
		
	
		
			
			from fastapi import APIRouter, Body, Depends, FastAPI, HTTPException  
		
	
		
			
			from fastapi import Path, Request  
		
	
	
		
			
				
				
				
				
					 
			
			@@ -53,6 +53,7 @@ from .data import *  
		
	
		
			
			from .abstract import *  
		
	
		
			
			from .snmp import *  
		
	
		
			
			from .mocks import *  
		
	
		
			
			from .iso8601 import parse_date  
		
	
		
			
			 
		
	
		
			
			import asyncio  
		
	
		
			
			import contextlib  
		
	
	
		
			
				
				
				
				
					 
			
			@@ -72,6 +73,8 @@ import unittest  
		
	
		
			
			import urllib  
		
	
		
			
			import websockets  
		
	
		
			
			 
		
	
		
			
			epsilon = sys.float_info.epsilon  
		
	
		
			
			 
		
	
		
			
			# fix up parse_socket_addr for hypercorn  
		
	
		
			
			from hypercorn.utils import parse_socket_addr  
		
	
		
			
			from hypercorn.asyncio import tcp_server  
		
	
	
		
			
				
				
				
				
					 
			
			@@ -83,6 +86,47 @@ def new_parse_socket_addr(domain, addr):  
		
	
		
			
			 
		
	
		
			
			tcp_server.parse_socket_addr = new_parse_socket_addr  
		
	
		
			
			 
		
	
		
			
			def looptoutc(looptime):  
		
	
		
			
				'''The argument looptime, which is a time stamp relative to the  
		
	
		
			
				current event loop's clock, to UTC.  
		
	
		
			
			 
		
	
		
			
				It does this by calculating the current offset, and applying that  
		
	
		
			
				offset.  This will deal with any time drift issues as it is  
		
	
		
			
				expected that the loop's clock does not stay in sync w/ UTC, but  
		
	
		
			
				it does mean that large differences from the current time are less  
		
	
		
			
				accurate.  That is if the returned value - current UTC is large,  
		
	
		
			
				then the accuracy of the time is not very high.  
		
	
		
			
			 
		
	
		
			
				Modern clocks are pretty accurate, but modern crystals do have an  
		
	
		
			
				error that will accumulate over time.  
		
	
		
			
			 
		
	
		
			
				This is only tested to nanosecond precision, as floating point does  
		
	
		
			
				not allow higher precision (and even if it did, as there is no way  
		
	
		
			
				to get the offset between the two in a single call, it will likely  
		
	
		
			
				introduce a larger offset than nanoseconds).  
		
	
		
			
				'''  
		
	
		
			
			 
		
	
		
			
				loop = asyncio.get_running_loop()  
		
	
		
			
				curlooptime = loop.time()  
		
	
		
			
				utctime = time.time()  
		
	
		
			
			 
		
	
		
			
				off = looptime - curlooptime  
		
	
		
			
			 
		
	
		
			
				return utctime + off  
		
	
		
			
			 
		
	
		
			
			def utctoloop(utctime):  
		
	
		
			
				'''For documentation, see looptoutc.  This is the inverse, but  
		
	
		
			
				all the warnings in there apply here as well.  
		
	
		
			
				'''  
		
	
		
			
			 
		
	
		
			
				loop = asyncio.get_running_loop()  
		
	
		
			
				looptime = loop.time()  
		
	
		
			
				curutctime = time.time()  
		
	
		
			
			 
		
	
		
			
				off = utctime - curutctime  
		
	
		
			
			 
		
	
		
			
				return looptime + off  
		
	
		
			
			 
		
	
		
			
			async def log_event(tag, board=None, user=None, extra={}):  
		
	
		
			
				info = extra.copy()  
		
	
		
			
				info['event'] = tag  
		
	
	
		
			
				
				
				
				
					 
			
			@@ -103,8 +147,101 @@ async def log_event(tag, board=None, user=None, extra={}):  
		
	
		
			
			 
		
	
		
			
				logging.info(json.dumps(info))  
		
	
		
			
			 
		
	
		
			
			class TimeOut(DefROAttribute):  
		
	
		
			
				defattername = 'timeout'  
		
	
		
			
			class TimeOut(Attribute):  
		
	
		
			
				'''  
		
	
		
			
				Implement a 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  
		
	
		
			
				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.  
		
	
		
			
			 
		
	
		
			
				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.  
		
	
		
			
				'''  
		
	
		
			
			 
		
	
		
			
				defattrname = 'timeout'  
		
	
		
			
			 
		
	
		
			
				def __init__(self, val):  
		
	
		
			
					self._value = val  
		
	
		
			
					self._brd = None  
		
	
		
			
			 
		
	
		
			
					# proteted by brd.lock  
		
	
		
			
					self._cb = None  
		
	
		
			
					self._task = None  
		
	
		
			
					self._exp = None  
		
	
		
			
			 
		
	
		
			
				async def getvalue(self):  
		
	
		
			
					if self._exp is None:  
		
	
		
			
						return self._value  
		
	
		
			
			 
		
	
		
			
					t = looptoutc(self._exp)  
		
	
		
			
			 
		
	
		
			
					return time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime(t)) + \  
		
	
		
			
					    '.%03dZ' % (int((t * 1000) % 1000),)  
		
	
		
			
			 
		
	
		
			
				async def setvalue(self, v):  
		
	
		
			
					if self._exp is None:  
		
	
		
			
						raise RuntimeError('cannot set when not activate')  
		
	
		
			
			 
		
	
		
			
					loop = asyncio.get_running_loop()  
		
	
		
			
			 
		
	
		
			
					t = parse_date(v).timestamp()  
		
	
		
			
					loopvalue = utctoloop(t)  
		
	
		
			
			 
		
	
		
			
					async with self._brd.lock:  
		
	
		
			
						if loopvalue > self._exp:  
		
	
		
			
							raise ValueError('value in the future')  
		
	
		
			
			 
		
	
		
			
						# I really don't know how test this  
		
	
		
			
						if self._task is not None:  
		
	
		
			
							raise ValueError('should never happen')  
		
	
		
			
			 
		
	
		
			
						await self.deactivate(self._brd)  
		
	
		
			
						self._cb = loop.call_at(loopvalue, self.timeout_callback)  
		
	
		
			
						self._exp = self._cb.when()  
		
	
		
			
			 
		
	
		
			
				async def activate(self, brd):  
		
	
		
			
					assert brd.lock.locked()  
		
	
		
			
			 
		
	
		
			
					loop = asyncio.get_running_loop()  
		
	
		
			
					self._brd = brd  
		
	
		
			
					self._cb = loop.call_later(self._value, self.timeout_callback)  
		
	
		
			
					self._exp = self._cb.when()  
		
	
		
			
					self._task = None  
		
	
		
			
			 
		
	
		
			
				async def deactivate(self, brd):  
		
	
		
			
					assert brd.lock.locked()  
		
	
		
			
			 
		
	
		
			
					if self._cb is not None:  
		
	
		
			
						self._cb.cancel()  
		
	
		
			
						self._cb = None  
		
	
		
			
			 
		
	
		
			
					if self._task is not None:  
		
	
		
			
						self._task.cancel()  
		
	
		
			
			 
		
	
		
			
						# awaiting on a canceled task blocks, spin the  
		
	
		
			
						# loop and make sure it was cancelled  
		
	
		
			
						await asyncio.sleep(0)  
		
	
		
			
						assert self._task.cancelled()  
		
	
		
			
						self._task = None  
		
	
		
			
			 
		
	
		
			
					self._exp = None  
		
	
		
			
			 
		
	
		
			
				@_tbprinter  
		
	
		
			
				async def timeout_coro(self):  
		
	
		
			
					print('tc1')  
		
	
		
			
					async with self._brd.lock:  
		
	
		
			
						print('tc2')  
		
	
		
			
						await self._brd.release()  
		
	
		
			
					print('tc3')  
		
	
		
			
			 
		
	
		
			
				def timeout_callback(self):  
		
	
		
			
					self._task = asyncio.create_task(self.timeout_coro())  
		
	
		
			
			 
		
	
		
			
			class EtherIface(DefROAttribute):  
		
	
		
			
				defattrname = 'eiface'  
		
	
	
		
			
				
				
					 
			
			@@ -147,7 +284,13 @@ class BoardImpl:  
		
	
		
			
					self.attrmap = {}  
		
	
		
			
					self.lock = asyncio.Lock()  
		
	
		
			
					for i in options:  
		
	
		
			
						self.attrmap[i.defattrname] = i  
		
	
		
			
						cls, kwargs = i  
		
	
		
			
						opt = cls(**kwargs)  
		
	
		
			
						if opt.defattrname in self.attrmap:  
		
	
		
			
							raise ValueError(  
		
	
		
			
							    'attribute name %s duplicated' %  
		
	
		
			
							    repr(opt.defattrname))  
		
	
		
			
						self.attrmap[opt.defattrname] = opt  
		
	
		
			
			 
		
	
		
			
					self.attrcache = {}  
		
	
		
			
			 
		
	
	
		
			
				
				
					 
			
			@@ -177,13 +320,13 @@ class BoardImpl:  
		
	
		
			
				async def activate(self):  
		
	
		
			
					assert self.lock.locked() and self.reserved  
		
	
		
			
			 
		
	
		
			
					for i in self.options :  
		
	
		
			
					for i in self.attrmap.values() :  
		
	
		
			
						await i.activate(self)  
		
	
		
			
			 
		
	
		
			
				async def deactivate(self):  
		
	
		
			
					assert self.lock.locked() and self.reserved  
		
	
		
			
			 
		
	
		
			
					for i in self.options :  
		
	
		
			
					for i in self.attrmap.values() :  
		
	
		
			
						await i.deactivate(self)  
		
	
		
			
			 
		
	
		
			
				def add_info(self, d):  
		
	
	
		
			
				
				
					 
			
			@@ -230,7 +373,7 @@ class BoardManager(object):  
		
	
		
			
					classes = conf['classes']  
		
	
		
			
			 
		
	
		
			
					brds = conf['boards']  
		
	
		
			
					makeopt = lambda x: cls._option_map[x['cls']](** { k: v for k, v in x.items() if k != 'cls' })  
		
	
		
			
					makeopt = lambda x: (cls._option_map[x['cls']],  { k: v for k, v in x.items() if k != 'cls' })  
		
	
		
			
					for i in brds:  
		
	
		
			
						opt = i['options']  
		
	
		
			
						opt[:] = [ makeopt(x) for x in opt ]  
		
	
	
		
			
				
				
					 
			
			@@ -1232,8 +1375,9 @@ class TestBiteLab(TestCommon):  
		
	
		
			
			class TestBoardImpl(unittest.IsolatedAsyncioTestCase):  
		
	
		
			
				async def test_activate(self):  
		
	
		
			
					# that a board impl  
		
	
		
			
					opt = create_autospec(Attribute)  
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ opt ])  
		
	
		
			
					opttup = create_autospec, dict(spec=Attribute)  
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ opttup ])  
		
	
		
			
					(opt,) = tuple(brd.attrmap.values())  
		
	
		
			
			 
		
	
		
			
					async with brd.lock:  
		
	
		
			
						await brd.reserve()  
		
	
	
		
			
				
				
				
				
					 
			
			@@ -1243,8 +1387,9 @@ class TestBoardImpl(unittest.IsolatedAsyncioTestCase):  
		
	
		
			
			 
		
	
		
			
				async def test_deactivate(self):  
		
	
		
			
					# that a board impl  
		
	
		
			
					opt = create_autospec(Attribute)  
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ opt ])  
		
	
		
			
					opttup = create_autospec, dict(spec=Attribute)  
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ opttup ])  
		
	
		
			
					(opt,) = tuple(brd.attrmap.values())  
		
	
		
			
			 
		
	
		
			
					async with brd.lock:  
		
	
		
			
						await brd.reserve()  
		
	
	
		
			
				
				
					 
			
			@@ -1299,7 +1444,11 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):  
		
	
		
			
				@patch('asyncio.create_subprocess_exec')  
		
	
		
			
				async def test_serialconsole(self, cse):  
		
	
		
			
					data = 'somepath'  
		
	
		
			
					sc = SerialConsole(data)  
		
	
		
			
			 
		
	
		
			
					sctup = (SerialConsole, dict(val=data))  
		
	
		
			
			 
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ sctup ])  
		
	
		
			
					sc = brd.attrmap['console']  
		
	
		
			
			 
		
	
		
			
					self.assertEqual(sc.defattrname, 'console')  
		
	
		
			
			 
		
	
	
		
			
				
				
				
				
					 
			
			@@ -1310,7 +1459,6 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):  
		
	
		
			
			 
		
	
		
			
					devfspath = 'eifd'  
		
	
		
			
			 
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ sc ])  
		
	
		
			
					brd.add_info(dict(devfspath=devfspath))  
		
	
		
			
			 
		
	
		
			
					wrap_subprocess_exec(cse, retcode=0)  
		
	
	
		
			
				
				
					 
			
			@@ -1337,7 +1485,11 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):  
		
	
		
			
				async def test_etheriface(self, cse):  
		
	
		
			
					eiface = 'aneiface'  
		
	
		
			
			 
		
	
		
			
					ei = EtherIface(eiface)  
		
	
		
			
					eitup = EtherIface, dict(val=eiface)  
		
	
		
			
			 
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ eitup ])  
		
	
		
			
			 
		
	
		
			
					ei = brd.attrmap['eiface']  
		
	
		
			
			 
		
	
		
			
					self.assertEqual(ei.defattrname, 'eiface')  
		
	
		
			
			 
		
	
	
		
			
				
				
				
				
					 
			
			@@ -1346,8 +1498,6 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):  
		
	
		
			
					with self.assertRaises(TypeError):  
		
	
		
			
						await ei.setvalue('randomdata')  
		
	
		
			
			 
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ ei ])  
		
	
		
			
			 
		
	
		
			
					wrap_subprocess_exec(cse, retcode=0)  
		
	
		
			
			 
		
	
		
			
					await ei.activate(brd)  
		
	
	
		
			
				
				
				
				
					 
			
			@@ -1360,9 +1510,172 @@ class TestAttrs(unittest.IsolatedAsyncioTestCase):  
		
	
		
			
					with self.assertRaises(RuntimeError):  
		
	
		
			
						await ei.activate(brd)  
		
	
		
			
			 
		
	
		
			
				async def test_multipleattrs(self):  
		
	
		
			
					attrs = [ (Power, dict()) ] * 2  
		
	
		
			
			 
		
	
		
			
					# That multiple attributes w/ same name raises ValueError  
		
	
		
			
					with self.assertRaises(ValueError):  
		
	
		
			
						BoardImpl('foo', 'bar', attrs)  
		
	
		
			
			 
		
	
		
			
				# Enough of this code depends upon the event loop using the  
		
	
		
			
				# code in BaseEventLoop wrt scheduling that this is not a  
		
	
		
			
				# terrible test.  If this fails, it is likely the selector  
		
	
		
			
				# doesn't use a sane event loop.  
		
	
		
			
				@patch('asyncio.BaseEventLoop.time')  
		
	
		
			
				@patch('time.time')  
		
	
		
			
				async def test_looptoutc(self, ttime, beltime):  
		
	
		
			
					loop = asyncio.get_running_loop()  
		
	
		
			
			 
		
	
		
			
					utctime = 19239  
		
	
		
			
					belsrctime = 892934  
		
	
		
			
					ttime.return_value = utctime  
		
	
		
			
					beltime.return_value = belsrctime  
		
	
		
			
			 
		
	
		
			
					# that when given the current loop time, that  
		
	
		
			
					# it returns the current utc time  
		
	
		
			
					self.assertEqual(looptoutc(belsrctime), utctime)  
		
	
		
			
			 
		
	
		
			
					# then when an offset is applied  
		
	
		
			
					import random  
		
	
		
			
					offset = random.random() * 1000000  
		
	
		
			
					offset = .000999 * 100000  
		
	
		
			
			 
		
	
		
			
					# the utc has the same offset  
		
	
		
			
					# it'd be nice if this was exact, but it's not because  
		
	
		
			
					# floating point.  9 places gets us nanosecond precision  
		
	
		
			
					self.assertAlmostEqual(looptoutc(belsrctime + offset), utctime + offset, places=9)  
		
	
		
			
			 
		
	
		
			
					# make sure w/ the new code, it round trips  
		
	
		
			
					sometime = 238974.34  
		
	
		
			
					self.assertAlmostEqual(utctoloop(looptoutc(sometime)), sometime)  
		
	
		
			
					self.assertAlmostEqual(looptoutc(utctoloop(sometime)), sometime)  
		
	
		
			
			 
		
	
		
			
				@timeout(2)  
		
	
		
			
				@patch('asyncio.BaseEventLoop.time')  
		
	
		
			
				@patch('time.time')  
		
	
		
			
				async def test_timeout_vals(self, ttime, belt):  
		
	
		
			
					# that a TimeOut with args  
		
	
		
			
					totup = TimeOut, dict(val=10)  
		
	
		
			
			 
		
	
		
			
					# passed to a board w/ the totup  
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ totup ])  
		
	
		
			
			 
		
	
		
			
					to = brd.attrmap['timeout']  
		
	
		
			
			 
		
	
		
			
					with self.assertRaises(RuntimeError):  
		
	
		
			
						# that setting the value when not activate errors  
		
	
		
			
						await to.setvalue(234987)  
		
	
		
			
			 
		
	
		
			
					# that an update will populate the attrs.  
		
	
		
			
					await brd.update()  
		
	
		
			
			 
		
	
		
			
					# and that the board attrs will be present  
		
	
		
			
					# and contain the current timeout  
		
	
		
			
					self.assertEqual(brd.attrs, dict(timeout=10))  
		
	
		
			
			 
		
	
		
			
					# that a given loop time  
		
	
		
			
					looptime = 100.384  
		
	
		
			
					belt.return_value = 100.384  
		
	
		
			
			 
		
	
		
			
					# and a given UTC time (hu Dec 10 14:06:35 UTC 2020)  
		
	
		
			
					utctime = 1607609195.28  
		
	
		
			
					ttime.return_value = utctime  
		
	
		
			
			 
		
	
		
			
					# that when reserved/activated  
		
	
		
			
					async with brd.lock:  
		
	
		
			
						await brd.reserve()  
		
	
		
			
						await brd.activate()  
		
	
		
			
			 
		
	
		
			
					await brd.update()  
		
	
		
			
			 
		
	
		
			
					# That it returns timeout seconds in the future.  
		
	
		
			
					self.assertEqual(brd.attrs, dict(timeout='2020-12-10T14:06:45.280Z'))  
		
	
		
			
			 
		
	
		
			
					with self.assertRaises(ValueError):  
		
	
		
			
						# that setting it to a value farther into  
		
	
		
			
						# the future fails  
		
	
		
			
						await to.setvalue('2020-12-10T14:06:55.280Z')  
		
	
		
			
			 
		
	
		
			
					with self.assertRaises(ValueError):  
		
	
		
			
						# that passing a non-Z ending (not UTC) date fails  
		
	
		
			
						await to.setvalue('2020-12-10T14:06:55.28')  
		
	
		
			
			 
		
	
		
			
					# that setting it to a time slightly earlier  
		
	
		
			
					await to.setvalue('2020-12-10T14:06:44.280Z')  
		
	
		
			
			 
		
	
		
			
					await brd.update()  
		
	
		
			
			 
		
	
		
			
					# That it returns that time  
		
	
		
			
					self.assertEqual(brd.attrs, dict(timeout='2020-12-10T14:06:44.280Z'))  
		
	
		
			
			 
		
	
		
			
				@timeout(2)  
		
	
		
			
				async def test_timeout(self):  
		
	
		
			
					# that a TimeOut can be created  
		
	
		
			
					to = TimeOut(.1)  
		
	
		
			
					# that a TimeOut with args  
		
	
		
			
					totup = TimeOut, dict(val=.01)  
		
	
		
			
			 
		
	
		
			
					# passed to a board w/ the totup  
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ totup ])  
		
	
		
			
			 
		
	
		
			
					to = brd.attrmap['timeout']  
		
	
		
			
			 
		
	
		
			
					# that when reserved/activated  
		
	
		
			
					async with brd.lock:  
		
	
		
			
						await brd.reserve()  
		
	
		
			
						await brd.activate()  
		
	
		
			
			 
		
	
		
			
					evt = asyncio.Event()  
		
	
		
			
					loop = asyncio.get_running_loop()  
		
	
		
			
					loop.call_at(to._exp + epsilon, evt.set)  
		
	
		
			
					await evt.wait()  
		
	
		
			
			 
		
	
		
			
					# that the board is no longer reserved  
		
	
		
			
					self.assertFalse(brd.reserved)  
		
	
		
			
			 
		
	
		
			
					# that when reserved/activated/deactivated/released  
		
	
		
			
					async with brd.lock:  
		
	
		
			
						await brd.reserve()  
		
	
		
			
						await brd.activate()  
		
	
		
			
						exp = to._exp  
		
	
		
			
						await brd.deactivate()  
		
	
		
			
						await brd.release()  
		
	
		
			
			 
		
	
		
			
					# that the expiration is no longer there  
		
	
		
			
					self.assertIsNone(to._exp)  
		
	
		
			
					print('z')  
		
	
		
			
			 
		
	
		
			
					# and the timeout passes  
		
	
		
			
					evt = asyncio.Event()  
		
	
		
			
					loop = asyncio.get_running_loop()  
		
	
		
			
					loop.call_at(exp + epsilon, evt.set)  
		
	
		
			
					await evt.wait()  
		
	
		
			
			 
		
	
		
			
					print('a')  
		
	
		
			
					# that when reserved/activated  
		
	
		
			
					async with brd.lock:  
		
	
		
			
						await brd.reserve()  
		
	
		
			
						await brd.activate()  
		
	
		
			
			 
		
	
		
			
					print('b')  
		
	
		
			
					# but the board is locked for some reason  
		
	
		
			
					await brd.lock.acquire()  
		
	
		
			
			 
		
	
		
			
					print('c')  
		
	
		
			
					# and the callback is called  
		
	
		
			
					await asyncio.sleep(.02)  
		
	
		
			
			 
		
	
		
			
					print('d')  
		
	
		
			
					# that the task has been scheduled  
		
	
		
			
					self.assertIsNotNone(to._task)  
		
	
		
			
			 
		
	
		
			
					print('e')  
		
	
		
			
					# that it can be deactivated  
		
	
		
			
					await brd.deactivate()  
		
	
		
			
			 
		
	
		
			
					print('f')  
		
	
		
			
					# and when the board lock is released  
		
	
		
			
					brd.lock.release()  
		
	
		
			
			 
		
	
		
			
					print('g')  
		
	
		
			
					# that the board was not released  
		
	
		
			
					self.assertTrue(brd.reserved)  
		
	
		
			
			 
		
	
		
			
					# that a board w/ the to  
		
	
		
			
					brd = BoardImpl('foo', 'bar', [ to ])