mirror of
https://github.com/ansible/awx.git
synced 2026-02-26 07:26:03 -03:30
Merge pull request #795 from ryanpetrello/move-deprecated-stdout
move legacy UnifiedJob stdout data to a separate unmanaged model
This commit is contained in:
@@ -2663,12 +2663,6 @@ class InventoryUpdateList(ListAPIView):
|
|||||||
model = InventoryUpdate
|
model = InventoryUpdate
|
||||||
serializer_class = InventoryUpdateListSerializer
|
serializer_class = InventoryUpdateListSerializer
|
||||||
|
|
||||||
def get_queryset(self):
|
|
||||||
qs = super(InventoryUpdateList, self).get_queryset()
|
|
||||||
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
|
|
||||||
qs = qs.defer('result_stdout_text')
|
|
||||||
return qs
|
|
||||||
|
|
||||||
|
|
||||||
class InventoryUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
|
class InventoryUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
|
||||||
|
|
||||||
|
|||||||
@@ -140,14 +140,7 @@ def get_user_capabilities(user, instance, **kwargs):
|
|||||||
convenient for the user interface to consume and hide or show various
|
convenient for the user interface to consume and hide or show various
|
||||||
actions in the interface.
|
actions in the interface.
|
||||||
'''
|
'''
|
||||||
cls = instance.__class__
|
access_class = access_registry[instance.__class__]
|
||||||
# When `.defer()` is used w/ the Django ORM, the result is a subclass of
|
|
||||||
# the original that represents e.g.,
|
|
||||||
# awx.main.models.ad_hoc_commands.AdHocCommand_Deferred_result_stdout_text
|
|
||||||
# We want to do the access registry lookup keyed on the base class name.
|
|
||||||
if getattr(cls, '_deferred', False):
|
|
||||||
cls = instance.__class__.__bases__[0]
|
|
||||||
access_class = access_registry[cls]
|
|
||||||
return access_class(user).get_user_capabilities(instance, **kwargs)
|
return access_class(user).get_user_capabilities(instance, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@@ -2075,8 +2068,7 @@ class UnifiedJobAccess(BaseAccess):
|
|||||||
Q(job__inventory__organization__in=org_auditor_qs) |
|
Q(job__inventory__organization__in=org_auditor_qs) |
|
||||||
Q(job__project__organization__in=org_auditor_qs)
|
Q(job__project__organization__in=org_auditor_qs)
|
||||||
)
|
)
|
||||||
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
|
return qs
|
||||||
return qs.defer('result_stdout_text')
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleAccess(BaseAccess):
|
class ScheduleAccess(BaseAccess):
|
||||||
|
|||||||
44
awx/main/migrations/0013_move_deprecated_stdout.py
Normal file
44
awx/main/migrations/0013_move_deprecated_stdout.py
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.7 on 2017-12-12 18:56
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0012_non_blank_workflow'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='unifiedjob',
|
||||||
|
name='result_stdout_text',
|
||||||
|
field=models.TextField(editable=False, null=True),
|
||||||
|
),
|
||||||
|
# Using SeparateDatabaseAndState here allows us to update the migration
|
||||||
|
# state so that Django thinks the UnifiedJob.result_stdout_text field
|
||||||
|
# is gone _without_ actually deleting the underlying column/data
|
||||||
|
migrations.SeparateDatabaseAndState(state_operations=[
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='unifiedjob',
|
||||||
|
name='result_stdout_text',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
# On other side of the equation, this migration introduces a new model
|
||||||
|
# which is *unmanaged* (meaning, a new table is not created for it);
|
||||||
|
# instead, this sort of "virtual" model is used to maintain an ORM
|
||||||
|
# reference to the actual `main_unifiedjob.result_stdout_text` column
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UnifiedJobDeprecatedStdout',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('result_stdout_text', models.TextField(editable=False, null=True))
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'db_table': 'main_unifiedjob',
|
||||||
|
'managed': False,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -145,17 +145,3 @@ activity_stream_registrar.connect(WorkflowJob)
|
|||||||
|
|
||||||
# prevent API filtering on certain Django-supplied sensitive fields
|
# prevent API filtering on certain Django-supplied sensitive fields
|
||||||
prevent_search(User._meta.get_field('password'))
|
prevent_search(User._meta.get_field('password'))
|
||||||
|
|
||||||
|
|
||||||
# Always, always, always defer result_stdout_text for polymorphic UnifiedJob rows
|
|
||||||
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
|
|
||||||
def defer_stdout(f):
|
|
||||||
def _wrapped(*args, **kwargs):
|
|
||||||
objs = f(*args, **kwargs)
|
|
||||||
objs.query.deferred_loading[0].add('result_stdout_text')
|
|
||||||
return objs
|
|
||||||
return _wrapped
|
|
||||||
|
|
||||||
|
|
||||||
for cls in UnifiedJob.__subclasses__():
|
|
||||||
cls.base_objects.filter = defer_stdout(cls.base_objects.filter)
|
|
||||||
|
|||||||
@@ -492,6 +492,18 @@ class UnifiedJobTypeStringMixin(object):
|
|||||||
return UnifiedJobTypeStringMixin._camel_to_underscore(self.__class__.__name__)
|
return UnifiedJobTypeStringMixin._camel_to_underscore(self.__class__.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UnifiedJobDeprecatedStdout(models.Model):
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
managed = False
|
||||||
|
db_table = 'main_unifiedjob'
|
||||||
|
|
||||||
|
result_stdout_text = models.TextField(
|
||||||
|
null=True,
|
||||||
|
editable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin, TaskManagerUnifiedJobMixin):
|
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin, TaskManagerUnifiedJobMixin):
|
||||||
'''
|
'''
|
||||||
Concrete base class for unified job run by the task engine.
|
Concrete base class for unified job run by the task engine.
|
||||||
@@ -620,11 +632,6 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
default='',
|
default='',
|
||||||
editable=False,
|
editable=False,
|
||||||
))
|
))
|
||||||
result_stdout_text = models.TextField(
|
|
||||||
blank=True,
|
|
||||||
default='',
|
|
||||||
editable=False,
|
|
||||||
)
|
|
||||||
result_stdout_file = models.TextField( # FilePathfield?
|
result_stdout_file = models.TextField( # FilePathfield?
|
||||||
blank=True,
|
blank=True,
|
||||||
default='',
|
default='',
|
||||||
@@ -882,6 +889,19 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
|
|||||||
config.credentials.add(*job_creds)
|
config.credentials.add(*job_creds)
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@property
|
||||||
|
def result_stdout_text(self):
|
||||||
|
related = UnifiedJobDeprecatedStdout.objects.get(pk=self.pk)
|
||||||
|
return related.result_stdout_text or ''
|
||||||
|
|
||||||
|
@result_stdout_text.setter
|
||||||
|
def result_stdout_text(self, value):
|
||||||
|
# TODO: remove this method once all stdout is based on jobevents
|
||||||
|
# (because it won't be used for writing anymore)
|
||||||
|
related = UnifiedJobDeprecatedStdout.objects.get(pk=self.pk)
|
||||||
|
related.result_stdout_text = value
|
||||||
|
related.save()
|
||||||
|
|
||||||
def result_stdout_raw_handle(self, attempt=0):
|
def result_stdout_raw_handle(self, attempt=0):
|
||||||
"""Return a file-like object containing the standard out of the
|
"""Return a file-like object containing the standard out of the
|
||||||
job's result.
|
job's result.
|
||||||
|
|||||||
@@ -516,6 +516,10 @@ class BaseTask(LogErrorsTask):
|
|||||||
update_fields.append(field)
|
update_fields.append(field)
|
||||||
if field == 'status':
|
if field == 'status':
|
||||||
update_fields.append('failed')
|
update_fields.append('failed')
|
||||||
|
if 'result_stdout_text' in update_fields:
|
||||||
|
# result_stdout_text is now deprecated, and is no longer
|
||||||
|
# an actual Django field (it's a property)
|
||||||
|
update_fields.remove('result_stdout_text')
|
||||||
instance.save(update_fields=update_fields)
|
instance.save(update_fields=update_fields)
|
||||||
return instance
|
return instance
|
||||||
except DatabaseError as e:
|
except DatabaseError as e:
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
from django.db import connection
|
||||||
|
from django.db.models.signals import post_migrate
|
||||||
|
from django.apps import apps
|
||||||
|
|
||||||
|
|
||||||
|
def app_post_migration(sender, app_config, **kwargs):
|
||||||
|
# our usage of pytest.django+sqlite doesn't actually run real migrations,
|
||||||
|
# so we've got to make sure the deprecated
|
||||||
|
# `main_unifiedjob.result_stdout_text` column actually exists
|
||||||
|
cur = connection.cursor()
|
||||||
|
cols = cur.execute(
|
||||||
|
'SELECT sql FROM sqlite_master WHERE tbl_name="main_unifiedjob";'
|
||||||
|
).fetchone()[0]
|
||||||
|
if 'result_stdout_text' not in cols:
|
||||||
|
cur.execute(
|
||||||
|
'ALTER TABLE main_unifiedjob ADD COLUMN result_stdout_text TEXT'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
post_migrate.connect(app_post_migration, sender=apps.get_app_config('main'))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,9 @@ TEST_STDOUTS.append({
|
|||||||
def test_cases(project):
|
def test_cases(project):
|
||||||
ret = []
|
ret = []
|
||||||
for e in TEST_STDOUTS:
|
for e in TEST_STDOUTS:
|
||||||
e['project'] = ProjectUpdate(project=project)
|
pu = ProjectUpdate(project=project)
|
||||||
|
pu.save()
|
||||||
|
e['project'] = pu
|
||||||
e['project'].result_stdout_text = e['text']
|
e['project'].result_stdout_text = e['text']
|
||||||
e['project'].save()
|
e['project'].save()
|
||||||
ret.append(e)
|
ret.append(e)
|
||||||
|
|||||||
@@ -8,15 +8,15 @@ from StringIO import StringIO
|
|||||||
from django.utils.timezone import now
|
from django.utils.timezone import now
|
||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.models import UnifiedJob
|
from awx.main import models
|
||||||
|
|
||||||
|
|
||||||
# stdout file present
|
# stdout file present
|
||||||
@mock.patch('os.path.exists', return_value=True)
|
@mock.patch('os.path.exists', return_value=True)
|
||||||
@mock.patch('codecs.open', return_value='my_file_handler')
|
@mock.patch('codecs.open', return_value='my_file_handler')
|
||||||
|
@mock.patch.object(models.UnifiedJob, 'result_stdout_text', '')
|
||||||
def test_result_stdout_raw_handle_file__found(exists, open):
|
def test_result_stdout_raw_handle_file__found(exists, open):
|
||||||
unified_job = UnifiedJob()
|
unified_job = models.UnifiedJob()
|
||||||
unified_job.result_stdout_file = 'dummy'
|
|
||||||
|
|
||||||
with mock.patch('os.stat', return_value=Mock(st_size=1)):
|
with mock.patch('os.stat', return_value=Mock(st_size=1)):
|
||||||
result = unified_job.result_stdout_raw_handle()
|
result = unified_job.result_stdout_raw_handle()
|
||||||
@@ -26,8 +26,9 @@ def test_result_stdout_raw_handle_file__found(exists, open):
|
|||||||
|
|
||||||
# stdout file missing, job finished
|
# stdout file missing, job finished
|
||||||
@mock.patch('os.path.exists', return_value=False)
|
@mock.patch('os.path.exists', return_value=False)
|
||||||
|
@mock.patch.object(models.UnifiedJob, 'result_stdout_text', '')
|
||||||
def test_result_stdout_raw_handle__missing(exists):
|
def test_result_stdout_raw_handle__missing(exists):
|
||||||
unified_job = UnifiedJob()
|
unified_job = models.UnifiedJob()
|
||||||
unified_job.result_stdout_file = 'dummy'
|
unified_job.result_stdout_file = 'dummy'
|
||||||
unified_job.finished = now()
|
unified_job.finished = now()
|
||||||
|
|
||||||
@@ -39,8 +40,9 @@ def test_result_stdout_raw_handle__missing(exists):
|
|||||||
|
|
||||||
# stdout file missing, job not finished
|
# stdout file missing, job not finished
|
||||||
@mock.patch('os.path.exists', return_value=False)
|
@mock.patch('os.path.exists', return_value=False)
|
||||||
|
@mock.patch.object(models.UnifiedJob, 'result_stdout_text', '')
|
||||||
def test_result_stdout_raw_handle__pending(exists):
|
def test_result_stdout_raw_handle__pending(exists):
|
||||||
unified_job = UnifiedJob()
|
unified_job = models.UnifiedJob()
|
||||||
unified_job.result_stdout_file = 'dummy'
|
unified_job.result_stdout_file = 'dummy'
|
||||||
unified_job.finished = None
|
unified_job.finished = None
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user