From 90e1ac859f06f5b9d67160e4152f4a4e6153d629 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Mon, 25 Aug 2025 17:10:31 +0530 Subject: [PATCH 1/5] Update gson date format for serializing/deserializing Date in MS stats (across multiple management servers) --- .../src/main/java/com/cloud/serializer/GsonHelper.java | 1 + .../cloud/server/ManagementServerHostStatsEntry.java | 2 +- .../src/main/java/com/cloud/server/StatsCollector.java | 10 ++++++---- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/cloud/serializer/GsonHelper.java b/core/src/main/java/com/cloud/serializer/GsonHelper.java index 2d2cecf26185..5b83af1a4ada 100644 --- a/core/src/main/java/com/cloud/serializer/GsonHelper.java +++ b/core/src/main/java/com/cloud/serializer/GsonHelper.java @@ -78,6 +78,7 @@ static Gson setDefaultGsonConfig(GsonBuilder builder) { }.getType(), new NwGroupsCommandTypeAdaptor()); builder.registerTypeAdapter(Storage.StoragePoolType.class, new StoragePoolTypeAdaptor()); builder.registerTypeAdapter(Hypervisor.HypervisorType.class, new HypervisorTypeAdaptor()); + builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); Gson gson = builder.create(); dsAdaptor.initGson(gson); dtAdaptor.initGson(gson); diff --git a/server/src/main/java/com/cloud/server/ManagementServerHostStatsEntry.java b/server/src/main/java/com/cloud/server/ManagementServerHostStatsEntry.java index 172ab1e83eb5..08cc54f9799b 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 void setManagementServerRunId(long managementServerRunId) { } @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 27ac0bb725d9..bba3ef157d9b 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -170,10 +170,10 @@ 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; /** @@ -293,7 +293,9 @@ public String toString() { private static StatsCollector s_instance = null; - private static Gson gson = new Gson(); + private static Gson gson = new GsonBuilder() + .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .create(); private ScheduledExecutorService _executor = null; @Inject @@ -1158,9 +1160,9 @@ public String newStatus(ClusterServicePdu pdu) { 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(){}.getType()); + hostStatsEntry = gson.fromJson(pdu.getJsonPackage(), ManagementServerHostStatsEntry.class); managementServerHostStats.put(hostStatsEntry.getManagementServerHostUuid(), hostStatsEntry); // Update peer state to Up in mshost_peer From 17e03c90f19644f7cf52fce4487de52bc29e700d Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Tue, 26 Aug 2025 10:02:07 +0530 Subject: [PATCH 2/5] review --- core/src/main/java/com/cloud/serializer/GsonHelper.java | 4 +++- server/src/main/java/com/cloud/server/StatsCollector.java | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/com/cloud/serializer/GsonHelper.java b/core/src/main/java/com/cloud/serializer/GsonHelper.java index 5b83af1a4ada..a805fa75dad1 100644 --- a/core/src/main/java/com/cloud/serializer/GsonHelper.java +++ b/core/src/main/java/com/cloud/serializer/GsonHelper.java @@ -50,6 +50,8 @@ public class GsonHelper { protected static final Gson s_gson; protected static final Gson s_gogger; + public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; + static { GsonBuilder gsonBuilder = new GsonBuilder(); s_gson = setDefaultGsonConfig(gsonBuilder); @@ -78,7 +80,7 @@ static Gson setDefaultGsonConfig(GsonBuilder builder) { }.getType(), new NwGroupsCommandTypeAdaptor()); builder.registerTypeAdapter(Storage.StoragePoolType.class, new StoragePoolTypeAdaptor()); builder.registerTypeAdapter(Hypervisor.HypervisorType.class, new HypervisorTypeAdaptor()); - builder.setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + builder.setDateFormat(DATE_FORMAT); Gson gson = builder.create(); dsAdaptor.initGson(gson); dtAdaptor.initGson(gson); diff --git a/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index bba3ef157d9b..d2ca6bbfc822 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -294,7 +294,7 @@ public String toString() { private static StatsCollector s_instance = null; private static Gson gson = new GsonBuilder() - .setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ") + .setDateFormat(GsonHelper.DATE_FORMAT) .create(); private ScheduledExecutorService _executor = null; From 8a451a4eaa781cdc9a2b93f266c197ca708a68cf Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Thu, 11 Sep 2025 16:12:20 +0530 Subject: [PATCH 3/5] review comments, and unit tests --- .../java/com/cloud/serializer/GsonHelper.java | 5 +- .../com/cloud/serializer/GsonHelperTest.java | 51 +++++++++++++++++++ .../java/com/cloud/server/StatsCollector.java | 3 +- .../main/java/com/cloud/utils/DateUtil.java | 2 +- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/com/cloud/serializer/GsonHelper.java b/core/src/main/java/com/cloud/serializer/GsonHelper.java index a805fa75dad1..b725c93353d1 100644 --- a/core/src/main/java/com/cloud/serializer/GsonHelper.java +++ b/core/src/main/java/com/cloud/serializer/GsonHelper.java @@ -22,6 +22,7 @@ import java.util.List; import com.cloud.hypervisor.Hypervisor; +import com.cloud.utils.DateUtil; import org.apache.cloudstack.transport.HypervisorTypeAdaptor; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; @@ -50,8 +51,6 @@ public class GsonHelper { protected static final Gson s_gson; protected static final Gson s_gogger; - public static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ssZ"; - static { GsonBuilder gsonBuilder = new GsonBuilder(); s_gson = setDefaultGsonConfig(gsonBuilder); @@ -80,7 +79,7 @@ static Gson setDefaultGsonConfig(GsonBuilder builder) { }.getType(), new NwGroupsCommandTypeAdaptor()); builder.registerTypeAdapter(Storage.StoragePoolType.class, new StoragePoolTypeAdaptor()); builder.registerTypeAdapter(Hypervisor.HypervisorType.class, new HypervisorTypeAdaptor()); - builder.setDateFormat(DATE_FORMAT); + builder.setDateFormat(DateUtil.ZONED_DATETIME_FORMAT); Gson gson = builder.create(); dsAdaptor.initGson(gson); dtAdaptor.initGson(gson); diff --git a/core/src/test/java/com/cloud/serializer/GsonHelperTest.java b/core/src/test/java/com/cloud/serializer/GsonHelperTest.java index e8b0b373060b..91ea109eaa2f 100644 --- a/core/src/test/java/com/cloud/serializer/GsonHelperTest.java +++ b/core/src/test/java/com/cloud/serializer/GsonHelperTest.java @@ -19,10 +19,14 @@ import com.cloud.agent.api.to.NfsTO; import com.cloud.storage.DataStoreRole; +import com.cloud.utils.DateUtil; import com.google.gson.Gson; import org.junit.Before; import org.junit.Test; +import java.text.SimpleDateFormat; +import java.util.Date; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -78,4 +82,51 @@ public void testGsonLoggerDeserialization() { assertEquals("http://example.com", deserializedNfsTO.getUrl()); assertEquals(DataStoreRole.Primary, deserializedNfsTO.getRole()); } + + @Test + public void testGsonDateFormatSerialization() { + Date now = new Date(); + TestClass testObj = new TestClass("TestString", 999, now); + String json = gson.toJson(testObj); + + assertTrue(json.contains("TestString")); + assertTrue(json.contains("999")); + String expectedDate = new SimpleDateFormat(DateUtil.ZONED_DATETIME_FORMAT).format(now); + assertTrue(json.contains(expectedDate)); + } + + @Test + public void testGsonDateFormatDeserialization() throws Exception { + String json = "{\"str\":\"TestString\",\"num\":999,\"date\":\"2025-08-22T15:39:43+0000\"}"; + TestClass testObj = gson.fromJson(json, TestClass.class); + + assertEquals("TestString", testObj.getStr()); + assertEquals(999, testObj.getNum()); + Date expectedDate = new SimpleDateFormat(DateUtil.ZONED_DATETIME_FORMAT).parse("2025-08-22T15:39:43+0000"); + assertEquals(expectedDate, testObj.getDate()); + } + + 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/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index d2ca6bbfc822..7e778fa449ae 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 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; @@ -294,7 +295,7 @@ public String toString() { private static StatsCollector s_instance = null; private static Gson gson = new GsonBuilder() - .setDateFormat(GsonHelper.DATE_FORMAT) + .setDateFormat(DateUtil.ZONED_DATETIME_FORMAT) .create(); private ScheduledExecutorService _executor = null; diff --git a/utils/src/main/java/com/cloud/utils/DateUtil.java b/utils/src/main/java/com/cloud/utils/DateUtil.java index fdf2ba8fe010..00ae5565dadc 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[]{ From 36ae0d426237cc64e9b5a17d28017814e8e25688 Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Mon, 15 Sep 2025 19:25:13 +0530 Subject: [PATCH 4/5] added unit test with different date format --- .../com/cloud/serializer/GsonHelperTest.java | 60 ++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/core/src/test/java/com/cloud/serializer/GsonHelperTest.java b/core/src/test/java/com/cloud/serializer/GsonHelperTest.java index 91ea109eaa2f..f9a1e867fc67 100644 --- a/core/src/test/java/com/cloud/serializer/GsonHelperTest.java +++ b/core/src/test/java/com/cloud/serializer/GsonHelperTest.java @@ -21,6 +21,7 @@ import com.cloud.storage.DataStoreRole; import com.cloud.utils.DateUtil; import com.google.gson.Gson; +import com.google.gson.JsonSyntaxException; import org.junit.Before; import org.junit.Test; @@ -96,7 +97,7 @@ public void testGsonDateFormatSerialization() { } @Test - public void testGsonDateFormatDeserialization() throws Exception { + public void testGsonDateFormatDeserializationWithSameDateFormat() throws Exception { String json = "{\"str\":\"TestString\",\"num\":999,\"date\":\"2025-08-22T15:39:43+0000\"}"; TestClass testObj = gson.fromJson(json, TestClass.class); @@ -106,6 +107,63 @@ public void testGsonDateFormatDeserialization() throws Exception { 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\"}"; + gson.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; From b1b008775a758bc0d9f7e6a093ff7ea24e4a284b Mon Sep 17 00:00:00 2001 From: Suresh Kumar Anaparti Date: Thu, 18 Sep 2025 17:22:14 +0530 Subject: [PATCH 5/5] Use separate Gson for MS stats serialization/deserialization --- .../java/com/cloud/serializer/GsonHelper.java | 2 - .../com/cloud/serializer/GsonHelperTest.java | 109 ----------------- .../java/com/cloud/server/StatsCollector.java | 8 +- .../com/cloud/server/StatsCollectorTest.java | 112 ++++++++++++++++++ 4 files changed, 116 insertions(+), 115 deletions(-) diff --git a/core/src/main/java/com/cloud/serializer/GsonHelper.java b/core/src/main/java/com/cloud/serializer/GsonHelper.java index b725c93353d1..2d2cecf26185 100644 --- a/core/src/main/java/com/cloud/serializer/GsonHelper.java +++ b/core/src/main/java/com/cloud/serializer/GsonHelper.java @@ -22,7 +22,6 @@ import java.util.List; import com.cloud.hypervisor.Hypervisor; -import com.cloud.utils.DateUtil; import org.apache.cloudstack.transport.HypervisorTypeAdaptor; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; @@ -79,7 +78,6 @@ static Gson setDefaultGsonConfig(GsonBuilder builder) { }.getType(), new NwGroupsCommandTypeAdaptor()); builder.registerTypeAdapter(Storage.StoragePoolType.class, new StoragePoolTypeAdaptor()); builder.registerTypeAdapter(Hypervisor.HypervisorType.class, new HypervisorTypeAdaptor()); - builder.setDateFormat(DateUtil.ZONED_DATETIME_FORMAT); Gson gson = builder.create(); dsAdaptor.initGson(gson); dtAdaptor.initGson(gson); diff --git a/core/src/test/java/com/cloud/serializer/GsonHelperTest.java b/core/src/test/java/com/cloud/serializer/GsonHelperTest.java index f9a1e867fc67..e8b0b373060b 100644 --- a/core/src/test/java/com/cloud/serializer/GsonHelperTest.java +++ b/core/src/test/java/com/cloud/serializer/GsonHelperTest.java @@ -19,15 +19,10 @@ import com.cloud.agent.api.to.NfsTO; import com.cloud.storage.DataStoreRole; -import com.cloud.utils.DateUtil; import com.google.gson.Gson; -import com.google.gson.JsonSyntaxException; import org.junit.Before; import org.junit.Test; -import java.text.SimpleDateFormat; -import java.util.Date; - import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; @@ -83,108 +78,4 @@ public void testGsonLoggerDeserialization() { assertEquals("http://example.com", deserializedNfsTO.getUrl()); assertEquals(DataStoreRole.Primary, deserializedNfsTO.getRole()); } - - @Test - public void testGsonDateFormatSerialization() { - Date now = new Date(); - TestClass testObj = new TestClass("TestString", 999, now); - String json = gson.toJson(testObj); - - assertTrue(json.contains("TestString")); - assertTrue(json.contains("999")); - String expectedDate = new SimpleDateFormat(DateUtil.ZONED_DATETIME_FORMAT).format(now); - 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 = gson.fromJson(json, TestClass.class); - - assertEquals("TestString", testObj.getStr()); - assertEquals(999, testObj.getNum()); - Date expectedDate = new SimpleDateFormat(DateUtil.ZONED_DATETIME_FORMAT).parse("2025-08-22T15:39:43+0000"); - 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\"}"; - gson.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/server/src/main/java/com/cloud/server/StatsCollector.java b/server/src/main/java/com/cloud/server/StatsCollector.java index 7e778fa449ae..acb1ef7da39c 100644 --- a/server/src/main/java/com/cloud/server/StatsCollector.java +++ b/server/src/main/java/com/cloud/server/StatsCollector.java @@ -294,7 +294,8 @@ public String toString() { private static StatsCollector s_instance = null; - private static Gson gson = new GsonBuilder() + private static Gson gson = new Gson(); + private static Gson msStatsGson = new GsonBuilder() .setDateFormat(DateUtil.ZONED_DATETIME_FORMAT) .create(); @@ -742,7 +743,6 @@ private void getDynamicDataFromDB() { dbStats.put(uptime, (Long.valueOf(stats.get(uptime)))); } - @Override protected Point createInfluxDbPoint(Object metricsObject) { return null; @@ -762,7 +762,7 @@ protected void runInContext() { 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); @@ -1163,7 +1163,7 @@ public String newStatus(ClusterServicePdu pdu) { ManagementServerHostStatsEntry hostStatsEntry; try { - hostStatsEntry = gson.fromJson(pdu.getJsonPackage(), ManagementServerHostStatsEntry.class); + 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 6a979259cd9a..3578e6948a45 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 @@ 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.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 influxDBFactoryMocked; private AutoCloseable closeable; @@ -125,6 +131,9 @@ public void setUp() throws Exception { 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 void testPoolNeedsIopsStatsUpdating_NullIops() { 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; + } + } }