Example #1
0
 def test_cache_update(self):
     c = Cache()
     c["path"] = 2
     c.apply_changes([[], {123: "test"}])
     self.assertEqual("test", c[123])
     with self.assertRaises(KeyError):
         c["path"]
Example #2
0
 def __init__(self, name, sync_factory):
     self.set_logger_name(name)
     self.name = name
     self.sync_factory = sync_factory
     self.q = self.create_queue()
     self._blocks = OrderedDict()  # block_name -> Block
     self._controllers = OrderedDict()  # block_name -> Controller
     self._block_state_cache = Cache()
     self._recv_spawned = None
     self._other_spawned = []
     # lookup of all Subscribe requests, ordered to guarantee subscription
     # notification ordering
     # {Request.generate_key(): Subscribe}
     self._subscriptions = OrderedDict()
     self.comms = []
     self._client_comms = OrderedDict()  # client comms -> list of blocks
     self._handle_functions = {
         Post: self._forward_block_request,
         Put: self._forward_block_request,
         Get: self._handle_get,
         Subscribe: self._handle_subscribe,
         Unsubscribe: self._handle_unsubscribe,
         BlockChanges: self._handle_block_changes,
         BlockRespond: self._handle_block_respond,
         BlockAdd: self._handle_block_add,
         BlockList: self._handle_block_list,
         AddSpawned: self._add_spawned,
     }
     self.create_process_block()
Example #3
0
    def __init__(self, process, _=None):
        super(PvaServerComms, self).__init__(process)

        self.name = "PvaServerComms"
        self.set_logger_name(self.name)

        self._lock = RLock()

        self._current_id = 1
        self._root_id = 0
        self._blocklist = {}
        self._cache = Cache()

        self._server = None
        self._endpoints = {}
        self._cb = None

        self._rpcs = {}
        self._puts = {}
        self._dead_rpcs = []

        # Create the V4 PVA server object
        self.create_pva_server()

        # Add a thread for executing the V4 PVA server
        self.add_spawn_function(self.start_pva_server)

        # Set up the subscription for everything (root down)
        request = Subscribe(None, self.q, [], True)
        request.set_id(self._root_id)
        self.process.q.put(request)
Example #4
0
 def test_cache_update(self):
     c = Cache()
     c["path"] = 2
     c.apply_changes([[], {123:"test"}])
     self.assertEqual("test", c[123])
     with self.assertRaises(KeyError):
         c["path"]
Example #5
0
 def __init__(self, name, sync_factory):
     self.set_logger_name(name)
     self.name = name
     self.sync_factory = sync_factory
     self.q = self.create_queue()
     self._blocks = OrderedDict()  # block_name -> Block
     self._controllers = OrderedDict()  # block_name -> Controller
     self._block_state_cache = Cache()
     self._recv_spawned = None
     self._other_spawned = []
     # lookup of all Subscribe requests, ordered to guarantee subscription
     # notification ordering
     # {Request.generate_key(): Subscribe}
     self._subscriptions = OrderedDict()
     self.comms = []
     self._client_comms = OrderedDict()  # client comms -> list of blocks
     self._handle_functions = {
         Post: self._forward_block_request,
         Put: self._forward_block_request,
         Get: self._handle_get,
         Subscribe: self._handle_subscribe,
         Unsubscribe: self._handle_unsubscribe,
         BlockChanges: self._handle_block_changes,
         BlockRespond: self._handle_block_respond,
         BlockAdd: self._handle_block_add,
         BlockList: self._handle_block_list,
         AddSpawned: self._add_spawned,
     }
     self.create_process_block()
 def __init__(self, name, sync_factory):
     self.set_logger_name(name)
     self.name = name
     self.sync_factory = sync_factory
     self.q = self.create_queue()
     self._blocks = OrderedDict()  # block name -> block
     self._block_state_cache = Cache()
     self._recv_spawned = None
     self._other_spawned = []
     self._subscriptions = []
     self.comms = []
     self._client_comms = OrderedDict()  # client comms -> list of blocks
     self._handle_functions = {
         Post: self._forward_block_request,
         Put: self._forward_block_request,
         Get: self._handle_get,
         Subscribe: self._handle_subscribe,
         Unsubscribe: self._handle_unsubscribe,
         BlockChanges: self._handle_block_changes,
         BlockRespond: self._handle_block_respond,
         BlockAdd: self._handle_block_add,
         BlockList: self._handle_block_list,
     }
     self.create_process_block()
Example #7
0
 def __init__(self, name, sync_factory):
     self.set_logger_name(name)
     self.name = name
     self.sync_factory = sync_factory
     self.q = self.create_queue()
     self._blocks = OrderedDict()  # block name -> block
     self._block_state_cache = Cache()
     self._recv_spawned = None
     self._other_spawned = []
     self._subscriptions = OrderedDict()  # block name -> list of subs
     self._last_changes = OrderedDict()  # block name -> list of changes
     self._client_comms = OrderedDict()  # client comms -> list of blocks
     self._handle_functions = {
         Post: self._forward_block_request,
         Put: self._forward_block_request,
         Get: self._handle_get,
         Subscribe: self._handle_subscribe,
         BlockNotify: self._handle_block_notify,
         BlockChanged: self._handle_block_changed,
         BlockRespond: self._handle_block_respond,
         BlockAdd: self._handle_block_add,
         BlockList: self._handle_block_list,
     }
     self.create_process_block()
Example #8
0
 def test_addition(self):
     c = Cache()
     c.apply_changes([["thing"], {1: 2}])
     self.assertEqual(c["thing"][1], 2)
Example #9
0
 def test_deletion(self):
     c = Cache()
     c["path"] = 2
     c.apply_changes([["path"]])
     self.assertEqual(list(c), [])
