Skip to content

Commit bf686df

Browse files
authored
Adjust token expiry window to 40 seconds because of Azure (#170)
## Changes <!-- Summary of your changes that are easy to understand --> - Align with databricks/databricks-sdk-py#392 - Add ClockSupplier so that we don't depend on system clock while testing. Modify Token class to be more test friendly. ## Tests <!-- How is this tested? --> Unit Tests All nightly tests passed except two tests (which failed due to Max limit of 100 scopes has been reached!). It's a known current test infra issue and unrelated to these changes.
1 parent 344c702 commit bf686df

File tree

7 files changed

+139
-6
lines changed

7 files changed

+139
-6
lines changed

databricks-sdk-java/src/main/java/com/databricks/sdk/core/ApiClient.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import com.databricks.sdk.core.http.HttpClient;
55
import com.databricks.sdk.core.http.Request;
66
import com.databricks.sdk.core.http.Response;
7-
import com.databricks.sdk.core.utils.RealTimer;
7+
import com.databricks.sdk.core.utils.SystemTimer;
88
import com.databricks.sdk.core.utils.Timer;
99
import com.fasterxml.jackson.annotation.JsonInclude;
1010
import com.fasterxml.jackson.core.JsonProcessingException;
@@ -46,7 +46,7 @@ public String configuredAccountID() {
4646
}
4747

4848
public ApiClient(DatabricksConfig config) {
49-
this(config, new RealTimer());
49+
this(config, new SystemTimer());
5050
}
5151

5252
public ApiClient(DatabricksConfig config, Timer timer) {

databricks-sdk-java/src/main/java/com/databricks/sdk/core/oauth/Token.java

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.databricks.sdk.core.oauth;
22

3+
import com.databricks.sdk.core.utils.ClockSupplier;
4+
import com.databricks.sdk.core.utils.SystemClockSupplier;
35
import com.fasterxml.jackson.annotation.JsonProperty;
46
import java.time.LocalDateTime;
57
import java.time.temporal.ChronoUnit;
@@ -21,28 +23,50 @@ public class Token {
2123
*/
2224
@JsonProperty private LocalDateTime expiry;
2325

26+
private final ClockSupplier clockSupplier;
27+
2428
/** Constructor for non-refreshable tokens (e.g. M2M). */
2529
public Token(String accessToken, String tokenType, LocalDateTime expiry) {
26-
this(accessToken, tokenType, null, expiry);
30+
this(accessToken, tokenType, null, expiry, new SystemClockSupplier());
31+
}
32+
33+
/** Constructor for non-refreshable tokens (e.g. M2M) with ClockSupplier */
34+
public Token(
35+
String accessToken, String tokenType, LocalDateTime expiry, ClockSupplier clockSupplier) {
36+
this(accessToken, tokenType, null, expiry, clockSupplier);
2737
}
2838

2939
/** Constructor for refreshable tokens. */
3040
public Token(String accessToken, String tokenType, String refreshToken, LocalDateTime expiry) {
41+
this(accessToken, tokenType, refreshToken, expiry, new SystemClockSupplier());
42+
}
43+
44+
/** Constructor for refreshable tokens with ClockSupplier. */
45+
public Token(
46+
String accessToken,
47+
String tokenType,
48+
String refreshToken,
49+
LocalDateTime expiry,
50+
ClockSupplier clockSupplier) {
3151
Objects.requireNonNull(accessToken, "accessToken must be defined");
3252
Objects.requireNonNull(tokenType, "tokenType must be defined");
3353
Objects.requireNonNull(expiry, "expiry must be defined");
54+
Objects.requireNonNull(clockSupplier, "clockSupplier must be defined");
3455
this.accessToken = accessToken;
3556
this.tokenType = tokenType;
3657
this.refreshToken = refreshToken;
3758
this.expiry = expiry;
59+
this.clockSupplier = clockSupplier;
3860
}
3961

4062
public boolean isExpired() {
4163
if (expiry == null) {
4264
return false;
4365
}
44-
LocalDateTime potentiallyExpired = expiry.minus(10, ChronoUnit.SECONDS);
45-
LocalDateTime now = LocalDateTime.now();
66+
// Azure Databricks rejects tokens that expire in 30 seconds or less,
67+
// so we refresh the token 40 seconds before it expires.
68+
LocalDateTime potentiallyExpired = expiry.minus(40, ChronoUnit.SECONDS);
69+
LocalDateTime now = LocalDateTime.now(clockSupplier.getClock());
4670
return potentiallyExpired.isBefore(now);
4771
}
4872

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.databricks.sdk.core.utils;
2+
3+
import java.time.Clock;
4+
5+
public interface ClockSupplier {
6+
Clock getClock();
7+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.databricks.sdk.core.utils;
2+
3+
import java.time.Clock;
4+
5+
public class SystemClockSupplier implements ClockSupplier {
6+
@Override
7+
public Clock getClock() {
8+
return Clock.systemDefaultZone();
9+
}
10+
}

databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/RealTimer.java renamed to databricks-sdk-java/src/main/java/com/databricks/sdk/core/utils/SystemTimer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package com.databricks.sdk.core.utils;
22

3-
public class RealTimer implements Timer {
3+
public class SystemTimer implements Timer {
44
@Override
55
public void wait(int milliseconds) throws InterruptedException {
66
Thread.sleep(milliseconds);
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.databricks.sdk.core.oauth;
2+
3+
import static org.junit.jupiter.api.Assertions.*;
4+
5+
import com.databricks.sdk.core.utils.FakeClockSupplier;
6+
import java.time.Instant;
7+
import java.time.LocalDateTime;
8+
import java.time.ZoneId;
9+
import org.junit.jupiter.api.Test;
10+
11+
class TokenTest {
12+
13+
private static final String accessToken = "testAccessToken";
14+
private static final String refreshToken = "testRefreshToken";
15+
private static final String tokenType = "testTokenType";
16+
private final LocalDateTime currentLocalDateTime;
17+
private final FakeClockSupplier fakeClockSupplier;
18+
19+
TokenTest() {
20+
Instant instant = Instant.parse("2023-10-18T12:00:00.00Z");
21+
ZoneId zoneId = ZoneId.of("UTC");
22+
fakeClockSupplier = new FakeClockSupplier(instant, zoneId);
23+
currentLocalDateTime = LocalDateTime.now(fakeClockSupplier.getClock());
24+
}
25+
26+
@Test
27+
void createNonRefreshableToken() {
28+
Token token =
29+
new Token(accessToken, tokenType, currentLocalDateTime.plusMinutes(5), fakeClockSupplier);
30+
assertEquals(accessToken, token.getAccessToken());
31+
assertEquals(tokenType, token.getTokenType());
32+
assertNull(token.getRefreshToken());
33+
assertTrue(token.isValid());
34+
}
35+
36+
@Test
37+
void createRefreshableToken() {
38+
Token token =
39+
new Token(
40+
accessToken,
41+
tokenType,
42+
refreshToken,
43+
currentLocalDateTime.plusMinutes(5),
44+
fakeClockSupplier);
45+
assertEquals(accessToken, token.getAccessToken());
46+
assertEquals(tokenType, token.getTokenType());
47+
assertEquals(refreshToken, token.getRefreshToken());
48+
assertTrue(token.isValid());
49+
}
50+
51+
@Test
52+
void tokenExpiryMoreThan40Seconds() {
53+
Token token =
54+
new Token(accessToken, tokenType, currentLocalDateTime.plusSeconds(50), fakeClockSupplier);
55+
assertFalse(token.isExpired());
56+
assertTrue(token.isValid());
57+
}
58+
59+
@Test
60+
void tokenExpiryLessThan40Seconds() {
61+
Token token =
62+
new Token(accessToken, tokenType, currentLocalDateTime.plusSeconds(30), fakeClockSupplier);
63+
assertTrue(token.isExpired());
64+
assertFalse(token.isValid());
65+
}
66+
67+
@Test
68+
void expiredToken() {
69+
Token token =
70+
new Token(accessToken, tokenType, currentLocalDateTime.minusSeconds(10), fakeClockSupplier);
71+
assertTrue(token.isExpired());
72+
assertFalse(token.isValid());
73+
}
74+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.databricks.sdk.core.utils;
2+
3+
import java.time.Clock;
4+
import java.time.Instant;
5+
import java.time.ZoneId;
6+
7+
public class FakeClockSupplier implements ClockSupplier {
8+
private final Clock clock;
9+
10+
public FakeClockSupplier(Instant fixedInstant, ZoneId zoneId) {
11+
clock = Clock.fixed(fixedInstant, zoneId);
12+
}
13+
14+
@Override
15+
public Clock getClock() {
16+
return clock;
17+
}
18+
}

0 commit comments

Comments
 (0)