Skip to content

Commit 2ac95a9

Browse files
authored
Merge pull request #3235 from rtfd/better-sync-reporting
Do some initial file syncing error handling cleanup
2 parents d6bc7ea + 4619034 commit 2ac95a9

File tree

8 files changed

+75
-65
lines changed

8 files changed

+75
-65
lines changed

readthedocs/builds/syncers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def copy(cls, path, target, host, is_file=False, **__):
140140
)
141141
ret = os.system(sync_cmd)
142142
if ret != 0:
143-
log.info("COPY ERROR to app servers.")
143+
log.error("COPY ERROR to app servers. Command: [{}] Return: [{}]".format(sync_cmd, ret))
144144

145145

146146
class Syncer(SettingsOverrideObject):

readthedocs/core/utils/__init__.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.utils.safestring import SafeText, mark_safe
1515
from django.utils.text import slugify as slugify_base
1616
from future.backports.urllib.parse import urlparse
17+
from celery import group, chord
1718

1819
from ..tasks import send_email_task
1920
from readthedocs.builds.constants import LATEST
@@ -25,7 +26,15 @@
2526
SYNC_USER = getattr(settings, 'SYNC_USER', getpass.getuser())
2627

2728

28-
def broadcast(type, task, args, kwargs=None): # pylint: disable=redefined-builtin
29+
def broadcast(type, task, args, kwargs=None, callback=None): # pylint: disable=redefined-builtin
30+
"""
31+
Run a broadcast across our servers.
32+
33+
Returns a task group that can be checked for results.
34+
35+
`callback` should be a task signature that will be run once,
36+
after all of the broadcast tasks have finished running.
37+
"""
2938
assert type in ['web', 'app', 'build']
3039
if kwargs is None:
3140
kwargs = {}
@@ -34,12 +43,16 @@ def broadcast(type, task, args, kwargs=None): # pylint: disable=redefined-built
3443
servers = getattr(settings, "MULTIPLE_APP_SERVERS", [default_queue])
3544
elif type in ['build']:
3645
servers = getattr(settings, "MULTIPLE_BUILD_SERVERS", [default_queue])
46+
47+
tasks = []
3748
for server in servers:
38-
task.apply_async(
39-
queue=server,
40-
args=args,
41-
kwargs=kwargs,
42-
)
49+
task_sig = task.s(*args, **kwargs).set(queue=server)
50+
tasks.append(task_sig)
51+
if callback:
52+
task_promise = chord(tasks)(callback).get()
53+
else:
54+
task_promise = group(*tasks).apply_async()
55+
return task_promise
4356

4457

4558
def clean_url(url):

readthedocs/projects/tasks.py

Lines changed: 49 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
from .exceptions import ProjectImportError
3030
from .models import ImportedFile, Project, Domain
3131
from .signals import before_vcs, after_vcs, before_build, after_build
32-
from readthedocs.api.client import api as api_v1
3332
from readthedocs.builds.constants import (LATEST,
3433
BUILD_STATE_CLONING,
3534
BUILD_STATE_INSTALLING,
@@ -224,6 +223,8 @@ def run_build(self, docker=False, record=True):
224223
pdf=bool(outcomes['pdf']),
225224
epub=bool(outcomes['epub']),
226225
)
226+
else:
227+
log.warning('No build ID, not syncing files')
227228

