示例#1
0
def test_broadcaster_checks():
    b = ca.Broadcaster(ca.CLIENT)
    with pytest.raises(ca.LocalProtocolError):
        b.send(
            ca.SearchRequest(name='LIRR',
                             cid=0,
                             version=ca.DEFAULT_PROTOCOL_VERSION))

    b.send(ca.RepeaterRegisterRequest('1.2.3.4'))
    res = ca.RepeaterConfirmResponse('5.6.7.8')
    commands = b.recv(bytes(res), ('5.6.7.8', 6666))
    assert commands[0] == res
    b.process_commands(commands)

    req = ca.SearchRequest(name='LIRR',
                           cid=0,
                           version=ca.DEFAULT_PROTOCOL_VERSION)
    with pytest.raises(ca.LocalProtocolError):
        b.send(req)
    b.send(ca.VersionRequest(priority=0, version=ca.DEFAULT_PROTOCOL_VERSION),
           req)

    res = ca.SearchResponse(port=6666,
                            ip='1.2.3.4',
                            cid=0,
                            version=ca.DEFAULT_PROTOCOL_VERSION)
    addr = ('1.2.3.4', 6666)
    commands = b.recv(bytes(res), addr)
    # see changes to _broadcaster.py.  Apparently rsrv does not always conform
    # to the protocol and include a version response before search responsesq
    # with pytest.raises(ca.RemoteProtocolError):
    #     b.process_commands(commands)
    # commands = b.recv(bytes(ca.VersionResponse(version=ca.DEFAULT_PROTOCOL_VERSION)) + bytes(res), addr)
    b.process_commands(commands)  # this gets both
示例#2
0
def test_counter_skipping(circuit_pair):
    circuit, _ = circuit_pair
    broadcaster = ca.Broadcaster(ca.CLIENT)

    # Force the initial value of search IDs to make this test work
    broadcaster._search_id_counter.value = -1
    circuit._channel_id_counter.value = -1

    broadcaster.unanswered_searches[0] = 'placeholder'
    broadcaster.unanswered_searches[2] = 'placeholder'
    assert broadcaster.new_search_id() == 1
    assert list(broadcaster.unanswered_searches) == [0, 2]
    assert broadcaster.new_search_id() == 3

    circuit.channels[2] = 'placeholder'
    assert circuit.new_channel_id() == 0
    assert circuit.new_channel_id() == 1
    # should skip 2
    assert circuit.new_channel_id() == 3

    circuit._ioids[0] = 'placeholder'
    # should skip 0
    circuit.new_ioid() == 1

    circuit.event_add_commands[0] = 'placeholder'
    # should skip 0
    circuit.new_subscriptionid() == 1
示例#3
0
    def __init__(self, pvdb, interfaces=None):
        if interfaces is None:
            interfaces = ca.get_server_address_list()
        self.interfaces = interfaces
        self.udp_socks = {}  # map each interface to a UDP socket for searches
        self.beacon_socks = {}  # map each interface to a UDP socket for beacons
        self.pvdb = pvdb
        self.log = logging.getLogger(f'caproto.ctx.{id(self)}')

        self.circuits = set()
        self.broadcaster = ca.Broadcaster(our_role=ca.SERVER)

        self.subscriptions = defaultdict(deque)
        # Map Subscription to {'before': last_update, 'after': last_update}
        # to silence duplicates for Subscriptions that use edge-triggered sync
        # Channel Filter.
        self.last_sync_edge_update = defaultdict(lambda: defaultdict(dict))
        self.last_dead_band = {}
        self.beacon_count = 0

        self.environ = get_environment_variables()

        # ca_server_port: the default tcp/udp port from the environment
        self.ca_server_port = self.environ['EPICS_CA_SERVER_PORT']
        # the specific tcp port in use by this server
        self.port = None

        self.log.debug('EPICS_CA_SERVER_PORT set to %d. This is the UDP port '
                       'to be used for searches, and the first TCP server port'
                       ' to be tried.', self.ca_server_port)

        ignore_addresses = self.environ['EPICS_CAS_IGNORE_ADDR_LIST']
        self.ignore_addresses = ignore_addresses.split(' ')
示例#4
0
def test_counter_wraparound(circuit_pair):
    circuit, _ = circuit_pair
    broadcaster = ca.Broadcaster(ca.CLIENT)

    MAX = 2**16
    for i in range(MAX + 2):
        assert i % MAX == circuit.new_channel_id()
        assert i % MAX == circuit.new_subscriptionid()
        assert i % MAX == circuit.new_ioid()
        assert i % MAX == broadcaster.new_search_id()
