Skip to content

Commit 05bec92

Browse files
authored
Merge pull request ansible#795 from ryanpetrello/move-deprecated-stdout
move legacy UnifiedJob stdout data to a separate unmanaged model
2 parents 40d1f26 + 202161f commit 05bec92

File tree

9 files changed

+108
-41
lines changed

9 files changed

+108
-41
lines changed

awx/api/views.py

-6
Original file line numberDiff line numberDiff line change
@@ -2663,12 +2663,6 @@ class InventoryUpdateList(ListAPIView):
26632663
model = InventoryUpdate
26642664
serializer_class = InventoryUpdateListSerializer
26652665

2666-
def get_queryset(self):
2667-
qs = super(InventoryUpdateList, self).get_queryset()
2668-
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
2669-
qs = qs.defer('result_stdout_text')
2670-
return qs
2671-
26722666

26732667
class InventoryUpdateDetail(UnifiedJobDeletionMixin, RetrieveDestroyAPIView):
26742668

awx/main/access.py

+2-10
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,7 @@ def get_user_capabilities(user, instance, **kwargs):
140140
convenient for the user interface to consume and hide or show various
141141
actions in the interface.
142142
'''
143-
cls = instance.__class__
144-
# When `.defer()` is used w/ the Django ORM, the result is a subclass of
145-
# the original that represents e.g.,
146-
# awx.main.models.ad_hoc_commands.AdHocCommand_Deferred_result_stdout_text
147-
# We want to do the access registry lookup keyed on the base class name.
148-
if getattr(cls, '_deferred', False):
149-
cls = instance.__class__.__bases__[0]
150-
access_class = access_registry[cls]
143+
access_class = access_registry[instance.__class__]
151144
return access_class(user).get_user_capabilities(instance, **kwargs)
152145

153146

@@ -2075,8 +2068,7 @@ def filtered_queryset(self):
20752068
Q(job__inventory__organization__in=org_auditor_qs) |
20762069
Q(job__project__organization__in=org_auditor_qs)
20772070
)
2078-
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
2079-
return qs.defer('result_stdout_text')
2071+
return qs
20802072

20812073

20822074
class ScheduleAccess(BaseAccess):
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# -*- coding: utf-8 -*-
2+
# Generated by Django 1.11.7 on 2017-12-12 18:56
3+
from __future__ import unicode_literals
4+
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
10+
dependencies = [
11+
('main', '0012_non_blank_workflow'),
12+
]
13+
14+
operations = [
15+
migrations.AlterField(
16+
model_name='unifiedjob',
17+
name='result_stdout_text',
18+
field=models.TextField(editable=False, null=True),
19+
),
20+
# Using SeparateDatabaseAndState here allows us to update the migration
21+
# state so that Django thinks the UnifiedJob.result_stdout_text field
22+
# is gone _without_ actually deleting the underlying column/data
23+
migrations.SeparateDatabaseAndState(state_operations=[
24+
migrations.RemoveField(
25+
model_name='unifiedjob',
26+
name='result_stdout_text',
27+
),
28+
]),
29+
# On other side of the equation, this migration introduces a new model
30+
# which is *unmanaged* (meaning, a new table is not created for it);
31+
# instead, this sort of "virtual" model is used to maintain an ORM
32+
# reference to the actual `main_unifiedjob.result_stdout_text` column
33+
migrations.CreateModel(
34+
name='UnifiedJobDeprecatedStdout',
35+
fields=[
36+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
37+
('result_stdout_text', models.TextField(editable=False, null=True))
38+
],
39+
options={
40+
'db_table': 'main_unifiedjob',
41+
'managed': False,
42+
},
43+
),
44+
]

awx/main/models/__init__.py

-14
Original file line numberDiff line numberDiff line change
@@ -145,17 +145,3 @@ def user_is_in_enterprise_category(user, category):
145145

146146
# prevent API filtering on certain Django-supplied sensitive fields
147147
prevent_search(User._meta.get_field('password'))
148-
149-
150-
# Always, always, always defer result_stdout_text for polymorphic UnifiedJob rows
151-
# TODO: remove this defer in 3.3 when we implement https://github.com/ansible/ansible-tower/issues/5436
152-
def defer_stdout(f):
153-
def _wrapped(*args, **kwargs):
154-
objs = f(*args, **kwargs)
155-
objs.query.deferred_loading[0].add('result_stdout_text')
156-
return objs
157-
return _wrapped
158-
159-
160-
for cls in UnifiedJob.__subclasses__():
161-
cls.base_objects.filter = defer_stdout(cls.base_objects.filter)

awx/main/models/unified_jobs.py

+25-5
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,18 @@ def model_to_str(self):
492492
return UnifiedJobTypeStringMixin._camel_to_underscore(self.__class__.__name__)
493493

494494

495+
class UnifiedJobDeprecatedStdout(models.Model):
496+
497+
class Meta:
498+
managed = False
499+
db_table = 'main_unifiedjob'
500+
501+
result_stdout_text = models.TextField(
502+
null=True,
503+
editable=False,
504+
)
505+
506+
495507
class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique, UnifiedJobTypeStringMixin, TaskManagerUnifiedJobMixin):
496508
'''
497509
Concrete base class for unified job run by the task engine.
@@ -620,11 +632,6 @@ class Meta:
620632
default='',
621633
editable=False,
622634
))
623-
result_stdout_text = models.TextField(
624-
blank=True,
625-
default='',
626-
editable=False,
627-
)
628635
result_stdout_file = models.TextField( # FilePathfield?
629636
blank=True,
630637
default='',
@@ -882,6 +889,19 @@ def create_config_from_prompts(self, kwargs):
882889
config.credentials.add(*job_creds)
883890
return config
884891

892+
@property
893+
def result_stdout_text(self):
894+
related = UnifiedJobDeprecatedStdout.objects.get(pk=self.pk)
895+
return related.result_stdout_text or ''
896+
897+
@result_stdout_text.setter
898+
def result_stdout_text(self, value):
899+
# TODO: remove this method once all stdout is based on jobevents
900+
# (because it won't be used for writing anymore)
901+
related = UnifiedJobDeprecatedStdout.objects.get(pk=self.pk)
902+
related.result_stdout_text = value
903+
related.save()
904+
885905
def result_stdout_raw_handle(self, attempt=0):
886906
"""Return a file-like object containing the standard out of the
887907
job's result.

awx/main/tasks.py

+4
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,10 @@ def update_model(self, pk, _attempt=0, **updates):
516516
update_fields.append(field)
517517
if field == 'status':
518518
update_fields.append('failed')
519+
if 'result_stdout_text' in update_fields:
520+
# result_stdout_text is now deprecated, and is no longer
521+
# an actual Django field (it's a property)
522+
update_fields.remove('result_stdout_text')
519523
instance.save(update_fields=update_fields)
520524
return instance
521525
except DatabaseError as e:

awx/main/tests/functional/__init__.py

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from django.db import connection
2+
from django.db.models.signals import post_migrate
3+
from django.apps import apps
4+
5+
6+
def app_post_migration(sender, app_config, **kwargs):
7+
# our usage of pytest.django+sqlite doesn't actually run real migrations,
8+
# so we've got to make sure the deprecated
9+
# `main_unifiedjob.result_stdout_text` column actually exists
10+
cur = connection.cursor()
11+
cols = cur.execute(
12+
'SELECT sql FROM sqlite_master WHERE tbl_name="main_unifiedjob";'
13+
).fetchone()[0]
14+
if 'result_stdout_text' not in cols:
15+
cur.execute(
16+
'ALTER TABLE main_unifiedjob ADD COLUMN result_stdout_text TEXT'
17+
)
18+
19+
20+
post_migrate.connect(app_post_migration, sender=apps.get_app_config('main'))
21+
22+
23+

awx/main/tests/functional/api/test_unified_jobs_view.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
def test_cases(project):
3434
ret = []
3535
for e in TEST_STDOUTS:
36-
e['project'] = ProjectUpdate(project=project)
36+
pu = ProjectUpdate(project=project)
37+
pu.save()
38+
e['project'] = pu
3739
e['project'].result_stdout_text = e['text']
3840
e['project'].save()
3941
ret.append(e)

awx/main/tests/unit/test_unified_jobs.py

+7-5
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
from django.utils.timezone import now
99

1010
# AWX
11-
from awx.main.models import UnifiedJob
11+
from awx.main import models
1212

1313

1414
# stdout file present
1515
@mock.patch('os.path.exists', return_value=True)
1616
@mock.patch('codecs.open', return_value='my_file_handler')
17+
@mock.patch.object(models.UnifiedJob, 'result_stdout_text', '')
1718
def test_result_stdout_raw_handle_file__found(exists, open):
18-
unified_job = UnifiedJob()
19-
unified_job.result_stdout_file = 'dummy'
19+
unified_job = models.UnifiedJob()
2020

2121
with mock.patch('os.stat', return_value=Mock(st_size=1)):
2222
result = unified_job.result_stdout_raw_handle()
@@ -26,8 +26,9 @@ def test_result_stdout_raw_handle_file__found(exists, open):
2626

2727
# stdout file missing, job finished
2828
@mock.patch('os.path.exists', return_value=False)
29+
@mock.patch.object(models.UnifiedJob, 'result_stdout_text', '')
2930
def test_result_stdout_raw_handle__missing(exists):
30-
unified_job = UnifiedJob()
31+
unified_job = models.UnifiedJob()
3132
unified_job.result_stdout_file = 'dummy'
3233
unified_job.finished = now()
3334

@@ -39,8 +40,9 @@ def test_result_stdout_raw_handle__missing(exists):
3940

4041
# stdout file missing, job not finished
4142
@mock.patch('os.path.exists', return_value=False)
43+
@mock.patch.object(models.UnifiedJob, 'result_stdout_text', '')
4244
def test_result_stdout_raw_handle__pending(exists):
43-
unified_job = UnifiedJob()
45+
unified_job = models.UnifiedJob()
4446
unified_job.result_stdout_file = 'dummy'
4547
unified_job.finished = None
4648

0 commit comments

Comments
 (0)