Merge pull request #1500 from coreos-inc/better-errors

Better errors
This commit is contained in:
josephschorr 2016-05-31 15:54:41 -04:00
commit 1ddc73416c
8 changed files with 57 additions and 30 deletions

View file

@ -61,8 +61,8 @@ class RedisBuildLogs(object):
llen = self._redis.llen(self._logs_key(build_id)) llen = self._redis.llen(self._logs_key(build_id))
log_entries = self._redis.lrange(self._logs_key(build_id), start_index, -1) log_entries = self._redis.lrange(self._logs_key(build_id), start_index, -1)
return (llen, (json.loads(entry) for entry in log_entries)) return (llen, (json.loads(entry) for entry in log_entries))
except redis.ConnectionError: except redis.ConnectionError as ce:
raise BuildStatusRetrievalError('Cannot retrieve build logs') raise BuildStatusRetrievalError('Cannot retrieve build logs: %s' % ce)
def expire_log_entries(self, build_id): def expire_log_entries(self, build_id):
""" """
@ -87,8 +87,8 @@ class RedisBuildLogs(object):
""" """
try: try:
fetched = self._redis.get(self._status_key(build_id)) fetched = self._redis.get(self._status_key(build_id))
except redis.ConnectionError: except redis.ConnectionError as ce:
raise BuildStatusRetrievalError('Cannot retrieve build status') raise BuildStatusRetrievalError('Cannot retrieve build status: %s' % ce)
return json.loads(fetched) if fetched else None return json.loads(fetched) if fetched else None

View file

@ -8,11 +8,11 @@ import hashlib
from flask import request from flask import request
from rfc3987 import parse as uri_parse from rfc3987 import parse as uri_parse
from app import app, userfiles as user_files, build_logs, log_archive, dockerfile_build_queue from app import userfiles as user_files, build_logs, log_archive, dockerfile_build_queue
from buildtrigger.basehandler import BuildTriggerHandler from buildtrigger.basehandler import BuildTriggerHandler
from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource, from endpoints.api import (RepositoryParamResource, parse_args, query_param, nickname, resource,
require_repo_read, require_repo_write, validate_json_request, require_repo_read, require_repo_write, validate_json_request,
ApiResource, internal_only, format_date, api, path_param, ApiResource, internal_only, format_date, api, path_param,
require_repo_admin) require_repo_admin)
from endpoints.exception import Unauthorized, NotFound, InvalidRequest from endpoints.exception import Unauthorized, NotFound, InvalidRequest
from endpoints.building import start_build, PreparedBuild from endpoints.building import start_build, PreparedBuild
@ -20,7 +20,8 @@ from data import database
from data import model from data import model
from auth.auth_context import get_authenticated_user from auth.auth_context import get_authenticated_user
from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission, from auth.permissions import (ReadRepositoryPermission, ModifyRepositoryPermission,
AdministerRepositoryPermission, AdministerOrganizationPermission) AdministerRepositoryPermission, AdministerOrganizationPermission,
SuperUserPermission)
from data.buildlogs import BuildStatusRetrievalError from data.buildlogs import BuildStatusRetrievalError
from util.names import parse_robot_username from util.names import parse_robot_username
@ -87,26 +88,33 @@ def trigger_view(trigger, can_read=False, can_admin=False, for_build=False):
def build_status_view(build_obj): def build_status_view(build_obj):
phase = build_obj.phase phase = build_obj.phase
status = {}
error = None
try: try:
status = build_logs.get_status(build_obj.uuid) status = build_logs.get_status(build_obj.uuid)
except BuildStatusRetrievalError: except BuildStatusRetrievalError as bsre:
status = {}
phase = 'cannot_load' phase = 'cannot_load'
if SuperUserPermission().can():
error = str(bsre)
else:
error = 'Redis may be down. Please contact support.'
# If the status contains a heartbeat, then check to see if has been written in the last few if phase != 'cannot_load':
# minutes. If not, then the build timed out. # If the status contains a heartbeat, then check to see if has been written in the last few
if phase != database.BUILD_PHASE.COMPLETE and phase != database.BUILD_PHASE.ERROR: # minutes. If not, then the build timed out.
if status is not None and 'heartbeat' in status and status['heartbeat']: if phase != database.BUILD_PHASE.COMPLETE and phase != database.BUILD_PHASE.ERROR:
heartbeat = datetime.datetime.utcfromtimestamp(status['heartbeat']) if status is not None and 'heartbeat' in status and status['heartbeat']:
if datetime.datetime.utcnow() - heartbeat > datetime.timedelta(minutes=1): heartbeat = datetime.datetime.utcfromtimestamp(status['heartbeat'])
phase = database.BUILD_PHASE.INTERNAL_ERROR if datetime.datetime.utcnow() - heartbeat > datetime.timedelta(minutes=1):
phase = database.BUILD_PHASE.INTERNAL_ERROR
# If the phase is internal error, return 'error' instead if the number of retries # If the phase is internal error, return 'error' instead if the number of retries
# on the queue item is 0. # on the queue item is 0.
if phase == database.BUILD_PHASE.INTERNAL_ERROR: if phase == database.BUILD_PHASE.INTERNAL_ERROR:
retry = build_obj.queue_id and dockerfile_build_queue.has_retries_remaining(build_obj.queue_id) retry = build_obj.queue_id and dockerfile_build_queue.has_retries_remaining(build_obj.queue_id)
if not retry: if not retry:
phase = database.BUILD_PHASE.ERROR phase = database.BUILD_PHASE.ERROR
repo_namespace = build_obj.repository.namespace_user.username repo_namespace = build_obj.repository.namespace_user.username
repo_name = build_obj.repository.name repo_name = build_obj.repository.name
@ -134,7 +142,8 @@ def build_status_view(build_obj):
'repository': { 'repository': {
'namespace': repo_namespace, 'namespace': repo_namespace,
'name': repo_name 'name': repo_name
} },
'error': error,
} }
if can_write: if can_write:

View file

@ -6,7 +6,7 @@
box-shadow: inset 10px 10px 5px -9px rgba(0,0,0,0.75); box-shadow: inset 10px 10px 5px -9px rgba(0,0,0,0.75);
background-color: #263945; background-color: #263945;
min-height: 100px; min-height: 74px;
} }
.build-logs-view .co-alert { .build-logs-view .co-alert {

View file

@ -24,12 +24,17 @@
please check for JavaScript or networking issues and contact support. please check for JavaScript or networking issues and contact support.
</div> </div>
<div class="co-alert co-alert-danger" ng-if="loadError == 'unauthorized'">
You are not authorized to view builds logs. Please have the owner of the repository grant you
admin access to this repository.
</div>
<div ng-show="!loadError && pollChannel.skipping"> <div ng-show="!loadError && pollChannel.skipping">
Refreshing Build Status... Refreshing Build Status...
<span class="cor-loader"></span> <span class="cor-loader"></span>
</div> </div>
<div ng-show="!pollChannel.skipping"> <div ng-show="!loadError && !pollChannel.skipping">
<span class="no-logs" ng-if="!logEntries.length && currentBuild.phase == 'waiting'"> <span class="no-logs" ng-if="!logEntries.length && currentBuild.phase == 'waiting'">
(Waiting for build to start) (Waiting for build to start)
</span> </span>

View file

@ -132,10 +132,16 @@ angular.module('quay').directive('buildLogsView', function () {
} }
handleLogsData(resp, callback); handleLogsData(resp, callback);
}, function() { }, function(resp) {
if (resp.status == 403) {
$scope.loadError = 'unauthorized';
} else {
$scope.loadError = 'request-failed';
}
callback(false); callback(false);
}); });
}, function() { }, function() {
$scope.loadError = 'request-failed';
callback(false); callback(false);
}); });
}; };

View file

@ -15,7 +15,7 @@ angular.module('quay').directive('buildMessage', function () {
$scope.getBuildMessage = function (phase) { $scope.getBuildMessage = function (phase) {
switch (phase) { switch (phase) {
case 'cannot_load': case 'cannot_load':
return 'Cannot load build status - Please report this error'; return 'Cannot load build status';
case 'starting': case 'starting':
case 'initializing': case 'initializing':

File diff suppressed because one or more lines are too long

View file

@ -19,8 +19,14 @@
<!-- Build Information --> <!-- Build Information -->
<div class="build-info-bar" build="build" show-time="false"></div> <div class="build-info-bar" build="build" show-time="false"></div>
<!-- Error Status -->
<div class="co-alert co-alert-warning" ng-show="originalBuild.error"
style="margin-bottom: 8px; margin-top: 12px;">
{{ originalBuild.error }}
</div>
<!-- Current Status --> <!-- Current Status -->
<div class="build-status-header"> <div class="build-status-header" ng-show="!originalBuild.error">
<span class="build-icon-message" ng-class="build.phase"> <span class="build-icon-message" ng-class="build.phase">
<span class="cor-loader-inline" ng-if="isBuilding(build)"></span> <span class="cor-loader-inline" ng-if="isBuilding(build)"></span>
<span ng-if="!isBuilding(build)"> <span ng-if="!isBuilding(build)">
@ -57,7 +63,8 @@
<div class="build-logs-view" <div class="build-logs-view"
build="originalBuild" build="originalBuild"
use-timestamps="showLogTimestamps" use-timestamps="showLogTimestamps"
build-updated="setUpdatedBuild(build)"></div> build-updated="setUpdatedBuild(build)"
ng-show="!originalBuild.error"></div>
</div> </div>
</div> </div>
</div> </div>