class Process(Loggable):
    """Hosts a number of Blocks, distributing requests between them"""

    def __init__(self, name, sync_factory):
        self.set_logger_name(name)
        self.name = name
        self.sync_factory = sync_factory
        self.q = self.create_queue()
        self._blocks = OrderedDict()  # block name -> block
        self._block_state_cache = Cache()
        self._recv_spawned = None
        self._other_spawned = []
        self._subscriptions = []
        self.comms = []
        self._client_comms = OrderedDict()  # client comms -> list of blocks
        self._handle_functions = {
            Post: self._forward_block_request,
            Put: self._forward_block_request,
            Get: self._handle_get,
            Subscribe: self._handle_subscribe,
            Unsubscribe: self._handle_unsubscribe,
            BlockChanges: self._handle_block_changes,
            BlockRespond: self._handle_block_respond,
            BlockAdd: self._handle_block_add,
            BlockList: self._handle_block_list,
        }
        self.create_process_block()

    def recv_loop(self):
        """Service self.q, distributing the requests to the right block"""
        while True:
            request = self.q.get()
            self.log_debug("Received request %s", request)
            if request is PROCESS_STOP:
                # Got the sentinel, stop immediately
                break
            try:
                self._handle_functions[type(request)](request)
            except Exception as e:  # pylint:disable=broad-except
                self.log_exception("Exception while handling %s", request)
                request.respond_with_error(str(e))

    def add_comms(self, comms):
        assert not self._recv_spawned, \
            "Can't add comms when process has been started"
        self.comms.append(comms)

    def start(self):
        """Start the process going"""
        self._recv_spawned = self.sync_factory.spawn(self.recv_loop)
        for comms in self.comms:
            comms.start()

    def stop(self, timeout=None):
        """Stop the process and wait for it to finish

        Args:
            timeout (float): Maximum amount of time to wait for each spawned
            process. None means forever
        """
        assert self._recv_spawned, "Process not started"
        self.q.put(PROCESS_STOP)
        for comms in self.comms:
            comms.stop()
        # Wait for recv_loop to complete first
        self._recv_spawned.wait(timeout=timeout)
        # Now wait for anything it spawned to complete
        for s in self._other_spawned:
            s.wait(timeout=timeout)

    def _forward_block_request(self, request):
        """Lookup target Block and spawn block.handle_request(request)

        Args:
            request (Request): The message that should be passed to the Block
        """
        block_name = request.endpoint[0]
        block = self._blocks[block_name]
        self._other_spawned.append(
            self.sync_factory.spawn(block.handle_request, request))

    def create_queue(self):
        """
        Create a queue using sync_factory object

        Returns:
            Queue: New queue
        """

        return self.sync_factory.create_queue()

    def create_lock(self):
        """
        Create a lock using sync_factory object

        Returns:
            Lock: New lock
        """
        return self.sync_factory.create_lock()

    def spawn(self, function, *args, **kwargs):
        """Calls SyncFactory.spawn()"""
        spawned = self.sync_factory.spawn(function, *args, **kwargs)
        self._other_spawned.append(spawned)
        return spawned

    def get_client_comms(self, block_name):
        for client_comms, blocks in list(self._client_comms.items()):
            if block_name in blocks:
                return client_comms

    def create_process_block(self):
        self.process_block = Block()
        # TODO: add a meta here
        children = OrderedDict()
        children["blocks"] = StringArrayMeta(
            description="Blocks hosted by this Process"
        ).make_attribute([])
        children["remoteBlocks"] = StringArrayMeta(
                description="Blocks reachable via ClientComms"
        ).make_attribute([])
        self.process_block.replace_endpoints(children)
        self.process_block.set_parent(self, self.name)
        self.add_block(self.process_block)

    def update_block_list(self, client_comms, blocks):
        self.q.put(BlockList(client_comms=client_comms, blocks=blocks))

    def _handle_block_list(self, request):
        self._client_comms[request.client_comms] = request.blocks
        remotes = []
        for blocks in self._client_comms.values():
            remotes += [b for b in blocks if b not in remotes]
        self.process_block["remoteBlocks"].set_value(remotes)

    def _handle_block_changes(self, request):
        """Update subscribers with changes and applies stored changes to the
        cached structure"""
        # update cached dict
        self._block_state_cache.apply_changes(*request.changes)

        for subscription in self._subscriptions:
            endpoint = subscription.endpoint
            # find stuff that's changed that is relevant to this subscriber
            changes = []
            for change in request.changes:
                change_path = change[0]
                # look for a change_path where the beginning matches the
                # endpoint path, then strip away the matching part and add
                # to the change set
                i = 0
                for (cp_element, ep_element) in zip(change_path, endpoint):
                    if cp_element != ep_element:
                        break
                    i += 1
                else:
                    # change has matching path, so keep it
                    # but strip off the end point path
                    filtered_change = [change_path[i:]] + change[1:]
                    changes.append(filtered_change)
            if len(changes) > 0:
                if subscription.delta:
                    # respond with the filtered changes
                    subscription.respond_with_delta(changes)
                else:
                    # respond with the structure of everything
                    # below the endpoint
                    d = self._block_state_cache.walk_path(endpoint)
                    subscription.respond_with_update(d)

    def report_changes(self, *changes):
        self.q.put(BlockChanges(changes=list(changes)))

    def block_respond(self, response, response_queue):
        self.q.put(BlockRespond(response, response_queue))

    def _handle_block_respond(self, request):
        """Push the response to the required queue"""
        request.response_queue.put(request.response)

    def add_block(self, block):
        """Add a block to be hosted by this process

        Args:
            block (Block): The block to be added
        """
        path = block.path_relative_to(self)
        assert len(path) == 1, \
            "Expected block %r to have %r as parent, got path %r" % \
            (block, self, path)
        name = path[0]
        assert name not in self._blocks, \
            "There is already a block called %r" % name
        request = BlockAdd(block=block, name=name)
        if self._recv_spawned:
            # Started, so call in Process thread
            self.q.put(request)
        else:
            # Not started yet so we are safe to add in this thread
            self._handle_block_add(request)

    def _handle_block_add(self, request):
        """Add a block to be hosted by this process"""
        block = request.block
        assert request.name not in self._blocks, \
            "There is already a block called %r" % request.name
        self._blocks[request.name] = block
        change_request = BlockChanges([[[request.name], block.to_dict()]])
        self._handle_block_changes(change_request)
        # Regenerate list of blocks
        self.process_block["blocks"].set_value(list(self._blocks))

    def get_block(self, block_name):
        try:
            return self._blocks[block_name]
        except KeyError:
            controller = ClientController(block_name, self)
            return controller.block

    def _handle_subscribe(self, request):
        """Add a new subscriber and respond with the current
        sub-structure state"""
        self._subscriptions.append(request)
        d = self._block_state_cache.walk_path(request.endpoint)
        self.log_debug("Initial subscription value %s", d)
        if request.delta:
            request.respond_with_delta([[[], d]])
        else:
            request.respond_with_update(d)

    def _handle_unsubscribe(self, request):
        """Remove a subscriber and respond with success or error"""
        subs = [s for s in self._subscriptions if s.id == request.id]
        # TODO: currently this will remove all subscriptions with a matching id
        #       there should only be one, we may want to warn if we see several
        #       Also, this should only filter by the queue/context, not sure
        #       which yet...
        if len(subs) == 0:
            request.respond_with_error(
                "No subscription found for id %d" % request.id)
        else:
            self._subscriptions = \
                [s for s in self._subscriptions if s.id != request.id]
            request.respond_with_return()

    def _handle_get(self, request):
        d = self._block_state_cache.walk_path(request.endpoint)
        request.respond_with_return(d)
Example #11
0
 def test_walk_path(self):
     c = Cache()
     c[1] = {2: {3: "end"}}
     walked = c.walk_path([1, 2, 3])
     self.assertEqual(walked, "end")
