Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support rfc3339 micro-sec (6 digit) timestamp #1477

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -26,8 +26,6 @@
import java.io.IOException;
import java.net.HttpURLConnection;
import java.time.Duration;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -78,21 +76,6 @@ public LeaderElectorTest(LockType lockType) {
} catch (IOException ex) {
throw new RuntimeException("Couldn't create ApiClient", ex);
}
// Lease resource requires special care with DateTime
if (lockType == LockType.Lease) {
// TODO: switch date-time library so that micro-sec timestamp can be serialized
// in RFC3339
// format w/ correct precision without the hacks

// This formatter is used for Lease resource spec's acquire/renewTime
DateTimeFormatter formatter =
new DateTimeFormatterBuilder()
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))
.toFormatter();

apiClient.setOffsetDateTimeFormat(formatter);
}
this.lockType = lockType;
}

Expand Down
Expand Up @@ -135,15 +135,23 @@ private LeaderElectionRecord getRecordFromLease(V1LeaseSpec lease) {
}

private V1LeaseSpec getLeaseFromRecord(LeaderElectionRecord record) {
return new V1LeaseSpec()
.acquireTime(
OffsetDateTime.ofInstant(
Instant.ofEpochMilli(record.getAcquireTime().getTime()), ZoneOffset.UTC))
.renewTime(
OffsetDateTime.ofInstant(
Instant.ofEpochMilli(record.getRenewTime().getTime()), ZoneOffset.UTC))
.holderIdentity(record.getHolderIdentity())
.leaseDurationSeconds(record.getLeaseDurationSeconds())
.leaseTransitions(record.getLeaderTransitions());
V1LeaseSpec spec =
new V1LeaseSpec()
.holderIdentity(record.getHolderIdentity())
.leaseDurationSeconds(record.getLeaseDurationSeconds())
.leaseTransitions(record.getLeaderTransitions());
if (record.getAcquireTime() != null) {
spec =
spec.acquireTime(
OffsetDateTime.ofInstant(
Instant.ofEpochMilli(record.getAcquireTime().getTime()), ZoneOffset.UTC));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This drops the milliseconds, right? Is that the intention? It also assumes that the timestamps are all UTC (but I think that is guaranteed by the k8s spec if they come from the server).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the leader-elector manages resource-locks w/ millis-sec timestamp, so it doesn't make a difference whether LeaseLock provides extra precision.

}
if (record.getRenewTime() != null) {
spec =
spec.renewTime(
OffsetDateTime.ofInstant(
Instant.ofEpochMilli(record.getRenewTime().getTime()), ZoneOffset.UTC));
}
return spec;
}
}
15 changes: 14 additions & 1 deletion kubernetes/src/main/java/io/kubernetes/client/openapi/JSON.java
Expand Up @@ -32,6 +32,8 @@
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.time.temporal.ChronoField;
import java.util.Date;
import java.util.Map;
import okio.ByteString;
Expand All @@ -42,11 +44,22 @@ public class JSON {

private boolean isLenientOnJson = false;

public static final DateTimeFormatter RFC3339MICRO_FORMATTER =
new DateTimeFormatterBuilder()
.parseDefaulting(ChronoField.OFFSET_SECONDS, 0)
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"))
.optionalStart()
.appendFraction(ChronoField.NANO_OF_SECOND, 6, 6, true)
.optionalEnd()
.appendLiteral("Z")
.toFormatter();

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is necessary. Why do we need an explicit formatter for a format that already works by default with OffsetDateTime (I believe)?

Copy link
Member Author

@yue9944882 yue9944882 Jan 6, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kubernetes server-side requires 6-digit fraction for micro timestamp types (e.g. LeaseSpec#accquireTime) where "0" should be padded in the end if necessary. however, the default DateTimeFormatter.ISO_OFFSET_DATE_TIME doesn't support right-padding for micro-sec timestamps. e.g. by the default formatter2018-04-03T11:32:26.1234Z is a valid timestamp, but kubernetes server will be rejecting b/c lacking right-padded "0"s, it's expected to be 2018-04-03T11:32:26.123400Z

private DateTypeAdapter dateTypeAdapter = new DateTypeAdapter();

private SqlDateTypeAdapter sqlDateTypeAdapter = new SqlDateTypeAdapter();

private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter = new OffsetDateTimeTypeAdapter();
private OffsetDateTimeTypeAdapter offsetDateTimeTypeAdapter =
new OffsetDateTimeTypeAdapter(RFC3339MICRO_FORMATTER);

private LocalDateTypeAdapter localDateTypeAdapter = new LocalDateTypeAdapter();

Expand Down
Expand Up @@ -15,6 +15,7 @@
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;

import java.time.OffsetDateTime;
import okio.ByteString;
import org.junit.Test;

Expand All @@ -36,4 +37,29 @@ public void testSerializeByteArray() {
final String decodedText = new String(byteStr.toByteArray());
assertThat(decodedText, is(plainText));
}

@Test
public void testOffsetDateTime1e6Parse() {
System.out.println(JSON.RFC3339MICRO_FORMATTER.format(OffsetDateTime.now()));
String timeStr = "2018-04-03T11:32:26.123456Z";
OffsetDateTime t = OffsetDateTime.parse(timeStr, JSON.RFC3339MICRO_FORMATTER);
String serializedTsStr = JSON.RFC3339MICRO_FORMATTER.format(t);
assertEquals(timeStr, serializedTsStr);
}

@Test
public void testOffsetDateTime1e4Parse() {
String timeStr = "2018-04-03T11:32:26.123400Z";
OffsetDateTime t = OffsetDateTime.parse(timeStr, JSON.RFC3339MICRO_FORMATTER);
String serializedTsStr = JSON.RFC3339MICRO_FORMATTER.format(t);
assertEquals(timeStr, serializedTsStr);
}

@Test
public void testOffsetDateTimeNoFractionParse() {
String timeStr = "2018-04-03T11:32:26Z";
OffsetDateTime t = OffsetDateTime.parse(timeStr, JSON.RFC3339MICRO_FORMATTER);
String serializedTsStr = JSON.RFC3339MICRO_FORMATTER.format(t);
assertEquals("2018-04-03T11:32:26.000000Z", serializedTsStr);
}
}