-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
More logic changes to error reporting, cleanup #3310
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
47df9ee
0fd8366
a9c2a22
bacef7e
4c3904e
d7513a6
3bf4b9c
f2e4096
28f00a6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
from readthedocs.builds.models import BuildCommandResultMixin | ||
from readthedocs.projects.constants import LOG_TEMPLATE | ||
from readthedocs.restapi.client import api as api_v2 | ||
from requests.exceptions import ConnectionError | ||
|
||
from .exceptions import (BuildEnvironmentException, BuildEnvironmentError, | ||
BuildEnvironmentWarning) | ||
|
@@ -275,15 +276,17 @@ class BuildEnvironment(object): | |
:param build: Build instance | ||
:param record: Record status of build object | ||
:param environment: shell environment variables | ||
:param finalize: finalize the build by setting a finished state on exit | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this sentence is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah true. I didn't like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe the |
||
""" | ||
|
||
def __init__(self, project=None, version=None, build=None, record=True, | ||
environment=None): | ||
environment=None, finalize=True): | ||
self.project = project | ||
self.version = version | ||
self.build = build | ||
self.record = record | ||
self.environment = environment or {} | ||
self.finalize = finalize | ||
|
||
self.commands = [] | ||
self.failure = None | ||
|
@@ -294,7 +297,7 @@ def __enter__(self): | |
|
||
def __exit__(self, exc_type, exc_value, tb): | ||
ret = self.handle_exception(exc_type, exc_value, tb) | ||
self.build['state'] = BUILD_STATE_FINISHED | ||
self.update_build(BUILD_STATE_FINISHED) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Won't this post that status to the API? Seems like we don't want to publish here, but only below if There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Gah ok. Lost that in the review. |
||
log.info(LOG_TEMPLATE | ||
.format(project=self.project.slug, | ||
version=self.version.slug, | ||
|
@@ -442,12 +445,13 @@ def update_build(self, state=None): | |
if isinstance(val, six.binary_type): | ||
self.build[key] = val.decode('utf-8', 'ignore') | ||
|
||
try: | ||
api_v2.build(self.build['id']).put(self.build) | ||
except HttpClientError as e: | ||
log.error("Unable to post a new build: %s", e.content) | ||
except Exception: | ||
log.exception("Unknown build exception") | ||
if self.finalize: | ||
try: | ||
api_v2.build(self.build['id']).put(self.build) | ||
except HttpClientError as e: | ||
log.error("Unable to post a new build: %s", e.content) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
except Exception: | ||
log.exception("Unknown build exception") | ||
|
||
|
||
class LocalEnvironment(BuildEnvironment): | ||
|
@@ -521,8 +525,15 @@ def __enter__(self): | |
.format(self.container_id)))) | ||
client = self.get_client() | ||
client.remove_container(self.container_id) | ||
except DockerAPIError: | ||
except (DockerAPIError, ConnectionError): | ||
# If there is an exception here, we swallow the exception as this | ||
# was just during a sanity check anyways. | ||
pass | ||
except BuildEnvironmentError: | ||
# There may have been a problem connecting to Docker altogether, or | ||
# some other handled exception here. | ||
self.__exit__(*sys.exc_info()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this not automatically call There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It won't, because this is raised in |
||
raise | ||
|
||
# Create the checkout path if it doesn't exist to avoid Docker creation | ||
if not os.path.exists(self.project.doc_path): | ||
|
@@ -537,28 +548,40 @@ def __enter__(self): | |
|
||
def __exit__(self, exc_type, exc_value, tb): | ||
"""End of environment context""" | ||
ret = self.handle_exception(exc_type, exc_value, tb) | ||
try: | ||
# Update buildenv state given any container error states first | ||
self.update_build_from_container_state() | ||
|
||
# Update buildenv state given any container error states first | ||
self.update_build_from_container_state() | ||
client = self.get_client() | ||
try: | ||
client.kill(self.container_id) | ||
except DockerAPIError: | ||
pass | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about logging something here (DEBUG level, maybe)? |
||
try: | ||
log.info('Removing container %s', self.container_id) | ||
client.remove_container(self.container_id) | ||
# Catch direct failures from Docker API, but also requests exceptions | ||
# with the HTTP request. These should not | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. "These should not" ? |
||
except (DockerAPIError, ConnectionError): | ||
log.exception( | ||
LOG_TEMPLATE | ||
.format( | ||
project=self.project.slug, | ||
version=self.version.slug, | ||
msg="Couldn't remove container", | ||
), | ||
) | ||
self.container = None | ||
except BuildEnvironmentError: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not following this. I think it's not possible to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
# Several interactions with Docker can result in a top level failure | ||
# here. We'll catch this and report if there were no reported errors | ||
# already. These errors are not as important as a failure at deeper | ||
# code | ||
if not all([exc_type, exc_value, tb]): | ||
exc_type, exc_value, tb = sys.exc_info() | ||
|
||
client = self.get_client() | ||
try: | ||
client.kill(self.container_id) | ||
except DockerAPIError: | ||
pass | ||
try: | ||
log.info('Removing container %s', self.container_id) | ||
client.remove_container(self.container_id) | ||
except DockerAPIError: | ||
log.error(LOG_TEMPLATE | ||
.format( | ||
project=self.project.slug, | ||
version=self.version.slug, | ||
msg="Couldn't remove container"), | ||
exc_info=True) | ||
self.container = None | ||
self.build['state'] = BUILD_STATE_FINISHED | ||
ret = self.handle_exception(exc_type, exc_value, tb) | ||
self.update_build(BUILD_STATE_FINISHED) | ||
log.info(LOG_TEMPLATE | ||
.format(project=self.project.slug, | ||
version=self.version.slug, | ||
|
@@ -655,11 +678,22 @@ def create_container(self): | |
mem_limit=self.container_mem_limit, | ||
) | ||
client.start(container=self.container_id) | ||
except ConnectionError as e: | ||
log.exception( | ||
LOG_TEMPLATE.format( | ||
project=self.project.slug, | ||
version=self.version.slug, | ||
msg=e, | ||
), | ||
) | ||
raise BuildEnvironmentError('There was a problem connecting to Docker') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here, the problem is similar than https://github.com/rtfd/readthedocs.org/pull/3310/files#diff-ca52b098301dd315a834b3556ab9a7d5R638 but in this case we are communicating the Docker error to the user. |
||
except DockerAPIError as e: | ||
log.error(LOG_TEMPLATE | ||
.format( | ||
project=self.project.slug, | ||
version=self.version.slug, | ||
msg=e.explanation), | ||
exc_info=True) | ||
log.exception( | ||
LOG_TEMPLATE | ||
.format( | ||
project=self.project.slug, | ||
version=self.version.slug, | ||
msg=e.explanation, | ||
), | ||
) | ||
raise BuildEnvironmentError('Build environment creation failed') |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,46 @@ | ||
"""Project exceptions""" | ||
|
||
from django.conf import settings | ||
from django.utils.translation import ugettext_noop as _ | ||
|
||
class ProjectImportError (Exception): | ||
from readthedocs.doc_builder.exceptions import BuildEnvironmentError | ||
|
||
"""Failure to import a project from a repository.""" | ||
|
||
pass | ||
class ProjectConfigurationError(BuildEnvironmentError): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this class is missing the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be inheriting this from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems we have a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exception is the base class, Error is the specific class that kills a build and reports to a user, Warning also is absed on Exception and doesn't kill the build. |
||
|
||
"""Error raised trying to configure a project for build""" | ||
|
||
NOT_FOUND = _( | ||
'A configuration file was not found. ' | ||
'Make sure you have a conf.py file in your repository.' | ||
) | ||
|
||
|
||
class RepositoryError(BuildEnvironmentError): | ||
|
||
"""Failure during repository operation.""" | ||
|
||
PRIVATE_REPO = _( | ||
'There was a problem connecting to your repository, ' | ||
'ensure that your repository URL is correct.' | ||
) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If it's a private repo, shouldn't we warn them here? Why are these messages the same? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We aren't thoroughly testing if this was a private repo or not, we'd have to grep the git output. @humitos raised a similar question and the best answer is to execute checkout in a build environment and report the commands, not parse them to determine what the error was. Messages aren't the same, but usage could be clearer. I should probably rename these constants, as In fact, I'll update the copy here to be more explicit about repo privacy. |
||
PUBLIC_REPO = _( | ||
'There was a problem connecting to your repository, ' | ||
'ensure that your repository URL is correct and your repository is public.' | ||
) | ||
|
||
def get_default_message(self): | ||
if settings.ALLOW_PRIVATE_REPOS: | ||
return self.PRIVATE_REPO | ||
return self.PUBLIC_REPO | ||
|
||
|
||
class ProjectSpamError(Exception): | ||
|
||
"""Error raised when a project field has detected spam""" | ||
"""Error raised when a project field has detected spam | ||
|
||
This error is not raised to users, we use this for banning users in the | ||
background. | ||
""" | ||
|
||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this
six.reraise
? It's not the same than justraise ProjectImportError(...)
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure either, the docs aren't clear to me:
https://pythonhosted.org/six/#six.reraise
I'll leave it, assuming someone with more knowledge of py2/3 compat did this :)