feat: invalid hostname and password error messages (MySQL) (#14089)

* custom errors for mySQL

* initial custom errors and tests for MySQL

* revisions
This commit is contained in:
AAfghahi 2021-04-13 17:42:31 -04:00 committed by GitHub
parent 353038e59e
commit b77477a9dd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 128 additions and 3 deletions

View File

@ -129,7 +129,7 @@ running a command. Please reach out to your administrator.
Superset encountered an unexpected error. Superset encountered an unexpected error.
``` ```
Someething unexpected happened in the Superset backend. Please reach out Something unexpected happened in the Superset backend. Please reach out
to your administrator. to your administrator.
## Issue 1012 ## Issue 1012
@ -147,5 +147,13 @@ that the username is typed correctly and exists in the database.
The password provided when connecting to a database is not valid. The password provided when connecting to a database is not valid.
``` ```
The user provided a password that is incorrect. Please check that the The user provided a password that is incorrect. Please check that the password is typed correctly.
password is typed correctly.
## Issue 1014
```
Either the username or the password used are incorrect.
```
Either the username provided does not exist or the password was written incorrectly. Please
check that the username and password were typed correctly.

View File

@ -36,6 +36,7 @@ export const ErrorTypeEnum = {
'TEST_CONNECTION_INVALID_HOSTNAME_ERROR', 'TEST_CONNECTION_INVALID_HOSTNAME_ERROR',
TEST_CONNECTION_PORT_CLOSED_ERROR: 'TEST_CONNECTION_PORT_CLOSED_ERROR', TEST_CONNECTION_PORT_CLOSED_ERROR: 'TEST_CONNECTION_PORT_CLOSED_ERROR',
TEST_CONNECTION_HOST_DOWN_ERROR: 'TEST_CONNECTION_HOST_DOWN_ERROR', TEST_CONNECTION_HOST_DOWN_ERROR: 'TEST_CONNECTION_HOST_DOWN_ERROR',
TEST_CONNECTION_ACCESS_DENIED_ERROR: 'TEST_CONNECTION_ACCESS_DENIED_ERROR',
// Viz errors // Viz errors
VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR', VIZ_GET_DF_ERROR: 'VIZ_GET_DF_ERROR',

View File

@ -19,6 +19,7 @@ from datetime import datetime
from typing import Any, Callable, Dict, Match, Optional, Pattern, Tuple, Union from typing import Any, Callable, Dict, Match, Optional, Pattern, Tuple, Union
from urllib import parse from urllib import parse
from flask_babel import gettext as __
from sqlalchemy.dialects.mysql import ( from sqlalchemy.dialects.mysql import (
BIT, BIT,
DECIMAL, DECIMAL,
@ -35,9 +36,21 @@ from sqlalchemy.engine.url import URL
from sqlalchemy.types import TypeEngine from sqlalchemy.types import TypeEngine
from superset.db_engine_specs.base import BaseEngineSpec from superset.db_engine_specs.base import BaseEngineSpec
from superset.errors import SupersetErrorType
from superset.utils import core as utils from superset.utils import core as utils
from superset.utils.core import ColumnSpec, GenericDataType from superset.utils.core import ColumnSpec, GenericDataType
# Regular expressions to catch custom errors
TEST_CONNECTION_ACCESS_DENIED_REGEX = re.compile(
"Access denied for user '(?P<username>.*?)'@'(?P<hostname>.*?)'. "
)
TEST_CONNECTION_INVALID_HOSTNAME_REGEX = re.compile(
"Unknown MySQL server host '(?P<hostname>.*?)'."
)
TEST_CONNECTION_HOST_DOWN_REGEX = re.compile(
"Can't connect to MySQL server on '(?P<hostname>.*?)'."
)
class MySQLEngineSpec(BaseEngineSpec): class MySQLEngineSpec(BaseEngineSpec):
engine = "mysql" engine = "mysql"
@ -93,6 +106,21 @@ class MySQLEngineSpec(BaseEngineSpec):
type_code_map: Dict[int, str] = {} # loaded from get_datatype only if needed type_code_map: Dict[int, str] = {} # loaded from get_datatype only if needed
custom_errors = {
TEST_CONNECTION_ACCESS_DENIED_REGEX: (
__('Either the username "%(username)s" or the password is incorrect.'),
SupersetErrorType.TEST_CONNECTION_ACCESS_DENIED_ERROR,
),
TEST_CONNECTION_INVALID_HOSTNAME_REGEX: (
__('Unknown MySQL server host "%(hostname)s".'),
SupersetErrorType.TEST_CONNECTION_INVALID_HOSTNAME_ERROR,
),
TEST_CONNECTION_HOST_DOWN_REGEX: (
__('The host "%(hostname)s" might be down and can\'t be reached.'),
SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR,
),
}
@classmethod @classmethod
def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]: def convert_dttm(cls, target_type: str, dttm: datetime) -> Optional[str]:
tt = target_type.upper() tt = target_type.upper()

View File

@ -44,6 +44,7 @@ class SupersetErrorType(str, Enum):
TEST_CONNECTION_INVALID_HOSTNAME_ERROR = "TEST_CONNECTION_INVALID_HOSTNAME_ERROR" TEST_CONNECTION_INVALID_HOSTNAME_ERROR = "TEST_CONNECTION_INVALID_HOSTNAME_ERROR"
TEST_CONNECTION_PORT_CLOSED_ERROR = "TEST_CONNECTION_PORT_CLOSED_ERROR" TEST_CONNECTION_PORT_CLOSED_ERROR = "TEST_CONNECTION_PORT_CLOSED_ERROR"
TEST_CONNECTION_HOST_DOWN_ERROR = "TEST_CONNECTION_HOST_DOWN_ERROR" TEST_CONNECTION_HOST_DOWN_ERROR = "TEST_CONNECTION_HOST_DOWN_ERROR"
TEST_CONNECTION_ACCESS_DENIED_ERROR = "TEST_CONNECTION_ACCESS_DENIED_ERROR"
# Viz errors # Viz errors
VIZ_GET_DF_ERROR = "VIZ_GET_DF_ERROR" VIZ_GET_DF_ERROR = "VIZ_GET_DF_ERROR"
@ -173,6 +174,12 @@ ERROR_TYPES_TO_ISSUE_CODES_MAPPING = {
), ),
}, },
], ],
SupersetErrorType.TEST_CONNECTION_ACCESS_DENIED_ERROR: [
{
"code": 1014,
"message": _("Issue 1014 - Either the username or the password is wrong."),
}
],
} }