Example #12
0
class Process(Loggable):
    """Hosts a number of Blocks, distributing requests between them"""
    def __init__(self, name, sync_factory):
        self.set_logger_name(name)
        self.name = name
        self.sync_factory = sync_factory
        self.q = self.create_queue()
        self._blocks = OrderedDict()  # block_name -> Block
        self._controllers = OrderedDict()  # block_name -> Controller
        self._block_state_cache = Cache()
        self._recv_spawned = None
        self._other_spawned = []
        # lookup of all Subscribe requests, ordered to guarantee subscription
        # notification ordering
        # {Request.generate_key(): Subscribe}
        self._subscriptions = OrderedDict()
        self.comms = []
        self._client_comms = OrderedDict()  # client comms -> list of blocks
        self._handle_functions = {
            Post: self._forward_block_request,
            Put: self._forward_block_request,
            Get: self._handle_get,
            Subscribe: self._handle_subscribe,
            Unsubscribe: self._handle_unsubscribe,
            BlockChanges: self._handle_block_changes,
            BlockRespond: self._handle_block_respond,
            BlockAdd: self._handle_block_add,
            BlockList: self._handle_block_list,
            AddSpawned: self._add_spawned,
        }
        self.create_process_block()

    def recv_loop(self):
        """Service self.q, distributing the requests to the right block"""
        while True:
            request = self.q.get()
            self.log_debug("Received request %s", request)
            if request is PROCESS_STOP:
                # Got the sentinel, stop immediately
                break
            try:
                self._handle_functions[type(request)](request)
            except Exception as e:  # pylint:disable=broad-except
                self.log_exception("Exception while handling %s", request)
                try:
                    request.respond_with_error(str(e))
                except Exception:
                    pass

    def add_comms(self, comms):
        assert not self._recv_spawned, \
            "Can't add comms when process has been started"
        self.comms.append(comms)

    def start(self):
        """Start the process going"""
        self._recv_spawned = self.sync_factory.spawn(self.recv_loop)
        for comms in self.comms:
            comms.start()

    def stop(self, timeout=None):
        """Stop the process and wait for it to finish

        Args:
            timeout (float): Maximum amount of time to wait for each spawned
                process. None means forever
        """
        assert self._recv_spawned, "Process not started"
        self.q.put(PROCESS_STOP)
        for comms in self.comms:
            comms.stop()
        # Wait for recv_loop to complete first
        self._recv_spawned.wait(timeout=timeout)
        # Now wait for anything it spawned to complete
        for s, _ in self._other_spawned:
            s.wait(timeout=timeout)
        # Garbage collect the syncfactory
        del self.sync_factory

    def _forward_block_request(self, request):
        """Lookup target Block and spawn block.handle_request(request)

        Args:
            request (Request): The message that should be passed to the Block
        """
        block_name = request.endpoint[0]
        block = self._blocks[block_name]
        spawned = self.sync_factory.spawn(block.handle_request, request)
        self._add_spawned(AddSpawned(spawned, block.handle_request))

    def create_queue(self):
        """
        Create a queue using sync_factory object

        Returns:
            Queue: New queue
        """

        return self.sync_factory.create_queue()

    def create_lock(self):
        """
        Create a lock using sync_factory object

        Returns:
            New lock object
        """
        return self.sync_factory.create_lock()

    def spawn(self, function, *args, **kwargs):
        """Calls SyncFactory.spawn()"""
        def catching_function():
            try:
                function(*args, **kwargs)
            except Exception:
                self.log_exception("Exception calling %s(*%s, **%s)", function,
                                   args, kwargs)
                raise

        spawned = self.sync_factory.spawn(catching_function)
        request = AddSpawned(spawned, function)
        self.q.put(request)
        return spawned

    def _add_spawned(self, request):
        spawned = self._other_spawned
        self._other_spawned = []
        spawned.append((request.spawned, request.function))
        # Filter out the spawned that have completed to stop memory leaks
        for sp, f in spawned:
            if not sp.ready():
                self._other_spawned.append((sp, f))

    def get_client_comms(self, block_name):
        for client_comms, blocks in list(self._client_comms.items()):
            if block_name in blocks:
                return client_comms

    def create_process_block(self):
        self.process_block = Block()
        # TODO: add a meta here
        children = OrderedDict()
        children["blocks"] = StringArrayMeta(
            description="Blocks hosted by this Process").make_attribute([])
        children["remoteBlocks"] = StringArrayMeta(
            description="Blocks reachable via ClientComms").make_attribute([])
        self.process_block.replace_endpoints(children)
        self.process_block.set_process_path(self, [self.name])
        self.add_block(self.process_block, self)

    def update_block_list(self, client_comms, blocks):
        self.q.put(BlockList(client_comms=client_comms, blocks=blocks))

    def _handle_block_list(self, request):
        self._client_comms[request.client_comms] = request.blocks
        remotes = []
        for blocks in self._client_comms.values():
            remotes += [b for b in blocks if b not in remotes]
        self.process_block["remoteBlocks"].set_value(remotes)

    def _handle_block_changes(self, request):
        """Update subscribers with changes and applies stored changes to the
        cached structure"""
        # update cached dict
        subscription_changes = self._block_state_cache.apply_changes(
            *request.changes)

        # Send out the changes
        for subscription, changes in subscription_changes.items():
            if subscription.delta:
                # respond with the filtered changes
                subscription.respond_with_delta(changes)
            else:
                # respond with the structure of everything
                # below the endpoint
                d = self._block_state_cache.walk_path(subscription.endpoint)
                subscription.respond_with_update(d)

    def report_changes(self, *changes):
        self.q.put(BlockChanges(changes=list(changes)))

    def block_respond(self, response, response_queue):
        self.q.put(BlockRespond(response, response_queue))

    def _handle_block_respond(self, request):
        """Push the response to the required queue"""
        request.response_queue.put(request.response)

    def add_block(self, block, controller):
        """Add a block to be hosted by this process

        Args:
            block (Block): The block to be added
            controller (Controller): Its controller
        """
        path = block.process_path
        assert len(path) == 1, \
            "Expected block %r to have %r as parent, got path %r" % \
            (block, self, path)
        name = path[0]
        assert name not in self._blocks, \
            "There is already a block called %r" % name
        request = BlockAdd(block=block, controller=controller, name=name)
        if self._recv_spawned:
            # Started, so call in Process thread
            self.q.put(request)
        else:
            # Not started yet so we are safe to add in this thread
            self._handle_block_add(request)

    def _handle_block_add(self, request):
        """Add a block to be hosted by this process"""
        assert request.name not in self._blocks, \
            "There is already a block called %r" % request.name
        self._blocks[request.name] = request.block
        self._controllers[request.name] = request.controller
        serialized = request.block.to_dict()
        change_request = BlockChanges([[[request.name], serialized]])
        self._handle_block_changes(change_request)
        # Regenerate list of blocks
        self.process_block["blocks"].set_value(list(self._blocks))

    def get_block(self, block_name):
        try:
            return self._blocks[block_name]
        except KeyError:
            if block_name in self.process_block.remoteBlocks:
                return self.make_client_block(block_name)
            else:
                raise

    def make_client_block(self, block_name):
        params = ClientController.MethodMeta.prepare_input_map(mri=block_name)
        controller = ClientController(self, {}, params)
        return controller.block

    def get_controller(self, block_name):
        return self._controllers[block_name]

    def _handle_subscribe(self, request):
        """Add a new subscriber and respond with the current
        sub-structure state"""
        key = request.generate_key()
        assert key not in self._subscriptions, \
            "Subscription on %s already exists" % (key,)
        self._subscriptions[key] = request
        self._block_state_cache.add_subscriber(request, request.endpoint)
        d = self._block_state_cache.walk_path(request.endpoint)
        self.log_debug("Initial subscription value %s", d)
        if request.delta:
            request.respond_with_delta([[[], d]])
        else:
            request.respond_with_update(d)

    def _handle_unsubscribe(self, request):
        """Remove a subscriber and respond with success or error"""
        key = request.generate_key()
        try:
            subscription = self._subscriptions.pop(key)
        except KeyError:
            request.respond_with_error("No subscription found for %s" %
                                       (key, ))
        else:
            self._block_state_cache.remove_subscriber(subscription,
                                                      subscription.endpoint)
            request.respond_with_return()

    def _handle_get(self, request):
        d = self._block_state_cache.walk_path(request.endpoint)
        request.respond_with_return(d)
Example #13
0
class Process(Loggable):
    """Hosts a number of Blocks, distributing requests between them"""

    def __init__(self, name, sync_factory):
        self.set_logger_name(name)
        self.name = name
        self.sync_factory = sync_factory
        self.q = self.create_queue()
        self._blocks = OrderedDict()  # block_name -> Block
        self._controllers = OrderedDict()  # block_name -> Controller
        self._block_state_cache = Cache()
        self._recv_spawned = None
        self._other_spawned = []
        # lookup of all Subscribe requests, ordered to guarantee subscription
        # notification ordering
        # {Request.generate_key(): Subscribe}
        self._subscriptions = OrderedDict()
        self.comms = []
        self._client_comms = OrderedDict()  # client comms -> list of blocks
        self._handle_functions = {
            Post: self._forward_block_request,
            Put: self._forward_block_request,
            Get: self._handle_get,
            Subscribe: self._handle_subscribe,
            Unsubscribe: self._handle_unsubscribe,
            BlockChanges: self._handle_block_changes,
            BlockRespond: self._handle_block_respond,
            BlockAdd: self._handle_block_add,
            BlockList: self._handle_block_list,
            AddSpawned: self._add_spawned,
        }
        self.create_process_block()

    def recv_loop(self):
        """Service self.q, distributing the requests to the right block"""
        while True:
            request = self.q.get()
            self.log_debug("Received request %s", request)
            if request is PROCESS_STOP:
                # Got the sentinel, stop immediately
                break
            try:
                self._handle_functions[type(request)](request)
            except Exception as e:  # pylint:disable=broad-except
                self.log_exception("Exception while handling %s", request)
                try:
                    request.respond_with_error(str(e))
                except Exception:
                    pass

    def add_comms(self, comms):
        assert not self._recv_spawned, \
            "Can't add comms when process has been started"
        self.comms.append(comms)

    def start(self):
        """Start the process going"""
        self._recv_spawned = self.sync_factory.spawn(self.recv_loop)
        for comms in self.comms:
            comms.start()

    def stop(self, timeout=None):
        """Stop the process and wait for it to finish

        Args:
            timeout (float): Maximum amount of time to wait for each spawned
                process. None means forever
        """
        assert self._recv_spawned, "Process not started"
        self.q.put(PROCESS_STOP)
        for comms in self.comms:
            comms.stop()
        # Wait for recv_loop to complete first
        self._recv_spawned.wait(timeout=timeout)
        # Now wait for anything it spawned to complete
        for s, _ in self._other_spawned:
            s.wait(timeout=timeout)
        # Garbage collect the syncfactory
        del self.sync_factory

    def _forward_block_request(self, request):
        """Lookup target Block and spawn block.handle_request(request)

        Args:
            request (Request): The message that should be passed to the Block
        """
        block_name = request.endpoint[0]
        block = self._blocks[block_name]
        spawned = self.sync_factory.spawn(block.handle_request, request)
        self._add_spawned(AddSpawned(spawned, block.handle_request))

    def create_queue(self):
        """
        Create a queue using sync_factory object

        Returns:
            Queue: New queue
        """

        return self.sync_factory.create_queue()

    def create_lock(self):
        """
        Create a lock using sync_factory object

        Returns:
            New lock object
        """
        return self.sync_factory.create_lock()

    def spawn(self, function, *args, **kwargs):
        """Calls SyncFactory.spawn()"""
        def catching_function():
            try:
                function(*args, **kwargs)
            except Exception:
                self.log_exception(
                    "Exception calling %s(*%s, **%s)", function, args, kwargs)
                raise
        spawned = self.sync_factory.spawn(catching_function)
        request = AddSpawned(spawned, function)
        self.q.put(request)
        return spawned

    def _add_spawned(self, request):
        spawned = self._other_spawned
        self._other_spawned = []
        spawned.append((request.spawned, request.function))
        # Filter out the spawned that have completed to stop memory leaks
        for sp, f in spawned:
            if not sp.ready():
                self._other_spawned.append((sp, f))

    def get_client_comms(self, block_name):
        for client_comms, blocks in list(self._client_comms.items()):
            if block_name in blocks:
                return client_comms

    def create_process_block(self):
        self.process_block = Block()
        # TODO: add a meta here
        children = OrderedDict()
        children["blocks"] = StringArrayMeta(
            description="Blocks hosted by this Process"
        ).make_attribute([])
        children["remoteBlocks"] = StringArrayMeta(
                description="Blocks reachable via ClientComms"
        ).make_attribute([])
        self.process_block.replace_endpoints(children)
        self.process_block.set_process_path(self, [self.name])
        self.add_block(self.process_block, self)

    def update_block_list(self, client_comms, blocks):
        self.q.put(BlockList(client_comms=client_comms, blocks=blocks))

    def _handle_block_list(self, request):
        self._client_comms[request.client_comms] = request.blocks
        remotes = []
        for blocks in self._client_comms.values():
            remotes += [b for b in blocks if b not in remotes]
        self.process_block["remoteBlocks"].set_value(remotes)

    def _handle_block_changes(self, request):
        """Update subscribers with changes and applies stored changes to the
        cached structure"""
        # update cached dict
        subscription_changes = self._block_state_cache.apply_changes(
            *request.changes)

        # Send out the changes
        for subscription, changes in subscription_changes.items():
            if subscription.delta:
                # respond with the filtered changes
                subscription.respond_with_delta(changes)
            else:
                # respond with the structure of everything
                # below the endpoint
                d = self._block_state_cache.walk_path(subscription.endpoint)
                subscription.respond_with_update(d)

    def report_changes(self, *changes):
        self.q.put(BlockChanges(changes=list(changes)))

    def block_respond(self, response, response_queue):
        self.q.put(BlockRespond(response, response_queue))

    def _handle_block_respond(self, request):
        """Push the response to the required queue"""
        request.response_queue.put(request.response)

    def add_block(self, block, controller):
        """Add a block to be hosted by this process

        Args:
            block (Block): The block to be added
            controller (Controller): Its controller
        """
        path = block.process_path
        assert len(path) == 1, \
            "Expected block %r to have %r as parent, got path %r" % \
            (block, self, path)
        name = path[0]
        assert name not in self._blocks, \
            "There is already a block called %r" % name
        request = BlockAdd(block=block, controller=controller, name=name)
        if self._recv_spawned:
            # Started, so call in Process thread
            self.q.put(request)
        else:
            # Not started yet so we are safe to add in this thread
            self._handle_block_add(request)

    def _handle_block_add(self, request):
        """Add a block to be hosted by this process"""
        assert request.name not in self._blocks, \
            "There is already a block called %r" % request.name
        self._blocks[request.name] = request.block
        self._controllers[request.name] = request.controller
        serialized = request.block.to_dict()
        change_request = BlockChanges([[[request.name], serialized]])
        self._handle_block_changes(change_request)
        # Regenerate list of blocks
        self.process_block["blocks"].set_value(list(self._blocks))

    def get_block(self, block_name):
        try:
            return self._blocks[block_name]
        except KeyError:
            if block_name in self.process_block.remoteBlocks:
                return self.make_client_block(block_name)
            else:
                raise

    def make_client_block(self, block_name):
        params = ClientController.MethodMeta.prepare_input_map(
            mri=block_name)
        controller = ClientController(self, {}, params)
        return controller.block

    def get_controller(self, block_name):
        return self._controllers[block_name]

    def _handle_subscribe(self, request):
        """Add a new subscriber and respond with the current
        sub-structure state"""
        key = request.generate_key()
        assert key not in self._subscriptions, \
            "Subscription on %s already exists" % (key,)
        self._subscriptions[key] = request
        self._block_state_cache.add_subscriber(request, request.endpoint)
        d = self._block_state_cache.walk_path(request.endpoint)
        self.log_debug("Initial subscription value %s", d)
        if request.delta:
            request.respond_with_delta([[[], d]])
        else:
            request.respond_with_update(d)

    def _handle_unsubscribe(self, request):
        """Remove a subscriber and respond with success or error"""
        key = request.generate_key()
        try:
            subscription = self._subscriptions.pop(key)
        except KeyError:
            request.respond_with_error(
                "No subscription found for %s" % (key,))
        else:
            self._block_state_cache.remove_subscriber(
                subscription, subscription.endpoint)
            request.respond_with_return()

    def _handle_get(self, request):
        d = self._block_state_cache.walk_path(request.endpoint)
        request.respond_with_return(d)
