Skip to content

Commit

Permalink
add modify reservation unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
hanstrompert committed Sep 3, 2024
1 parent b1b2184 commit ce2e276
Show file tree
Hide file tree
Showing 4 changed files with 334 additions and 2 deletions.
7 changes: 6 additions & 1 deletion src/supa/job/reserve.py
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,11 @@ def __call__(self) -> None:
dpsm.current_state == DataPlaneStateMachine.Activated
or dpsm.current_state == DataPlaneStateMachine.AutoEnd
):
self.log.info(
"modify bandwidth on connection",
old_bandwidth=old_bandwidth,
new_bandwidth=new_bandwidth,
)
if circuit_id := backend.modify(**connection_to_dict(connection)):
connection.circuit_id = circuit_id

Check warning on line 533 in src/supa/job/reserve.py

View check run for this annotation

Codecov / codecov/patch

src/supa/job/reserve.py#L533

Added line #L533 was not covered by tests

Expand Down Expand Up @@ -555,7 +560,7 @@ def __call__(self) -> None:
self.log.info("Cancel previous auto end")
scheduler.remove_job(job_id=AutoEndJob(self.connection_id).job_id)
if schedule_auto_end:
self.log.info("Schedule auto end", job="AutoEndJob", end_time=new_end_time.isoformat())
self.log.info("Schedule new auto end", job="AutoEndJob", end_time=new_end_time.isoformat())
scheduler.add_job(job := AutoEndJob(self.connection_id), trigger=job.trigger(), id=job.job_id)
register_result(request, ResultType.ReserveCommitConfirmed)
self.log.debug("Sending message", method="ReserveCommitConfirmed", request_message=request)
Expand Down
137 changes: 137 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from supa.grpc_nsi import connection_provider_pb2_grpc
from supa.job.dataplane import AutoEndJob, AutoStartJob
from supa.job.reserve import ReserveTimeoutJob
from supa.util.timestamp import NO_END_DATE
from supa.util.type import RequestType


Expand Down Expand Up @@ -494,6 +495,21 @@ def auto_end(connection_id: Column) -> Generator:
pass # job already removed from job store


@pytest.fixture
def auto_end_job(connection_id: Column) -> Generator:
"""Run AutoEndtJob for connection_id."""
from supa import scheduler

job_handle = scheduler.add_job(job := AutoEndJob(connection_id), trigger=job.trigger(), id=job.job_id)

yield None

try:
job_handle.remove()
except JobLookupError:
pass # job already removed from job store


@pytest.fixture
def deactivating(connection_id: Column) -> None:
"""Set data plane state machine of reservation identified by connection_id to state Deactivating."""
Expand Down Expand Up @@ -522,3 +538,124 @@ def flag_reservation_timeout(connection_id: Column) -> None:
with db_session() as session:
reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
reservation.reservation_timeout = True


@pytest.fixture
def start_now(connection_id: Column) -> None:
"""Set reservation start time to now."""
from supa.db.session import db_session

with db_session() as session:
reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
reservation.schedule.start_time = datetime.now(timezone.utc)


@pytest.fixture
def no_end_time(connection_id: Column) -> None:
"""Set reservation start time to now."""
from supa.db.session import db_session

with db_session() as session:
reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
reservation.schedule.end_time = NO_END_DATE


def p2p_criteria_from_p2_criteria(p2p_criteria: P2PCriteria) -> P2PCriteria:
"""Create deepcopy of given P2PCriteria object with version set to 1."""
return P2PCriteria(
version=1,
bandwidth=p2p_criteria.bandwidth,
symmetric=p2p_criteria.symmetric,
src_domain=p2p_criteria.src_domain,
src_topology=p2p_criteria.src_topology,
src_stp_id=p2p_criteria.src_stp_id,
src_vlans=p2p_criteria.src_vlans,
src_selected_vlan=p2p_criteria.src_selected_vlan,
dst_domain=p2p_criteria.dst_domain,
dst_topology=p2p_criteria.dst_topology,
dst_stp_id=p2p_criteria.dst_stp_id,
dst_vlans=p2p_criteria.dst_vlans,
dst_selected_vlan=p2p_criteria.dst_selected_vlan,
)