228229
if self.build_env.failed:
229230
self.send_notifications()
@@ -280,11 +281,10 @@ def setup_vcs(self):
280281
if commit:
281282
self.build['commit'] = commit
282283
except ProjectImportError as e:
283-
log.error(
284+
log.exception(
284285
LOG_TEMPLATE.format(project=self.project.slug,
285286
version=self.version.slug,
286-
msg=str(e)),
287-
exc_info=True,
287+
msg='Failed to import Project: '),
288288
)
289289
raise BuildEnvironmentError('Failed to import project: %s' % e,
290290
status_code=404)
@@ -346,35 +346,27 @@ def update_app_instances(self, html=False, localmedia=False, search=False,
346346
'active': True,
347347
'built': True,
348348
})
349-
except HttpClientError as e:
350-
log.error('Updating version failed, skipping file sync: version=%s',
351-
self.version.pk, exc_info=True)
352-
else:
353-
# Broadcast finalization steps to web application instances
354-
broadcast(
355-
type='app',
356-
task=sync_files,
357-
args=[
358-
self.project.pk,
359-
self.version.pk,
360-
],
361-
kwargs=dict(
362-
hostname=socket.gethostname(),
363-
html=html,
364-
localmedia=localmedia,
365-
search=search,
366-
pdf=pdf,
367-
epub=epub,
368-
)
369-
)
370-
371-
# Delayed tasks
372-
# TODO these should be chained on to the broadcast calls. The
373-
# broadcast calls could be lumped together into a promise, and on
374-
# task result, these next few tasks can be updated, also in a
375-
# chained fashion
376-
fileify.delay(self.version.pk, commit=self.build.get('commit'))
377-
update_search.delay(self.version.pk, commit=self.build.get('commit'))
349+
except HttpClientError:
350+
log.exception('Updating version failed, skipping file sync: version=%s' % self.version)
351+
352+
# Broadcast finalization steps to web application instances
353+
broadcast(
354+
type='app',
355+
task=sync_files,
356+
args=[
357+
self.project.pk,
358+
self.version.pk,
359+
],
360+
kwargs=dict(
361+
hostname=socket.gethostname(),
362+
html=html,
363+
localmedia=localmedia,
364+
search=search,
365+
pdf=pdf,
366+
epub=epub,
367+
),
368+
callback=sync_callback.s(version_pk=self.version.pk, commit=self.build['commit']),
369+
)
378370

379371
def setup_environment(self):
380372
"""
@@ -442,8 +434,7 @@ def build_docs_html(self):
442434
kwargs=dict(html=True)
443435
)
444436
except socket.error:
445-
# TODO do something here
446-
pass
437+
log.exception('move_files task has failed on socket error.')
447438

448439
return success
449440

@@ -550,8 +541,6 @@ def update_imported_docs(version_pk):
550541
version_slug = LATEST
551542
version_repo = project.vcs_repo(version_slug)
552543
ret_dict['checkout'] = version_repo.update()
553-
except Exception:
554-
raise
555544
finally:
556545
after_vcs.send(sender=version)
557546

@@ -575,10 +564,10 @@ def update_imported_docs(version_pk):
575564

576565
try:
577566
api_v2.project(project.pk).sync_versions.post(version_post_data)
578-
except HttpClientError as e:
579-
log.error("Sync Versions Exception: %s", e.content)
580-
except Exception as e:
581-
log.error("Unknown Sync Versions Exception", exc_info=True)
567+
except HttpClientError:
568+
log.exception("Sync Versions Exception")
569+
except Exception:
570+
log.exception("Unknown Sync Versions Exception")
582571
return ret_dict
583572

584573

@@ -634,6 +623,8 @@ def move_files(version_pk, hostname, html=False, localmedia=False, search=False,
634623
:type epub: bool
635624
"""
636625
version = Version.objects.get(pk=version_pk)
626+
log.debug(LOG_TEMPLATE.format(project=version.project.slug, version=version.slug,
627+
msg='Moving files: {}'.format(locals())))
637628

638629
if html:
639630
from_path = version.project.artifact_path(
@@ -642,18 +633,18 @@ def move_files(version_pk, hostname, html=False, localmedia=False, search=False,
642633
Syncer.copy(from_path, target, host=hostname)
643634

644635
if 'sphinx' in version.project.documentation_type:
645-
if localmedia:
636+
if search:
646637
from_path = version.project.artifact_path(
647-
version=version.slug, type_='sphinx_localmedia')
638+
version=version.slug, type_='sphinx_search')
648639
to_path = version.project.get_production_media_path(
649-
type_='htmlzip', version_slug=version.slug, include_file=False)
640+
type_='json', version_slug=version.slug, include_file=False)
650641
Syncer.copy(from_path, to_path, host=hostname)
651642