Example #14
0
class Process(Loggable):
    """Hosts a number of Blocks, distributing requests between them"""
    def __init__(self, name, sync_factory):
        self.set_logger_name(name)
        self.name = name
        self.sync_factory = sync_factory
        self.q = self.create_queue()
        self._blocks = OrderedDict()  # block name -> block
        self._block_state_cache = Cache()
        self._recv_spawned = None
        self._other_spawned = []
        self._subscriptions = OrderedDict()  # block name -> list of subs
        self._last_changes = OrderedDict()  # block name -> list of changes
        self._client_comms = OrderedDict()  # client comms -> list of blocks
        self._handle_functions = {
            Post: self._forward_block_request,
            Put: self._forward_block_request,
            Get: self._handle_get,
            Subscribe: self._handle_subscribe,
            BlockNotify: self._handle_block_notify,
            BlockChanged: self._handle_block_changed,
            BlockRespond: self._handle_block_respond,
            BlockAdd: self._handle_block_add,
            BlockList: self._handle_block_list,
        }
        self.create_process_block()

    def recv_loop(self):
        """Service self.q, distributing the requests to the right block"""
        while True:
            request = self.q.get()
            self.log_debug("Received request %s", request)
            if request is PROCESS_STOP:
                # Got the sentinel, stop immediately
                break
            try:
                self._handle_functions[type(request)](request)
            except Exception:
                self.log_exception("Exception while handling %s", request)

    def start(self):
        """Start the process going"""
        self._recv_spawned = self.sync_factory.spawn(self.recv_loop)

    def stop(self, timeout=None):
        """Stop the process and wait for it to finish

        Args:
            timeout (float): Maximum amount of time to wait for each spawned
            process. None means forever
        """
        assert self._recv_spawned, "Process not started"
        self.q.put(PROCESS_STOP)
        # Wait for recv_loop to complete first
        self._recv_spawned.wait(timeout=timeout)
        # Now wait for anything it spawned to complete
        for s in self._other_spawned:
            s.wait(timeout=timeout)

    def _forward_block_request(self, request):
        """Lookup target Block and spawn block.handle_request(request)

        Args:
            request (Request): The message that should be passed to the Block
        """
        block_name = request.endpoint[0]
        block = self._blocks[block_name]
        self._other_spawned.append(
            self.sync_factory.spawn(block.handle_request, request))

    def create_queue(self):
        """
        Create a queue using sync_factory object

        Returns:
            Queue: New queue
        """

        return self.sync_factory.create_queue()

    def create_lock(self):
        """
        Create a lock using sync_factory object

        Returns:
            Lock: New lock
        """
        return self.sync_factory.create_lock()

    def spawn(self, function, *args, **kwargs):
        """Calls SyncFactory.spawn()"""
        spawned = self.sync_factory.spawn(function, *args, **kwargs)
        self._other_spawned.append(spawned)
        return spawned

    def get_client_comms(self, block_name):
        for client_comms, blocks in list(self._client_comms.items()):
            if block_name in blocks:
                return client_comms

    def create_process_block(self):
        self.process_block = Block()
        a = Attribute(
            StringArrayMeta(description="Blocks hosted by this Process"))
        self.process_block.add_attribute("blocks", a)
        a = Attribute(
            StringArrayMeta(description="Blocks reachable via ClientComms"))
        self.process_block.add_attribute("remoteBlocks", a)
        self.add_block(self.name, self.process_block)

    def update_block_list(self, client_comms, blocks):
        self.q.put(BlockList(client_comms=client_comms, blocks=blocks))

    def _handle_block_list(self, request):
        self._client_comms[request.client_comms] = request.blocks
        remotes = []
        for blocks in self._client_comms.values():
            remotes += [b for b in blocks if b not in remotes]
        self.process_block.remoteBlocks.set_value(remotes)

    def notify_subscribers(self, block_name):
        self.q.put(BlockNotify(name=block_name))

    def _handle_block_notify(self, request):
        """Update subscribers with changes and applies stored changes to the
        cached structure"""
        # update cached dict
        for delta in self._last_changes.setdefault(request.name, []):
            self._block_state_cache.delta_update(delta)

        for subscription in self._subscriptions.setdefault(request.name, []):
            endpoint = subscription.endpoint
            # find stuff that's changed that is relevant to this subscriber
            changes = []
            for change in self._last_changes[request.name]:
                change_path = change[0]
                # look for a change_path where the beginning matches the
                # endpoint path, then strip away the matching part and add
                # to the change set
                i = 0
                for (cp_element, ep_element) in zip(change_path, endpoint):
                    if cp_element != ep_element:
                        break
                    i += 1
                else:
                    # change has matching path, so keep it
                    # but strip off the end point path
                    filtered_change = [change_path[i:]] + change[1:]
                    changes.append(filtered_change)
            if len(changes) > 0:
                if subscription.delta:
                    # respond with the filtered changes
                    response = Delta(subscription.id_, subscription.context,
                                     changes)
                else:
                    # respond with the structure of everything
                    # below the endpoint
                    d = self._block_state_cache.walk_path(endpoint)
                    response = Update(subscription.id_, subscription.context,
                                      d)
                self.log_debug("Responding to subscription %s", response)
                subscription.response_queue.put(response)
        self._last_changes[request.name] = []

    def on_changed(self, change, notify=True):
        self.q.put(BlockChanged(change=change))
        if notify:
            block_name = change[0][0]
            self.notify_subscribers(block_name)

    def _handle_block_changed(self, request):
        """Record changes to made to a block"""
        # update changes
        path = request.change[0]
        block_changes = self._last_changes.setdefault(path[0], [])
        block_changes.append(request.change)

    def block_respond(self, response, response_queue):
        self.q.put(BlockRespond(response, response_queue))

    def _handle_block_respond(self, request):
        """Push the response to the required queue"""
        request.response_queue.put(request.response)

    def add_block(self, name, block):
        """Add a block to be hosted by this process

        Args:
            block (Block): The block to be added
        """
        assert name not in self._blocks, \
            "There is already a block called %s" % name
        block.set_parent(self, name)
        self.q.put(BlockAdd(block=block))

    def _handle_block_add(self, request):
        """Add a block to be hosted by this process"""
        block = request.block
        assert block.name not in self._blocks, \
            "There is already a block called %s" % block.name
        self._blocks[block.name] = block
        self._block_state_cache[block.name] = block.to_dict()
        block.lock = self.create_lock()
        # Regenerate list of blocks
        self.process_block.blocks.set_value(list(self._blocks))

    def _handle_subscribe(self, request):
        """Add a new subscriber and respond with the current
        sub-structure state"""
        subs = self._subscriptions.setdefault(request.endpoint[0], [])
        subs.append(request)
        d = self._block_state_cache.walk_path(request.endpoint)
        self.log_debug("Initial subscription value %s", d)
        if request.delta:
            request.respond_with_delta([[[], d]])
        else:
            request.respond_with_update(d)

    def _handle_get(self, request):
        d = self._block_state_cache.walk_path(request.endpoint)
        response = Return(request.id_, request.context, d)
        request.response_queue.put(response)
