This is an automated email from the ASF dual-hosted git repository.

DaanHoogland pushed a commit to branch bulk-power-state-update-time-gmt
in repository https://gitbox.apache.org/repos/asf/cloudstack.git

commit 5da21becf1ea74d38de9e1857bc67e9325c7c965
Author: Daan Hoogland <[email protected]>
AuthorDate: Wed Oct 7 15:56:32 2026 +0200

    update powerstate timestamps to the right timezone
---
 .../java/com/cloud/vm/dao/VMInstanceDaoImpl.java   |  4 +-
 .../com/cloud/vm/dao/VMInstanceDaoImplTest.java    | 55 ++++++++++++++++++++++
 2 files changed, 58 insertions(+), 1 deletion(-)

diff --git 
a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java 
b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java
index 518bc3cf497..232a99211a1 100755
--- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java
+++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDaoImpl.java
@@ -19,6 +19,7 @@ package com.cloud.vm.dao;
 import java.sql.PreparedStatement;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Date;
@@ -1037,7 +1038,7 @@ public class VMInstanceDaoImpl extends 
GenericDaoBase<VMInstanceVO, Long> implem
             return notUpdated;
         }
         StringBuilder sql = new StringBuilder("UPDATE `cloud`.`vm_instance` 
SET " +
-                "`power_host` = ?, `power_state_update_time` = now(), 
`power_state` = CASE ");
+                "`power_host` = ?, `power_state_update_time` = ?, 
`power_state` = CASE ");
         updateCounts.keySet().forEach(key -> {
             sql.append("WHEN id = ").append(key).append(" THEN 
'").append(instancePowerStates.get(key)).append("' ");
         });
@@ -1052,6 +1053,7 @@ public class VMInstanceDaoImpl extends 
GenericDaoBase<VMInstanceVO, Long> implem
         TransactionLegacy txn = TransactionLegacy.currentTxn();
         try (PreparedStatement pstmt = 
txn.prepareAutoCloseStatement(sql.toString())) {
             pstmt.setLong(1, powerHostId);
+            pstmt.setTimestamp(2, new 
Timestamp(DateUtil.currentGMTTime().getTime()), gmtCalendar());
             pstmt.executeUpdate();
         } catch (SQLException e) {
             logger.error("Unable to execute update power states SQL from VMs 
{} due to: {}",
diff --git 
a/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java 
b/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java
index 5f8b2dd90ec..5317f3a6689 100644
--- a/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java
+++ b/engine/schema/src/test/java/com/cloud/vm/dao/VMInstanceDaoImplTest.java
@@ -20,19 +20,26 @@ package com.cloud.vm.dao;
 import static com.cloud.vm.VirtualMachine.State.Running;
 import static com.cloud.vm.VirtualMachine.State.Stopped;
 import static 
com.cloud.vm.dao.VMInstanceDaoImpl.MAX_CONSECUTIVE_SAME_STATE_UPDATE_COUNT;
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import java.sql.PreparedStatement;
+import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
 
 import com.cloud.host.dao.HostDao;
 import org.joda.time.DateTime;
@@ -40,8 +47,10 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.InjectMocks;
 import org.mockito.Mock;
+import org.mockito.MockedStatic;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.mockito.Spy;
@@ -50,6 +59,7 @@ import com.cloud.utils.Pair;
 import com.cloud.utils.db.Filter;
 import com.cloud.utils.db.SearchBuilder;
 import com.cloud.utils.db.SearchCriteria;
+import com.cloud.utils.db.TransactionLegacy;
 import com.cloud.vm.VMInstanceVO;
 import com.cloud.vm.VirtualMachine;
 import org.mockito.junit.MockitoJUnitRunner;
@@ -210,6 +220,51 @@ public class VMInstanceDaoImplTest {
         assertTrue(result);
     }
 
+    @Test
+    public void testUpdatePowerStatesBindsGmtUpdateTimeInsteadOfDbNow() throws 
Exception {
+        TimeZone defaultTimeZone = TimeZone.getDefault();
+        try {
+            for (String timeZone : List.of("UTC", "Europe/Amsterdam", 
"America/New_York", "Asia/Kolkata", "Pacific/Auckland")) {
+                TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
+                verifyBulkUpdatePowerStatesBindsGmtUpdateTime(timeZone);
+            }
+        } finally {
+            TimeZone.setDefault(defaultTimeZone);
+        }
+    }
+
+    private void verifyBulkUpdatePowerStatesBindsGmtUpdateTime(String 
timeZone) throws Exception {
+        VMInstanceVO instance = Mockito.mock(VMInstanceVO.class);
+        when(instance.getId()).thenReturn(1L);
+        
when(instance.getPowerState()).thenReturn(VirtualMachine.PowerState.PowerOn);
+        
doReturn(List.of(instance)).when(vmInstanceDao).listSelectPowerStateByIds(any());
+        TransactionLegacy txn = Mockito.mock(TransactionLegacy.class);
+        PreparedStatement pstmt = Mockito.mock(PreparedStatement.class);
+        ArgumentCaptor<String> sqlCaptor = 
ArgumentCaptor.forClass(String.class);
+        
when(txn.prepareAutoCloseStatement(sqlCaptor.capture())).thenReturn(pstmt);
+        ArgumentCaptor<Timestamp> timeCaptor = 
ArgumentCaptor.forClass(Timestamp.class);
+        ArgumentCaptor<Calendar> calendarCaptor = 
ArgumentCaptor.forClass(Calendar.class);
+
+        long before = System.currentTimeMillis();
+        Map<Long, VirtualMachine.PowerState> notUpdated;
+        try (MockedStatic<TransactionLegacy> ignored = 
Mockito.mockStatic(TransactionLegacy.class)) {
+            ignored.when(TransactionLegacy::currentTxn).thenReturn(txn);
+            notUpdated = vmInstanceDao.updatePowerState(Map.of(1L, 
VirtualMachine.PowerState.PowerOff), 2L, new Date());
+        }
+        long after = System.currentTimeMillis();
+
+        assertTrue(timeZone, notUpdated.isEmpty());
+        String sql = sqlCaptor.getValue();
+        assertFalse(timeZone, sql.toLowerCase().contains("now()"));
+        assertTrue(timeZone, sql.contains("`power_state_update_time` = ?"));
+        verify(pstmt).setLong(1, 2L);
+        verify(pstmt).setTimestamp(eq(2), timeCaptor.capture(), 
calendarCaptor.capture());
+        verify(pstmt).executeUpdate();
+        long written = timeCaptor.getValue().getTime();
+        assertTrue(timeZone, written >= before && written <= after);
+        assertEquals(timeZone, "GMT", 
calendarCaptor.getValue().getTimeZone().getID());
+    }
+
     @Test
     public void testSearchRemovedByRemoveDate() {
         SearchBuilder<VMInstanceVO> sb = Mockito.mock(SearchBuilder.class);

Reply via email to