applications: mark job as waiting when modules are still running (#89124)
This commit is contained in:
parent
25be449635
commit
422f55c2c5
|
@ -544,6 +544,7 @@ class Version(models.Model):
|
|||
STATUS_CHOICES = [
|
||||
('registered', _('Registered')),
|
||||
('running', _('Running')),
|
||||
('waiting', _('Waiting for modules')),
|
||||
('failed', _('Failed')),
|
||||
('completed', _('Completed')),
|
||||
]
|
||||
|
@ -602,9 +603,16 @@ class AsyncJob(models.Model):
|
|||
raise
|
||||
finally:
|
||||
if self.status == 'running':
|
||||
self.status = 'completed'
|
||||
if not self.progression_urls:
|
||||
self.status = 'completed'
|
||||
else:
|
||||
self.status = 'waiting'
|
||||
self.completion_timestamp = now()
|
||||
self.save()
|
||||
if self.status == 'waiting':
|
||||
# the job is may be running in the same time that AsyncJobView is viewed
|
||||
# the SQL transaction is not the same, run modules completion check now
|
||||
self.check_modules_completion()
|
||||
|
||||
def get_diff_details(self):
|
||||
# collect service bundle-check results
|
||||
|
@ -659,3 +667,36 @@ class AsyncJob(models.Model):
|
|||
if relation.element.slug in no_history.get(relation.element.type, []):
|
||||
result_no_history.append(relation)
|
||||
return result_diffs, result_not_found, result_no_history, result_legacy
|
||||
|
||||
def check_modules_completion(self):
|
||||
if self.status != 'waiting':
|
||||
return {}
|
||||
context = {'job_progression': {}}
|
||||
for service_id, services in self.progression_urls.items():
|
||||
for service, url in services.items():
|
||||
response = requests.get(url)
|
||||
if not response.ok:
|
||||
continue
|
||||
context['job_progression'][service] = response.json()
|
||||
context['job_progression'][service].update({'service_id': service_id})
|
||||
context['services_all_completed'] = all(
|
||||
[s['data']['status'] == 'completed' for s in context['job_progression'].values()]
|
||||
)
|
||||
failed = [
|
||||
s['service_id'] for s in context['job_progression'].values() if s['data']['status'] == 'failed'
|
||||
]
|
||||
if failed:
|
||||
self.status = 'failed'
|
||||
if len(failed) > 1:
|
||||
self.exception = 'Failed to deploy modules %s' % ', '.join(failed)
|
||||
else:
|
||||
self.exception = 'Failed to deploy module %s' % failed[0]
|
||||
self.completion_timestamp = now()
|
||||
self.save()
|
||||
elif context['services_all_completed']:
|
||||
self.status = 'completed'
|
||||
self.completion_timestamp = now()
|
||||
self.save()
|
||||
context['wait_for_services'] = True
|
||||
context['service_job_status_choices'] = {c[0]: c[1] for c in STATUS_CHOICES}
|
||||
return context
|
||||
|
|
|
@ -28,7 +28,7 @@ from django.shortcuts import get_object_or_404
|
|||
from django.urls import reverse
|
||||
from django.utils.formats import date_format
|
||||
from django.utils.text import slugify
|
||||
from django.utils.timezone import localtime, now
|
||||
from django.utils.timezone import localtime
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import DetailView, FormView, ListView, RedirectView, TemplateView
|
||||
from django.views.generic.edit import CreateView, DeleteView, UpdateView
|
||||
|
@ -37,7 +37,6 @@ from hobo.environment.models import Variable
|
|||
|
||||
from .forms import GenerateForm, InstallForm, MetadataForm, VersionSelectForm
|
||||
from .models import (
|
||||
STATUS_CHOICES,
|
||||
Application,
|
||||
ApplicationError,
|
||||
AsyncJob,
|
||||
|
@ -673,32 +672,9 @@ class AsyncJobView(DetailView):
|
|||
template_name = 'hobo/applications/job.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs['job_progression'] = {}
|
||||
job = self.object
|
||||
if job.action == 'deploy' and job.status != 'failed':
|
||||
for service_id, services in job.progression_urls.items():
|
||||
for service, url in services.items():
|
||||
response = requests.get(url)
|
||||
if not response.ok:
|
||||
continue
|
||||
kwargs['job_progression'][service] = response.json()
|
||||
kwargs['job_progression'][service].update({'service_id': service_id})
|
||||
kwargs['services_all_completed'] = all(
|
||||
[s['data']['status'] == 'completed' for s in kwargs['job_progression'].values()]
|
||||
)
|
||||
failed = [
|
||||
s['service_id'] for s in kwargs['job_progression'].values() if s['data']['status'] == 'failed'
|
||||
]
|
||||
if failed:
|
||||
job.status = 'failed'
|
||||
if len(failed) > 1:
|
||||
job.exception = 'Failed to deploy modules %s' % ', '.join(failed)
|
||||
else:
|
||||
job.exception = 'Failed to deploy module %s' % failed[0]
|
||||
job.completion_timestamp = now()
|
||||
job.save()
|
||||
kwargs['wait_for_services'] = True
|
||||
kwargs['service_job_status_choices'] = {c[0]: c[1] for c in STATUS_CHOICES}
|
||||
kwargs['app'] = job.application
|
||||
kwargs.update(job.check_modules_completion())
|
||||
return super().get_context_data(**kwargs)
|
||||
|
||||
def get_redirect_url(self):
|
||||
|
|
|
@ -233,6 +233,11 @@ def mocked_http(url, request):
|
|||
'content': json.dumps({'err': 0, 'url': 'https://wcs.example.invalid/api/jobs/job-uuid/'}),
|
||||
'status_code': 200,
|
||||
}
|
||||
if url.path.startswith('/api/jobs/'):
|
||||
return {
|
||||
'content': json.dumps({'err': 0, 'data': {'status': 'completed', 'completion_status': '100%'}}),
|
||||
'status_code': 200,
|
||||
}
|
||||
|
||||
if url.path == '/api/export-import/bundle-check/':
|
||||
return {
|
||||
|
@ -1675,13 +1680,13 @@ def test_deploy_application_roles(app, admin_user, settings, app_bundle_roles):
|
|||
)
|
||||
# roles
|
||||
assert mocked_http.call['requests'][1].url.startswith(
|
||||
'https://idp.example.invalid/api/roles/?update'
|
||||
'https://idp.example.invalid/api/roles/?update_or_create'
|
||||
)
|
||||
assert mocked_http.call['requests'][2].url.startswith(
|
||||
'https://idp.example.invalid/api/provision/'
|
||||
)
|
||||
assert mocked_http.call['requests'][3].url.startswith(
|
||||
'https://idp.example.invalid/api/roles/?update'
|
||||
'https://idp.example.invalid/api/roles/?update_or_create'
|
||||
)
|
||||
assert mocked_http.call['requests'][4].url.startswith(
|
||||
'https://idp.example.invalid/api/provision/'
|
||||
|
@ -1695,11 +1700,14 @@ def test_deploy_application_roles(app, admin_user, settings, app_bundle_roles):
|
|||
)
|
||||
# then element refresh
|
||||
available_objects = [o for o in WCS_AVAILABLE_OBJECTS['data'] if o['id'] in ['forms', 'roles']]
|
||||
assert mocked_http.call['count'] == 6 + 1 + len(available_objects)
|
||||
assert mocked_http.call['count'] == 6 + 1 + len(available_objects) + 1
|
||||
for i, object_type in enumerate(available_objects):
|
||||
assert mocked_http.call['requests'][7 + i].url.startswith(
|
||||
'https://wcs.example.invalid/api/export-import/%s/?' % object_type['id']
|
||||
)
|
||||
assert mocked_http.call['requests'][9].url.startswith(
|
||||
'https://wcs.example.invalid/api/jobs/job-uuid/'
|
||||
)
|
||||
|
||||
resp = resp.follow()
|
||||
assert resp.pyquery('h2.application-title').text() == 'Test'
|
||||
|
@ -1819,7 +1827,7 @@ def test_job_status_page(app, admin_user, settings):
|
|||
application=application,
|
||||
action='deploy',
|
||||
progression_urls={'wcs': {'Foobar': 'https://wcs.example.invalid/api/jobs/job-uuid/'}},
|
||||
status='completed', # completed, with async job on wcs part
|
||||
status='waiting',
|
||||
)
|
||||
|
||||
def response_content(url, request):
|
||||
|
@ -1837,17 +1845,7 @@ def test_job_status_page(app, admin_user, settings):
|
|||
assert 'window.location.reload' in resp
|
||||
assert 'window.location = "/applications/manifest/test/"' not in resp
|
||||
|
||||
def response_content(url, request): # noqa pylint: disable=function-redefined
|
||||
if url.path.startswith('/api/jobs/'):
|
||||
return {
|
||||
'content': json.dumps(
|
||||
{'err': 0, 'data': {'status': 'completed', 'completion_status': '100%'}}
|
||||
),
|
||||
'status_code': 200,
|
||||
}
|
||||
return mocked_http(url, request)
|
||||
|
||||
with StatefulHTTMock(response_content):
|
||||
with StatefulHTTMock(httmock.remember_called(mocked_http)):
|
||||
resp = app.get('/applications/manifest/test/job/%s/' % job.pk)
|
||||
assert 'Please wait…' in resp
|
||||
assert 'Completed: 100%' in resp
|
||||
|
@ -1863,7 +1861,7 @@ def test_job_status_page(app, admin_user, settings):
|
|||
assert 'window.location.reload' not in resp
|
||||
assert 'window.location = "/applications/manifest/test/"' not in resp
|
||||
|
||||
job.status = 'running'
|
||||
job.status = 'waiting'
|
||||
job.exception = ''
|
||||
job.save()
|
||||
|
||||
|
|
Loading…
Reference in New Issue