View File

@ -20,6 +20,7 @@ from sqlalchemy.dialects import mysql
from sqlalchemy.dialects.mysql import DATE, NVARCHAR, TEXT, VARCHAR from sqlalchemy.dialects.mysql import DATE, NVARCHAR, TEXT, VARCHAR
from superset.db_engine_specs.mysql import MySQLEngineSpec from superset.db_engine_specs.mysql import MySQLEngineSpec
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.utils.core import GenericDataType from superset.utils.core import GenericDataType
from tests.db_engine_specs.base_tests import TestDbEngineSpec from tests.db_engine_specs.base_tests import TestDbEngineSpec
@ -104,3 +105,83 @@ class TestMySQLEngineSpecsDbEngineSpec(TestDbEngineSpec):
exception = OperationalError(123, message) exception = OperationalError(123, message)
extracted_message = MySQLEngineSpec._extract_error_message(exception) extracted_message = MySQLEngineSpec._extract_error_message(exception)
assert extracted_message == message assert extracted_message == message
def test_extract_errors(self):
"""
Test that custom error messages are extracted correctly.
"""
msg = "mysql: Access denied for user 'test'@'testuser.com'. "
result = MySQLEngineSpec.extract_errors(Exception(msg))
assert result == [
SupersetError(
error_type=SupersetErrorType.TEST_CONNECTION_ACCESS_DENIED_ERROR,
message='Either the username "test" or the password is incorrect.',
level=ErrorLevel.ERROR,
extra={
"engine_name": "MySQL",
"issue_codes": [
{
"code": 1014,
"message": "Issue 1014 - Either the username or the password is wrong.",
}
],
},
)
]
msg = "mysql: Unknown MySQL server host 'badhostname.com'. "
result = MySQLEngineSpec.extract_errors(Exception(msg))
assert result == [
SupersetError(
error_type=SupersetErrorType.TEST_CONNECTION_INVALID_HOSTNAME_ERROR,
message='Unknown MySQL server host "badhostname.com".',
level=ErrorLevel.ERROR,
extra={
"engine_name": "MySQL",
"issue_codes": [
{
"code": 1007,
"message": "Issue 1007 - The hostname provided can't be resolved.",
}
],
},
)
]
msg = "mysql: Can't connect to MySQL server on 'badconnection.com'."
result = MySQLEngineSpec.extract_errors(Exception(msg))
assert result == [
SupersetError(
error_type=SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR,
message='The host "badconnection.com" might be down and can\'t be reached.',
level=ErrorLevel.ERROR,
extra={
"engine_name": "MySQL",
"issue_codes": [
{
"code": 1007,
"message": "Issue 1007 - The hostname provided can't be resolved.",
}
],
},
)
]
msg = "mysql: Can't connect to MySQL server on '93.184.216.34'."
result = MySQLEngineSpec.extract_errors(Exception(msg))
assert result == [
SupersetError(
error_type=SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR,
message='The host "93.184.216.34" might be down and can\'t be reached.',
level=ErrorLevel.ERROR,
extra={
"engine_name": "MySQL",
"issue_codes": [
{
"code": 10007,
"message": "Issue 1007 - The hostname provided can't be resolved.",
}
],
},
)
]