예제 #1
0
    def test_alloc_triads_single(self):
        # Should be able to allocate single blocks
        w, h = 3, 4
        next_id = 10
        a = Allocator(w, h, next_id=next_id)

        for _ in range(w * h):
            allocation_id, boards, periphery, torus = a._alloc_triads(1, 1)

            assert torus is WrapAround.none

            assert allocation_id == next_id
            next_id += 1

            assert len(boards) == 3
            xys = set((x, y) for (x, y, z) in boards)
            assert len(xys) == 1
            assert set(z for (x, y, z) in boards) == set(range(3))

            x, y = xys.pop()
            assert periphery == set((x, y, z, link)
                                    for z in range(3)
                                    for link in Links
                                    if (board_down_link(x, y, z,
                                                        link, w, h)[:2] !=
                                        (x, y)))

        # Should get full
        assert a._alloc_triads(1, 1) is None
    def test_call_success(self, have_dead_links, have_dead_boards):
        # If "None" is provided for maximum links/boards we should always
        # succeed
        w, h = 10, 9
        dead_boards = set([0, 0, 1]) if have_dead_boards else set()
        dead_links = set([0, 0, 0, Links.north]) if have_dead_links else set()
        cf = _CandidateFilter(w, h, dead_boards, dead_links, None, None, False)

        assert cf(0, 0, 1, 1) is True
        assert cf.boards == set((0, 0, z) for z in range(3))
        assert cf.periphery == set(  # pragma: no branch
            (0, 0, z, link) for z in range(3) for link in Links
            if board_down_link(0, 0, z, link, w, h)[:2] != (0, 0))
        assert cf.torus == WrapAround.none
def test_create_job(conn, m):
    controller = conn._bmp_controllers[m][(0, 0)]

    with controller.handler_lock:
        # Make sure newly added jobs can start straight away and have the BMP
        # block
        job_id1 = conn.create_job(None, 1, 1, owner="me", keepalive=60.0)

        # Sane default keepalive should be selected
        assert conn._jobs[job_id1].keepalive == 60.0

        # BMPs should have been told to power-on
        assert all(
            set(requests.power_on_boards) == set(range(3))
            for requests in controller.add_requests_calls)

        # Links around the allocation should have been disabled
        assert all(_e is False for requests in controller.add_requests_calls
                   for _b, _l, _e in requests.link_requests)
        assert (  # pragma: no branch
            set((_b, _l) for requests in controller.add_requests_calls
                for _b, _l, _e in requests.link_requests) == set(
                    (_b, _l) for _b in range(3) for _l in Links
                    if board_down_link(0, 0, _b, _l, 1, 2)[:2] != (0, 0)))

        # Job should be waiting for power-on since the BMP is blocked
        time.sleep(0.05)
        assert conn.get_job_state(None, job_id1).state is JobState.power

    # Job should be powered on once the BMP process returns
    time.sleep(0.05)
    assert conn.get_job_state(None, job_id1).state is JobState.ready

    # Adding another job which will be queued should result in a job in the
    # right state.
    job_id2 = conn.create_job(None, 1, 2, owner="me", keepalive=10.0)
    assert conn._jobs[job_id2].keepalive == 10.0
    assert job_id1 != job_id2
    assert conn.get_job_state(None, job_id2).state is JobState.queued

    # Adding a job which cannot fit should come out immediately cancelled
    job_id3 = conn.create_job(None, 2, 2, owner="me", keepalive=60.0)
    assert job_id1 != job_id3 and job_id2 != job_id3
    assert conn.get_job_state(None, job_id3).state is JobState.destroyed
    assert conn.get_job_state(None, job_id3).reason \
        == "Cancelled: No suitable machines available."
