diff --git a/swh/lister/launchpad/lister.py b/swh/lister/launchpad/lister.py --- a/swh/lister/launchpad/lister.py +++ b/swh/lister/launchpad/lister.py @@ -11,7 +11,8 @@ import iso8601 from launchpadlib.launchpad import Launchpad from lazr.restfulclient.errors import RestfulError -from lazr.restfulclient.resource import Collection +from lazr.restfulclient.resource import Collection, Resource +from tenacity.before_sleep import before_sleep_log from swh.lister.utils import retry_if_exception, throttling_retry from swh.scheduler.interface import SchedulerInterface @@ -99,12 +100,20 @@ d[attribute_name] = date_last_modified.isoformat() return d - @throttling_retry(retry=retry_if_restful_error) + @throttling_retry( + retry=retry_if_restful_error, + before_sleep=before_sleep_log(logger, logging.WARNING), + ) def _page_request( self, launchpad, vcs_type: str, date_last_modified: Optional[datetime] - ) -> Optional[Collection]: - """Querying the page of results for a given vcs_type since the date_last_modified. If - some issues occurs, this will deal with the retrying policy. + ) -> Collection: + """Querying the page of results for a given vcs_type since the + date_last_modified. If some api communication issues occurs, this will be + dealt with the retrying policy decorator. + + Raises: + RestfulError in case of issues with the api and the decorator exhausts its + tryouts. """ get_vcs_fns = { @@ -135,13 +144,27 @@ launchpad, vcs_type, self.date_last_modified[vcs_type] ) except RestfulError as e: - logger.warning("Listing %s origins raised %s", vcs_type, e) + logger.warning("Listing %s origins raised %s, skipping", vcs_type, e) result = None if not result: continue yield vcs_type, result - @throttling_retry(retry=retry_if_restful_error) + @throttling_retry( + retry=retry_if_restful_error, + before_sleep=before_sleep_log(logger, logging.WARNING), + ) + def get_next_repo(self, repos_it: Iterator[Resource]) -> Resource: + """Get the next repository information. If some api communication issues occurs, + this will be dealt with the retrying policy decorator. + + Raises: + RestfulError in case of issues with the api and the decorator exhausts its + tryouts. + + """ + return next(repos_it) + def get_origins_from_page(self, page: LaunchpadPageType) -> Iterator[ListedOrigin]: """ Iterate on all git repositories and yield ListedOrigin instances. @@ -149,8 +172,18 @@ assert self.lister_obj.id is not None vcs_type, repos = page + repos_it = iter(repos) + while True: + try: + repo = self.get_next_repo(repos_it) + except RestfulError as e: + logger.warning( + "Fetch %s origin detail raised %s, skipping", vcs_type, e + ) + continue + except StopIteration: - for repo in repos: + break origin_url = origin(vcs_type, repo) # filter out origins with invalid URL diff --git a/swh/lister/launchpad/tests/test_lister.py b/swh/lister/launchpad/tests/test_lister.py --- a/swh/lister/launchpad/tests/test_lister.py +++ b/swh/lister/launchpad/tests/test_lister.py @@ -26,18 +26,23 @@ class _Collection: entries: List[_Repo] = [] - def __init__(self, file): - self.entries = [_Repo(r) for r in file] + def __init__(self, repos): + self.repos = repos + self.it = iter(self.repos) + + def __next__(self): + return next(self.it) def __getitem__(self, key): - return self.entries[key] + return self.repos[key] def __len__(self): - return len(self.entries) + return len(self.repos) def _launchpad_response(datadir, datafile): - return _Collection(json.loads(Path(datadir, datafile).read_text())) + repos = json.loads(Path(datadir, datafile).read_text()) + return _Collection([_Repo(r) for r in repos]) @pytest.fixture @@ -194,7 +199,9 @@ def test_launchpad_lister_invalid_url_filtering( swh_scheduler, mocker, ): - invalid_origin = [_Repo({"git_https_url": "tag:launchpad.net:2008:redacted",})] + invalid_origin = _Collection( + [_Repo({"git_https_url": "tag:launchpad.net:2008:redacted",})] + ) _mock_launchpad(mocker, invalid_origin) lister = LaunchpadLister(scheduler=swh_scheduler) stats = lister.run() @@ -213,7 +220,7 @@ "date_last_modified": "2021-01-14 21:05:31.231406+00:00", } ) - origins = [origin, origin] + origins = _Collection([origin, origin]) _mock_launchpad(mocker, origins) lister = LaunchpadLister(scheduler=swh_scheduler) stats = lister.run() @@ -254,3 +261,15 @@ assert lister.updated assert stats.pages == 1 assert stats.origins == len(launchpad_response1) + + +def test_launchpad_lister_raise_during_fetching_repo_detail(swh_scheduler, mocker): + mock = mocker.patch("swh.lister.launchpad.lister.LaunchpadLister.get_next_repo") + mock.side_effect = [RestfulError("Failure to communicate"), StopIteration] + + lister = LaunchpadLister(scheduler=swh_scheduler) + + stats = lister.run() + + assert stats.pages == 2, "1 git page and 1 bzr page with no results is expected" + assert stats.origins == 0