diff --git a/superset/db_engine_specs/redshift.py b/superset/db_engine_specs/redshift.py index ef7b0e29a3e..90afc27dc05 100644 --- a/superset/db_engine_specs/redshift.py +++ b/superset/db_engine_specs/redshift.py @@ -14,7 +14,31 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import re + +from flask_babel import gettext as __ + from superset.db_engine_specs.postgres import PostgresBaseEngineSpec +from superset.errors import SupersetErrorType + +# Regular expressions to catch custom errors +TEST_CONNECTION_ACCESS_DENIED_REGEX = re.compile( + 'password authentication failed for user "(?P.*?)"' +) +TEST_CONNECTION_INVALID_HOSTNAME_REGEX = re.compile( + 'could not translate host name "(?P.*?)" to address: ' + "nodename nor servname provided, or not known" +) +TEST_CONNECTION_PORT_CLOSED_REGEX = re.compile( + r"could not connect to server: Connection refused\s+Is the server " + r'running on host "(?P.*?)" (\(.*?\) )?and accepting\s+TCP/IP ' + r"connections on port (?P.*?)\?" +) +TEST_CONNECTION_HOST_DOWN_REGEX = re.compile( + r"could not connect to server: (?P.*?)\s+Is the server running on " + r'host "(?P.*?)" (\(.*?\) )?and accepting\s+TCP/IP ' + r"connections on port (?P.*?)\?" +) class RedshiftEngineSpec(PostgresBaseEngineSpec): @@ -22,6 +46,28 @@ class RedshiftEngineSpec(PostgresBaseEngineSpec): engine_name = "Amazon Redshift" max_column_name_length = 127 + 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: ( + __('The hostname "%(hostname)s" cannot be resolved.'), + SupersetErrorType.TEST_CONNECTION_INVALID_HOSTNAME_ERROR, + ), + TEST_CONNECTION_PORT_CLOSED_REGEX: ( + __('Port %(port)s on hostname "%(hostname)s" refused the connection.'), + SupersetErrorType.TEST_CONNECTION_PORT_CLOSED_ERROR, + ), + TEST_CONNECTION_HOST_DOWN_REGEX: ( + __( + 'The host "%(hostname)s" might be down, and can\'t be ' + "reached on port %(port)s." + ), + SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR, + ), + } + @staticmethod def _mutate_label(label: str) -> str: """ diff --git a/tests/db_engine_specs/redshift_tests.py b/tests/db_engine_specs/redshift_tests.py new file mode 100644 index 00000000000..cacb90a8eaa --- /dev/null +++ b/tests/db_engine_specs/redshift_tests.py @@ -0,0 +1,147 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +from textwrap import dedent +from unittest import mock + +from superset.db_engine_specs.redshift import RedshiftEngineSpec +from superset.errors import ErrorLevel, SupersetError, SupersetErrorType +from tests.db_engine_specs.base_tests import TestDbEngineSpec + + +class TestRedshiftDbEngineSpec(TestDbEngineSpec): + def test_extract_errors(self): + """ + Test that custom error messages are extracted correctly. + """ + msg = 'FATAL: password authentication failed for user "wronguser"' + result = RedshiftEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_ACCESS_DENIED_ERROR, + message='Either the username "wronguser" or the password is incorrect.', + level=ErrorLevel.ERROR, + extra={ + "engine_name": "Amazon Redshift", + "issue_codes": [ + { + "code": 1014, + "message": "Issue 1014 - Either the username or the password is wrong", + } + ], + }, + ) + ] + + msg = 'redshift: error: could not translate host name "badhost" to address: nodename nor servname provided, or not known' + result = RedshiftEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_INVALID_HOSTNAME_ERROR, + message='The hostname "badhost" cannot be resolved.', + level=ErrorLevel.ERROR, + extra={ + "engine_name": "Amazon Redshift", + "issue_codes": [ + { + "code": 1007, + "message": "Issue 1007 - The hostname provided can't be resolved.", + } + ], + }, + ) + ] + msg = dedent( + """ +psql: error: could not connect to server: Connection refused + Is the server running on host "localhost" (::1) and accepting + TCP/IP connections on port 12345? +could not connect to server: Connection refused + Is the server running on host "localhost" (127.0.0.1) and accepting + TCP/IP connections on port 12345? + """ + ) + result = RedshiftEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_PORT_CLOSED_ERROR, + message='Port 12345 on hostname "localhost" refused the connection.', + level=ErrorLevel.ERROR, + extra={ + "engine_name": "Amazon Redshift", + "issue_codes": [ + {"code": 1008, "message": "Issue 1008 - The port is closed."} + ], + }, + ) + ] + + msg = dedent( + """ +psql: error: could not connect to server: Operation timed out + Is the server running on host "example.com" (93.184.216.34) and accepting + TCP/IP connections on port 12345? + """ + ) + result = RedshiftEngineSpec.extract_errors(Exception(msg)) + assert result == [ + SupersetError( + error_type=SupersetErrorType.TEST_CONNECTION_HOST_DOWN_ERROR, + message=( + 'The host "example.com" might be down, ' + "and can't be reached on port 12345." + ), + level=ErrorLevel.ERROR, + extra={ + "engine_name": "Amazon Redshift", + "issue_codes": [ + { + "code": 1009, + "message": "Issue 1009 - The host might be down, and can't be reached on the provided port.", + } + ], + }, + ) + ] + + # response with IP only + msg = dedent( + """ +psql: error: could not connect to server: Operation timed out + Is the server running on host "93.184.216.34" and accepting + TCP/IP connections on port 12345? + """ + ) + result = RedshiftEngineSpec.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 on port 12345." + ), + level=ErrorLevel.ERROR, + extra={ + "engine_name": "Amazon Redshift", + "issue_codes": [ + { + "code": 1009, + "message": "Issue 1009 - The host might be down, and can't be reached on the provided port.", + } + ], + }, + ) + ]