예제 #4
0
    def _enumerate_boards(self, x, y, width, height):
        """Starting from board (x, y, 0), enumerate as many reachable and
        working boards as possible within the rectangle width x height triads.

        Returns
        -------
        set([(x, y, z), ...])
        """
        # The set of visited (and working) boards
        boards = set()

        to_visit = deque([(x, y, 0)])
        while to_visit:
            x1, y1, z1 = to_visit.popleft()

            # Skip dead boards and boards we've seen before
            if ((x1, y1, z1) in self.dead_boards or
                    (x1, y1, z1) in boards):
                continue

            boards.add((x1, y1, z1))

            # Visit neighbours which are within the range
            for link in Links:
                # Skip dead links
                if (x1, y1, z1, link) in self.dead_links:
                    continue

                x2, y2, z2, _ = board_down_link(x1, y1, z1, link,
                                                self.width, self.height)

                # Skip links to boards outside the specified range
                if not (x <= x2 < x + width and
                        y <= y2 < y + height):
                    continue

                to_visit.append((x2, y2, z2))

        # Return the set of boards we could reach
        return boards
예제 #5
0
def test_board_down_link(x1, y1, z1, link, width, height, x2, y2, z2, wrapped):
    assert (board_down_link(x1, y1, z1, link, width,
                            height) == (x2, y2, z2, wrapped))
예제 #6
0
    def _classify_links(self, boards):
        """Get a list of links of various classes connected to the supplied set
        of boards.

        Parameters
        ----------
        boards : set([(x, y, z), ...])
            A set of fully-connected, alive boards.

        Returns
        -------
        alive : set([(x, y, z, :py:class:`rig.links.Links`), ...])
            Links which are working and connect one board
            in the set to another.
        wrap : set([(x, y, z, :py:class:`rig.links.Links`), ...])
            Working links between working boards in the set which wrap-around
            the toroid.
        dead : set([(x, y, z, :py:class:`rig.links.Links`), ...])
            Links which are not working and connect one board in the set to
            another.
        dead_wrap : set([(x, y, z, :py:class:`rig.links.Links`), ...])
            Dead links between working boards in the set which wrap-around the
            toroid.
        periphery : set([(x, y, z, :py:class:`rig.links.Links`), ...])
            Links are those which connect from one board in the set to a board
            outside the set. These links may be dead or alive.
        wrap_around_type : :py:class:`~spalloc_server.coordinates.WrapAround`
            What types of wrap-around links are present (making no distinction
            between dead and alive links)?
        """
        alive = set()
        wrap = set()
        dead = set()
        dead_wrap = set()
        periphery = set()
        wrap_around_type = WrapAround.none

        for x1, y1, z1 in boards:
            for link in Links:
                is_dead = (x1, y1, z1, link) in self.dead_links
                x2, y2, z2, wrapped = board_down_link(x1, y1, z1, link,
                                                      self.width, self.height)
                in_set = (x2, y2, z2) in boards

                if in_set:
                    wrap_around_type |= wrapped

                    if wrapped:
                        if is_dead:
                            dead_wrap.add((x1, y1, z1, link))
                        else:
                            wrap.add((x1, y1, z1, link))
                    else:
                        if is_dead:
                            dead.add((x1, y1, z1, link))
                        else:
                            alive.add((x1, y1, z1, link))
                else:
                    periphery.add((x1, y1, z1, link))

        return (alive, wrap, dead, dead_wrap, periphery,
                WrapAround(wrap_around_type))
