Commit b2309ae6 authored by Markus Scheidgen's avatar Markus Scheidgen
Browse files

Improved gui error handling. Added chaos monkey to API. Fixed #188

parent 8c1fa0a8
Pipeline #53971 passed with stages
in 17 minutes and 12 seconds
......@@ -39,7 +39,6 @@ let gui_upload_id_counter = 0
class Upload {
constructor(json, api) {
this.api = api
this.handleApiError = api.handleApiError.bind(api)
// Cannot use upload_id as key in GUI, because uploads don't have an upload_id
// before upload is completed
......@@ -77,7 +76,7 @@ class Upload {
}
)
if (uploadRequest.error) {
this.handleApiError(uploadRequest.error)
handleApiError(uploadRequest.error)
}
if (uploadRequest.aborted) {
throw Error('User abort')
......@@ -103,7 +102,7 @@ class Upload {
order_by: orderBy || 'mainfile',
order: order || -1
}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.then(uploadJson => {
Object.assign(this, uploadJson)
......@@ -116,6 +115,33 @@ class Upload {
}
}
function handleApiError(e) {
if (e.name === 'CannotReachApi' || e.name === 'NotAuthorized' || e.name === 'DoesNotExist') {
throw e
}
if (e.response) {
const body = e.response.body
const message = (body && body.message) ? body.message : e.response.statusText
const errorMessage = `${message} (${e.response.status})`
let error = null
if (e.response.status === 404) {
error = new DoesNotExist(errorMessage)
} else if (e.response.status === 401) {
error = new NotAuthorized(errorMessage)
} else if (e.response.status === 502) {
error = new ApiError(errorMessage)
} else {
error = new Error(errorMessage)
}
error.status = e.response.status
throw error
} else {
const errorMessage = e.status ? `${e} (${e.status})` : '' + e
throw new Error(errorMessage)
}
}
class Api {
static async createSwaggerClient(userNameToken, password) {
let data
......@@ -134,42 +160,28 @@ class Api {
data = {authorizations: auth}
}
return Swagger(`${apiBase}/swagger.json`, data)
try {
return await Swagger(`${apiBase}/swagger.json`, data)
} catch (e) {
throw new ApiError()
}
}
constructor(user) {
this.onStartLoading = () => null
this.onFinishLoading = () => null
this.handleApiError = this.handleApiError.bind(this)
user = user || {}
this.auth_headers = {
'X-Token': user.token
}
this.swaggerPromise = Api.createSwaggerClient(user.token).catch(this.handleApiError)
this.swaggerPromise = Api.createSwaggerClient(user.token).catch(handleApiError)
// keep a list of localUploads, these are uploads that are currently uploaded through
// the browser and that therefore not yet returned by the backend
this.localUploads = []
}
handleApiError(e) {
if (e.response) {
const body = e.response.body
const message = (body && body.message) ? body.message : e.response.statusText
if (e.response.status === 404) {
throw new DoesNotExist(message)
} else if (e.response.status === 401) {
throw new NotAuthorized(message)
} else {
throw Error(`API error (${e.response.status}): ${message}`)
}
} else {
throw new ApiError()
}
}
createUpload(name) {
const upload = new Upload({
name: name,
......@@ -186,7 +198,7 @@ class Api {
this.onStartLoading()
return this.swaggerPromise
.then(client => client.apis.uploads.get_uploads({state: 'unpublished', page: 1, per_page: 1000}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => ({
...response.body,
results: response.body.results.map(uploadJson => {
......@@ -202,7 +214,7 @@ class Api {
this.onStartLoading()
return this.swaggerPromise
.then(client => client.apis.uploads.get_uploads({state: 'published', page: page || 1, per_page: perPage || 10}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => ({
...response.body,
results: response.body.results.map(uploadJson => {
......@@ -221,7 +233,7 @@ class Api {
upload_id: uploadId,
calc_id: calcId
}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => {
const result = response.body || response.text || response.data
if (typeof result === 'string') {
......@@ -248,7 +260,7 @@ class Api {
upload_id: uploadId,
calc_id: calcId
}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.text)
.finally(this.onFinishLoading)
}
......@@ -261,7 +273,7 @@ class Api {
calc_id: calcId,
path: null
}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
......@@ -273,7 +285,7 @@ class Api {
upload_id: uploadId,
calc_id: calcId
}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
......@@ -282,7 +294,7 @@ class Api {
this.onStartLoading()
return this.swaggerPromise
.then(client => client.apis.repo.search(search))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
......@@ -291,7 +303,7 @@ class Api {
this.onStartLoading()
return this.swaggerPromise
.then(client => client.apis.uploads.delete_upload({upload_id: uploadId}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
......@@ -308,7 +320,7 @@ class Api {
}
}
}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
......@@ -317,7 +329,7 @@ class Api {
this.onStartLoading()
return this.swaggerPromise
.then(client => client.apis.auth.get_token())
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
......@@ -337,7 +349,7 @@ class Api {
const loadMetaInfo = async(path) => {
return this.swaggerPromise
.then(client => client.apis.archive.get_metainfo({metainfo_package_name: path}))
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
}
const metaInfo = await loadMetaInfo(pkg)
......@@ -360,7 +372,7 @@ class Api {
.then(client => {
return client.apis.info.get_info()
.then(response => response.body)
.catch(this.handleApiError)
.catch(handleApiError)
})
.finally(this.onFinishLoading)
}
......@@ -371,7 +383,7 @@ class Api {
this.onStartLoading()
return this.swaggerPromise
.then(client => client.apis.uploads.get_upload_command())
.catch(this.handleApiError)
.catch(handleApiError)
.then(response => response.body)
.finally(this.onFinishLoading)
}
......@@ -423,7 +435,7 @@ export class ApiProviderComponent extends React.Component {
.then(client => {
client.apis.auth.get_user()
.catch(error => {
if (error.response.status !== 401) {
if (error.response && error.response.status !== 401) {
throw error
}
})
......@@ -449,11 +461,7 @@ export class ApiProviderComponent extends React.Component {
})
.catch(error => {
this.setState({api: this.createApi(), isLoggingIn: false, user: null})
if (error.message === 'Failed to fetch') {
this.props.raiseError(new ApiError())
} else {
this.props.raiseError(error)
}
this.props.raiseError(error)
})
},
logout: () => {
......
......@@ -16,7 +16,7 @@
All APIs are served by one Flask app (:py:mod:`nomad.api.app`) under different paths.
"""
from flask import Flask, jsonify, url_for
from flask import Flask, jsonify, url_for, abort
from flask_restplus import Api, fields
from flask_cors import CORS
from werkzeug.exceptions import HTTPException
......@@ -25,6 +25,7 @@ import os.path
import inspect
from datetime import datetime
import pytz
import random
from nomad import config, utils
......@@ -149,3 +150,10 @@ class RFC3339DateTime(fields.DateTime):
rfc3339DateTime = RFC3339DateTime()
@app.before_request
def before_request():
if config.services.api_chaos > 0:
if random.randint(0, 100) <= config.services.api_chaos:
abort(random.choice([400, 404, 500]), 'With best wishes from the chaos monkey.')
......@@ -32,7 +32,9 @@ def worker():
@run.command(help='Run the nomad development api.')
@click.option('--debug', help='Does run flask in debug.', is_flag=True)
def api(debug: bool):
@click.option('--with-chaos', type=int, default=0, help='Enable a certain percentage of chaos.')
def api(debug: bool, with_chaos: int):
config.services.api_chaos = with_chaos
run_api(debug=debug)
......
......@@ -121,6 +121,7 @@ services = NomadConfig(
api_port=8000,
api_base_path='/fairdi/nomad/latest/api',
api_secret='defaultApiSecret',
api_chaos=0,
admin_password='password',
disable_reset=True,
not_processed_value='not processed',
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment