示例#1
0
 def test_update_entities_two_types(self):
     config = ActorRuntimeConfig()
     config.update_entities(['actortype1', 'actortype1'])
     self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600))
     self.assertEqual(config._actor_scan_interval, timedelta(seconds=30))
     self.assertEqual(config._drain_ongoing_call_timeout,
                      timedelta(seconds=60))
     self.assertEqual(config._drain_rebalanced_actors, True)
     self.assertEqual(config._entities, ['actortype1', 'actortype1'])
 def test_set_reminders_storage_partitions(self):
     config = ActorRuntimeConfig(reminders_storage_partitions=12)
     self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600))
     self.assertEqual(config._actor_scan_interval, timedelta(seconds=30))
     self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60))
     self.assertEqual(config._drain_rebalanced_actors, True)
     self.assertNotIn('reentrancy', config.as_dict().keys())
     self.assertEqual(config._reminders_storage_partitions, 12)
     self.assertEqual(config.as_dict()['remindersStoragePartitions'], 12)
 def test_update_entities_two_types(self):
     config = ActorRuntimeConfig()
     config.update_entities(['actortype1', 'actortype1'])
     self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600))
     self.assertEqual(config._actor_scan_interval, timedelta(seconds=30))
     self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60))
     self.assertEqual(config._drain_rebalanced_actors, True)
     self.assertEqual(config._entities, ['actortype1', 'actortype1'])
     self.assertNotIn('remindersStoragePartitions', config.as_dict().keys())
    def test_default_config(self):
        config = ActorRuntimeConfig()

        self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600))
        self.assertEqual(config._actor_scan_interval, timedelta(seconds=30))
        self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60))
        self.assertEqual(config._drain_rebalanced_actors, True)
        self.assertEqual(config._reentrancy, None)
        self.assertEqual(config._entities, [])
        self.assertNotIn('reentrancy', config.as_dict().keys())
        self.assertNotIn('remindersStoragePartitions', config.as_dict().keys())
示例#5
0
 def setUp(self):
     ActorRuntime._actor_managers = {}
     ActorRuntime.set_actor_config(ActorRuntimeConfig())
     self._serializer = DefaultJSONSerializer()
     _run(ActorRuntime.register_actor(FakeSimpleActor))
     _run(ActorRuntime.register_actor(FakeMultiInterfacesActor))
     _run(ActorRuntime.register_actor(FakeSimpleTimerActor))
示例#6
0
 def setUp(self):
     ActorRuntime._actor_managers = {}
     ActorRuntime.set_actor_config(
         ActorRuntimeConfig(reentrancy=ActorReentrancyConfig(enabled=True)))
     self._serializer = DefaultJSONSerializer()
     _run(ActorRuntime.register_actor(FakeReentrantActor))
     _run(ActorRuntime.register_actor(FakeSlowReentrantActor))
     _run(ActorRuntime.register_actor(FakeMultiInterfacesActor))
示例#7
0
    def test_default_config(self):
        config = ActorRuntimeConfig()

        self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600))
        self.assertEqual(config._actor_scan_interval, timedelta(seconds=30))
        self.assertEqual(config._drain_ongoing_call_timeout,
                         timedelta(seconds=60))
        self.assertEqual(config._drain_rebalanced_actors, True)
        self.assertEqual(config._entities, [])
示例#8
0
    def test_entities_update(self):
        # Clean up managers
        ActorRuntime._actor_managers = {}
        ActorRuntime.set_actor_config(ActorRuntimeConfig())

        config = ActorRuntime.get_actor_config()
        with self.assertRaises(ValueError):
            config._entities.index(FakeSimpleActor.__name__)

        _run(ActorRuntime.register_actor(FakeSimpleActor))
        config = ActorRuntime.get_actor_config()
        self.assertTrue(config._entities.index(FakeSimpleActor.__name__) >= 0)
    def test_default_config_with_reentrancy(self):
        reentrancyConfig = ActorReentrancyConfig(enabled=True)
        config = ActorRuntimeConfig(reentrancy=reentrancyConfig)

        self.assertEqual(config._actor_idle_timeout, timedelta(seconds=3600))
        self.assertEqual(config._actor_scan_interval, timedelta(seconds=30))
        self.assertEqual(config._drain_ongoing_call_timeout, timedelta(seconds=60))
        self.assertEqual(config._drain_rebalanced_actors, True)
        self.assertEqual(config._reentrancy, reentrancyConfig)
        self.assertEqual(config._entities, [])
        self.assertEqual(config.as_dict()['reentrancy'], reentrancyConfig.as_dict())
        self.assertEqual(config.as_dict()['reentrancy']['enabled'], True)
        self.assertEqual(config.as_dict()['reentrancy']['maxStackDepth'], 32)
        self.assertNotIn('remindersStoragePartitions', config.as_dict().keys())
示例#10
0
    def test_header_passthrough_reentrancy_disabled(self):
        config = ActorRuntimeConfig(reentrancy=None)
        ActorRuntime.set_actor_config(config)
        _run(ActorRuntime.register_actor(FakeReentrantActor))
        _run(ActorRuntime.register_actor(FakeSlowReentrantActor))

        request_body = self._serializer.serialize({
            "message": "Normal",
        })

        async def expected_return_value(*args, **kwargs):
            return ["expected", "None"]

        reentrancy_id = "f6319f23-dc0a-4880-90d9-87b23c19c20a"
        actor = FakeSlowReentrantActor.__name__
        method = 'ReentrantMethod'

        with mock.patch('dapr.clients.http.client.DaprHttpClient.send_bytes'
                        ) as mocked:

            mocked.side_effect = expected_return_value
            _run(
                ActorRuntime.dispatch(FakeReentrantActor.__name__,
                                      'test-id',
                                      'ReentrantMethodWithPassthrough',
                                      request_body,
                                      reentrancy_id=reentrancy_id))

            mocked.assert_called_with(
                method="POST",
                url=
                f'http://127.0.0.1:3500/v1.0/actors/{actor}/test-id/method/{method}',
                data=None,
                headers={})

        _run(ActorRuntime.deactivate(FakeReentrantActor.__name__, 'test-id'))

        # Ensure test-id is deactivated
        with self.assertRaises(ValueError):
            _run(
                ActorRuntime.deactivate(FakeReentrantActor.__name__,
                                        'test-id'))
示例#11
0
    def test_actor_config(self):
        config = ActorRuntime.get_actor_config()

        self.assertTrue(config._drain_rebalanced_actors)
        self.assertEqual(timedelta(hours=1), config._actor_idle_timeout)
        self.assertEqual(timedelta(seconds=30), config._actor_scan_interval)
        self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout)
        self.assertEqual(2, len(config._entities))

        # apply new config
        new_config = ActorRuntimeConfig(
            timedelta(hours=3), timedelta(seconds=10), timedelta(minutes=1), False)

        ActorRuntime.set_actor_config(new_config)
        config = ActorRuntime.get_actor_config()

        self.assertFalse(config._drain_rebalanced_actors)
        self.assertEqual(timedelta(hours=3), config._actor_idle_timeout)
        self.assertEqual(timedelta(seconds=10), config._actor_scan_interval)
        self.assertEqual(timedelta(minutes=1), config._drain_ongoing_call_timeout)
        self.assertEqual(2, len(config._entities))
