def create_tasks( loop: asyncio.AbstractEventLoop, lifecycle: Optional[Callable] = None, registry: Optional[registries.BaseRegistry] = None, standalone: bool = False, priority: int = 0, peering_name: str = peering.PEERING_DEFAULT_NAME, namespace: Optional[str] = None, ): """ Create all the tasks needed to run the operator, but do not spawn/start them. The tasks are properly inter-connected depending on the runtime specification. They can be injected into any event loop as needed. """ # The freezer and the registry are scoped to this whole task-set, to sync them all. lifecycle = lifecycle if lifecycle is not None else lifecycles.get_default_lifecycle( ) registry = registry if registry is not None else registries.get_default_registry( ) freeze = asyncio.Event() tasks = [] # Monitor the peers, unless explicitly disabled. ourselves: Optional[peering.Peer] = peering.Peer.detect( id=peering.detect_own_id(), priority=priority, standalone=standalone, namespace=namespace, name=peering_name, ) if ourselves: tasks.extend([ loop.create_task(peering.peers_keepalive(ourselves=ourselves)), loop.create_task( watcher(namespace=namespace, resource=ourselves.resource, handler=functools.partial( peering.peers_handler, ourselves=ourselves, freeze=freeze))), # freeze is set/cleared ]) # Resource event handling, only once for every known resource (de-duplicated). for resource in registry.resources: tasks.extend([ loop.create_task( watcher(namespace=namespace, resource=resource, handler=functools.partial( handling.custom_object_handler, lifecycle=lifecycle, registry=registry, resource=resource, freeze=freeze))), # freeze is only checked ]) return tasks
async def spawn_tasks( *, lifecycle: Optional[lifecycles.LifeCycleFn] = None, registry: Optional[registries.OperatorRegistry] = None, memories: Optional[containers.ResourceMemories] = None, standalone: bool = False, priority: int = 0, peering_name: Optional[str] = None, liveness_endpoint: Optional[str] = None, namespace: Optional[str] = None, stop_flag: Optional[primitives.Flag] = None, ready_flag: Optional[primitives.Flag] = None, vault: Optional[credentials.Vault] = None, ) -> Tasks: """ Spawn all the tasks needed to run the operator. The tasks are properly inter-connected with the synchronisation primitives. """ loop = asyncio.get_running_loop() # The freezer and the registry are scoped to this whole task-set, to sync them all. lifecycle = lifecycle if lifecycle is not None else lifecycles.get_default_lifecycle() registry = registry if registry is not None else registries.get_default_registry() memories = memories if memories is not None else containers.ResourceMemories() vault = vault if vault is not None else global_vault vault = vault if vault is not None else credentials.Vault() event_queue: posting.K8sEventQueue = asyncio.Queue() freeze_mode: primitives.Toggle = primitives.Toggle() signal_flag: asyncio_Future = asyncio.Future() ready_flag = ready_flag if ready_flag is not None else asyncio.Event() tasks: MutableSequence[asyncio_Task] = [] # Global credentials store for this operator, also for CRD-reading & peering mode detection. auth.vault_var.set(vault) # Few common background forever-running infrastructural tasks (irregular root tasks). tasks.extend([ loop.create_task(_stop_flag_checker( signal_flag=signal_flag, stop_flag=stop_flag, )), loop.create_task(_startup_cleanup_activities( root_tasks=tasks, # used as a "live" view, populated later. ready_flag=ready_flag, registry=registry, vault=vault, # to purge & finalize the caches in the end. )), ]) # Keeping the credentials fresh and valid via the authentication handlers on demand. tasks.extend([ loop.create_task(_root_task_checker( name="credentials retriever", ready_flag=ready_flag, coro=activities.authenticator( registry=registry, vault=vault))), ]) # K8s-event posting. Events are queued in-memory and posted in the background. # NB: currently, it is a global task, but can be made per-resource or per-object. tasks.extend([ loop.create_task(_root_task_checker( name="poster of events", ready_flag=ready_flag, coro=posting.poster( event_queue=event_queue))), ]) # Liveness probing -- so that Kubernetes would know that the operator is alive. if liveness_endpoint: tasks.extend([ loop.create_task(_root_task_checker( name="health reporter", ready_flag=ready_flag, coro=probing.health_reporter( registry=registry, endpoint=liveness_endpoint))), ]) # Monitor the peers, unless explicitly disabled. ourselves: Optional[peering.Peer] = await peering.Peer.detect( id=peering.detect_own_id(), priority=priority, standalone=standalone, namespace=namespace, name=peering_name, ) if ourselves: tasks.extend([ loop.create_task(peering.peers_keepalive( ourselves=ourselves)), loop.create_task(_root_task_checker( name="watcher of peering", ready_flag=ready_flag, coro=queueing.watcher( namespace=namespace, resource=ourselves.resource, processor=functools.partial(peering.process_peering_event, ourselves=ourselves, freeze_mode=freeze_mode)))), ]) # Resource event handling, only once for every known resource (de-duplicated). for resource in registry.resources: tasks.extend([ loop.create_task(_root_task_checker( name=f"watcher of {resource.name}", ready_flag=ready_flag, coro=queueing.watcher( namespace=namespace, resource=resource, freeze_mode=freeze_mode, processor=functools.partial(processing.process_resource_event, lifecycle=lifecycle, registry=registry, memories=memories, resource=resource, event_queue=event_queue)))), ]) # On Ctrl+C or pod termination, cancel all tasks gracefully. if threading.current_thread() is threading.main_thread(): # Handle NotImplementedError when ran on Windows since asyncio only supports Unix signals try: loop.add_signal_handler(signal.SIGINT, signal_flag.set_result, signal.SIGINT) loop.add_signal_handler(signal.SIGTERM, signal_flag.set_result, signal.SIGTERM) except NotImplementedError: logger.warning("OS signals are ignored: can't add signal handler in Windows.") else: logger.warning("OS signals are ignored: running not in the main thread.") return tasks
async def spawn_tasks( lifecycle: Optional[Callable] = None, registry: Optional[registries.GlobalRegistry] = None, standalone: bool = False, priority: int = 0, peering_name: Optional[str] = None, namespace: Optional[str] = None, ) -> Collection[asyncio.Task]: """ Spawn all the tasks needed to run the operator. The tasks are properly inter-connected with the synchronisation primitives. """ loop = asyncio.get_running_loop() # The freezer and the registry are scoped to this whole task-set, to sync them all. lifecycle = lifecycle if lifecycle is not None else lifecycles.get_default_lifecycle( ) registry = registry if registry is not None else registries.get_default_registry( ) event_queue = asyncio.Queue(loop=loop) freeze_flag = asyncio.Event(loop=loop) should_stop = asyncio.Event(loop=loop) tasks = [] # A top-level task for external stopping by setting a stop-flag. Once set, # this task will exit, and thus all other top-level tasks will be cancelled. tasks.extend([ loop.create_task(_stop_flag_checker(should_stop)), ]) # K8s-event posting. Events are queued in-memory and posted in the background. # NB: currently, it is a global task, but can be made per-resource or per-object. tasks.extend([ loop.create_task(posting.poster(event_queue=event_queue)), ]) # Monitor the peers, unless explicitly disabled. ourselves: Optional[peering.Peer] = peering.Peer.detect( id=peering.detect_own_id(), priority=priority, standalone=standalone, namespace=namespace, name=peering_name, ) if ourselves: tasks.extend([ loop.create_task(peering.peers_keepalive(ourselves=ourselves)), loop.create_task( queueing.watcher( namespace=namespace, resource=ourselves.resource, handler=functools.partial( peering.peers_handler, ourselves=ourselves, freeze=freeze_flag))), # freeze is set/cleared ]) # Resource event handling, only once for every known resource (de-duplicated). for resource in registry.resources: tasks.extend([ loop.create_task( queueing.watcher( namespace=namespace, resource=resource, handler=functools.partial( handling.custom_object_handler, lifecycle=lifecycle, registry=registry, resource=resource, event_queue=event_queue, freeze=freeze_flag))), # freeze is only checked ]) # On Ctrl+C or pod termination, cancel all tasks gracefully. if threading.current_thread() is threading.main_thread(): loop.add_signal_handler(signal.SIGINT, should_stop.set) loop.add_signal_handler(signal.SIGTERM, should_stop.set) else: logger.warning( "OS signals are ignored: running not in the main thread.") return tasks