aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAurélien Poscia <aurelien.poscia@sonarsource.com>2024-06-06 11:29:50 +0200
committersonartech <sonartech@sonarsource.com>2024-06-06 20:02:43 +0000
commitae4d19e26165d25a653a6ee63ef77c5a67935792 (patch)
tree2a05cf82e5ff1d225849372b418ef5cc5d4bd844
parent32a9a1730baad588acb37495a99d6384b973618c (diff)
downloadsonarqube-ae4d19e26165d25a653a6ee63ef77c5a67935792.tar.gz
sonarqube-ae4d19e26165d25a653a6ee63ef77c5a67935792.zip
SONAR-22201 Fix GitHub rate limit computation (#11200)
Co-authored-by: Peter Horvath <peter.horvath@sonarsource.com>
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/GenericPaginatedHttpClient.java4
-rw-r--r--server/sonar-alm-client/src/main/java/org/sonar/alm/client/RatioBasedRateLimitChecker.java10
-rw-r--r--server/sonar-alm-client/src/test/java/org/sonar/alm/client/GenericPaginatedHttpClientImplTest.java16
-rw-r--r--server/sonar-alm-client/src/test/java/org/sonar/alm/client/RatioBasedRateLimitCheckerTest.java15
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 {