Skip to content

Commit 08eecdb

Browse files
authored
Improve error messages in case of connection errors (#2210)
## Changes Improve error messages in case of connection errors ### Linked issues Partially resolves #1323 ### Functionality - [x] modified existing command: `databricks labs ucx (un)install` ### Tests - [x] manually tested - [x] added integration tests
1 parent 9464220 commit 08eecdb

File tree

4 files changed

+77
-29
lines changed

4 files changed

+77
-29
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1350,7 +1350,7 @@ from a VPC, or from a specific IP range.
13501350

13511351
**Solution:** Please check that your laptop has network connectivity to
13521352
the Databricks account and workspace. If not, you might need to be
1353-
connected to a VPN to access your workspace.
1353+
connected to a VPN or configure an HTTP proxy to access your workspace.
13541354

13551355
**From local machine to GitHub:** UCX needs internet access to connect
13561356
to [<u>github.com</u>](https://github.com) (to download the tool) from

src/databricks/labs/ucx/install.py

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
from pathlib import Path
1212
from typing import Any
1313

14+
from requests.exceptions import ConnectionError as RequestsConnectionError
15+
1416
import databricks.sdk.errors
1517
from databricks.labs.blueprint.entrypoint import get_logger, is_in_debug
1618
from databricks.labs.blueprint.installation import Installation, SerdeError
@@ -174,36 +176,43 @@ def run(
174176
config: WorkspaceConfig | None = None,
175177
) -> WorkspaceConfig:
176178
logger.info(f"Installing UCX v{self.product_info.version()}")
177-
if config is None:
178-
config = self.configure(default_config)
179-
if self._is_testing():
180-
return config
181-
workflows_deployment = WorkflowsDeployment(
182-
config,
183-
self.installation,
184-
self.install_state,
185-
self.workspace_client,
186-
self.wheels,
187-
self.product_info,
188-
verify_timeout,
189-
self._tasks,
190-
)
191-
workspace_installation = WorkspaceInstallation(
192-
config,
193-
self.installation,
194-
self.install_state,
195-
self.sql_backend,
196-
self.workspace_client,
197-
workflows_deployment,
198-
self.prompts,
199-
self.product_info,
200-
)
201179
try:
180+
if config is None:
181+
config = self.configure(default_config)
182+
if self._is_testing():
183+
return config
184+
workflows_deployment = WorkflowsDeployment(
185+
config,
186+
self.installation,
187+
self.install_state,
188+
self.workspace_client,
189+
self.wheels,
190+
self.product_info,
191+
verify_timeout,
192+
self._tasks,
193+
)
194+
workspace_installation = WorkspaceInstallation(
195+
config,
196+
self.installation,
197+
self.install_state,
198+
self.sql_backend,
199+
self.workspace_client,
200+
workflows_deployment,
201+
self.prompts,
202+
self.product_info,
203+
)
202204
workspace_installation.run()
203205
except ManyError as err:
204206
if len(err.errs) == 1:
205207
raise err.errs[0] from None
206208
raise err
209+
except TimeoutError as err:
210+
if isinstance(err.__cause__, RequestsConnectionError):
211+
logger.warning(
212+
f"Cannot connect with {self.workspace_client.config.host} see "
213+
f"https://github.com/databrickslabs/ucx#network-connectivity-issues for help: {err}"
214+
)
215+
raise err
207216
return config
208217

209218
def _is_testing(self):
@@ -282,6 +291,11 @@ def _confirm_force_install(self) -> bool:
282291
return False
283292

284293
def configure(self, default_config: WorkspaceConfig | None = None) -> WorkspaceConfig:
294+
"""Configure the workspaces
295+
296+
Notes:
297+
1. Connection errors are not handled within this configure method.
298+
"""
285299
try:
286300
config = self.installation.load(WorkspaceConfig)
287301
self._compare_remote_local_versions()

tests/integration/install/test_installation.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import logging
44
from datetime import timedelta
55

6-
import pytest # pylint: disable=wrong-import-order
7-
from databricks.labs.ucx.__about__ import __version__
6+
import pytest
87

8+
import databricks
99
from databricks.labs.blueprint.installation import Installation
1010
from databricks.labs.blueprint.parallel import ManyError
1111
from databricks.labs.blueprint.tui import MockPrompts
@@ -18,16 +18,16 @@
1818
NotFound,
1919
ResourceConflict,
2020
)
21-
2221
from databricks.sdk.retries import retried
2322
from databricks.sdk.service import compute
2423
from databricks.sdk.service.iam import PermissionLevel
2524

26-
import databricks
25+
from databricks.labs.ucx.__about__ import __version__
2726
from databricks.labs.ucx.config import WorkspaceConfig
2827
from databricks.labs.ucx.install import WorkspaceInstaller
2928
from databricks.labs.ucx.workspace_access.groups import MigratedGroup
3029

30+
3131
logger = logging.getLogger(__name__)
3232

3333

tests/unit/test_install.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
import json
33
import logging
44
from datetime import datetime, timedelta
5+
from typing import Any
56
from unittest.mock import MagicMock, create_autospec, patch
67

78
import pytest
89
import yaml
10+
from requests.exceptions import ConnectionError as RequestsConnectionError
911
from databricks.labs.blueprint.installation import Installation, MockInstallation
1012
from databricks.labs.blueprint.installer import InstallState
1113
from databricks.labs.blueprint.parallel import ManyError
@@ -14,6 +16,8 @@
1416
from databricks.labs.lsql.backends import MockBackend
1517
from databricks.labs.lsql.dashboards import DashboardMetadata
1618
from databricks.sdk import AccountClient, WorkspaceClient
19+
from databricks.sdk.core import Config
20+
from databricks.sdk.credentials_provider import credentials_strategy
1721
from databricks.sdk.errors import ( # pylint: disable=redefined-builtin
1822
AlreadyExists,
1923
InvalidParameterValue,
@@ -1929,3 +1933,33 @@ def test_upload_dependencies(ws, mock_installation):
19291933
workspace_installation.run()
19301934
wheels.upload_wheel_dependencies.assert_called_once()
19311935
wheels.upload_to_wsfs.assert_called_once()
1936+
1937+
1938+
@pytest.fixture
1939+
def no_connection_ws() -> WorkspaceClient:
1940+
"""Configure a workspace like it does not have an internet connection."""
1941+
1942+
@credentials_strategy("raise_connection_error", [])
1943+
def raise_connection_error(_: Any):
1944+
"""Mock no internet access by raising a ConnectionError"""
1945+
1946+
def inner():
1947+
raise RequestsConnectionError("no internet")
1948+
1949+
return inner
1950+
1951+
config = Config(
1952+
host="https://adb-123456789.12.azuredatabricks.net/",
1953+
credentials_strategy=raise_connection_error,
1954+
retry_timeout_seconds=1,
1955+
)
1956+
return WorkspaceClient(config=config)
1957+
1958+
1959+
@pytest.mark.parametrize("default_config", [None, WorkspaceConfig("ucx")])
1960+
def test_workspace_installer_warns_about_connection_error(caplog, no_connection_ws, default_config):
1961+
"""This test runs both with and without internet connection"""
1962+
workspace_installer = WorkspaceInstaller(no_connection_ws)
1963+
with pytest.raises(TimeoutError), caplog.at_level(logging.WARNING, logger="databricks.labs.ucx.source_code.jobs"):
1964+
workspace_installer.run(default_config=default_config)
1965+
assert "Cannot connect with" in caplog.text

0 commit comments

Comments
 (0)