Example #15
0
 def test_deletion(self):
     c = Cache()
     c[1] = 2
     c.delta_update([[1]])
     self.assertEqual(list(c), [])
Example #16
0
 def test_change(self):
     c = Cache()
     c[1] = 3
     c.apply_changes([["path"], 4])
     self.assertEqual(c["path"], 4)
Example #17
0
 def test_update_root_errors(self):
     c = Cache()
     self.assertRaises(AssertionError, c.delta_update, [[], 3])
Example #18
0
class Process(Loggable):
    """Hosts a number of Blocks, distributing requests between them"""

    def __init__(self, name, sync_factory):
        self.set_logger_name(name)
        self.name = name
        self.sync_factory = sync_factory
        self.q = self.create_queue()
        self._blocks = OrderedDict()  # block name -> block
        self._block_state_cache = Cache()
        self._recv_spawned = None
        self._other_spawned = []
        self._subscriptions = OrderedDict()  # block name -> list of subs
        self._last_changes = OrderedDict()  # block name -> list of changes
        self._client_comms = OrderedDict()  # client comms -> list of blocks
        self._handle_functions = {
            Post: self._forward_block_request,
            Put: self._forward_block_request,
            Get: self._handle_get,
            Subscribe: self._handle_subscribe,
            BlockNotify: self._handle_block_notify,
            BlockChanged: self._handle_block_changed,
            BlockRespond: self._handle_block_respond,
            BlockAdd: self._handle_block_add,
            BlockList: self._handle_block_list,
        }
        self.create_process_block()

    def recv_loop(self):
        """Service self.q, distributing the requests to the right block"""
        while True:
            request = self.q.get()
            self.log_debug("Received request %s", request)
            if request is PROCESS_STOP:
                # Got the sentinel, stop immediately
                break
            try:
                self._handle_functions[type(request)](request)
            except Exception:
                self.log_exception("Exception while handling %s", request)

    def start(self):
        """Start the process going"""
        self._recv_spawned = self.sync_factory.spawn(self.recv_loop)

    def stop(self, timeout=None):
        """Stop the process and wait for it to finish

        Args:
            timeout (float): Maximum amount of time to wait for each spawned
            process. None means forever
        """
        assert self._recv_spawned, "Process not started"
        self.q.put(PROCESS_STOP)
        # Wait for recv_loop to complete first
        self._recv_spawned.wait(timeout=timeout)
        # Now wait for anything it spawned to complete
        for s in self._other_spawned:
            s.wait(timeout=timeout)

    def _forward_block_request(self, request):
        """Lookup target Block and spawn block.handle_request(request)

        Args:
            request (Request): The message that should be passed to the Block
        """
        block_name = request.endpoint[0]
        block = self._blocks[block_name]
        self._other_spawned.append(
            self.sync_factory.spawn(block.handle_request, request))

    def create_queue(self):
        """
        Create a queue using sync_factory object

        Returns:
            Queue: New queue
        """

        return self.sync_factory.create_queue()

    def create_lock(self):
        """
        Create a lock using sync_factory object

        Returns:
            Lock: New lock
        """
        return self.sync_factory.create_lock()

    def spawn(self, function, *args, **kwargs):
        """Calls SyncFactory.spawn()"""
        spawned = self.sync_factory.spawn(function, *args, **kwargs)
        self._other_spawned.append(spawned)
        return spawned

    def get_client_comms(self, block_name):
        for client_comms, blocks in list(self._client_comms.items()):
            if block_name in blocks:
                return client_comms

    def create_process_block(self):
        self.process_block = Block()
        a = Attribute(StringArrayMeta(
            description="Blocks hosted by this Process"))
        self.process_block.add_attribute("blocks", a)
        a = Attribute(StringArrayMeta(
                description="Blocks reachable via ClientComms"))
        self.process_block.add_attribute("remoteBlocks", a)
        self.add_block(self.name, self.process_block)

    def update_block_list(self, client_comms, blocks):
        self.q.put(BlockList(client_comms=client_comms, blocks=blocks))

    def _handle_block_list(self, request):
        self._client_comms[request.client_comms] = request.blocks
        remotes = []
        for blocks in self._client_comms.values():
            remotes += [b for b in blocks if b not in remotes]
        self.process_block.remoteBlocks.set_value(remotes)

    def notify_subscribers(self, block_name):
        self.q.put(BlockNotify(name=block_name))

    def _handle_block_notify(self, request):
        """Update subscribers with changes and applies stored changes to the
        cached structure"""
        # update cached dict
        for delta in self._last_changes.setdefault(request.name, []):
            self._block_state_cache.delta_update(delta)

        for subscription in self._subscriptions.setdefault(request.name, []):
            endpoint = subscription.endpoint
            # find stuff that's changed that is relevant to this subscriber
            changes = []
            for change in self._last_changes[request.name]:
                change_path = change[0]
                # look for a change_path where the beginning matches the
                # endpoint path, then strip away the matching part and add
                # to the change set
                i = 0
                for (cp_element, ep_element) in zip(change_path, endpoint):
                    if cp_element != ep_element:
                        break
                    i += 1
                else:
                    # change has matching path, so keep it
                    # but strip off the end point path
                    filtered_change = [change_path[i:]] + change[1:]
                    changes.append(filtered_change)
            if len(changes) > 0:
                if subscription.delta:
                    # respond with the filtered changes
                    response = Delta(
                        subscription.id_, subscription.context, changes)
                else:
                    # respond with the structure of everything
                    # below the endpoint
                    d = self._block_state_cache.walk_path(endpoint)
                    response = Update(
                        subscription.id_, subscription.context, d)
                self.log_debug("Responding to subscription %s", response)
                subscription.response_queue.put(response)
        self._last_changes[request.name] = []

    def on_changed(self, change, notify=True):
        self.q.put(BlockChanged(change=change))
        if notify:
            block_name = change[0][0]
            self.notify_subscribers(block_name)

    def _handle_block_changed(self, request):
        """Record changes to made to a block"""
        # update changes
        path = request.change[0]
        block_changes = self._last_changes.setdefault(path[0], [])
        block_changes.append(request.change)

    def block_respond(self, response, response_queue):
        self.q.put(BlockRespond(response, response_queue))

    def _handle_block_respond(self, request):
        """Push the response to the required queue"""
        request.response_queue.put(request.response)

    def add_block(self, name, block):
        """Add a block to be hosted by this process

        Args:
            block (Block): The block to be added
        """
        assert name not in self._blocks, \
            "There is already a block called %s" % name
        block.set_parent(self, name)
        self.q.put(BlockAdd(block=block))

    def _handle_block_add(self, request):
        """Add a block to be hosted by this process"""
        block = request.block
        assert block.name not in self._blocks, \
            "There is already a block called %s" % block.name
        self._blocks[block.name] = block
        self._block_state_cache[block.name] = block.to_dict()
        block.lock = self.create_lock()
        # Regenerate list of blocks
        self.process_block.blocks.set_value(list(self._blocks))

    def _handle_subscribe(self, request):
        """Add a new subscriber and respond with the current
        sub-structure state"""
        subs = self._subscriptions.setdefault(request.endpoint[0], [])
        subs.append(request)
        d = self._block_state_cache.walk_path(request.endpoint)
        self.log_debug("Initial subscription value %s", d)
        if request.delta:
            request.respond_with_delta([[[], d]])
        else:
            request.respond_with_update(d)

    def _handle_get(self, request):
        d = self._block_state_cache.walk_path(request.endpoint)
        response = Return(request.id_, request.context, d)
        request.response_queue.put(response)