@pytest.fixture
def modified_start_time(connection_id: Column) -> None:
"""Add Schedule with modified start time on connection set to Provisioned and AutoStart."""
from supa.db.session import db_session

with db_session() as session:
reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
reservation.data_plane_state = DataPlaneStateMachine.AutoStart.value
reservation.provision_state = ProvisionStateMachine.Provisioned.value
reservation.schedules.append(
Schedule(
version=1,
start_time=reservation.schedule.start_time + timedelta(minutes=1),
end_time=reservation.schedule.end_time,
)
)
reservation.p2p_criteria_list.append(p2p_criteria_from_p2_criteria(reservation.p2p_criteria))
reservation.version = reservation.version + 1


@pytest.fixture
def modified_no_end_time(connection_id: Column) -> None:
"""Add Schedule with no end time on connection set to Provisioned and AutoEnd."""
from supa.db.session import db_session

with db_session() as session:
reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
reservation.data_plane_state = DataPlaneStateMachine.AutoEnd.value
reservation.provision_state = ProvisionStateMachine.Provisioned.value
reservation.schedules.append(
Schedule(
version=1,
start_time=reservation.schedule.start_time,
end_time=NO_END_DATE,
)
)
reservation.p2p_criteria_list.append(p2p_criteria_from_p2_criteria(reservation.p2p_criteria))
reservation.version = reservation.version + 1


@pytest.fixture
def modified_end_time(connection_id: Column) -> None:
"""Add Schedule with end time of 30 minutes in the future on connection set to Provisioned and Activated."""
from supa.db.session import db_session

with db_session() as session:
reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
reservation.data_plane_state = DataPlaneStateMachine.Activated.value
reservation.provision_state = ProvisionStateMachine.Provisioned.value
reservation.schedules.append(
Schedule(
version=1,
start_time=reservation.schedule.start_time,
end_time=datetime.now(timezone.utc) + timedelta(minutes=30),
)
)
reservation.p2p_criteria_list.append(p2p_criteria_from_p2_criteria(reservation.p2p_criteria))
reservation.version = reservation.version + 1


@pytest.fixture
def modified_bandwidth(connection_id: Column) -> None:
"""Add P2PCriteria with modified bandwidth on connection set to Provisioned and Activated."""
from supa.db.session import db_session

with db_session() as session:
reservation = session.query(Reservation).filter(Reservation.connection_id == connection_id).one()
reservation.data_plane_state = DataPlaneStateMachine.Activated.value
reservation.provision_state = ProvisionStateMachine.Provisioned.value
reservation.schedules.append(
Schedule(
version=1,
start_time=reservation.schedule.start_time,
end_time=reservation.schedule.end_time,
)
)
new_p2p_criteria = p2p_criteria_from_p2_criteria(reservation.p2p_criteria)
new_p2p_criteria.bandwidth = new_p2p_criteria.bandwidth + 10
reservation.p2p_criteria_list.append(new_p2p_criteria)
reservation.version = reservation.version + 1
105 changes: 104 additions & 1 deletion tests/connection/provider/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from datetime import datetime, timedelta, timezone
from json import dumps
from typing import Any
from uuid import uuid4
from uuid import UUID, uuid4

