Receive addresses ================= This library offers the ability to calculate reception hosts and ports (i.e., endpoints) for the scan types associated to the script under execution. This process yields SDP's *receive addresses*. .. _usage: Usage ----- .. testsetup:: * from unittest.mock import patch from ska_sdp_config import Config from ska_sdp_scripting import ProcessingBlock, config from ska_sdp_scripting.receive_addresses import ReceiveAddresses, PartialReceiveAddresses from tests.utils import PB_ID, create_eb_pb, create_pb_states config.FEATURE_CONFIG_DB.set_default(False) config = Config(backend="memory", global_prefix="/docs_tests") config.backend.delete("/docs_tests", recursive=True, must_exist=False) patcher = patch("ska_sdp_scripting.processing_block.new_config_client") mock_client = patcher.start() mock_client.return_value = config create_eb_pb(config) create_pb_states(config) pb = ProcessingBlock(PB_ID) .. testcleanup:: * #phase.__exit__(None, None, None) pb.__exit__(None, None, None) patcher.stop() config.close() The process of calculating and publishing receive addresses works as follows: * User invoke :meth:`.ProcessingBlock.generate_receive_addresses`. This requires users to provide at least a *function* argument, indicating the beam function for which receive addresses will be generated. This must be one of the allowed ``function`` values in the :external+ska-schemas:ref:`Beam SDP schema <1.1#/definitions/beam_1.1>`, part of the ``AssignedResources`` command: .. doctest:: workflow # Request parameters >>> num_hosts = 1 # Generate partial addresses >>> partial_receive_addresses = pb.generate_receive_addresses("visibilities", num_hosts) * The returned object is of type :class:`.PartialReceiveAddresses`. It is *partial* in the sense that it has placeholders in the place of actual hostnames/IPs. It's the user's responsibility to acquire those separately. At least :attr:`.PartialReceiveAddresses.host_count` hostnames/IPs should be needed. This number should match the requested number of hosts too from the previous step. .. doctest:: workflow >>> isinstance(partial_receive_addresses, PartialReceiveAddresses) True >>> partial_receive_addresses.host_count 1 >>> partial_receive_addresses.host_count == num_hosts True * Once hostnames/IPs are acquired, calling :meth:`.PartialReceiveAddresses.replace_hosts` yields a :class:`.ReceiveAddresses` object with actual hostnames/IPs. .. doctest:: workflow >>> ips = ["1.1.1.1"] >>> receive_addresses = partial_receive_addresses.replace_hosts(ips) >>> isinstance(receive_addresses, ReceiveAddresses) True * To set these as the processing block's receive addresses users invoke :meth:`.ProcessingBlock.publish_receive_addresses`. .. doctest:: workflow # Register as this ProcessingBlock's receive addresses >>> pb.publish_receive_addresses(receive_addresses) # These can be read now from the SDP config DB >>> for txn in config.txn(): ... pb_state = txn.processing_block.state(pb._pb_id).get() ... recv_address_dict = pb_state.get("receive_addresses") >>> recv_address_dict {'target:a': {'vis0': {'function': 'visibilities', 'host': [[0, '1.1.1.1']], 'port': [[0, 21000], [2, 21001], [4, 21002], [6, 21003]]}}} The :class:`.ReceiveAddresses` and :class:`.PartialReceiveAddresses` classes also have a :attr:~.ReceiveAddresses.allocations` attribute with the individual host/port allocation spans, which can be inspected (mostly useful for debugging and testing). Allocation strategy ------------------- Hosts and ports are allocated for a Processing Block's scan types using the following logic: * Users provide a number of hosts to allocate resources on. This number is always respected. * Each scan type allocates ports across all hosts as evenly as possible. Note that each scan type can have a different number of channels, therefore the port count for each scan type on each host will be different. * Within a scan type, only beams matching the given *function* are considered. * In a scan type ports are fully allocated to each beam in the order in which they are declared in the Execution Block. * For each beam, ports are fully allocated to each Spectral Window in ascending order according to their ``first`` channel ID. * When ``channels_per_port`` is different to ``1`` multiple channels are assigned to the same port. However grouping of channels onto a single port only takes place within a Spectral Window. In other words a port will never be assigned to channels from different Spectral Windows or Beams.