Repository: getredash/redash

Issue:
For self-hosted instance, Google sheet connection test failed, but can't find any logs, how to trouble shooting such cases
![image](https://github.com/getredash/redash/assets/39286702/5b480d44-b38d-4035-a68e-49a5bc15220d)

![image](https://github.com/getredash/redash/assets/39286702/6c7a4e14-a7f7-4335-84e2-67eead78e260)

When test connections timeout, can't find any logs on worker/scheduler/server docker logs.
even make sure the log level is debug, can't find any related logs. how to trouble shooting this


Discussion:
I succeeded to reproduce this. 

When clicking "Test Connection", it shows "Connection Test Failed:" at right bottom on screen. However, there is no error log in redash-server and redash-worker.

#### redash-server-1
```
[2023-11-30 14:12:50,676][PID:11][INFO][metrics] method=POST path=/api/data_sources/2/test endpoint=datasourcetestresource status=200 content_type=application/json content_length=28 duration=2086.25 query_count=4 query_duration=24.14
[2023-11-30 14:12:50,694][PID:11][INFO][werkzeug] 10.0.2.2 - - [30/Nov/2023 14:12:50] "POST /api/data_sources/2/test HTTP/1.1" 200 -
```

#### redash-worker-1
```
[2023-11-30 14:12:50,676][PID:7][INFO][rq.worker] default: 023f6ae5-21ae-4146-9482-2829b3d06e8e
[2023-11-30 14:12:50,798][PID:552][INFO][rq.worker] default: Job OK (023f6ae5-21ae-4146-9482-2829b3d06e8e)
[2023-11-30 14:12:50,799][PID:552][INFO][rq.worker] Result is kept for 500 seconds
```


![image](https://github.com/getredash/redash/assets/36331/c10d68ba-a0a4-4118-aa4f-3cde9619436f)
By the way, the cause of the Connection Failure was that the Google Sheet API was not enabled. After enabling this, I ran "Test Connection" and it shows "Success".

I confirmed that the query can also be executed successfully. If you run a query with the Google Sheet API disabled, you will not get an error and will see 0 results. At this time, the following error is output in the redash-worker log.

```
  File "/usr/local/lib/python3.8/site-packages/gspread/client.py", line 199, in open_by_key
    spreadsheet = Spreadsheet(self, {"id": key})
  File "/usr/local/lib/python3.8/site-packages/gspread/spreadsheet.py", line 37, in __init__
    metadata = self.fetch_sheet_metadata()
  File "/usr/local/lib/python3.8/site-packages/gspread/spreadsheet.py", line 247, in fetch_sheet_metadata
    r = self.client.request("get", url, params=params)
  File "/usr/local/lib/python3.8/site-packages/gspread/client.py", line 94, in request
    raise APIError(response)
gspread.exceptions.APIError: {'code': 403, 'message': 'Google Sheets API has not been used in (...snip...)
```

Thanks masayuki038 for your kindly reply, but actually I have two self-hosted instance, one is OK for the API test, another is not, and from the docker logs, I can't tell any different, and I did the test with the same google sheet API and credential, don't know how to trouble shoot the issue and how to fix the instance
By the way, I found some evidence, but not sure what's going wrong.
For the not working instance, in the server logs, can see "[INFO][werkzeug] 10.100.3.61 - - [30/Nov/2023 10:57:56] "GET /api/data_sources/1 HTTP/1.1" 200 -" the IP address is not the IP of server running docker containers.
For the working instance, in the server logs, can see "[INFO][werkzeug] 172.21.0.1 - - [30/Nov/2023 21:50:35] "GET /api/admin/queries/rq_status HTTP/1.1" 200 -" the IP address is the Galway of running docker containers.

how did this happened 
@joeyrensh Could you try these?

1. For the not working instance, create a query with Google Sheets data source and run it even if "Test Connection" failed
2. Check **redash worker log**

You will find a traceback like the one I described in a previous comment. For security reasons, I omitted some parts in it, but they may contain information about the cause of the error. I noticed from this log that Google Sheet API is disabled.

Thanks @masayuki038 , follow your steps, I got below trace " HTTPSConnectionPool(host='oauth2.googleapis.com', port=443): Read timed out. (read timeout=120)" , do you know how to resolve it?  From host, I can ping 'oauth2.googleapis.com' with success response.
@joeyrensh Thanks for your confirmation.
Since "Read timed out" was shown, I think this is a network issue. Even if ping is OK, we don't know whether TLS is OK or not. Please your network settings and gateways.

If you run this command on your Redash server and it returns 404 or any HTTP response code, you can access to googleapi server with TLS.

```
curl -XGET https://oauth2.googleapis.com -o /dev/null -w '%{http_code}\n' -s
```
@masayuki038 Follow your steps, I think I have found some evidence, I try to explain a little bit.
1、For the workable instance, run above command in the worker container can get 404 response.
2、For the not workable instance, run above command in the worker container get no response, just hang in there.
3、when I build the local image, I found that, I need to add "network = host" mode to the docker compose build part, otherwise APT-get can't be updated.

so my guess is that, the docker may have network issue, it have different with the host network, but I haven't found any settings or configuration have problem, including the IPtables, do you have any proposal for this ?

below is the redash_default networking, no any issues found.

[
    {
        "Name": "redash_default",
        "Id": "1a5461c1f3b0fb298720fa6ab8221c31da4ac00f5addfee4cba6eec4147c1dc7",
        "Created": "2023-12-04T13:18:06.462520271+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "464f97dad1b1cc61b33371e08c5e9fdaf4b6cc22b7ee4a393efffbc3566dded9": {
                "Name": "redash-postgres-1",
                "EndpointID": "20e5ccd51eee64b8eb03a97c31e27864c5910e4d536d7598ced80b620e79f6bb",
                "MacAddress": "02:42:ac:12:00:04",
                "IPv4Address": "172.18.0.4/16",
                "IPv6Address": ""
            },
            "4d25c4c38c8bb5fbd4f8424f12c945b07a71088a2bb15ad2a0c54dc9b2954227": {
                "Name": "redash-worker-1",
                "EndpointID": "137fb29430f9abc7f8535ea7d57b036580c656267da8935b478c4596385d075d",
                "MacAddress": "02:42:ac:12:00:06",
                "IPv4Address": "172.18.0.6/16",
                "IPv6Address": ""
            },
            "7d0cccd8f56db9c5674d65ed0c2da728bef9b22e9100d245617be5c091f7e55e": {
                "Name": "redash-server-1",
                "EndpointID": "4757013004d6c24e0a03d54abb1e5c3f9ac57ac8afc2f1c630f09d9dceacbb12",
                "MacAddress": "02:42:ac:12:00:05",
                "IPv4Address": "172.18.0.5/16",
                "IPv6Address": ""
            },
            "e50c1fca6ed508f001200b3d370492f6f483a936f1c1c14449138f8f38c70e17": {
                "Name": "redash-redis-1",
                "EndpointID": "371c7aa28a10f0ad87ff4caa1e401642449103b34d13d0d0ae7d64acc842199c",
                "MacAddress": "02:42:ac:12:00:02",
                "IPv4Address": "172.18.0.2/16",
                "IPv6Address": ""
            },
            "ed217029feb3da964c0d064ec6dc4634c16035fa07463ff215db011b274609df": {
                "Name": "redash-email-1",
                "EndpointID": "25699a86bcc9ee597afbf489a9a15050487ea797d4714f7322bde3badbb1ba92",
                "MacAddress": "02:42:ac:12:00:03",
                "IPv4Address": "172.18.0.3/16",
                "IPv6Address": ""
            },
            "eeb9b3321513628a5ad2fbbbeda168f3a3e81fd6430649d4be596f08b54f0be0": {
                "Name": "redash-scheduler-1",
                "EndpointID": "e5f8e0c4dbfa6d680f173eea3016cddb391055cfc8552bace26c8bc096f6220a",
                "MacAddress": "02:42:ac:12:00:07",
                "IPv4Address": "172.18.0.7/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "redash",
            "com.docker.compose.version": "2.20.2"
        }
    }
]

@joeyrensh Thanks for your reply. However, sorry, but I don't think we should discuss Docker's network settings here. This is the place to discuss Redash issues.

> 3、when I build the local image, I found that, I need to add "network = host" mode to the docker compose build part, otherwise APT-get can't be updated.

I think it's best to contact the administrator of your Linux Server hosting Docker.

thanks a lot for your help , I have resolve all the issues, can close this ticket
@joeyrensh Thanks for reporting the result. I'm happy to hear it!
Please keep this issue since I think Redash should log an error on "Test Connection" failure. I will fix this.

Thanks for your contribution!

Resolving patch:
diff --git a/redash/query_runner/google_spreadsheets.py b/redash/query_runner/google_spreadsheets.py
--- a/redash/query_runner/google_spreadsheets.py
+++ b/redash/query_runner/google_spreadsheets.py
@@ -23,6 +23,7 @@
 try:
     import google.auth
     import gspread
+    from google.auth.exceptions import GoogleAuthError
     from google.oauth2.service_account import Credentials
     from gspread.exceptions import APIError
     from gspread.exceptions import WorksheetNotFound as GSWorksheetNotFound
@@ -230,13 +231,17 @@ def _get_spreadsheet_service(self):
         return spreadsheetservice
 
     def test_connection(self):
-        service = self._get_spreadsheet_service()
         test_spreadsheet_key = "1S0mld7LMbUad8LYlo13Os9f7eNjw57MqVC0YiCd1Jis"
         try:
+            service = self._get_spreadsheet_service()
             service.open_by_key(test_spreadsheet_key).worksheets()
         except APIError as e:
+            logger.exception(e)
             message = parse_api_error(e)
             raise Exception(message)
+        except GoogleAuthError as e:
+            logger.exception(e)
+            raise Exception(str(e))
 
     def run_query(self, query, user):
         logger.debug("Spreadsheet is about to execute query: %s", query)


Test patch:
diff --git a/tests/query_runner/test_google_spreadsheets.py b/tests/query_runner/test_google_spreadsheets.py
--- a/tests/query_runner/test_google_spreadsheets.py
+++ b/tests/query_runner/test_google_spreadsheets.py
@@ -1,12 +1,16 @@
 import datetime
 from unittest import TestCase
 
-from mock import MagicMock
+import pytest
+from google.auth.exceptions import TransportError
+from gspread.exceptions import APIError
+from mock import MagicMock, patch
 
 from redash.query_runner import TYPE_DATETIME, TYPE_FLOAT
 from redash.query_runner.google_spreadsheets import (
     TYPE_BOOLEAN,
     TYPE_STRING,
+    GoogleSpreadsheet,
     WorksheetNotFoundByTitleError,
     WorksheetNotFoundError,
     _get_columns_and_column_names,
@@ -171,3 +175,32 @@ def test_is_url_key(self):
 
         _key = "key|0"
         self.assertFalse(is_url_key(_key))
+
+
+class TestConnection(TestCase):
+    @patch("redash.query_runner.google_spreadsheets.google.auth.default")
+    @patch("redash.query_runner.google_spreadsheets.gspread.Client")
+    def test_connect_succuess(self, mock_client, _mock_auth_default):
+        try:
+            qr_gspread = GoogleSpreadsheet({})
+            qr_gspread.test_connection()
+            mock_client().login.assert_called_once_with()
+            mock_client().open_by_key.assert_called_once()
+        except Exception:
+            self.fail("test_connection failed")
+
+    @patch("redash.query_runner.google_spreadsheets.google.auth.default")
+    def test_connect_fail_with_transport_error(self, mock_auth_default):
+        mock_auth_default.side_effect = TransportError("Connection Refused")
+        qr_gspread = GoogleSpreadsheet({})
+        with pytest.raises(Exception):
+            qr_gspread.test_connection()
+
+    @patch("redash.query_runner.google_spreadsheets.google.auth.default")
+    def test_connect_fail_with_api_error(self, mock_auth_default):
+        mock_response = MagicMock()
+        mock_response.json.return_value = {"error": {"message": "Sheet API is disabled"}}
+        mock_auth_default.side_effect = APIError(mock_response)
+        qr_gspread = GoogleSpreadsheet({})
+        with pytest.raises(Exception):
+            qr_gspread.test_connection()