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[]{

Reply via email to