示例#5
0
    def __init__(self):
        self.broadcaster = ca.Broadcaster(our_role=ca.CLIENT)
        self.log = self.broadcaster.log
        self.command_bundle_queue = curio.Queue()
        self.broadcaster_command_condition = curio.Condition()

        # UDP socket broadcasting to CA servers
        self.udp_sock = ca.bcast_socket(socket)
        self.registered = False  # refers to RepeaterRegisterRequest
        self.loop_ready_event = curio.Event()
        self.unanswered_searches = {}  # map search id (cid) to name
        self.search_results = {}  # map name to address
示例#6
0
def test_counter_wraparound(circuit_pair):
    circuit, _ = circuit_pair
    broadcaster = ca.Broadcaster(ca.CLIENT)

    # Force the initial value of search IDs to make this test work
    broadcaster._search_id_counter.value = -1

    MAX = 2**16
    for i in range(MAX + 2):
        assert i % MAX == circuit.new_channel_id()
        assert i % MAX == circuit.new_subscriptionid()
        assert i % MAX == circuit.new_ioid()
        assert i % MAX == broadcaster.new_search_id()
示例#7
0
    def __init__(self):
        self.broadcaster = ca.Broadcaster(our_role=ca.CLIENT)
        self.log = self.broadcaster.log
        self.command_bundle_queue = curio.Queue()
        self.broadcaster_command_condition = curio.Condition()

        # UDP socket broadcasting to CA servers
        self.udp_sock = ca.bcast_socket(socket)
        self.broadcaster.our_address = safe_getsockname(self.udp_sock)
        self.registered = False  # refers to RepeaterRegisterRequest
        self.loop_ready_event = curio.Event()
        self.unanswered_searches = {}  # map search id (cid) to name
        self.search_results = {}  # map name to address

        self.environ = ca.get_environment_variables()
        self.ca_server_port = self.environ['EPICS_CA_SERVER_PORT']
示例#8
0
    def __init__(self, *, nursery):
        self.nursery = nursery
        self.broadcaster = ca.Broadcaster(our_role=ca.CLIENT)
        self.log = self.broadcaster.log
        self.command_bundle_queue = trio.Queue(capacity=1000)
        self.broadcaster_command_condition = trio.Condition()
        self._cleanup_condition = trio.Condition()
        self._cleanup_event = trio.Event()

        # UDP socket broadcasting to CA servers
        self.udp_sock = None
        self.registered = False  # refers to RepeaterRegisterRequest
        self.unanswered_searches = {}  # map search id (cid) to name
        self.search_results = {}  # map name to address
        self.new_id = ThreadsafeCounter(
            dont_clash_with=self.unanswered_searches)
示例#9
0
    def __init__(self, *, log_level='ERROR'):
        self.log_level = log_level
        self.udp_sock = None
        self.sock_thread = None
        self._search_lock = threading.RLock()

        self.search_results = {}  # map name to (time, address)
        self.unanswered_searches = {}  # map search id (cid) to name

        self.listeners = weakref.WeakSet()

        self.broadcaster = ca.Broadcaster(our_role=ca.CLIENT)
        self.broadcaster.log.setLevel(self.log_level)
        self.command_bundle_queue = queue.Queue()
        self.command_cond = threading.Condition()  # ConditionType
        self.command_thread = threading.Thread(target=self.command_loop,
                                               daemon=True)
        self.command_thread.start()
示例#10
0
文件: server.py 项目: ZLLentz/caproto
    def __init__(self, host, port, pvdb, *, log_level='ERROR'):
        self.host = host
        self.port = port
        self.pvdb = pvdb

        self.circuits = set()
        self.log_level = log_level
        self.broadcaster = ca.Broadcaster(our_role=ca.SERVER)
        self.broadcaster.log.setLevel(self.log_level)
        self.command_bundle_queue = curio.Queue()

        self.subscriptions = defaultdict(deque)
        self.subscription_queue = curio.UniversalQueue()
        self.beacon_count = 0
        self.environ = get_environment_variables()

        ignore_addresses = self.environ['EPICS_CAS_IGNORE_ADDR_LIST']
        self.ignore_addresses = ignore_addresses.split(' ')
示例#11
0
    def __init__(self, *, nursery):
        self.nursery = nursery
        self.broadcaster = ca.Broadcaster(our_role=ca.CLIENT)
        self.log = self.broadcaster.log
        self.command_chan = open_memory_channel(1000)
        self.broadcaster_command_condition = trio.Condition()
        self._cleanup_condition = trio.Condition()
        self._cleanup_event = trio.Event()

        # UDP socket broadcasting to CA servers
        self.udp_sock = None
        self.registered = False  # refers to RepeaterRegisterRequest
        self.unanswered_searches = {}  # map search id (cid) to name
        self.search_results = {}  # map name to address
        self.new_id = ThreadsafeCounter(
            dont_clash_with=self.unanswered_searches)

        self.environ = get_environment_variables()
        self.ca_server_port = self.environ['EPICS_CA_SERVER_PORT']