Example #19
0
 def test_change(self):
     c = Cache()
     c[1] = 3
     c.delta_update([[1], 4])
     self.assertEqual(c[1], 4)
Example #20
0
 def test_deletion(self):
     c = Cache()
     c[1] = 2
     c.delta_update([[1]])
     self.assertEqual(list(c), [])
Example #21
0
 def test_addition(self):
     c = Cache()
     c.delta_update([["thing"], {1: 2}])
     self.assertEqual(c["thing"][1], 2)
Example #22
0
class PvaServerComms(ServerComms, PvaUtil):
    """A class for communication between pva client and server"""
    CACHE_UPDATE = 0

    def __init__(self, process, _=None):
        super(PvaServerComms, self).__init__(process)

        self.name = "PvaServerComms"
        self.set_logger_name(self.name)

        self._lock = RLock()

        self._current_id = 1
        self._root_id = 0
        self._blocklist = {}
        self._cache = Cache()

        self._server = None
        self._endpoints = {}
        self._cb = None

        self._rpcs = {}
        self._puts = {}
        self._dead_rpcs = []

        # Create the V4 PVA server object
        self.create_pva_server()

        # Add a thread for executing the V4 PVA server
        self.add_spawn_function(self.start_pva_server)

        # Set up the subscription for everything (root down)
        request = Subscribe(None, self.q, [], True)
        request.set_id(self._root_id)
        self.process.q.put(request)

    def _get_unique_id(self):
        with self._lock:
            self._current_id += 1
            return self._current_id

    def _update_block_list(self):
        with self._lock:
            old_blocks = self._blocklist.copy()
            for name in self._cache:
                if name in self._blocklist:
                    old_blocks.pop(name)
                else:
                    # New block, so create the new Pva endpoint
                    self.log_debug("Adding malcolm block to PVA list: %s", name)
                    self._blocklist[name] = self._get_unique_id()
                    self._add_new_pva_channel(name)

            # Now loop over any remaining old blocks and remove their subscriptions
            for name in old_blocks:
                self.log_debug("Removing stale malcolm block: %s", name)

    def _update_cache(self, response):
        if response.changes:
            self.log_debug("Update received: %s", response.changes)
            self._cache.apply_changes(*response.changes)
            # Update the block list to create new PVA channels if required
            self._update_block_list()

    def send_to_client(self, response):
        """Abstract method to dispatch response to a client

        Args:
            response (Response): The message to pass to the client
        """
        if isinstance(response, Return):
            # Notify RPC of return value
            self.log_debug("Response: %s", response)
            if response["id"] in self._rpcs:
                self._rpcs[response["id"]].notify_reply(response)
            elif response["id"] in self._puts:
                self._puts[response["id"]].notify_reply(response)
        elif isinstance(response, Error):
            # Notify RPC of error value
            self.log_debug("Response: %s", response)
            if response["id"] in self._rpcs:
                self._rpcs[response["id"]].notify_reply(response)
            elif response["id"] in self._puts:
                self._puts[response["id"]].notify_reply(response)
        else:
            # Update the cache
            self._update_cache(response)

    def _add_new_pva_channel(self, block):
        """Create a new PVA endpoint for the block name

        Args:
            name (str): The name of the block to create the PVA endpoint for
        """
        self.log_debug("Creating PVA endpoint for %s", block)
        self._endpoints[block] = PvaEndpoint(self.name, block, self._server, self)

    def create_pva_server(self):
        #self.log_debug("Creating PVA server object")
        self._server = pvaccess.PvaServer()

    def start_pva_server(self):
        self.log_debug("Starting PVA server")
        #self._server.listen()
        self._server.startListener()

    def stop_pva_server(self):
        self.log_debug("Executing stop PVA server")
        self._server.stop()

    def register_rpc(self, id, rpc):
        with self._lock:
            self.log_debug("Registering RPC object with ID %d", id)
            self._rpcs[id] = rpc

    def register_put(self, id, put):
        with self._lock:
            self.log_debug("Registering Put object with ID %d", id)
            self._puts[id] = put

    def remove_put(self, id):
        with self._lock:
            self.log_debug("Removing Put object with ID %d", id)
            if id in self._puts:
                if self._puts[id].check_lock():
                    self._puts.pop(id, None)

    def register_dead_rpc(self, id):
        with self._lock:
            self.log_debug("Notifying server that RPC [%d] can be tested for purging", id)
            self._dead_rpcs.append(id)

    def purge_rpcs(self):
        with self._lock:
            rpc_list = list(self._dead_rpcs)
            for id in rpc_list:
                self.log_debug("Testing for purge RPC [%d]", id)
                if id in self._rpcs:
                    if self._rpcs[id].check_lock():
                        # We've gained the lock, so we are OK to purge this RPC
                        self.log_debug("Purging dead RPC [%d]", id)
                        self._rpcs.pop(id, None)
                        self._dead_rpcs.remove(id)
                else:
                    self.log_debug("RPC [%d] was not present (already purged?)", id)
                    if id in self._dead_rpcs:
                        self._dead_rpcs.remove(id)

    def cache_to_pvobject(self, name, paths=None):
        self.log_debug("Cache[%s]: %s", name, self._cache[name])
        # Test parsing the cache to create the PV structure
        try:
            block = self._cache[name]
            if not paths:
                # No paths provided, so just return the whole block
                pv_object = self.dict_to_pv_object(block)
            else:
                #self.log_debug("Path route: %s", paths)
                # List of path lists provided
                # Create empty dict to store the structure
                path_dict = OrderedDict()
                # Create empty dict to store the values
                val_dict = OrderedDict()
                # Loop over each path list
                for path in paths:
                    # Insert the block name as the first endpoint (needed for walking cache)
                    path.insert(0, name)
                    # set pointer to structure dict
                    d = path_dict
                    # set pointer to value dict
                    v = val_dict
                    # set pointer to cache
                    t = self._cache
                    # Loop over each node in the path (except for the last)
                    for node in path[:-1]:
                        #self.log_debug("Node: %s", node)
                        # Update the cache pointer
                        t = t[node]
                        # Check if the structure for this node has already been created
                        if node not in d:
                            # No structure, so create it
                            d[node] = OrderedDict()
                            # Collect and assign the correct type for this structure
                            d[node]["typeid"] = t["typeid"]
                        # Update the structure pointer
                        d = d[node]
                        # Check if the value structure for this node has already been created
                        if node not in v:
                            # No value structure so create it
                            v[node] = OrderedDict()
                        # Update the value pointer
                        v = v[node]
                    # Walk the cache path and update the structure with the final element
                    d[path[-1]] = self._cache.walk_path(path)
                    # Walk the cache path and update the value with the final element
                    v[path[-1]] = self._cache.walk_path(path)
                    #self.log_debug("Walk path: %s", path)
                    #self.log_debug("Path dict: %s", path_dict)
                # Create our PV object from the structure dict
                pv_object = self.dict_to_pv_object_structure(path_dict[name])
                # Set the value of the PV object from the value dict
                pv_object.set(self.strip_type_id(val_dict[name]))
        except:
            raise

        return pv_object

    def get_request(self, block, request):
        self.log_debug("Get request made with: %s", request)
        try:
            # We need to convert the request object into a set of paths
            if "field" not in request:
                pv_object = self.cache_to_pvobject(block)
            else:
                field_dict = request["field"]
                if not field_dict:
                    # The whole block has been requested
                    self.log_debug("Complete block %s requested for pvget", block)
                    # Retrieve the entire block structure
                    pv_object = self.cache_to_pvobject(block)
                else:
                    paths = self.dict_to_path(field_dict)
                    self.log_debug("Paths: %s", paths)
                    pv_object = self.cache_to_pvobject(block, paths)
        except:
            # There has been a failure, return an error object
            err = Error(id_=1, message="Failed to retrieve endpoints")
            response_dict = err.to_dict()
            response_dict.pop("id")
            pv_object = self.dict_to_pv_object(response_dict)

        return pv_object

    def dict_to_path(self, dict_in):
        items = []
        for item in dict_in:
            if not dict_in[item]:
                items.append([item])
            else:
                temp_list = self.dict_to_path(dict_in[item])
                for temp_item in temp_list:
                    if isinstance(temp_item, list):
                        temp_item.insert(0, item)
                        items.append(temp_item)
                    else:
                        items.append([item, temp_item])
        return items
Example #23
0
 def test_change(self):
     c = Cache()
     c[1] = 3
     c.apply_changes([["path"], 4])
     self.assertEqual(c["path"], 4)
Example #24
0
 def test_change(self):
     c = Cache()
     c[1] = 3
     c.delta_update([[1], 4])
     self.assertEqual(c[1], 4)
Example #25
0
 def test_non_string_path_errors(self):
     c = Cache()
     self.assertRaises(AssertionError, c.apply_changes, [[1], 3])
Example #26
0
 def test_addition(self):
     c = Cache()
     c.apply_changes([["thing"], {1: 2}])
     self.assertEqual(c["thing"][1], 2)
Example #27
0
 def test_walk_path(self):
     c = Cache()
     c[1] = {2: {3: "end"}}
     walked = c.walk_path([1, 2, 3])
     self.assertEqual(walked, "end")
Example #28
0
 def test_deletion(self):
     c = Cache()
     c["path"] = 2
     c.apply_changes([["path"]])
     self.assertEqual(list(c), [])
Example #29
0
 def test_addition(self):
     c = Cache()
     c.delta_update([["thing"], {1: 2}])
     self.assertEqual(c["thing"][1], 2)