Human readable database inconsistent error (#12950)

* Human readable database inconsistent error
In case a db error causes an exception, check validate and show the db errors to the user in the webui.

* only failed validations

* fix style
This commit is contained in:
Tony Murray
2021-06-14 13:33:59 -05:00
committed by GitHub
parent 20c44b85c3
commit bd3da058d3
8 changed files with 122 additions and 9 deletions

View File

@@ -0,0 +1,86 @@
<?php
/*
* DatabaseVersionTooLow.php
*
* -Description-
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @package LibreNMS
* @link http://librenms.org
* @copyright 2021 Tony Murray
* @author Tony Murray <murraytony@gmail.com>
*/
namespace LibreNMS\Exceptions;
use Illuminate\Database\QueryException;
use LibreNMS\Interfaces\Exceptions\UpgradeableException;
use LibreNMS\ValidationResult;
use LibreNMS\Validations\Database;
use LibreNMS\Validator;
use Throwable;
class DatabaseInconsistentException extends \Exception implements UpgradeableException
{
/**
* @var \LibreNMS\ValidationResult[]
*/
private $validationResults;
public function __construct($validationResults, $message = '', $code = 0, Throwable $previous = null)
{
$this->validationResults = $validationResults;
parent::__construct($message, $code, $previous);
}
public static function upgrade($exception)
{
if ($exception instanceof QueryException || $exception->getPrevious() instanceof QueryException) {
$validator = new Validator();
(new Database())->validate($validator);
// get only failed results
$results = array_filter($validator->getResults('database'), function (ValidationResult $result) {
return $result->getStatus() === ValidationResult::FAILURE;
});
if ($results) {
return new static($results, $exception->getMessage(), $exception->getCode(), $exception);
}
}
return null;
}
/**
* Render the exception into an HTTP or JSON response.
*
* @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response
*/
public function render(\Illuminate\Http\Request $request)
{
$message = trans('exceptions.database_inconsistent.title');
if (isset($this->validationResults[0])) {
$message .= ': ' . $this->validationResults[0]->getMessage();
}
return $request->wantsJson() ? response()->json([
'status' => 'error',
'message' => $message,
]) : response()->view('errors.db_inconsistent', [
'results' => $this->validationResults,
]);
}
}

View File

@@ -106,16 +106,16 @@ class Database extends BaseValidation
if (version_compare($version[0], self::MARIADB_MIN_VERSION, '<=')) {
$validator->fail(
'MariaDB version ' . self::MARIADB_MIN_VERSION . ' is the minimum supported version as of ' .
self::MARIADB_MIN_VERSION_DATE . '. Update MariaDB to a supported version ' .
self::MARIADB_RECOMMENDED_VERSION . ' suggested).'
self::MARIADB_MIN_VERSION_DATE . '.',
'Update MariaDB to a supported version, ' . self::MARIADB_RECOMMENDED_VERSION . ' suggested.'
);
}
} else {
if (version_compare($version[0], self::MYSQL_MIN_VERSION, '<=')) {
$validator->fail(
'MySQL version ' . self::MYSQL_MIN_VERSION . ' is the minimum supported version as of ' .
self::MYSQL_MIN_VERSION_DATE . '. Update MySQL to a supported version (' .
self::MYSQL_RECOMMENDED_VERSION . ' suggested).'
self::MYSQL_MIN_VERSION_DATE . '.',
'Update MySQL to a supported version, ' . self::MYSQL_RECOMMENDED_VERSION . ' suggested.'
);
}
}

View File

@@ -118,7 +118,7 @@ class Validator
* Get the ValidationResults for a specific validation group.
*
* @param string $validation_group
* @return array
* @return ValidationResult[]
*/
public function getResults($validation_group = null)
{

View File

@@ -2,7 +2,6 @@
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
@@ -33,6 +32,7 @@ class Handler extends ExceptionHandler
\LibreNMS\Exceptions\DuskUnsafeException::class,
\LibreNMS\Exceptions\UnserializableRouteCache::class,
\LibreNMS\Exceptions\MaximumExecutionTimeExceeded::class,
\LibreNMS\Exceptions\DatabaseInconsistentException::class,
];
public function render($request, Throwable $exception)

View File

@@ -79,11 +79,10 @@ class DatabaseController extends InstallationController implements InstallerStep
(new Database())->validateSystem($validator);
$results = $validator->getResults('database');
/** @var \LibreNMS\ValidationResult $result */
foreach ($results as $result) {
if ($result->getStatus() == ValidationResult::FAILURE) {
$ok = false;
$messages[] = $result->getMessage();
$messages[] = $result->getMessage() . ' ' . $result->getFix();
}
}
} catch (\Exception $e) {

View File

@@ -4,6 +4,10 @@ return [
'database_connect' => [
'title' => 'Error connecting to database',
],
'database_inconsistent' => [
'title' => 'Database inconsistent',
'header' => 'Database inconsistencies found during a database error, please fix to continue.',
],
'dusk_unsafe' => [
'title' => 'It is unsafe to run Dusk in production',
'message' => 'Run ":command" to remove Dusk or if you are a developer set the appropriate APP_ENV',

View File

@@ -0,0 +1,20 @@
@extends('layouts.error')
@section('title')
@lang('exceptions.database_inconsistent.title')
@endsection
@section('content')
<h3>@lang('exceptions.database_inconsistent.header')</h3>
<div class="message-block">
@foreach($results as $result)
<p>
<h2>{{ $result->getMessage() }}</h2>
@if($result->hasFix())
<div style="margin-left: 3em; font-size: 18px">{{ $result->getFix() }}</div>
@endif
</p>
@endforeach
</div>
@endsection

View File

@@ -46,6 +46,8 @@
.trace-method { color: #B0413E; font-weight: bold; }
.trace-arguments { color: #777; font-weight: normal; padding-left: 2px; }
.message-block { margin: 30px 0; }
hr.separator { border: 0; margin: 1.8em 0; height: 1px; background: #333 linear-gradient(to right, #ccc, #333, #ccc); }
@media (min-width: 575px) {
@@ -65,7 +67,9 @@
</div>
<div class="container">
@yield('content')
<div class="message-block">
@yield('content')
</div>
<hr class="separator"/>
<p>@lang("Check your log for more details.") ({{ isset($log_file) ? $log_file : 'librenms.log' }})</p>