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())
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))
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))
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, [])
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())
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'))
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))
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)