예제 #7
0
def test_enqueue_job(q, on_allocate, on_cancel):
    # Make sure that the _enqueue_job method does what it should (tested via
    # calls to create_job).

    q.add_machine("1x4", 1, 4)
    q.add_machine("1x2", 1, 2)
    q.add_machine("1x5", 1, 5)
    q.add_machine("1x2_pie", 1, 2, tags=set(["pie", "chips"]))
    q.add_machine("1x2_curry", 1, 2, tags=set(["curry", "chips"]))

    # If we create a 1x1 job we should get put on the first available machine.
    # Also verifies the call to on_allocate.
    q.create_job(1, 1, job_id=100)
    on_allocate.assert_called_once_with(
        100, "1x4",
        set((0, 0, z) for z in range(3)),
        set((0, 0, z, link)
            for z in range(3)
            for link in Links
            if board_down_link(0, 0, z, link, 1, 4)[:2] != (0, 0)),
        True)
    on_allocate.reset_mock()

    # We should make use of the first machine which fits and skip over anything
    # which is full or too small.
    q.create_job(1, 4, job_id=110)
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 110
    assert on_allocate.mock_calls[0][1][1] == "1x5"
    on_allocate.reset_mock()

    # We should still be able to fit small things in the first available
    # machine
    q.create_job(1, 3, job_id=120)
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 120
    assert on_allocate.mock_calls[0][1][1] == "1x4"
    on_allocate.reset_mock()

    # Should be able to specify a specific machine
    q.create_job(1, 1, job_id=130, machine="1x5")
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 130
    assert on_allocate.mock_calls[0][1][1] == "1x5"
    on_allocate.reset_mock()

    # Fill up the last available (default-tagged) free slot
    q.create_job(1, 2, job_id=140)
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 140
    assert on_allocate.mock_calls[0][1][1] == "1x2"
    on_allocate.reset_mock()

    # The next job created should get queued on all default-tagged machines
    q.create_job(1, 1, job_id=150)
    assert len(on_allocate.mock_calls) == 0
    assert list(q._machines["1x4"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2"].queue) == [q._jobs[150]]
    assert list(q._machines["1x5"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2_pie"].queue) == []
    assert list(q._machines["1x2_curry"].queue) == []

    # Should be able to create jobs on machines by tag, choosing the first one
    # when possible
    q.create_job(1, 1, job_id=160, tags=set(["chips"]))
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 160
    assert on_allocate.mock_calls[0][1][1] == "1x2_pie"
    on_allocate.reset_mock()

    # Should be able to create jobs on machines by tag, selecting by multiple
    # tags at once.
    q.create_job(1, 1, job_id=170, tags=set(["curry", "chips"]))
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 170
    assert on_allocate.mock_calls[0][1][1] == "1x2_curry"
    on_allocate.reset_mock()

    # Should be able to queue on tagged things
    q.create_job(1, 2, job_id=180, tags=set(["chips"]))
    assert list(q._machines["1x4"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2"].queue) == [q._jobs[150]]
    assert list(q._machines["1x5"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2_pie"].queue) == [q._jobs[180]]
    assert list(q._machines["1x2_curry"].queue) == [q._jobs[180]]

    # Should be able to queue on all things
    q.create_job(1, 2, job_id=190, tags=set())
    assert list(q._machines["1x4"].queue) == [q._jobs[150], q._jobs[190]]
    assert list(q._machines["1x2"].queue) == [q._jobs[150], q._jobs[190]]
    assert list(q._machines["1x5"].queue) == [q._jobs[150], q._jobs[190]]
    assert list(q._machines["1x2_pie"].queue) == [q._jobs[180], q._jobs[190]]
    assert list(q._machines["1x2_curry"].queue) == [q._jobs[180], q._jobs[190]]

    # Should fail if we specify a machine which doesn't exist.
    q.create_job(1, 1, job_id=200, machine="xxx")
    on_cancel.assert_called_once_with(200, "No suitable machines available.")
    on_cancel.reset_mock()

    # Should fail if we specify a tag doesn't exist.
    q.create_job(1, 1, job_id=210, tags=set(["xxx"]))
    on_cancel.assert_called_once_with(210, "No suitable machines available.")
    on_cancel.reset_mock()

    # Should fail if we specify impossible requirements for any machine
    q.create_job(10, 10, job_id=220)
    on_cancel.assert_called_once_with(220, "No suitable machines available.")
    on_cancel.reset_mock()
def test_board_down_link(x1, y1, z1, link, width, height, x2, y2, z2, wrapped):
    assert (board_down_link(x1, y1, z1, link, width, height) ==
            (x2, y2, z2, wrapped))
예제 #9
0
def test_enqueue_job(q, on_allocate, on_cancel):
    # Make sure that the _enqueue_job method does what it should (tested via
    # calls to create_job).

    q.add_machine("1x4", 1, 4)
    q.add_machine("1x2", 1, 2)
    q.add_machine("1x5", 1, 5)
    q.add_machine("1x2_pie", 1, 2, tags=set(["pie", "chips"]))
    q.add_machine("1x2_curry", 1, 2, tags=set(["curry", "chips"]))

    # If we create a 1x1 job we should get put on the first available machine.
    # Also verifies the call to on_allocate.
    q.create_job(1, 1, job_id=100)
    on_allocate.assert_called_once_with(
        100, "1x4",
        set((0, 0, z) for z in range(3)),
        set((0, 0, z, link)
            for z in range(3)
            for link in Links
            if board_down_link(0, 0, z, link, 1, 4)[:2] != (0, 0)),
        True)
    on_allocate.reset_mock()

    # We should make use of the first machine which fits and skip over anything
    # which is full or too small.
    q.create_job(1, 4, job_id=110)
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 110
    assert on_allocate.mock_calls[0][1][1] == "1x5"
    on_allocate.reset_mock()

    # We should still be able to fit small things in the first available
    # machine
    q.create_job(1, 3, job_id=120)
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 120
    assert on_allocate.mock_calls[0][1][1] == "1x4"
    on_allocate.reset_mock()

    # Should be able to specify a specific machine
    q.create_job(1, 1, job_id=130, machine="1x5")
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 130
    assert on_allocate.mock_calls[0][1][1] == "1x5"
    on_allocate.reset_mock()

    # Fill up the last available (default-tagged) free slot
    q.create_job(1, 2, job_id=140)
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 140
    assert on_allocate.mock_calls[0][1][1] == "1x2"
    on_allocate.reset_mock()

    # The next job created should get queued on all default-tagged machines
    q.create_job(1, 1, job_id=150)
    assert len(on_allocate.mock_calls) == 0
    assert list(q._machines["1x4"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2"].queue) == [q._jobs[150]]
    assert list(q._machines["1x5"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2_pie"].queue) == []
    assert list(q._machines["1x2_curry"].queue) == []

    # Should be able to create jobs on machines by tag, choosing the first one
    # when possible
    q.create_job(1, 1, job_id=160, tags=set(["chips"]))
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 160
    assert on_allocate.mock_calls[0][1][1] == "1x2_pie"
    on_allocate.reset_mock()

    # Should be able to create jobs on machines by tag, selecting by multiple
    # tags at once.
    q.create_job(1, 1, job_id=170, tags=set(["curry", "chips"]))
    assert len(on_allocate.mock_calls) == 1
    assert on_allocate.mock_calls[0][1][0] == 170
    assert on_allocate.mock_calls[0][1][1] == "1x2_curry"
    on_allocate.reset_mock()

    # Should be able to queue on tagged things
    q.create_job(1, 2, job_id=180, tags=set(["chips"]))
    assert list(q._machines["1x4"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2"].queue) == [q._jobs[150]]
    assert list(q._machines["1x5"].queue) == [q._jobs[150]]
    assert list(q._machines["1x2_pie"].queue) == [q._jobs[180]]
    assert list(q._machines["1x2_curry"].queue) == [q._jobs[180]]

    # Should be able to queue on all things
    q.create_job(1, 2, job_id=190, tags=set())
    assert list(q._machines["1x4"].queue) == [q._jobs[150], q._jobs[190]]
    assert list(q._machines["1x2"].queue) == [q._jobs[150], q._jobs[190]]
    assert list(q._machines["1x5"].queue) == [q._jobs[150], q._jobs[190]]
    assert list(q._machines["1x2_pie"].queue) == [q._jobs[180], q._jobs[190]]
    assert list(q._machines["1x2_curry"].queue) == [q._jobs[180], q._jobs[190]]

    # Should fail if we specify a machine which doesn't exist.
    q.create_job(1, 1, job_id=200, machine="xxx")
    on_cancel.assert_called_once_with(200, "No suitable machines available.")
    on_cancel.reset_mock()

    # Should fail if we specify a tag doesn't exist.
    q.create_job(1, 1, job_id=210, tags=set(["xxx"]))
    on_cancel.assert_called_once_with(210, "No suitable machines available.")
    on_cancel.reset_mock()

    # Should fail if we specify impossible requirements for any machine
    q.create_job(10, 10, job_id=220)
    on_cancel.assert_called_once_with(220, "No suitable machines available.")
    on_cancel.reset_mock()