server: Update gson date format for serializing/deserializing Date in MS stats (#11506)

* Update gson date format for serializing/deserializing Date in MS stats (across multiple management servers)

* review

* review comments, and unit tests

* added unit test with different date format

* Use separate Gson for MS stats serialization/deserialization
This commit is contained in:
Suresh Kumar Anaparti 2025-09-22 15:52:50 +05:30 committed by GitHub
parent 393b5d2b77
commit 12513e18fa
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 122 additions and 7 deletions

View File

@ -105,7 +105,7 @@ public class ManagementServerHostStatsEntry implements ManagementServerHostStats
}
@Override
public Date getCollectionTime(){
public Date getCollectionTime() {
return collectionTime;
}

View File

@ -46,6 +46,7 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import com.cloud.utils.DateUtil;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider;
@ -170,10 +171,10 @@ import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.reflect.TypeToken;
import com.sun.management.OperatingSystemMXBean;
/**
@ -294,6 +295,9 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
private static StatsCollector s_instance = null;
private static Gson gson = new Gson();
private static Gson msStatsGson = new GsonBuilder()
.setDateFormat(DateUtil.ZONED_DATETIME_FORMAT)
.create();
private ScheduledExecutorService _executor = null;
@Inject
@ -739,7 +743,6 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
dbStats.put(uptime, (Long.valueOf(stats.get(uptime))));
}
@Override
protected Point createInfluxDbPoint(Object metricsObject) {
return null;
@ -759,7 +762,7 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
hostStatsEntry = getDataFrom(mshost);
managementServerHostStats.put(mshost.getUuid(), hostStatsEntry);
// send to other hosts
clusterManager.publishStatus(gson.toJson(hostStatsEntry));
clusterManager.publishStatus(msStatsGson.toJson(hostStatsEntry));
} catch (Throwable t) {
// pokemon catch to make sure the thread stays running
logger.error("Error trying to retrieve management server host statistics", t);
@ -1158,9 +1161,9 @@ public class StatsCollector extends ManagerBase implements ComponentMethodInterc
logger.debug(String.format("StatusUpdate from %s, json: %s", pdu.getSourcePeer(), pdu.getJsonPackage()));
}
ManagementServerHostStatsEntry hostStatsEntry = null;
ManagementServerHostStatsEntry hostStatsEntry;
try {
hostStatsEntry = gson.fromJson(pdu.getJsonPackage(),new TypeToken<ManagementServerHostStatsEntry>(){}.getType());
hostStatsEntry = msStatsGson.fromJson(pdu.getJsonPackage(), ManagementServerHostStatsEntry.class);
managementServerHostStats.put(hostStatsEntry.getManagementServerHostUuid(), hostStatsEntry);
// Update peer state to Up in mshost_peer

View File

@ -20,8 +20,10 @@ package com.cloud.server;
import static org.mockito.Mockito.when;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
@ -33,6 +35,8 @@ import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import com.cloud.utils.DateUtil;
import com.google.gson.JsonSyntaxException;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.commons.collections.CollectionUtils;
@ -116,6 +120,8 @@ public class StatsCollectorTest {
private static Gson gson = new Gson();
private Gson msStatsGson;
private MockedStatic<InfluxDBFactory> influxDBFactoryMocked;
private AutoCloseable closeable;
@ -125,6 +131,9 @@ public class StatsCollectorTest {
closeable = MockitoAnnotations.openMocks(this);
statsCollector.vmStatsDao = vmStatsDaoMock;
statsCollector.volumeStatsDao = volumeStatsDao;
Field msStatsGsonField = StatsCollector.class.getDeclaredField("msStatsGson");
msStatsGsonField.setAccessible(true);
msStatsGson = (Gson) msStatsGsonField.get(null);
}
@After
@ -612,4 +621,107 @@ public class StatsCollectorTest {
Mockito.verify(mockPool, Mockito.never()).setCapacityIops(Mockito.anyLong());
Mockito.verify(mockPool, Mockito.never()).setUsedIops(Mockito.anyLong());
}
@Test
public void testGsonDateFormatSerialization() {
Date now = new Date();
TestClass testObj = new TestClass("TestString", 999, now);
String json = msStatsGson.toJson(testObj);
Assert.assertTrue(json.contains("TestString"));
Assert.assertTrue(json.contains("999"));
String expectedDate = new SimpleDateFormat(DateUtil.ZONED_DATETIME_FORMAT).format(now);
Assert.assertTrue(json.contains(expectedDate));
}
@Test
public void testGsonDateFormatDeserializationWithSameDateFormat() throws Exception {
String json = "{\"str\":\"TestString\",\"num\":999,\"date\":\"2025-08-22T15:39:43+0000\"}";
TestClass testObj = msStatsGson.fromJson(json, TestClass.class);
Assert.assertEquals("TestString", testObj.getStr());
Assert.assertEquals(999, testObj.getNum());
Date expectedDate = new SimpleDateFormat(DateUtil.ZONED_DATETIME_FORMAT).parse("2025-08-22T15:39:43+0000");
Assert.assertEquals(expectedDate, testObj.getDate());
}
@Test (expected = JsonSyntaxException.class)
public void testGsonDateFormatDeserializationWithDifferentDateFormat() throws Exception {
String json = "{\"str\":\"TestString\",\"num\":999,\"date\":\"22/08/2025T15:39:43+0000\"}";
msStatsGson.fromJson(json, TestClass.class);
/* Deserialization throws the below exception:
com.google.gson.JsonSyntaxException: 22/08/2025T15:39:43+0000
at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserializeToDate(DefaultTypeAdapters.java:376)
at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserialize(DefaultTypeAdapters.java:351)
at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserialize(DefaultTypeAdapters.java:307)
at com.google.gson.JsonDeserializationVisitor.invokeCustomDeserializer(JsonDeserializationVisitor.java:92)
at com.google.gson.JsonObjectDeserializationVisitor.visitFieldUsingCustomHandler(JsonObjectDeserializationVisitor.java:117)
at com.google.gson.ReflectingFieldNavigator.visitFieldsReflectively(ReflectingFieldNavigator.java:63)
at com.google.gson.ObjectNavigator.accept(ObjectNavigator.java:120)
at com.google.gson.JsonDeserializationContextDefault.fromJsonObject(JsonDeserializationContextDefault.java:76)
at com.google.gson.JsonDeserializationContextDefault.deserialize(JsonDeserializationContextDefault.java:54)
at com.google.gson.Gson.fromJson(Gson.java:551)
at com.google.gson.Gson.fromJson(Gson.java:498)
at com.google.gson.Gson.fromJson(Gson.java:467)
at com.google.gson.Gson.fromJson(Gson.java:417)
at com.google.gson.Gson.fromJson(Gson.java:389)
at com.cloud.serializer.GsonHelperTest.testGsonDateFormatDeserializationWithDifferentDateFormat(GsonHelperTest.java:113)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69)
at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:231)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: java.text.ParseException: Unparseable date: "22/08/2025T15:39:43+0000"
at java.base/java.text.DateFormat.parse(DateFormat.java:395)
at com.google.gson.DefaultTypeAdapters$DefaultDateTypeAdapter.deserializeToDate(DefaultTypeAdapters.java:374)
... 42 more
*/
}
private static class TestClass {
private String str;
private int num;
private Date date;
public TestClass(String str, int num, Date date) {
this.str = str;
this.num = num;
this.date = date;
}
public String getStr() {
return str;
}
public int getNum() {
return num;
}
public Date getDate() {
return date;
}
}
}

View File

@ -48,7 +48,7 @@ public class DateUtil {
public static final TimeZone GMT_TIMEZONE = TimeZone.getTimeZone("GMT");
public static final String YYYYMMDD_FORMAT = "yyyyMMddHHmmss";
private static final String ZONED_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
public static final String ZONED_DATETIME_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ";
private static final DateFormat ZONED_DATETIME_SIMPLE_FORMATTER = new SimpleDateFormat(ZONED_DATETIME_FORMAT);
private static final DateTimeFormatter[] parseFormats = new DateTimeFormatter[]{