Skip to content

Commit b75911f

Browse files
committed
revert back the rate format to ISO-8601
1 parent 6126d23 commit b75911f

File tree

3 files changed

+69
-82
lines changed

3 files changed

+69
-82
lines changed

dockerfile-image-update/src/main/java/com/salesforce/dockerfileimageupdate/utils/RateLimit.java

Lines changed: 49 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,32 @@
11
package com.salesforce.dockerfileimageupdate.utils;
22

33
import java.time.Duration;
4+
import java.time.format.DateTimeParseException;
45
import java.util.UnknownFormatConversionException;
5-
import java.util.regex.Matcher;
6-
import java.util.regex.Pattern;
76
import org.apache.commons.lang3.StringUtils;
8-
import org.apache.commons.lang3.math.NumberUtils;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
99

1010
/**
11-
* Creating this as inner class as existence of this class outside
12-
* RateLimiter class will make a little sense. This class is solely
13-
* meant to encapsulate the primitives needed for the RateLimiter
14-
* and logic for populating those after tokenizing the input
11+
* Class meant to tokenize input and encapsulate the primitives needed for
12+
* the RateLimiter
13+
*
14+
* @see RateLimiter
1515
*/
1616
public class RateLimit {
1717
public static final String ERROR_MESSAGE = "Unexpected format or unit encountered, valid input is " +
18-
"<integer>-per-<time-unit> where time-init is one of 's', 'm', or 'h'. " +
19-
"Example: 500-per-h";
20-
private static final Pattern eventPattern =
21-
Pattern.compile("(^[1-9]\\d{0,18})(-per-)?([1-9]\\d{0,18})*([smh]?)", Pattern.CASE_INSENSITIVE);
18+
"<integer>-<ISO-8601_formatted_time> Example: 500-PT1S means(500 per second) \n" +
19+
"500-PT1M means(500 per Minute)";
20+
private static final Logger log = LoggerFactory.getLogger(RateLimit.class);
2221
private final long rate; //Maximum count of PRs to be sent per rLimitDuration
2322
private final Duration duration; //Rate Limit duration
2423
private final Duration tokenAddingRate; //Rate at which tokens are added in the bucket
2524

2625
/**
2726
* Initialize RateLimit object with based on parameters passed
28-
* @param rate Maximum count of PRs to be sent per rLimitDuration
29-
* @param duration Rate Limit duration
27+
*
28+
* @param rate Maximum count of PRs to be sent per rLimitDuration
29+
* @param duration Rate Limit duration
3030
* @param tokenAddingRate Rate at which tokens are added in the bucket
3131
*/
3232
public RateLimit(long rate, Duration duration, Duration tokenAddingRate) {
@@ -43,68 +43,50 @@ public RateLimit() {
4343
Constants.DEFAULT_TOKEN_ADDING_RATE);
4444
}
4545

46-
public static RateLimit tokenizeAndGetRateLimit(String input) {
47-
48-
if (input == null) {
46+
/**
47+
* This method will accept an input string in format <integer>-<ISO-8601_formatted_time> and
48+
* will tokenize it to create a RateLimit object.
49+
* <pre>
50+
* Examples of tokenization
51+
* input : 500-PT1S -> {rate : 500, duration 1 second (PT1S), token adding rate: every .002 seconds (PT0.002S)}
52+
* 60-PT1M -> {rate : 60, duration 1 minute (PT1M), token adding rate: every 1 seconds (PT1S)}
53+
* </pre>
54+
*
55+
* @param input string in format <integer>-<ISO-8601 formatted_time]> example 500-PT1S , 500-PT60S , 500-PT1H
56+
* @return RateLimit based on the input value
57+
* @throws UnknownFormatConversionException if input is not a string with expected format <integer>-<ISO-8601_formatted_time>
58+
*/
59+
public static RateLimit tokenizeAndGetRateLimit(String input) throws UnknownFormatConversionException {
60+
if (StringUtils.isEmpty(input) || !input.contains("-")) {
4961
throw new UnknownFormatConversionException(ERROR_MESSAGE);
5062
}
5163

52-
long rLimit;
53-
Duration rLimitDuration;
54-
Duration tokAddingRate;
55-
String literalConstant;
56-
String rateDuration;
57-
String rateDurationUnitChar;
64+
int delimiterIndex = input.indexOf('-');
5865

59-
Matcher matcher = eventPattern.matcher(input);
66+
try {
67+
long inputRate = Long.parseLong(input.substring(0, delimiterIndex));
68+
// "PT20.345S" -- parses as "20.345 seconds"
69+
// "PT15M" -- parses as "15 minutes" (where a minute is 60 seconds)
70+
// "PT10H" -- parses as "10 hours" (where an hour is 3600 seconds)
71+
// "P2D" -- parses as "2 days" (where a day is 24 hours or 86400
72+
Duration inputDuration = Duration.parse(input.substring(delimiterIndex + 1));
6073

61-
if (!matcher.matches()) {
62-
throw new UnknownFormatConversionException(ERROR_MESSAGE);
63-
} else {
64-
/**
65-
* eventPattern regex is divided into 5 groups
66-
* 0 group being the whole expression
67-
* 1 group is numeric rate limit value and will always
68-
* be present if exp matched
69-
* 2 is literal constant '-per-' and is optional
70-
* 3 is numeric rate duration and is optional
71-
* 4 is a char(s/m/h) and is optional
72-
*/
73-
rLimit = Long.parseLong(matcher.group(1)); // will always have a numeric value that fits in Long
74-
literalConstant = matcher.group(2); // can be null or empty or '-per-'
75-
rateDuration = matcher.group(3);// can be null or empty or numeric value
76-
rateDurationUnitChar = matcher.group(4); //can be empty or char s,m,h
77-
}
74+
// Keeping token adding rate based on the lowest supported rate i.e. nanoseconds.
75+
// means new tokens can be added to the rate limiter every nanosecond based on
76+
// the input passed.
77+
// Duration is auto casted to upper unit of time. so input of 600-PT60M
78+
// will be set inputTokAddingRate to PT6S means every 6 seconds one token will be
79+
// added to the pool to be consumed
80+
//
81+
Duration inputTokAddingRate = Duration.ofNanos(inputDuration.toNanos() / inputRate);
7882

79-
if (StringUtils.isEmpty(literalConstant)) {
80-
//return rate with default duration and token adding rate
81-
return new RateLimit(rLimit, Constants.DEFAULT_RATE_LIMIT_DURATION, Constants.DEFAULT_TOKEN_ADDING_RATE);
83+
log.info("constructing rate limit object with rate: {}, duration: {}, and token adding rate: {}",
84+
inputRate, inputDuration, inputTokAddingRate);
85+
return new RateLimit(inputRate, inputDuration, inputTokAddingRate);
86+
} catch (NumberFormatException | DateTimeParseException ex) {
87+
throw new UnknownFormatConversionException(ERROR_MESSAGE);
8288
}
8389

84-
// value is either going to be a valid number or null. defaulting to 1 unit(every hour/every min and so on)
85-
long duration = NumberUtils.isParsable(rateDuration) ? Long.parseLong(rateDuration) : 1;
86-
87-
switch (rateDurationUnitChar) {
88-
case "s":
89-
case "S":
90-
rLimitDuration = Duration.ofSeconds(duration);
91-
tokAddingRate = Duration.ofSeconds(rLimit / duration);
92-
break;
93-
case "m":
94-
case "M":
95-
rLimitDuration = Duration.ofMinutes(duration);
96-
tokAddingRate = Duration.ofMinutes(rLimit / duration);
97-
break;
98-
case "h":
99-
case "H":
100-
rLimitDuration = Duration.ofHours(duration);
101-
tokAddingRate = Duration.ofHours(rLimit / duration);
102-
break;
103-
default:
104-
//should not reach here are regex will enforce char, keeping it for any unexpected use case.
105-
throw new UnknownFormatConversionException(ERROR_MESSAGE);
106-
}
107-
return new RateLimit(rLimit, rLimitDuration, tokAddingRate);
10890
}
10991

11092
public long getRate() {

dockerfile-image-update/src/test/java/com/salesforce/dockerfileimageupdate/utils/RateLimitTest.java

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,20 @@
44
import java.util.UnknownFormatConversionException;
55
import org.testng.annotations.DataProvider;
66
import org.testng.annotations.Test;
7-
import static org.testng.Assert.*;
7+
import static org.testng.Assert.assertEquals;
8+
import static org.testng.Assert.assertThrows;
89

910
public class RateLimitTest {
1011
@DataProvider(name = "rateLimitInputSuccessData")
1112
public static Object[][] rateLimitInputSuccessData() {
1213
return new Object[][]{
13-
{"500", 500, Constants.DEFAULT_RATE_LIMIT_DURATION, Constants.DEFAULT_TOKEN_ADDING_RATE},
14-
{"500-per-s", 500, Duration.ofSeconds(1), Duration.ofSeconds(500)},
15-
{"500-per-60s", 500, Duration.ofSeconds(60), Duration.ofSeconds(500 / 60)},
16-
{"500-per-1m", 500, Duration.ofMinutes(1), Duration.ofMinutes(500)},
17-
{"500-per-1h", 500, Duration.ofHours(1), Duration.ofHours(500)},
18-
{"1234-per-2h", 1234, Duration.ofHours(2), Duration.ofHours(1234 / 2)},
14+
{"500-PT1S", 500, "PT1S", "PT0.002S"},
15+
{"500-PT60S", 500, "PT1M", "PT0.12S"},
16+
{"500-PT1M", 500, "PT1M", "PT0.12S"},
17+
{"500-PT1H", 500, "PT1H", "PT7.2S"},
18+
{"500-PT6H", 500, "PT6H", "PT43.2S"},
19+
{"600-PT1H", 600, "PT1H", "PT6S"},
20+
{"86400-PT24H", 86400, "PT24H", "PT1S"},
1921
};
2022
}
2123

@@ -31,6 +33,8 @@ public static Object[][] rateLimitInputFailureData() {
3133
{null, UnknownFormatConversionException.class},
3234
{"2dse2", UnknownFormatConversionException.class},
3335
{"null", UnknownFormatConversionException.class},
36+
{"500-per-s", UnknownFormatConversionException.class},
37+
{"500-PT1H-PT1H", UnknownFormatConversionException.class},
3438

3539
};
3640
}
@@ -41,10 +45,10 @@ public void testingFailureTokenizingOfInputString(String rateLimitInputStr, Clas
4145
}
4246

4347
@Test(dataProvider = "rateLimitInputSuccessData")
44-
public void testingSuccessTokenizingOfInputString(String rateLimitInputStr, int rateLimitInputArg, Duration rateLimitDurationInputArg, Duration tokenAddingRateInputArg) {
48+
public void testingSuccessTokenizingOfInputString(String rateLimitInputStr, long rateLimitInputArg, String rlDuration, String rlTokAddingRate) {
4549
RateLimit rateLimit = RateLimit.tokenizeAndGetRateLimit(rateLimitInputStr);
4650
assertEquals(rateLimit.getRate(), rateLimitInputArg);
47-
assertEquals(rateLimit.getDuration(), rateLimitDurationInputArg);
48-
assertEquals(rateLimit.getTokenAddingRate(), tokenAddingRateInputArg);
51+
assertEquals(rateLimit.getDuration(), Duration.parse(rlDuration));
52+
assertEquals(rateLimit.getTokenAddingRate(), Duration.parse(rlTokAddingRate));
4953
}
5054
}

dockerfile-image-update/src/test/java/com/salesforce/dockerfileimageupdate/utils/RateLimiterTest.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ public static Object[][] getRateLimitingData() {
2323
@DataProvider(name = "dataForTestingRateLimitingEventCreation")
2424
public static Object[][] getDataForTestingRateLimitingEventCreation() {
2525
return new Object[][]{
26-
{"1234-per-2h", RateLimiter.class, false},
27-
{"500", RateLimiter.class, false},
28-
{"500-per-s", RateLimiter.class, false},
29-
{"500-per-60s", RateLimiter.class, false},
26+
{"500-PT1S", RateLimiter.class, false},
27+
{"500-PT60S", RateLimiter.class, false},
28+
{"500-PT1M", RateLimiter.class, false},
29+
{"500-PT1H", RateLimiter.class, false},
3030
{"image", null, true},
3131
{"500-random", null, true},
3232
{"", null, true},
33-
{"null", null, true}
33+
{"null", null, true},
34+
{"500", null, true}
3435
};
3536
}
3637

0 commit comments

Comments
 (0)