Skip to content

Commit 7e2a3d4

Browse files
feat(service): restore optimized migration check (#2854)
- handle the GitLab library specific errors. - fall back to full repository checkout in case of unforeseen errors. fix #2546
1 parent fe1c2c7 commit 7e2a3d4

File tree

7 files changed

+94
-16
lines changed

7 files changed

+94
-16
lines changed

renku/ui/service/controllers/cache_migrations_check.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
from pathlib import Path
2222

2323
from renku.command.migrate import migrations_check
24-
from renku.core.errors import RenkuException
24+
from renku.core.errors import AuthenticationError, ProjectNotFound, RenkuException
2525
from renku.core.util.contexts import click_context
2626
from renku.ui.service.controllers.api.abstract import ServiceCtrl
2727
from renku.ui.service.controllers.api.mixins import RenkuOperationMixin
@@ -71,11 +71,15 @@ def renku_op(self):
7171

7272
def to_response(self):
7373
"""Execute controller flow and serialize to service response."""
74-
return result_response(self.RESPONSE_SERIALIZER, self.execute_op())
74+
if "project_id" in self.context:
75+
result = self.execute_op()
76+
else:
77+
# NOTE: use quick flow but fallback to regular flow in case of unexpected exceptions
78+
try:
79+
result = self._fast_op_without_cache()
80+
except (AuthenticationError, ProjectNotFound):
81+
raise
82+
except BaseException:
83+
result = self.execute_op()
7584

76-
# TODO: Enable this optimization. See https://github.com/SwissDataScienceCenter/renku-python/issues/2546
77-
# if "project_id" in self.context:
78-
# # use regular flow using cache
79-
# return result_response(self.RESPONSE_SERIALIZER, self.execute_op())
80-
#
81-
# return result_response(self.RESPONSE_SERIALIZER, self._fast_op_without_cache())
85+
return result_response(self.RESPONSE_SERIALIZER, result)

renku/ui/service/errors.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ class IntermittentAuthenticationError(ServiceError):
611611
This may happen for a number of reasons. Triggering a new login will likely fix it.
612612
"""
613613

614-
code = SVC_ERROR_USER + 30
614+
code = SVC_ERROR_INTERMITTENT + 30
615615
userMessage = "Invalid user credentials. Please try to log out and in again."
616616
devMessage = "Authentication error. Check the Sentry exception to inspect the headers"
617617

renku/ui/service/gateways/gitlab_api_provider.py

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,25 @@
2222

2323
import gitlab
2424

25+
from renku.core import errors
2526
from renku.core.util.os import delete_file
2627
from renku.domain_model.git import GitURL
2728
from renku.ui.service.interfaces.git_api_provider import IGitAPIProvider
2829

2930

3031
class GitlabAPIProvider(IGitAPIProvider):
31-
"""Interface a Git API Provider."""
32+
"""GitLab API provider abstraction layer.
33+
34+
Args:
35+
paths: List of files to download.
36+
target_folder: Folder to use to download the files.
37+
remote: Remote repository URL.
38+
token: User bearer token.
39+
ref: optional reference to checkout,
40+
Raises:
41+
errors.ProjectNotFound: If the remote URL is not accessible.
42+
errors.AuthenticationError: If the bearer token is invalid in any way.
43+
"""
3244

3345
def download_files_from_api(
3446
self,
@@ -45,8 +57,22 @@ def download_files_from_api(
4557
target_folder = Path(target_folder)
4658

4759
git_data = GitURL.parse(remote)
48-
gl = gitlab.Gitlab(git_data.instance_url, private_token=token)
49-
project = gl.projects.get(f"{git_data.owner}/{git_data.name}")
60+
try:
61+
gl = gitlab.Gitlab(git_data.instance_url, private_token=token)
62+
project = gl.projects.get(f"{git_data.owner}/{git_data.name}")
63+
except gitlab.GitlabAuthenticationError:
64+
# NOTE: Invalid or expired tokens fail even on public projects. Let's give it a try without tokens
65+
try:
66+
gl = gitlab.Gitlab(git_data.instance_url)
67+
project = gl.projects.get(f"{git_data.owner}/{git_data.name}")
68+
except gitlab.GitlabAuthenticationError as e:
69+
raise errors.AuthenticationError from e
70+
except gitlab.GitlabGetError as e:
71+
# NOTE: better to re-raise this as a core error since it's a common case
72+
if "project not found" in getattr(e, "error_message", "").lower():
73+
raise errors.ProjectNotFound from e
74+
else:
75+
raise
5076

5177
result_paths = []
5278

renku/ui/service/views/error_handlers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
from requests import RequestException
2727

2828
from renku.core.errors import (
29+
AuthenticationError,
2930
DatasetExistsError,
3031
DatasetImageError,
3132
DockerfileUpdateError,
@@ -35,6 +36,7 @@
3536
MigrationError,
3637
MigrationRequired,
3738
ParameterError,
39+
ProjectNotFound,
3840
RenkuException,
3941
TemplateMissingReferenceError,
4042
TemplateUpdateError,
@@ -126,7 +128,7 @@ def decorated_function(*args, **kwargs):
126128
"""Represents decorated function."""
127129
try:
128130
return f(*args, **kwargs)
129-
except (ExpiredSignatureError, ImmatureSignatureError, InvalidIssuedAtError) as e:
131+
except (AuthenticationError, ExpiredSignatureError, ImmatureSignatureError, InvalidIssuedAtError) as e:
130132
raise IntermittentAuthenticationError(e)
131133

132134
return decorated_function
@@ -392,6 +394,8 @@ def decorated_function(*args, **kwargs):
392394
raise UserProjectTemplateReferenceError(e)
393395
except (InvalidTemplateError, TemplateUpdateError) as e:
394396
raise IntermittentProjectTemplateUnavailable(e)
397+
except ProjectNotFound as e:
398+
raise UserRepoUrlInvalidError(e)
395399

396400
return decorated_function
397401

tests/fixtures/config.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@
2121

2222
import pytest
2323

24+
IT_REMOTE_REPO_URL = os.getenv(
25+
"IT_REMOTE_REPOSITORY", "https://dev.renku.ch/gitlab/renku-python-integration-tests/core-integration-test"
26+
)
2427
IT_PROTECTED_REMOTE_REPO_URL = os.getenv(
2528
"IT_PROTECTED_REMOTE_REPO", "https://dev.renku.ch/gitlab/renku-python-integration-tests/core-it-protected.git"
2629
)
27-
28-
IT_REMOTE_REPO_URL = os.getenv(
29-
"IT_REMOTE_REPOSITORY", "https://dev.renku.ch/gitlab/renku-python-integration-tests/core-integration-test"
30+
IT_PUBLIC_REMOTE_REPO_URL = os.getenv(
31+
"IT_PUBLIC_REMOTE_REPO", "https://dev.renku.ch/gitlab/renku-python-integration-tests/core-it-public"
3032
)
3133
IT_REMOTE_NON_RENKU_REPO_URL = os.getenv(
3234
"IT_REMOTE_NON_RENKU_REPO_URL", "https://dev.renku.ch/gitlab/renku-python-integration-tests/core-it-non-renku"

tests/service/fixtures/service_projects.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ def it_remote_repo_url():
6363
return IT_REMOTE_REPO_URL
6464

6565

66+
@pytest.fixture(scope="module")
67+
def it_remote_public_renku_repo_url():
68+
"""Returns a remote path to a public integration test repository."""
69+
from tests.fixtures.config import IT_PUBLIC_REMOTE_REPO_URL
70+
71+
return IT_PUBLIC_REMOTE_REPO_URL
72+
73+
6674
@pytest.fixture(scope="module")
6775
def it_remote_public_repo_url():
6876
"""Returns a remote path to a public integration test repository."""

tests/service/views/test_cache_views.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
IntermittentProjectTemplateUnavailable,
3535
UserAnonymousError,
3636
UserProjectTemplateReferenceError,
37+
UserRepoUrlInvalidError,
3738
)
3839
from renku.ui.service.serializers.headers import JWT_TOKEN_SECRET
3940
from tests.utils import assert_rpc_response, retry_failed
@@ -685,6 +686,39 @@ def test_check_migrations_remote(svc_client, identity_headers, it_remote_repo_ur
685686
assert response.json["result"]["core_renku_version"]
686687

687688

689+
@pytest.mark.service
690+
@pytest.mark.integration
691+
def test_check_migrations_remote_errors(
692+
svc_client, identity_headers, it_remote_repo_url, it_remote_public_renku_repo_url
693+
):
694+
"""Check migrations throws the correct errors."""
695+
696+
# NOTE: repo doesn't exist
697+
fake_url = f"{it_remote_repo_url}FAKE_URL"
698+
response = svc_client.get("/cache.migrations_check", query_string=dict(git_url=fake_url), headers=identity_headers)
699+
700+
assert_rpc_response(response, "error")
701+
assert UserRepoUrlInvalidError.code == response.json["error"]["code"]
702+
703+
# NOTE: token errors
704+
response = svc_client.get(
705+
"/cache.migrations_check", query_string=dict(git_url=it_remote_repo_url), headers=identity_headers
706+
)
707+
assert_rpc_response(response)
708+
709+
identity_headers["Authorization"] = "Bearer 123abc"
710+
response = svc_client.get(
711+
"/cache.migrations_check", query_string=dict(git_url=it_remote_repo_url), headers=identity_headers
712+
)
713+
assert_rpc_response(response, "error")
714+
assert UserRepoUrlInvalidError.code == response.json["error"]["code"]
715+
716+
response = svc_client.get(
717+
"/cache.migrations_check", query_string=dict(git_url=it_remote_public_renku_repo_url), headers=identity_headers
718+
)
719+
assert_rpc_response(response)
720+
721+
688722
@pytest.mark.service
689723
@pytest.mark.integration
690724
def test_mirgate_wrong_template_failure(svc_client_with_repo, template, monkeypatch):

0 commit comments

Comments
 (0)