diff options
author | Aurélien Poscia <aurelien.poscia@sonarsource.com> | 2024-06-06 11:29:50 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2024-06-06 20:02:43 +0000 |
commit | ae4d19e26165d25a653a6ee63ef77c5a67935792 (patch) | |
tree | 2a05cf82e5ff1d225849372b418ef5cc5d4bd844 | |
parent | 32a9a1730baad588acb37495a99d6384b973618c (diff) | |
download | sonarqube-ae4d19e26165d25a653a6ee63ef77c5a67935792.tar.gz sonarqube-ae4d19e26165d25a653a6ee63ef77c5a67935792.zip |
SONAR-22201 Fix GitHub rate limit computation (#11200)
Co-authored-by: Peter Horvath <peter.horvath@sonarsource.com>
4 files changed, 26 insertions, 19 deletions
diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java index 74835e08794..d4a62724e59 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java @@ -23,6 +23,7 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Function; import javax.annotation.Nullable; +import org.kohsuke.github.GHRateLimit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.sonar.alm.client.ApplicationHttpClient.GetResponse; @@ -65,7 +66,8 @@ public abstract class GenericPaginatedHttpClient implements PaginatedHttpClient return; } try { - rateLimitChecker.checkRateLimit(rateLimit); + GHRateLimit.Record rateLimitRecord = new GHRateLimit.Record(rateLimit.limit(), rateLimit.remaining(), rateLimit.reset()); + rateLimitChecker.checkRateLimit(rateLimitRecord, 0); } catch (InterruptedException e) { Thread.currentThread().interrupt(); LOG.warn(format("Thread interrupted: %s", e.getMessage()), e); diff --git a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/RatioBasedRateLimitChecker.java b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/RatioBasedRateLimitChecker.java index 78054169f30..24ddad8d6a2 100644 --- a/server/sonar-alm-client/src/main/java/org/sonar/alm/client/RatioBasedRateLimitChecker.java +++ b/server/sonar-alm-client/src/main/java/org/sonar/alm/client/RatioBasedRateLimitChecker.java @@ -38,15 +38,15 @@ public class RatioBasedRateLimitChecker extends RateLimitChecker { private static final int MAX_PERCENTAGE_OF_CALLS_FOR_PROVISIONING = 90; - public boolean checkRateLimit(ApplicationHttpClient.RateLimit rateLimitRecord) throws InterruptedException { - int limit = rateLimitRecord.limit(); - int apiCallsUsed = limit - rateLimitRecord.remaining(); + @Override + protected boolean checkRateLimit(GHRateLimit.Record rateLimitRecord, long count) throws InterruptedException { + int limit = rateLimitRecord.getLimit(); + int apiCallsUsed = limit - rateLimitRecord.getRemaining(); double percentageOfCallsUsed = computePercentageOfCallsUsed(apiCallsUsed, limit); LOGGER.debug("{} external system API calls used of {}", apiCallsUsed, limit); if (percentageOfCallsUsed >= MAX_PERCENTAGE_OF_CALLS_FOR_PROVISIONING) { LOGGER.warn(RATE_RATIO_EXCEEDED_MESSAGE, apiCallsUsed, limit); - GHRateLimit.Record rateLimit = new GHRateLimit.Record(rateLimitRecord.limit(), rateLimitRecord.remaining(), rateLimitRecord.reset()); - return sleepUntilReset(rateLimit); + return sleepUntilReset(rateLimitRecord); } return false; } diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java index 54ba48fa24d..0e4da3d8084 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java @@ -28,6 +28,7 @@ import java.util.Optional; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.kohsuke.github.GHRateLimit; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -40,6 +41,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatIllegalStateException; import static org.assertj.core.api.Assertions.assertThatNoException; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; @@ -123,12 +125,12 @@ public class GenericPaginatedHttpClientImplTest { assertThat(results) .containsExactly("result1", "result2", "result3"); - ArgumentCaptor<ApplicationHttpClient.RateLimit> rateLimitRecordCaptor = ArgumentCaptor.forClass(ApplicationHttpClient.RateLimit.class); - verify(rateLimitChecker).checkRateLimit(rateLimitRecordCaptor.capture()); - ApplicationHttpClient.RateLimit rateLimitRecord = rateLimitRecordCaptor.getValue(); - assertThat(rateLimitRecord.limit()).isEqualTo(10); - assertThat(rateLimitRecord.remaining()).isEqualTo(1); - assertThat(rateLimitRecord.reset()).isZero(); + ArgumentCaptor<GHRateLimit.Record> rateLimitRecordCaptor = ArgumentCaptor.forClass(GHRateLimit.Record.class); + verify(rateLimitChecker).checkRateLimit(rateLimitRecordCaptor.capture(), anyLong()); + GHRateLimit.Record rateLimitRecord = rateLimitRecordCaptor.getValue(); + assertThat(rateLimitRecord.getLimit()).isEqualTo(10); + assertThat(rateLimitRecord.getRemaining()).isEqualTo(1); + assertThat(rateLimitRecord.getResetEpochSeconds()).isZero(); } private static GetResponse mockResponseWithPaginationAndRateLimit(String content, String nextEndpoint) { @@ -165,7 +167,7 @@ public class GenericPaginatedHttpClientImplTest { GetResponse response2 = mockResponseWithoutPagination("[\"result3\"]"); when(appHttpClient.get(APP_URL, accessToken, ENDPOINT + "?per_page=100")).thenReturn(response1); when(appHttpClient.get(APP_URL, accessToken, "/next-endpoint")).thenReturn(response2); - doThrow(new InterruptedException("interrupted")).when(rateLimitChecker).checkRateLimit(any(ApplicationHttpClient.RateLimit.class)); + doThrow(new InterruptedException("interrupted")).when(rateLimitChecker).checkRateLimit(any(GHRateLimit.Record.class), anyLong()); assertThatNoException() .isThrownBy(() -> underTest.get(APP_URL, accessToken, ENDPOINT, result -> gson.fromJson(result, STRING_LIST_TYPE))); diff --git a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/RatioBasedRateLimitCheckerTest.java b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/RatioBasedRateLimitCheckerTest.java index b7d1b2aee8d..71b3befd578 100644 --- a/server/sonar-alm-client/src/test/java/org/sonar/alm/client/RatioBasedRateLimitCheckerTest.java +++ b/server/sonar-alm-client/src/test/java/org/sonar/alm/client/RatioBasedRateLimitCheckerTest.java @@ -22,9 +22,11 @@ package org.sonar.alm.client; import com.tngtech.java.junit.dataprovider.DataProvider; import com.tngtech.java.junit.dataprovider.DataProviderRunner; import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Date; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.kohsuke.github.GHRateLimit; import org.slf4j.event.Level; import org.sonar.api.testfixtures.log.LogTester; @@ -60,19 +62,20 @@ public class RatioBasedRateLimitCheckerTest { @Test @UseDataProvider("rates") public void checkRateLimit(int limit, int remaining, boolean rateLimitShouldBeExceeded) throws InterruptedException { - ApplicationHttpClient.RateLimit record = mock(); - when(record.limit()).thenReturn(limit); - when(record.remaining()).thenReturn(remaining); - when(record.reset()).thenReturn(System.currentTimeMillis() / 1000 + TIME_UNTIL_RESET); + GHRateLimit.Record ghRateLimitRecord = mock(); + when(ghRateLimitRecord.getLimit()).thenReturn(limit); + when(ghRateLimitRecord.getRemaining()).thenReturn(remaining); + when(ghRateLimitRecord.getResetDate()).thenReturn(new Date(System.currentTimeMillis() + MILLIS_BEFORE_RESET)); + when(ghRateLimitRecord.getResetEpochSeconds()).thenReturn(System.currentTimeMillis() / 1000 + TIME_UNTIL_RESET); long start = System.currentTimeMillis(); - boolean result = ratioBasedRateLimitChecker.checkRateLimit(record); + boolean result = ratioBasedRateLimitChecker.checkRateLimit(ghRateLimitRecord, 0); long stop = System.currentTimeMillis(); long totalTime = stop - start; if (rateLimitShouldBeExceeded) { assertThat(result).isTrue(); - assertThat(stop).isGreaterThanOrEqualTo(record.reset()); + assertThat(stop).isGreaterThanOrEqualTo(ghRateLimitRecord.getResetEpochSeconds()); assertThat(logTester.logs(Level.WARN)).contains( format(RATE_RATIO_EXCEEDED_MESSAGE.replaceAll("\\{\\}", "%s"), limit - remaining, limit)); } else { |