From 03c54534757deb696de6590f21f1715822fc437c Mon Sep 17 00:00:00 2001 From: Jitka Halova Date: Tue, 30 Jun 2026 16:54:14 +0200 Subject: [PATCH] Add domain support to TaskSchedule closes #7829 Assisted By: Claude Opus 4.6 --- CHANGES/plugin_api/7829.feature | 1 + ...domain_alter_taskschedule_name_and_more.py | 29 +++++++++++ pulpcore/app/models/task.py | 4 +- pulpcore/tasking/_util.py | 3 +- pulpcore/tests/functional/api/test_workers.py | 50 +++++++++++++++++++ 5 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 CHANGES/plugin_api/7829.feature create mode 100644 pulpcore/app/migrations/0153_taskschedule_pulp_domain_alter_taskschedule_name_and_more.py diff --git a/CHANGES/plugin_api/7829.feature b/CHANGES/plugin_api/7829.feature new file mode 100644 index 00000000000..ed7b6483486 --- /dev/null +++ b/CHANGES/plugin_api/7829.feature @@ -0,0 +1 @@ +Added `pulp_domain` to `TaskSchedule` so scheduled tasks dispatch in the correct domain. Name uniqueness is now per domain. diff --git a/pulpcore/app/migrations/0153_taskschedule_pulp_domain_alter_taskschedule_name_and_more.py b/pulpcore/app/migrations/0153_taskschedule_pulp_domain_alter_taskschedule_name_and_more.py new file mode 100644 index 00000000000..4052435ae15 --- /dev/null +++ b/pulpcore/app/migrations/0153_taskschedule_pulp_domain_alter_taskschedule_name_and_more.py @@ -0,0 +1,29 @@ +# Generated by Django 5.2.13 on 2026-06-30 14:28 + +import django.db.models.deletion +import pulpcore.app.util +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0152_alter_repositoryversion_content_ids'), + ] + + operations = [ + migrations.AddField( + model_name='taskschedule', + name='pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.CASCADE, to='core.domain'), + ), + migrations.AlterField( + model_name='taskschedule', + name='name', + field=models.TextField(), + ), + migrations.AlterUniqueTogether( + name='taskschedule', + unique_together={('name', 'pulp_domain')}, + ), + ] diff --git a/pulpcore/app/models/task.py b/pulpcore/app/models/task.py index a24d1870163..f6ab7712e19 100644 --- a/pulpcore/app/models/task.py +++ b/pulpcore/app/models/task.py @@ -439,15 +439,17 @@ class CreatedResource(GenericRelationModel): class TaskSchedule(BaseModel): - name = models.TextField(unique=True, null=False) + name = models.TextField(null=False) next_dispatch = models.DateTimeField(default=timezone.now, null=True) dispatch_interval = models.DurationField(null=True) task_name = models.TextField() task_args = EncryptedJSONField(default=list) task_kwargs = EncryptedJSONField(default=dict) last_task = models.ForeignKey(Task, null=True, on_delete=models.SET_NULL) + pulp_domain = models.ForeignKey("Domain", default=get_domain_pk, on_delete=models.CASCADE) class Meta: + unique_together = ("name", "pulp_domain") permissions = [ ("manage_roles_taskschedule", "Can manage role assignments on task schedules"), ] diff --git a/pulpcore/tasking/_util.py b/pulpcore/tasking/_util.py index abfd706cdb9..24f664a8f8e 100644 --- a/pulpcore/tasking/_util.py +++ b/pulpcore/tasking/_util.py @@ -17,6 +17,7 @@ from django_guid import set_guid from django_guid.utils import generate_guid +from pulpcore.app.contexts import with_domain from pulpcore.app.models import Artifact, Content, ProfileArtifact, Task, TaskSchedule from pulpcore.app.util import ( configure_analytics, @@ -298,7 +299,7 @@ def dispatch_scheduled_tasks(): # Do not schedule in the past task_schedule.next_dispatch += task_schedule.dispatch_interval set_guid(generate_guid()) - with transaction.atomic(): + with with_domain(task_schedule.pulp_domain), transaction.atomic(): task_schedule.last_task = dispatch( task_schedule.task_name, args=task_schedule.task_args, diff --git a/pulpcore/tests/functional/api/test_workers.py b/pulpcore/tests/functional/api/test_workers.py index 5a94ef1e731..a0c120221c1 100644 --- a/pulpcore/tests/functional/api/test_workers.py +++ b/pulpcore/tests/functional/api/test_workers.py @@ -7,6 +7,8 @@ import pytest +from pulpcore.app import settings + _DYNAMIC_WORKER_ATTRS = ("last_heartbeat", "current_task") """Worker attributes that are dynamically set by Pulp, not set by a user.""" @@ -124,3 +126,51 @@ def test_task_schedule(task_schedule, pulpcore_bindings): else: assert ts.dispatch_interval is not None assert ts.next_dispatch is not None + + +@pytest.mark.parallel +@pytest.mark.skipif(not settings.DOMAIN_ENABLED, reason="Domains not enabled") +def test_task_schedule_domain(domain_factory, pulpcore_bindings): + """Test that a scheduled task dispatches in the TaskSchedule's domain, not the default.""" + domain = domain_factory() + domain_name = domain.name + name = str(uuid.uuid4()) + task_name = "pulpcore.app.tasks.test.dummy_task" + + schedule_commands = ( + "from django.utils.timezone import now;" + "from datetime import timedelta;" + "from pulpcore.app.models import TaskSchedule, Domain;" + f"domain = Domain.objects.get(name='{domain_name}');" + "TaskSchedule(" + f" name='{name}', task_name='{task_name}'," + " next_dispatch=now() + timedelta(seconds=5)," + " pulp_domain=domain" + ").save();" + ) + process = subprocess.run(["pulpcore-manager", "shell", "-c", schedule_commands]) + assert process.returncode == 0 + + try: + result = None + for i in range(20): + sleep(1) + result = pulpcore_bindings.TaskSchedulesApi.list(name=name, pulp_domain=domain_name) + assert result.count == 1 + if result.results[0].last_task is not None: + break + + assert result is not None and result.results[0].last_task is not None + tasks = pulpcore_bindings.TasksApi.list(name=task_name, pulp_domain=domain_name) + assert tasks.count == 1 + assert tasks.results[0].state == "completed" + finally: + subprocess.run( + [ + "pulpcore-manager", + "shell", + "-c", + f"from pulpcore.app.models import TaskSchedule;" + f"TaskSchedule.objects.filter(name='{name}').delete();", + ] + )