Skip to content

Commit b0f2fa1

Browse files
GetConnection(s) to Support Pin Groups (#639)
* feat: support get connection(s) for pins group(s)
1 parent 64ec57a commit b0f2fa1

4 files changed

Lines changed: 154 additions & 11 deletions

File tree

ni_measurementlink_service/session_management/_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import logging
66
import threading
77
import warnings
8-
from typing import Dict, Iterable, Optional, Union
8+
from typing import Dict, Iterable, Mapping, Optional, Union
99

1010
import google.protobuf.internal.containers
1111
import grpc
@@ -398,7 +398,7 @@ def _to_group_mappings_dict(
398398
mappings: google.protobuf.internal.containers.MessageMap[
399399
str, session_management_service_pb2.ResolvedPinsOrRelays
400400
]
401-
) -> Dict[str, Iterable[str]]:
401+
) -> Mapping[str, Iterable[str]]:
402402
group_mappings: Dict[str, Iterable[str]] = {}
403403
if mappings is not None:
404404
for key, value in mappings.items():

ni_measurementlink_service/session_management/_reservation.py

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Iterable,
2121
List,
2222
Literal,
23+
Mapping,
2324
NamedTuple,
2425
Optional,
2526
Sequence,
@@ -568,7 +569,7 @@ def __init__(
568569
multiplexer_session_info: Optional[
569570
Sequence[session_management_service_pb2.MultiplexerSessionInformation]
570571
] = None,
571-
pin_or_relay_group_mappings: Optional[Dict[str, Iterable[str]]] = None,
572+
pin_or_relay_group_mappings: Optional[Mapping[str, Iterable[str]]] = None,
572573
reserved_pin_or_relay_names: Union[str, Iterable[str], None] = None,
573574
reserved_sites: Optional[Iterable[int]] = None,
574575
) -> None:
@@ -583,7 +584,7 @@ def __init__(
583584
self._multiplexer_session_container = MultiplexerSessionContainer(
584585
session_management_client, multiplexer_session_info
585586
)
586-
self._pin_or_relay_group_mappings: Dict[str, Iterable[str]] = {}
587+
self._pin_or_relay_group_mappings: Mapping[str, Iterable[str]] = {}
587588
if pin_or_relay_group_mappings is not None:
588589
self._pin_or_relay_group_mappings = pin_or_relay_group_mappings
589590

@@ -810,19 +811,27 @@ def _get_connections_core(
810811
) -> Sequence[TypedConnection[TSession]]:
811812
_check_optional_str_param("instrument_type_id", instrument_type_id)
812813

813-
requested_pins = _to_iterable(pin_or_relay_names, self._reserved_pin_or_relay_names)
814+
requested_pin_or_relay_names = _to_iterable(
815+
pin_or_relay_names, self._reserved_pin_or_relay_names
816+
)
814817
requested_sites = _to_iterable(sites, self._reserved_sites)
815818
requested_instrument_type_ids = _to_iterable(
816819
instrument_type_id, self._reserved_instrument_type_ids
817820
)
818821

822+
resolved_pin_or_relay_names = _to_ordered_set(
823+
self._get_resolved_pin_or_relay_names(requested_pin_or_relay_names)
824+
)
825+
819826
# Validate that each requested pin, site, or instrument type ID is
820827
# present in the reserved pins, reserved sites, and reserved instrument
821828
# type IDs. This rejects unknown or invalid inputs such as
822829
# pin_or_relay_names="NonExistentPin" or sites=[0, 1, 65535].
823830
if pin_or_relay_names is not None:
824831
_check_matching_criterion(
825-
"pin or relay name(s)", requested_pins, self._reserved_pin_or_relay_names
832+
"pin or relay name(s)",
833+
resolved_pin_or_relay_names,
834+
self._reserved_pin_or_relay_names,
826835
)
827836
if sites is not None:
828837
_check_matching_criterion("site(s)", requested_sites, self._reserved_sites)
@@ -842,7 +851,7 @@ def _get_connections_core(
842851
results: List[TypedConnection[TSession]] = []
843852
matching_pins: Set[str] = set()
844853
for site in requested_sites_with_system:
845-
for pin in requested_pins:
854+
for pin in resolved_pin_or_relay_names:
846855
for instrument_type in requested_instrument_type_ids:
847856
key = _ConnectionKey(pin, site, instrument_type)
848857
value = self._connection_cache.get(key)
@@ -862,10 +871,10 @@ def _get_connections_core(
862871

863872
# If the user specified pins to match, validate that each one matched a connection.
864873
if pin_or_relay_names is not None and not all(
865-
pin in matching_pins for pin in requested_pins
874+
pin in matching_pins for pin in resolved_pin_or_relay_names
866875
):
867876
extra_pins_str = ", ".join(
868-
_quote(pin) for pin in requested_pins if pin not in matching_pins
877+
_quote(pin) for pin in resolved_pin_or_relay_names if pin not in matching_pins
869878
)
870879
criteria = _describe_matching_criteria(None, sites, instrument_type_id)
871880
# Emphasize the extra pin/relay names, but also list the other criteria.

tests/integration/session_management/test_reservation.py

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,92 @@ def test___sessions_reserved_using_nested_relay_group___get_connections_by_relay
254254
]
255255

256256

257+
def test___sessions_reserved___get_connections_by_pin_group___returns_connections(
258+
pin_map_client: PinMapClient,
259+
pin_map_directory: pathlib.Path,
260+
session_management_client: SessionManagementClient,
261+
) -> None:
262+
with ExitStack() as stack:
263+
pin_map_id = pin_map_client.update_pin_map(pin_map_directory / _PIN_MAP_C)
264+
pin_map_context = PinMapContext(pin_map_id=pin_map_id, sites=[0])
265+
pin_group = ["PinGroup1"]
266+
reservation = stack.enter_context(
267+
session_management_client.reserve_sessions(pin_map_context, pin_group)
268+
)
269+
270+
connections = reservation.get_connections(object, pin_or_relay_names=pin_group)
271+
272+
assert [get_connection_subset(conn) for conn in connections] == [
273+
ConnectionSubset("A", 0, "DCPower1/0, DCPower1/2, DCPower2/1", "DCPower1/0"),
274+
ConnectionSubset("S1", -1, "SCOPE1", "1"),
275+
]
276+
277+
278+
def test___sessions_reserved___get_connections_by_nested_pin_group___returns_connections(
279+
pin_map_client: PinMapClient,
280+
pin_map_directory: pathlib.Path,
281+
session_management_client: SessionManagementClient,
282+
) -> None:
283+
with ExitStack() as stack:
284+
pin_map_id = pin_map_client.update_pin_map(pin_map_directory / _PIN_MAP_C)
285+
pin_map_context = PinMapContext(pin_map_id=pin_map_id, sites=[0])
286+
pin_groups = ["PinGroup1", "PinGroup2"]
287+
reservation = stack.enter_context(
288+
session_management_client.reserve_sessions(pin_map_context, pin_groups)
289+
)
290+
291+
connections = reservation.get_connections(object, pin_or_relay_names=pin_groups)
292+
293+
assert [get_connection_subset(conn) for conn in connections] == [
294+
ConnectionSubset("A", 0, "DCPower1/0, DCPower1/2, DCPower2/1", "DCPower1/0"),
295+
ConnectionSubset("C", 0, "SCOPE1", "2"),
296+
ConnectionSubset("S1", -1, "SCOPE1", "1"),
297+
]
298+
299+
300+
def test___sessions_reserved___get_connections_by_relay_group___returns_connections(
301+
pin_map_client: PinMapClient,
302+
pin_map_directory: pathlib.Path,
303+
session_management_client: SessionManagementClient,
304+
) -> None:
305+
with ExitStack() as stack:
306+
pin_map_id = pin_map_client.update_pin_map(pin_map_directory / _PIN_MAP_C)
307+
pin_map_context = PinMapContext(pin_map_id=pin_map_id, sites=[0])
308+
relay_group = ["RelayGroup1"]
309+
reservation = stack.enter_context(
310+
session_management_client.reserve_sessions(pin_map_context, relay_group)
311+
)
312+
313+
connections = reservation.get_connections(object, pin_or_relay_names=relay_group)
314+
315+
assert [get_connection_subset(conn) for conn in connections] == [
316+
ConnectionSubset("RelayUsingSameDriver", 0, "RelayDriver1", "K0"),
317+
ConnectionSubset("SystemRelay", -1, "RelayDriver1", "K60"),
318+
]
319+
320+
321+
def test___sessions_reserved___get_connections_by_nested_relay_groups___returns_connections(
322+
pin_map_client: PinMapClient,
323+
pin_map_directory: pathlib.Path,
324+
session_management_client: SessionManagementClient,
325+
) -> None:
326+
with ExitStack() as stack:
327+
pin_map_id = pin_map_client.update_pin_map(pin_map_directory / _PIN_MAP_C)
328+
pin_map_context = PinMapContext(pin_map_id=pin_map_id, sites=[0])
329+
relay_groups = ["RelayGroup1", "RelayGroup2"]
330+
reservation = stack.enter_context(
331+
session_management_client.reserve_sessions(pin_map_context, relay_groups)
332+
)
333+
334+
connections = reservation.get_connections(object, pin_or_relay_names=relay_groups)
335+
336+
assert [get_connection_subset(conn) for conn in connections] == [
337+
ConnectionSubset("RelayUsingSameDriver", 0, "RelayDriver1", "K0"),
338+
ConnectionSubset("RelayUsingDifferentDrivers", 0, "RelayDriver1", "K10"),
339+
ConnectionSubset("SystemRelay", -1, "RelayDriver1", "K60"),
340+
]
341+
342+
257343
def test___reserve_sessions_with_multiplexer___get_connections_with_multiplexer___returns_connections(
258344
pin_map_client: PinMapClient,
259345
pin_map_directory: pathlib.Path,

tests/unit/test_reservation.py

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import functools
22
from contextlib import ExitStack
3-
from typing import Any, Dict, Iterable, List
3+
from typing import Any, Dict, List
44
from unittest.mock import Mock
55

66
import pytest
@@ -242,7 +242,7 @@ def test___session_reserved_using_pin_group___get_connection_by_pin___returns_co
242242
grpc_session_infos = create_nifake_session_infos(1)
243243
grpc_session_infos[0].channel_mappings.add(pin_or_relay_name="Pin1", site=2, channel="3")
244244
grpc_session_infos[0].channel_mappings.add(pin_or_relay_name="Pin2", site=2, channel="2")
245-
group_mappings: Dict[str, Iterable[str]] = {"PinGroup1": ["Pin1", "Pin2"]}
245+
group_mappings = {"PinGroup1": ["Pin1", "Pin2"]}
246246
reservation = MultiSessionReservation(
247247
session_management_client,
248248
grpc_session_infos,
@@ -261,6 +261,54 @@ def test___session_reserved_using_pin_group___get_connection_by_pin___returns_co
261261
assert connection.session_info == session_info
262262

263263

264+
def test___duplicate_pins___get_connections___returns_single_connection(
265+
session_management_client: Mock,
266+
) -> None:
267+
with ExitStack() as stack:
268+
grpc_session_infos = create_nifake_session_infos(1)
269+
grpc_session_infos[0].channel_mappings.add(pin_or_relay_name="Pin1", site=2, channel="3")
270+
reservation = MultiSessionReservation(session_management_client, grpc_session_infos)
271+
session_info = stack.enter_context(
272+
reservation.initialize_session(construct_session, "nifake")
273+
)
274+
275+
connections = reservation.get_connections(
276+
fake_driver.Session, pin_or_relay_names=["Pin1", "Pin1"]
277+
)
278+
279+
assert len(connections) == 1
280+
assert connections[0].pin_or_relay_name == "Pin1"
281+
assert connections[0].site == 2
282+
assert connections[0].channel_name == "3"
283+
assert connections[0].session_info == session_info
284+
285+
286+
def test___session_reserved___get_connections_by_pin_group___returns_connections(
287+
session_management_client: Mock,
288+
) -> None:
289+
with ExitStack() as stack:
290+
grpc_session_infos = create_nifake_session_infos(1)
291+
grpc_session_infos[0].channel_mappings.add(pin_or_relay_name="Pin1", site=2, channel="3")
292+
grpc_session_infos[0].channel_mappings.add(pin_or_relay_name="Pin2", site=2, channel="2")
293+
group_mappings = {"PinGroup1": ["Pin1", "Pin2"]}
294+
reservation = MultiSessionReservation(
295+
session_management_client,
296+
grpc_session_infos,
297+
pin_or_relay_group_mappings=group_mappings,
298+
reserved_pin_or_relay_names=["PinGroup1"],
299+
)
300+
_ = stack.enter_context(reservation.initialize_session(construct_session, "nifake"))
301+
302+
connections = reservation.get_connections(
303+
fake_driver.Session, pin_or_relay_names=["PinGroup1"]
304+
)
305+
306+
assert [get_connection_subset(conn) for conn in connections] == [
307+
ConnectionSubset("Pin1", 2, "Dev0", "3"),
308+
ConnectionSubset("Pin2", 2, "Dev0", "2"),
309+
]
310+
311+
264312
def test___multiple_connections___get_connection___value_error_raised(
265313
session_management_client: Mock,
266314
) -> None:

0 commit comments

Comments
 (0)