示例#12
0
def test_broadcaster_checks():
    b = ca.Broadcaster(ca.CLIENT)
    with pytest.raises(ca.LocalProtocolError):
        b.send(ca.SearchRequest(name='LIRR', cid=0, version=13))

    b.send(ca.RepeaterRegisterRequest('1.2.3.4'))
    res = ca.RepeaterConfirmResponse('5.6.7.8')
    commands = b.recv(bytes(res), ('5.6.7.8', 6666))
    assert commands[0] == res
    b.process_commands(commands)

    req = ca.SearchRequest(name='LIRR', cid=0, version=13)
    with pytest.raises(ca.LocalProtocolError):
        b.send(req)
    b.send(ca.VersionRequest(priority=0, version=13), req)

    res = ca.SearchResponse(port=6666, ip='1.2.3.4', cid=0, version=13)
    addr = ('1.2.3.4', 6666)
    commands = b.recv(bytes(res), addr)
    with pytest.raises(ca.RemoteProtocolError):
        b.process_commands(commands)
    commands = b.recv(bytes(ca.VersionResponse(version=13)) + bytes(res), addr)
    b.process_commands(commands)  # this gets both
示例#13
0
    def __init__(self, pvdb, interfaces=None):
        if interfaces is None:
            interfaces = ca.get_server_address_list()
        self.interfaces = interfaces
        self.udp_socks = {}  # map each interface to a UDP socket for searches
        self.beacon_socks = {
        }  # map each interface to a UDP socket for beacons
        self.pvdb = pvdb
        self.log = logging.getLogger(f'caproto.ctx.{id(self)}')

        self.circuits = set()
        self.broadcaster = ca.Broadcaster(our_role=ca.SERVER)

        self.subscriptions = defaultdict(deque)
        # Map Subscription to {'before': last_update, 'after': last_update}
        # to silence duplicates for Subscriptions that use edge-triggered sync
        # Channel Filter.
        self.last_sync_edge_update = defaultdict(lambda: defaultdict(dict))
        self.last_dead_band = {}
        self.beacon_count = 0
        self.environ = get_environment_variables()

        ignore_addresses = self.environ['EPICS_CAS_IGNORE_ADDR_LIST']
        self.ignore_addresses = ignore_addresses.split(' ')
示例#14
0
def test_register_convenience_method():
    broadcaster = ca.Broadcaster(ca.CLIENT)
    broadcaster.register()
示例#15
0
import caproto as ca
import time
import socket

CA_REPEATER_PORT = 5065
CA_SERVER_PORT = 5064
pv1 = "XF:31IDA-OP{Tbl-Ax:X1}Mtr.VAL"
ip = '127.0.0.1'

# Make a Broadcaster.
b = ca.Broadcaster(our_role=ca.CLIENT)
b.log.setLevel('DEBUG')

# Make a dict to hold our tcp sockets.
sockets = {}


# Convenience functions that do both transport caproto validation/ingest.
def send(circuit, command):
    buffers_to_send = circuit.send(command)
    sockets[circuit].sendmsg(buffers_to_send)


def recv(circuit):
    bytes_received = sockets[circuit].recv(4096)
    commands, _ = circuit.recv(bytes_received)
    for c in commands:
        circuit.process_command(c)
    return commands

示例#16
0
import caproto as ca
import pytest


pv1 = "synctest1"
cli_addr = ('127.0.0.1', 6666)
repeater_addr = ('127.0.0.1', 5065)

# Make a Broadcaster for the client and one for the server.
cli_b = ca.Broadcaster(our_role=ca.CLIENT)
srv_b = ca.Broadcaster(our_role=ca.SERVER)
cli_b.log.setLevel('DEBUG')
srv_b.log.setLevel('DEBUG')

req_cache = bytearray()
res_cache = bytearray()


def srv_send(circuit, command):
    buffers_to_send = circuit.send(command)
    for buffer in buffers_to_send:
        res_cache.extend(bytes(buffer))


def srv_recv(circuit):
    bytes_received = bytes(req_cache)
    assert len(bytes_received)
    req_cache.clear()
    commands, num_bytes_needed = circuit.recv(bytes_received)
    for command in commands:
        circuit.process_command(command)