import pytest
from google.protobuf.json_format import Parse
Expand Down Expand Up @@ -65,6 +65,7 @@ def pb_reservation_request_criteria(pb_schedule: Schedule, pb_ptps: PointToPoint
reservation_request_criteria.schedule.CopyFrom(pb_schedule)
reservation_request_criteria.service_type = const.SERVICE_TYPE
reservation_request_criteria.ptps.CopyFrom(pb_ptps)
reservation_request_criteria.version = 1
return reservation_request_criteria


Expand Down Expand Up @@ -184,6 +185,108 @@ def test_reserve_request_end_time_in_past(pb_reserve_request_end_time_in_past: R
assert "End time lies in the past" in caplog.text


def test_reserve_modify(pb_reserve_request: ReserveRequest, connection_id: UUID, connection: None, caplog: Any) -> None:
"""Test the connection provider Reserve Modify happy path."""
service = ConnectionProviderService()
mock_context = unittest.mock.create_autospec(spec=ServicerContext)
request_correlation_id = pb_reserve_request.header.correlation_id
# add existing connection_id to reservation to mark this a modify request
pb_reserve_request.connection_id = str(connection_id)
reserve_response = service.Reserve(pb_reserve_request, mock_context)
assert request_correlation_id == reserve_response.header.correlation_id
assert not reserve_response.header.reply_to
assert reserve_response.connection_id == str(connection_id)
assert not reserve_response.HasField("service_exception")
assert "modify reservation" in caplog.text
assert "Schedule reserve" in caplog.text
assert "Schedule reserve timeout" in caplog.text


def test_reserve_modify_illegal_version(
pb_reserve_request: ReserveRequest, connection_id: UUID, connection: None, caplog: Any
) -> None:
"""Test the connection provider Reserve Modify happy path."""
service = ConnectionProviderService()
mock_context = unittest.mock.create_autospec(spec=ServicerContext)
request_correlation_id = pb_reserve_request.header.correlation_id
# add existing connection_id to reservation to mark this a modify request
pb_reserve_request.connection_id = str(connection_id)
# criteria version may only be incremented by 1
pb_reserve_request.criteria.version = pb_reserve_request.criteria.version + 2
reserve_response = service.Reserve(pb_reserve_request, mock_context)
assert request_correlation_id == reserve_response.header.correlation_id
assert not reserve_response.header.reply_to
assert not reserve_response.connection_id
assert reserve_response.HasField("service_exception")
assert reserve_response.service_exception.error_id == "00102"
assert reserve_response.service_exception.connection_id == str(connection_id)
assert "version may only be incremented by 1" in caplog.text


def test_reserve_modify_unknown_connection_id(
pb_reserve_request: ReserveRequest, connection: None, caplog: Any
) -> None:
"""Test the connection provider Reserve Modify happy path."""
service = ConnectionProviderService()
mock_context = unittest.mock.create_autospec(spec=ServicerContext)
request_correlation_id = pb_reserve_request.header.correlation_id
# add unknown connection_id to this modify request
non_existing_connection_id = str(uuid4())
pb_reserve_request.connection_id = str(non_existing_connection_id)
reserve_response = service.Reserve(pb_reserve_request, mock_context)
assert request_correlation_id == reserve_response.header.correlation_id
assert not reserve_response.header.reply_to
assert not reserve_response.connection_id
assert reserve_response.HasField("service_exception")
assert reserve_response.service_exception.connection_id == non_existing_connection_id
assert reserve_response.service_exception.error_id == "00203"
assert "Connection ID does not exist" in caplog.text
assert reserve_response.service_exception.variables[0].type == "connectionId"
assert reserve_response.service_exception.variables[0].value == non_existing_connection_id


def test_reserve_modify_reservation_already_started(
pb_reserve_request: ReserveRequest, connection_id: UUID, start_now: None, connection: None, caplog: Any
) -> None:
"""Test the connection provider Reserve Modify happy path."""
service = ConnectionProviderService()
mock_context = unittest.mock.create_autospec(spec=ServicerContext)
request_correlation_id = pb_reserve_request.header.correlation_id
# add existing connection_id to reservation to mark this a modify request
pb_reserve_request.connection_id = str(connection_id)
# change start time to 1 minute in te future
pb_reserve_request.criteria.schedule.start_time.FromDatetime(datetime.now(timezone.utc) + timedelta(minutes=1))
reserve_response = service.Reserve(pb_reserve_request, mock_context)
assert request_correlation_id == reserve_response.header.correlation_id
assert not reserve_response.header.reply_to
assert not reserve_response.connection_id
assert reserve_response.HasField("service_exception")
assert reserve_response.service_exception.connection_id == str(connection_id)
assert reserve_response.service_exception.error_id == "00102"
assert "cannot change start time when reservation already started" in caplog.text


def test_reserve_modify_invalid_transition(
pb_reserve_request: ReserveRequest, connection_id: UUID, reserve_held: None, connection: None, caplog: Any
) -> None:
"""Test the connection provider Reserve Modify happy path."""
service = ConnectionProviderService()
mock_context = unittest.mock.create_autospec(spec=ServicerContext)
request_correlation_id = pb_reserve_request.header.correlation_id
# add existing connection_id to reservation to mark this a modify request
pb_reserve_request.connection_id = str(connection_id)
reserve_response = service.Reserve(pb_reserve_request, mock_context)
assert request_correlation_id == reserve_response.header.correlation_id
assert not reserve_response.header.reply_to
assert not reserve_response.connection_id
assert reserve_response.HasField("service_exception")
assert reserve_response.service_exception.connection_id == str(connection_id)
assert reserve_response.service_exception.error_id == "00201"
assert "Can't reserve_request when in ReserveHeld" in caplog.text
assert reserve_response.service_exception.variables[0].type == "connectionId"
assert reserve_response.service_exception.variables[0].value == str(connection_id)


def test_reserve_commit(pb_reserve_commit_request: GenericRequest, reserve_held: None, caplog: Any) -> None:
"""Test the connection provider ReserveCommit happy path."""
service = ConnectionProviderService()
Expand Down
87 changes: 87 additions & 0 deletions tests/job/test_reserve.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,93 @@ def test_reserve_commit_job_reserve_commit_confirmed(
assert state_machine.is_reserve_start(connection_id)


def test_reserve_commit_modified_start_time(
connection_id: UUID,
connection: None,
reserve_committing: None,
modified_start_time: None,
auto_start_job: None,
get_stub: None,
caplog: Any,
) -> None:
"""Test ReserveCommitJob to reschedule AutoStartJob when start time is modified.
Verify (see fake_servicer) that a ReserveCommitJob will
......
"""
reserve_commit_job = ReserveCommitJob(connection_id)
reserve_commit_job.__call__()
assert state_machine.is_reserve_start(connection_id)
assert "Reschedule auto start" in caplog.text


def test_reserve_commit_modified_no_end_time(
connection_id: UUID,
connection: None,
reserve_committing: None,
modified_no_end_time: None,
auto_end_job: None,
get_stub: None,
caplog: Any,
) -> None:
"""Test ReserveCommitJob to reschedule AutoStartJob when start time is modified.
Verify (see fake_servicer) that a ReserveCommitJob will
transition reservation state machine to ReserveStart,
and transition data plane state machine from AutoEnd to Activated,
and that the previous auto end job is canceled.
"""
reserve_commit_job = ReserveCommitJob(connection_id)
reserve_commit_job.__call__()
assert state_machine.is_reserve_start(connection_id)
assert state_machine.is_activated(connection_id)
assert "Cancel previous auto end" in caplog.text


def test_reserve_commit_modified_set_end_time(
connection_id: UUID,
connection: None,
reserve_committing: None,
modified_end_time: None,
get_stub: None,
caplog: Any,
) -> None:
"""Test ReserveCommitJob to schedule AutoEndJob when end time is set on connection without end time.
Verify (see fake_servicer) that a ReserveCommitJob will
transition reservation state machine to ReserveStart,
and transition data plane state machine from Activated to AutoEnd,
and that a auto end job is scheduled.
"""
reserve_commit_job = ReserveCommitJob(connection_id)
reserve_commit_job.__call__()
assert state_machine.is_reserve_start(connection_id)
assert state_machine.is_auto_end(connection_id)
assert "Schedule new auto end" in caplog.text


def test_reserve_commit_modified_change_bandwidth(
connection_id: UUID,
connection: None,
reserve_committing: None,
modified_bandwidth: None,
get_stub: None,
caplog: Any,
) -> None:
"""Test ReserveCommitJob to call modify() on backend of activated connection without end time.
Verify (see fake_servicer) that a ReserveCommitJob will
transition reservation state machine to ReserveStart,
and that modify() is called on the backend.
"""
reserve_commit_job = ReserveCommitJob(connection_id)
reserve_commit_job.__call__()
assert state_machine.is_reserve_start(connection_id)
assert state_machine.is_activated(connection_id)
assert "modify bandwidth on connection" in caplog.text
assert "Modify resources in NRM" in caplog.text


def test_reserve_commit_job_recover(connection_id: UUID, reserve_committing: None, get_stub: None, caplog: Any) -> None:
"""Test ReserveCommitJob to recover reservations in state ReserveCommitting."""
reserve_commit_job = ReserveCommitJob(connection_id)
Expand Down

0 comments on commit ce2e276

Please sign in to comment.