mirror of
https://github.com/ansible/awx.git
synced 2026-10-29 22:03:20 -02:30
AAP-90155 task_manager.py scheduler loop blocked by InvalidToken when decrypting corrupt start_args of a single job (#16633)
* Catch and handle InvalidToken error * test clean up
This commit is contained in:
@@ -12,6 +12,8 @@ import signal
|
|||||||
|
|
||||||
import redis
|
import redis
|
||||||
|
|
||||||
|
from cryptography.fernet import InvalidToken
|
||||||
|
|
||||||
# Django
|
# Django
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.utils.translation import gettext_lazy as _, gettext_noop
|
from django.utils.translation import gettext_lazy as _, gettext_noop
|
||||||
@@ -271,7 +273,10 @@ class WorkflowManager(TaskBase):
|
|||||||
)
|
)
|
||||||
if can_start:
|
if can_start:
|
||||||
if workflow_job.start_args:
|
if workflow_job.start_args:
|
||||||
|
try:
|
||||||
start_args = json.loads(decrypt_field(workflow_job, 'start_args'))
|
start_args = json.loads(decrypt_field(workflow_job, 'start_args'))
|
||||||
|
except (ValueError, InvalidToken):
|
||||||
|
start_args = {}
|
||||||
else:
|
else:
|
||||||
start_args = {}
|
start_args = {}
|
||||||
can_start = job.signal_start(**start_args)
|
can_start = job.signal_start(**start_args)
|
||||||
@@ -362,7 +367,7 @@ class DependencyManager(TaskBase):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
start_args = json.loads(decrypt_field(task, field_name="start_args"))
|
start_args = json.loads(decrypt_field(task, field_name="start_args"))
|
||||||
except ValueError:
|
except (ValueError, InvalidToken):
|
||||||
start_args = dict()
|
start_args = dict()
|
||||||
# generator for update-on-launch inventory sources related to this task
|
# generator for update-on-launch inventory sources related to this task
|
||||||
for inventory_source in self.all_inventory_sources.get(task.inventory_id, []):
|
for inventory_source in self.all_inventory_sources.get(task.inventory_id, []):
|
||||||
|
|||||||
@@ -451,6 +451,58 @@ def test_job_dependency_with_already_updated(controlplane_instance_group, job_te
|
|||||||
TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance)
|
TaskManager.start_task.assert_called_once_with(j, controlplane_instance_group, instance)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_undecryptable_start_args_does_not_freeze_scheduler(controlplane_instance_group, job_template_factory):
|
||||||
|
"""
|
||||||
|
A single pending job whose start_args cannot be decrypted with the
|
||||||
|
current SECRET_KEY (e.g. restored from a different environment) must not
|
||||||
|
crash DependencyManager and must not prevent every other pending job in
|
||||||
|
the same batch from being marked dependencies_processed.
|
||||||
|
"""
|
||||||
|
objects = job_template_factory('jt', organization='org1', project='proj', inventory='inv', credential='cred')
|
||||||
|
|
||||||
|
bad_job = create_job(objects.job_template, dependencies_processed=False)
|
||||||
|
bad_job.start_args = json.dumps({'inventory_sources_already_updated': []})
|
||||||
|
bad_job.start_args = encrypt_field(bad_job, field_name='start_args', secret_key='a-different-environments-secret-key')
|
||||||
|
bad_job.save(update_fields=['start_args'])
|
||||||
|
|
||||||
|
good_job = create_job(objects.job_template, dependencies_processed=False)
|
||||||
|
|
||||||
|
dm = DependencyManager()
|
||||||
|
dm.schedule() # must not raise cryptography.fernet.InvalidToken
|
||||||
|
|
||||||
|
bad_job.refresh_from_db()
|
||||||
|
good_job.refresh_from_db()
|
||||||
|
assert bad_job.dependencies_processed is True
|
||||||
|
assert good_job.dependencies_processed is True
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_workflow_job_undecryptable_start_args_does_not_block_spawn(inventory, project, controlplane_instance_group):
|
||||||
|
"""
|
||||||
|
A running workflow job whose start_args cannot be decrypted with the
|
||||||
|
current SECRET_KEY must not crash WorkflowManager and must not prevent
|
||||||
|
its ready-to-run node from being spawned.
|
||||||
|
"""
|
||||||
|
jt = JobTemplate.objects.create(inventory=inventory, project=project, playbook='helloworld.yml')
|
||||||
|
wfjt = WorkflowJobTemplate.objects.create(name='wfjt-aap-90155')
|
||||||
|
wfjt.workflow_nodes.create(unified_job_template=jt)
|
||||||
|
|
||||||
|
wj = wfjt.create_unified_job()
|
||||||
|
wj.signal_start()
|
||||||
|
TaskManager().schedule() # transitions wj from pending to running
|
||||||
|
wj.refresh_from_db()
|
||||||
|
assert wj.status == 'running'
|
||||||
|
|
||||||
|
wj.start_args = json.dumps({})
|
||||||
|
wj.start_args = encrypt_field(wj, field_name='start_args', secret_key='a-different-environments-secret-key')
|
||||||
|
wj.save(update_fields=['start_args'])
|
||||||
|
|
||||||
|
WorkflowManager().schedule() # must not raise cryptography.fernet.InvalidToken
|
||||||
|
|
||||||
|
assert jt.jobs.count() == 1 # node's job was still spawned
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_shared_dependencies_launch(controlplane_instance_group, job_template_factory, mocker, inventory_source_factory):
|
def test_shared_dependencies_launch(controlplane_instance_group, job_template_factory, mocker, inventory_source_factory):
|
||||||
instance = controlplane_instance_group.instances.all()[0]
|
instance = controlplane_instance_group.instances.all()[0]
|
||||||
|
|||||||
Reference in New Issue
Block a user