示例#17
0
    def __init__(self):
        self.remotes = {}

        self.broadcaster = caproto.Broadcaster(our_role=caproto.SERVER)
        super().__init__()
示例#18
0
def search(pv_name, udp_sock, timeout, *, max_retries=2):
    # Set Broadcaster log level to match our logger.
    b = ca.Broadcaster(our_role=ca.CLIENT)
    b.client_address = safe_getsockname(udp_sock)

    # Send registration request to the repeater
    logger.debug('Registering with the Channel Access repeater.')
    bytes_to_send = b.send(ca.RepeaterRegisterRequest())

    env = get_environment_variables()
    repeater_port = env['EPICS_CA_REPEATER_PORT']

    client_address_list = ca.get_client_address_list()
    local_address = ca.get_local_address()

    try:
        udp_sock.sendto(bytes_to_send, (local_address, repeater_port))
    except OSError as exc:
        raise ca.CaprotoNetworkError(
            f"Failed to send to {local_address}:{repeater_port}") from exc

    logger.debug("Searching for %r....", pv_name)
    commands = (ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION),
                ca.SearchRequest(pv_name, 0, ca.DEFAULT_PROTOCOL_VERSION))
    bytes_to_send = b.send(*commands)
    tags = {
        'role': 'CLIENT',
        'our_address': b.client_address,
        'direction': '--->>>'
    }

    def send_search():
        for dest in client_address_list:
            tags['their_address'] = dest
            b.log.debug('%d commands %dB',
                        len(commands),
                        len(bytes_to_send),
                        extra=tags)
            try:
                udp_sock.sendto(bytes_to_send, dest)
            except OSError as exc:
                host, port = dest
                raise ca.CaprotoNetworkError(
                    f"Failed to send to {host}:{port}") from exc

    def check_timeout():
        nonlocal retry_at

        if time.monotonic() >= retry_at:
            send_search()
            retry_at = time.monotonic() + retry_timeout

        if time.monotonic() - t > timeout:
            raise CaprotoTimeoutError(
                f"Timed out while awaiting a response "
                f"from the search for {pv_name!r}. Search "
                f"requests were sent to this address list: "
                f"{ca.get_address_list()}.")

    # Initial search attempt
    send_search()

    # Await a search response, and keep track of registration status
    retry_timeout = timeout / max((max_retries, 1))
    t = time.monotonic()
    retry_at = t + retry_timeout

    try:
        orig_timeout = udp_sock.gettimeout()
        udp_sock.settimeout(retry_timeout)
        while True:
            try:
                bytes_received, address = udp_sock.recvfrom(ca.MAX_UDP_RECV)
            except socket.timeout:
                check_timeout()
                continue

            check_timeout()

            commands = b.recv(bytes_received, address)
            b.process_commands(commands)
            for command in commands:
                if isinstance(command, ca.SearchResponse) and command.cid == 0:
                    address = ca.extract_address(command)
                    logger.debug('Found %r at %s:%d', pv_name, *address)
                    return address
            else:
                # None of the commands we have seen are a reply to our request.
                # Receive more data.
                continue
    finally:
        udp_sock.settimeout(orig_timeout)
示例#19
0
def test_broadcaster():
    with pytest.raises(ca.CaprotoValueError):
        ca.Broadcaster(our_role=None)
示例#20
0
    def __init__(self):
        self.host = socket.gethostbyname(socket.gethostname())
        self.remotes = {}

        self.broadcaster = caproto.Broadcaster(our_role=caproto.SERVER)
        super().__init__()
