This is an automated email from the ASF dual-hosted git repository.
weizhou pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.20 by this push:
new 12513e18fa3 server: Update gson date format for
serializing/deserializing Date in MS stats (#11506)
12513e18fa3 is described below
commit 12513e18fa3f77752d5fefc833561e8e44034920
Author: Suresh Kumar Anaparti <[email protected]>
AuthorDate: Mon Sep 22 15:52:50 2025 +0530
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
---
.../server/ManagementServerHostStatsEntry.java | 2 +-
.../main/java/com/cloud/server/StatsCollector.java | 13 ++-
.../java/com/cloud/server/StatsCollectorTest.java | 112 +++++++++++++++++++++
utils/src/main/java/com/cloud/utils/DateUtil.java | 2 +-
4 files changed, 122 insertions(+), 7 deletions(-)
diff --git
a/server/src/main/java/com/cloud/server/ManagementServerHostStatsEntry.java
b/server/src/main/java/com/cloud/server/ManagementServerHostStatsEntry.java
index 172ab1e83eb..08cc54f9799 100644
--- a/server/src/main/java/com/cloud/server/ManagementServerHostStatsEntry.java
+++ b/server/src/main/java/com/cloud/server/ManagementServerHostStatsEntry.java
@@ -105,7 +105,7 @@ public class ManagementServerHostStatsEntry implements
ManagementServerHostStats
}
@Override
- public Date getCollectionTime(){
+ public Date getCollectionTime() {
return collectionTime;
}
diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java
b/server/src/main/java/com/cloud/server/StatsCollector.java
index 4e1e78f1610..7e83d452bb9 100644
--- a/server/src/main/java/com/cloud/server/StatsCollector.java
+++ b/server/src/main/java/com/cloud/server/StatsCollector.java
@@ -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
diff --git a/server/src/test/java/com/cloud/server/StatsCollectorTest.java
b/server/src/test/java/com/cloud/server/StatsCollectorTest.java
index 6a979259cd9..3578e6948a4 100644
--- a/server/src/test/java/com/cloud/server/StatsCollectorTest.java
+++ b/server/src/test/java/com/cloud/server/StatsCollectorTest.java
@@ -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;
+ }
+ }
}
diff --git a/utils/src/main/java/com/cloud/utils/DateUtil.java
b/utils/src/main/java/com/cloud/utils/DateUtil.java
index fdf2ba8fe01..00ae5565dad 100644
--- a/utils/src/main/java/com/cloud/utils/DateUtil.java
+++ b/utils/src/main/java/com/cloud/utils/DateUtil.java
@@ -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[]{