def test_bad_infer_width_and_height(working_args): a = working_args.copy() del a["width"] with pytest.raises(TypeError): Machine(**a) a = working_args.copy() del a["height"] with pytest.raises(TypeError): Machine(**a)
def test_with_standard_ips(): board_locations = {(x, y, z): (x, y, z) for x in range(2) for y in range(2) for z in range(3)} m = Machine.with_standard_ips("m", board_locations=board_locations) assert m.bmp_ips == { (0, 0): "192.168.0.0", (0, 1): "192.168.1.0", (1, 0): "192.168.5.0", (1, 1): "192.168.6.0", } assert m.spinnaker_ips == { (0, 0, 0): "192.168.0.1", (0, 0, 1): "192.168.0.9", (0, 0, 2): "192.168.0.17", (0, 1, 0): "192.168.1.1", (0, 1, 1): "192.168.1.9", (0, 1, 2): "192.168.1.17", (1, 0, 0): "192.168.5.1", (1, 0, 1): "192.168.5.9", (1, 0, 2): "192.168.5.17", (1, 1, 0): "192.168.6.1", (1, 1, 1): "192.168.6.9", (1, 1, 2): "192.168.6.17", }
def test_single_board(): m = Machine.single_board("m", set(["default"]), "bmp", "spinn") assert m.name == "m" assert m.tags == set(["default"]) assert m.width == 1 assert m.height == 1 assert m.dead_boards == set([(0, 0, 1), (0, 0, 2)]) assert m.dead_links == set() assert m.board_locations == {(0, 0, 0): (0, 0, 0)} assert m.bmp_ips == {(0, 0): "bmp"} assert m.spinnaker_ips == {(0, 0, 0): "spinn"}
def test_single_board_no_ip(): with pytest.raises(TypeError): Machine.single_board("m", set(["default"])) with pytest.raises(TypeError): Machine.single_board("m", set(["default"]), bmp_ip="foo") with pytest.raises(TypeError): Machine.single_board("m", set(["default"]), spinnaker_ip="bar")
def big_m_with_hole(conn): """Add a larger 4x2 machine to the controller with board (2, 1, 0) dead.""" conn.machines = { "big_m": Machine.with_standard_ips(name="big_m", dead_boards=set([(2, 1, 0)]), board_locations={(x, y, z): (x * 10, y * 10, z * 10) for x in range(4) for y in range(2) for z in range(3) if (x, y, z) != (2, 1, 0)}), } return "big_m"
def simple_machine(name, width=1, height=2, tags=set(["default"]), dead_boards=None, dead_links=None, ip_prefix=""): """Construct a simple machine with nothing broken etc.""" return Machine(name=name, tags=tags, width=width, height=height, dead_boards=dead_boards or set(), dead_links=dead_links or set(), board_locations={(x, y, z): (x, y, z) for x in range(width) for y in range(height) for z in range(3)}, bmp_ips={(x, y): "{}10.1.{}.{}".format(ip_prefix, x, y) for x in range(width) for y in range(height)}, spinnaker_ips={(x, y, z): "{}11.{}.{}.{}".format( ip_prefix, x, y, z) for x in range(width) for y in range(height) for z in range(3)})
def test_with_standard_ips_bad_ias(): board_locations = {(x, y, z): (x, y, z) for x in range(2) for y in range(2) for z in range(3)} # Not IPv4 address with pytest.raises(ValueError): Machine.with_standard_ips("m", board_locations=board_locations, base_ip="spinn-4") # Malformed IPv4 addresses with pytest.raises(ValueError): Machine.with_standard_ips("m", board_locations=board_locations, base_ip="1.2.3") with pytest.raises(ValueError): Machine.with_standard_ips("m", board_locations=board_locations, base_ip="-1.2.3.4") with pytest.raises(ValueError): Machine.with_standard_ips("m", board_locations=board_locations, base_ip="256.2.3.4")
def test_machine(name="m", bmp_prefix=None, spinnaker_prefix=None): """A minimal set of valid arguments for a Machine's constructor.""" bmp_prefix = bmp_prefix or "bmp_{}".format(name) spinnaker_prefix = spinnaker_prefix or "spinn_{}".format(name) return Machine( name=name, tags=set("default"), width=2, height=1, dead_boards=set(), dead_links=set(), board_locations={(x, y, z): (x * 10, y * 10, z * 10) for x in range(2) for y in range(1) for z in range(3)}, bmp_ips={(c * 10, f * 10): "{}_{}_{}".format(bmp_prefix, c, f) for c in range(2) for f in range(1)}, spinnaker_ips={(x, y, z): "{}_{}_{}_{}".format(spinnaker_prefix, x, y, z) for x in range(2) for y in range(1) for z in range(3)}, )
def test_bad_dead_links(working_args, x, y, z): # If any links are out of range, should fail working_args["dead_links"].add((x, y, z, Links.north)) with pytest.raises(ValueError): Machine(**working_args)
def test_valid_args(working_args): # Should not fail to validate something valid Machine(**working_args)
def test_controller_set_machines(conn, mock_abc): # Test the ability to add machines # Create a set of machines machines = OrderedDict() for num in range(3): m = Machine(name="m{}".format(num), tags=set(["default", "num{}".format(num)]), width=1 + num, height=2, dead_boards=set([(0, 0, 1)]), dead_links=set([(0, 0, 2, Links.north)]), board_locations={(x, y, z): (x * 10, y * 10, z * 10) for x in range(1 + num) for y in range(2) for z in range(3)}, bmp_ips={(c * 10, f * 10): "10.1.{}.{}".format(c, f) for c in range(1 + num) for f in range(2)}, spinnaker_ips={(x, y, z): "11.{}.{}.{}".format(x, y, z) for x in range(1 + num) for y in range(2) for z in range(3)}) machines[m.name] = m m0 = machines["m0"] m1 = machines["m1"] m2 = machines["m2"] # Special case: Setting no machines should not break anything machines = OrderedDict() conn.machines = machines assert len(conn._machines) == 0 assert len(conn._job_queue._machines) == 0 assert len(conn._bmp_controllers) == 0 assert mock_abc.running_theads == 0 # Try adding a pair of machines machines["m1"] = m1 machines["m0"] = m0 conn.machines = machines # Check that the set of machines copies across assert conn._machines == machines # Make sure things are passed into the job queue correctly assert list(conn._job_queue._machines) == ["m1", "m0"] assert conn._job_queue._machines["m0"].tags == m0.tags assert conn._job_queue._machines["m0"].allocator.dead_boards \ == m0.dead_boards assert conn._job_queue._machines["m0"].allocator.dead_links \ == m0.dead_links assert conn._job_queue._machines["m1"].tags == m1.tags assert conn._job_queue._machines["m1"].allocator.dead_boards \ == m1.dead_boards assert conn._job_queue._machines["m1"].allocator.dead_links \ == m1.dead_links # Make sure BMP controllers are spun-up correctly assert len(conn._bmp_controllers) == 2 assert len(conn._bmp_controllers["m0"]) == m0.width * m0.height assert len(conn._bmp_controllers["m1"]) == m1.width * m1.height for m_name, controllers in conn._bmp_controllers.items(): for c in range(machines[m_name].width): for f in range(machines[m_name].height): assert controllers[(c*10, f*10)].hostname \ == "10.1.{}.{}".format(c, f) assert mock_abc.running_theads == mock_abc.num_created == ( (m1.width * m1.height) + (m0.width * m0.height)) # If we pass in the same machines in, nothing should get changed conn.machines = machines assert conn._machines == machines assert list(conn._job_queue._machines) == list(machines) assert mock_abc.running_theads == mock_abc.num_created == ( (m1.width * m1.height) + (m0.width * m0.height)) # If we pass in the same machines in a different order, the order should # change but nothing should get spun up/down machines = OrderedDict() machines["m0"] = m0 machines["m1"] = m1 conn.machines = machines assert conn._machines == machines assert list(conn._job_queue._machines) == list(machines) assert mock_abc.running_theads == mock_abc.num_created == ( (m1.width * m1.height) + (m0.width * m0.height)) # Adding a new machine should spin just one new machine up leaving the # others unchanged machines = OrderedDict() machines["m0"] = m0 machines["m1"] = m1 machines["m2"] = m2 conn.machines = machines assert conn._machines == machines assert list(conn._job_queue._machines) == list(machines) assert mock_abc.running_theads == mock_abc.num_created == ( m2.width * m2.height + m1.width * m1.height + m0.width * m0.height) # Modifying a machine in minor ways: should not respin anything but the # change should be applied m0 = Machine(name=m0.name, tags=set(["new tags"]), width=m0.width, height=m0.height, dead_boards=set([(0, 0, 0)]), dead_links=set([(0, 0, 0, Links.south)]), board_locations=m0.board_locations, bmp_ips=m0.bmp_ips, spinnaker_ips=m0.spinnaker_ips) machines["m0"] = m0 conn.machines = machines # Machine list should be updated assert conn._machines == machines # Job queue should be updated assert list(conn._job_queue._machines) == list(machines) assert conn._job_queue._machines["m0"].tags == set(["new tags"]) assert conn._job_queue._machines["m0"].allocator.dead_boards \ == set([(0, 0, 0)]) assert conn._job_queue._machines["m0"].allocator.dead_links \ == set([(0, 0, 0, Links.south)]) # Nothing should be spun up assert mock_abc.running_theads == mock_abc.num_created == ( m2.width * m2.height + m1.width * m1.height + m0.width * m0.height) # Removing a machine should result in things being spun down del machines["m0"] conn.machines = machines # Machine list should be updated assert conn._machines == machines # Job queue should be updated assert list(conn._job_queue._machines) == list(machines) # Some BMPs should now be shut down time.sleep(0.05) assert mock_abc.running_theads == ((m2.width * m2.height) + (m1.width * m1.height)) # Nothing new should be spun up assert mock_abc.num_created == (m2.width * m2.height + m1.width * m1.height + m0.width * m0.height) # Making any significant change to a machine should result in it being # re-spun. m1 = Machine( name=m1.name, tags=m1.tags, width=m1.width - 1, # A significant change(!) height=m1.height, dead_boards=m1.dead_boards, dead_links=m1.dead_links, board_locations=m0.board_locations, bmp_ips=m0.bmp_ips, spinnaker_ips=m0.spinnaker_ips) machines["m1"] = m1 m1_alloc_before = conn._job_queue._machines["m1"].allocator m2_alloc_before = conn._job_queue._machines["m2"].allocator conn.machines = machines m1_alloc_after = conn._job_queue._machines["m1"].allocator m2_alloc_after = conn._job_queue._machines["m2"].allocator # Machine list should be updated assert conn._machines == machines time.sleep(0.05) # Job queue should be updated and a new allocator etc. made for the new # machine assert list(conn._job_queue._machines) == list(machines) assert m1_alloc_before is not m1_alloc_after assert m2_alloc_before is m2_alloc_after # Same number of BMPs should be up assert mock_abc.running_theads == ((m2.width * m2.height) + (m1.width * m1.height)) # But a new M1 should be spun up assert mock_abc.num_created == ((m2.width * m2.height) + ((m1.width + 1) * m1.height) + (m1.width * m1.height) + (m0.width * m0.height))
def test_bad_dead_boards_type(working_args): working_args["dead_boards"] = [(0, 0, 0)] with pytest.raises(TypeError): Machine(**working_args)
def test_bad_tags(working_args): working_args["tags"] = ["foo"] with pytest.raises(TypeError): Machine(**working_args)
def test_spinnaker_ips_defined(working_args): # All boards whose location is specified should have a BMP IP del working_args["spinnaker_ips"][(0, 0, 0)] with pytest.raises(ValueError): Machine(**working_args)
def test_infer_width_and_height(working_args): del working_args["width"] del working_args["height"] m = Machine(**working_args) assert m.width == 2 assert m.height == 1
def test_board_locations_defined(working_args): # If any live board locations are not given, we should fail. We reomve a # dead board whose location is otherwise not set working_args["dead_boards"].clear() with pytest.raises(ValueError): Machine(**working_args)
def test_board_locations_no_duplicates(working_args): # No two boards should have the same location working_args["board_locations"][(0, 0, 0)] = (0, 0, 0) working_args["board_locations"][(0, 0, 1)] = (0, 0, 0) with pytest.raises(ValueError): Machine(**working_args)
def test_board_locations_in_machine(working_args, x, y, z): # If any live board locations are given for boards outside the system, we # should fail working_args["board_locations"][(x, y, z)] = (100, 100, 100) with pytest.raises(ValueError): Machine(**working_args)
def test_bad_dead_links_type(working_args): working_args["dead_links"] = [(0, 0, 0, Links.north)] with pytest.raises(TypeError): Machine(**working_args)
def test_bad_dead_boards(working_args, x, y, z): # If any boards are out of range, should fail working_args["dead_boards"].add((x, y, z)) with pytest.raises(ValueError): Machine(**working_args)