示例#12
0
class ActorRuntime:
    """The class that creates instances of :class:`Actor` and
    activates and deactivates :class:`Actor`.
    """

    _actor_config = ActorRuntimeConfig()

    _actor_managers: Dict[str, ActorManager] = {}
    _actor_managers_lock = asyncio.Lock()

    @classmethod
    async def register_actor(
        cls,
        actor: Type[Actor],
        message_serializer: Serializer = DefaultJSONSerializer(),
        state_serializer: Serializer = DefaultJSONSerializer(),
        http_timeout_seconds: int = settings.DAPR_HTTP_TIMEOUT_SECONDS
    ) -> None:
        """Registers an :class:`Actor` object with the runtime.

        Args:
            actor (:class:`Actor`): Actor implementation.
            message_serializer (:class:`Serializer`): A serializer that serializes message
                between actors.
            state_serializer (:class:`Serializer`): Serializer that serializes state values.
            http_timeout_seconds (:int:): a configurable timeout value
        """
        type_info = ActorTypeInformation.create(actor)
        # TODO: We will allow to use gRPC client later.
        actor_client = DaprActorHttpClient(message_serializer,
                                           timeout=http_timeout_seconds)
        ctx = ActorRuntimeContext(type_info, message_serializer,
                                  state_serializer, actor_client)

        # Create an ActorManager, override existing entry if registered again.
        async with cls._actor_managers_lock:
            cls._actor_managers[type_info.type_name] = ActorManager(ctx)
            cls._actor_config.update_entities(
                ActorRuntime.get_registered_actor_types())

    @classmethod
    def get_registered_actor_types(cls) -> List[str]:
        """Gets registered actor types."""
        return [actor_type for actor_type in cls._actor_managers.keys()]

    @classmethod
    async def deactivate(cls, actor_type_name: str, actor_id: str) -> None:
        """Deactivates an actor for an actor type with given actor id.

        Args:
            actor_type_name (str): the name of actor type.
            actor_id (str): the actor id.

        Raises:
            ValueError: `actor_type_name` actor type is not registered.
        """
        manager = await cls._get_actor_manager(actor_type_name)
        if not manager:
            raise ValueError(f'{actor_type_name} is not registered.')
        await manager.deactivate_actor(ActorId(actor_id))

    @classmethod
    async def dispatch(cls,
                       actor_type_name: str,
                       actor_id: str,
                       actor_method_name: str,
                       request_body: bytes,
                       reentrancy_id: Optional[str] = None) -> bytes:
        """Dispatches actor method defined in actor_type.

        Args:
            actor_type_name (str): the name of actor type.
            actor_id (str): Actor ID.
            actor_method_name (str): the method name that is dispatched.
            request_body (bytes): the body of request that is passed to actor method arguments.
            reentrancy_id (str): reentrancy ID obtained from the dapr_reentrancy_id header
                if present.

        Returns:
            bytes: serialized response data.

        Raises:
            ValueError: `actor_type_name` actor type is not registered.
        """
        if cls._actor_config._reentrancy and cls._actor_config._reentrancy._enabled:
            reentrancy_ctx.set(reentrancy_id)
        manager = await cls._get_actor_manager(actor_type_name)
        if not manager:
            raise ValueError(f'{actor_type_name} is not registered.')
        return await manager.dispatch(ActorId(actor_id), actor_method_name,
                                      request_body)

    @classmethod
    async def fire_reminder(cls, actor_type_name: str, actor_id: str,
                            name: str, state: bytes) -> None:
        """Fires a reminder for the Actor.

        Args:
            actor_type_name (str): the name of actor type.
            actor_id (str): Actor ID.
            name (str): the name of reminder.
            state (bytes): the body of request that is passed to reminder callback.

        Raises:
            ValueError: `actor_type_name` actor type is not registered.
        """

        manager = await cls._get_actor_manager(actor_type_name)
        if not manager:
            raise ValueError(f'{actor_type_name} is not registered.')
        await manager.fire_reminder(ActorId(actor_id), name, state)

    @classmethod
    async def fire_timer(cls, actor_type_name: str, actor_id: str, name: str,
                         state: bytes) -> None:
        """Fires a timer for the Actor.

        Args:
            actor_type_name (str): the name of actor type.
            actor_id (str): Actor ID.
            name (str): the timer's name.
            state (bytes): the timer's trigger body.

        Raises:
            ValueError: `actor_type_name` actor type is not registered.
        """
        manager = await cls._get_actor_manager(actor_type_name)
        if not manager:
            raise ValueError(f'{actor_type_name} is not registered.')
        await manager.fire_timer(ActorId(actor_id), name, state)

    @classmethod
    def set_actor_config(cls, config: ActorRuntimeConfig) -> None:
        """Sets actor runtime config

        Args:
            config (:class:`ActorRuntimeConfig`): The config to set up actor runtime
        """
        cls._actor_config = config
        cls._actor_config.update_entities(
            ActorRuntime.get_registered_actor_types())

    @classmethod
    def get_actor_config(cls) -> ActorRuntimeConfig:
        """Gets :class:`ActorRuntimeConfig`."""
        return cls._actor_config

    @classmethod
    async def _get_actor_manager(
            cls, actor_type_name: str) -> Optional[ActorManager]:
        """Gets :class:`ActorManager` object for actor_type_name.

        Args:
            actor_type_name (str): the type name of actor.

        Returns:
            :class:`ActorManager`: an actor manager object for actor_type_name actor.
        """
        async with cls._actor_managers_lock:
            return cls._actor_managers.get(actor_type_name)