652-
if search:
643+
if localmedia:
653644
from_path = version.project.artifact_path(
654-
version=version.slug, type_='sphinx_search')
645+
version=version.slug, type_='sphinx_localmedia')
655646
to_path = version.project.get_production_media_path(
656-
type_='json', version_slug=version.slug, include_file=False)
647+
type_='htmlzip', version_slug=version.slug, include_file=False)
657648
Syncer.copy(from_path, to_path, host=hostname)
658649

659650
# Always move PDF's because the return code lies.
@@ -984,3 +975,14 @@ def clear_html_artifacts(version):
984975
if isinstance(version, int):
985976
version = Version.objects.get(pk=version)
986977
remove_dir(version.project.rtd_build_path(version=version.slug))
978+
979+
980+
@task(queue='web')
981+
def sync_callback(_, version_pk, commit, *args, **kwargs):
982+
"""
983+
This will be called once the sync_files tasks are done.
984+
985+
The first argument is the result from previous tasks, which we discard.
986+
"""
987+
fileify(version_pk, commit=commit)
988+
update_search(version_pk, commit=commit)

readthedocs/rtd_tests/tests/projects/test_admin_actions.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,8 @@ def test_project_delete(self, remove_dir):
7171
action_data
7272
)
7373
self.assertFalse(Project.objects.filter(pk=self.project.pk).exists())
74-
remove_dir.apply_async.assert_has_calls([
74+
remove_dir.s.assert_has_calls([
7575
mock.call(
76-
kwargs={},
77-
queue='celery',
78-
args=[self.project.doc_path]
76+
self.project.doc_path,
7977
),
8078
])

readthedocs/search/parse_json.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,6 @@ def generate_sections_from_pyquery(body):
8888
'title': title,
8989
'content': content,
9090
}
91-
log.debug("(Search Index) Section [%s:%s]: %s",
92-
section_id, title, content)
9391

9492

9593
def process_file(filename):

readthedocs/search/utils.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,8 +212,6 @@ def parse_sphinx_sections(content):
212212
'title': title,
213213
'content': content,
214214
}
215-
log.debug("(Search Index) Section [%s:%s]: %s",
216-
section_id, title, content)
217215

218216

219217
def parse_mkdocs_sections(content):
@@ -265,8 +263,6 @@ def parse_mkdocs_sections(content):
265263
'title': h2_title,
266264
'content': h2_content,
267265
}
268-
log.debug("(Search Index) Section [%s:%s]: %s",
269-
section_id, h2_title, h2_content)
270266
# we're unsure which exceptions can be raised
271267
# pylint: disable=bare-except
272268
except:

readthedocs/settings/base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ def INSTALLED_APPS(self): # noqa
357357
'debug': {
358358
'level': 'DEBUG',
359359
'class': 'logging.handlers.RotatingFileHandler',
360-
'filename': 'debug.log',
360+
'filename': os.path.join(LOGS_ROOT, 'debug.log'),
361361
'formatter': 'default',
362362
},
363363
},

readthedocs/settings/dev.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def DATABASES(self): # noqa
3333

3434
BROKER_URL = 'redis://localhost:6379/0'
3535
CELERY_RESULT_BACKEND = 'redis://localhost:6379/0'
36+
CELERY_RESULT_SERIALIZER = 'json'
3637
CELERY_ALWAYS_EAGER = True
3738

3839
HAYSTACK_CONNECTIONS = {
@@ -55,6 +56,8 @@ def DATABASES(self): # noqa
5556
def LOGGING(self): # noqa - avoid pep8 N802
5657
logging = super(CommunityDevSettings, self).LOGGING
5758
logging['formatters']['default']['format'] = '[%(asctime)s] ' + self.LOG_FORMAT
59+
# Remove double logging
60+
logging['loggers']['']['handlers'] = []
5861
return logging
5962

6063

0 commit comments

Comments
 (0)