示例#21
0
def search(pv_name, udp_sock, timeout, *, max_retries=2):
    # Set Broadcaster log level to match our logger.
    b = ca.Broadcaster(our_role=ca.CLIENT)

    # Send registration request to the repeater
    logger.debug('Registering with the Channel Access repeater.')
    bytes_to_send = b.send(ca.RepeaterRegisterRequest())

    repeater_port = os.environ.get('EPICS_CA_REPEATER_PORT', 5065)
    for host in ca.get_address_list():
        udp_sock.sendto(bytes_to_send, (host, repeater_port))

    logger.debug("Searching for '%s'....", pv_name)
    bytes_to_send = b.send(
        ca.VersionRequest(0, ca.DEFAULT_PROTOCOL_VERSION),
        ca.SearchRequest(pv_name, 0, ca.DEFAULT_PROTOCOL_VERSION))

    def send_search():
        for host in ca.get_address_list():
            if ':' in host:
                host, _, specified_port = host.partition(':')
                dest = (host, int(specified_port))
            else:
                dest = (host, CA_SERVER_PORT)
            udp_sock.sendto(bytes_to_send, dest)
            logger.debug('Search request sent to %r.', dest)

    def check_timeout():
        nonlocal retry_at

        if time.monotonic() >= retry_at:
            send_search()
            retry_at = time.monotonic() + retry_timeout

        if time.monotonic() - t > timeout:
            raise TimeoutError(f"Timed out while awaiting a response "
                               f"from the search for {pv_name!r}. Search "
                               f"requests were sent to this address list: "
                               f"{ca.get_address_list()}.")

    # Initial search attempt
    send_search()

    # Await a search response, and keep track of registration status
    retry_timeout = timeout / max((max_retries, 1))
    t = time.monotonic()
    retry_at = t + retry_timeout

    try:
        orig_timeout = udp_sock.gettimeout()
        udp_sock.settimeout(retry_timeout)
        while True:
            try:
                bytes_received, address = udp_sock.recvfrom(ca.MAX_UDP_RECV)
            except socket.timeout:
                check_timeout()
                continue

            check_timeout()

            commands = b.recv(bytes_received, address)
            b.process_commands(commands)
            for command in commands:
                if isinstance(command, ca.SearchResponse) and command.cid == 0:
                    address = ca.extract_address(command)
                    logger.debug('Found %s at %s', pv_name, address)
                    return address
            else:
                # None of the commands we have seen are a reply to our request.
                # Receive more data.
                continue
    finally:
        udp_sock.settimeout(orig_timeout)
示例#22
0
def test_empty_datagram():
    broadcaster = ca.Broadcaster(ca.CLIENT)
    commands = broadcaster.recv(b'', ('127.0.0.1', 6666))
    assert commands == []
示例#23
0
def _run_repeater(server_sock, bind_addr):
    'Run the repeater using server_socket and bind_address'
    bind_host, bind_port = bind_addr

    servers = {}
    clients = {}
    broadcaster = caproto.Broadcaster(our_role=caproto.SERVER)

    logger.info("Repeater is listening on %s:%d", bind_host, bind_port)
    while True:
        msg, addr = server_sock.recvfrom(MAX_UDP_RECV)
        host, port = addr

        if port in clients and clients[port] != host:
            # broadcast only from one interface
            continue
        elif (port in servers and servers[port]['host'] != host):
            continue

        try:
            commands = broadcaster.recv(msg, addr)
            broadcaster.process_commands(commands)
        except Exception:
            logger.exception('Failed to process incoming datagram')
            continue

        if not commands:
            # NOTE: additional valid way of registration is an empty
            # message, according to broadcaster source
            if port not in clients:
                clients[port] = host
                logger.debug('New client %s (zero-length registration)', addr)
            continue

        to_forward = []
        for command in commands:
            if isinstance(command, caproto.Beacon):
                # Update our records of the last time each server checked in
                # (i.e. issued a heartbeat).
                servers[command.server_port] = dict(up_at=time.time(),
                                                    host=host)
                # The sender of this command may leave the IP field empty (0),
                # leaving it up to the repeater to fill in the address so that
                # the ultimate recipient knows the correct origin. By leaving
                # that up to the repeater, the sender avoids providing the
                # wrong return address (i.e. picking the wrong interface). It
                # is safer to let the repeater determine the return address
                # by inspection.
                if command.header.parameter2 == 0:
                    updated_ip = caproto.ipv4_to_int32(host)
                    command.header.parameter2 = updated_ip
                to_forward.append(command)
            elif isinstance(command, caproto.RepeaterRegisterRequest):
                if port not in clients:
                    clients[port] = host
                    logger.debug('New client %s', addr)

                confirmation_bytes = broadcaster.send(
                    caproto.RepeaterConfirmResponse(host))

                try:
                    server_sock.sendto(confirmation_bytes, (host, port))
                except OSError as exc:
                    raise caproto.CaprotoNetworkError(
                        f"Failed to send to {host}:{port}") from exc

                remove_clients = list(check_clients(clients, skip=port))
                _update_all(clients, servers, remove_clients=remove_clients)
                # Do not broadcast registration requests to other clients.
                # Omit it from `to_forward`.
            else:
                to_forward.append(command)
        bytes_to_broadcast = b''.join(bytes(cmd) for cmd in to_forward)
        to_remove = []
        for other_port, other_host in clients.items():
            if other_port != port:
                try:
                    server_sock.sendto(bytes_to_broadcast,
                                       (other_host, other_port))
                except Exception:
                    to_remove.append((other_host, other_port))

        if to_remove:
            _update_all(clients, servers, remove_clients=to_remove)