diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java index d0c8e588450..63419680fea 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -25,6 +25,8 @@ import org.apache.cloudstack.backup.Backup; import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; +import java.util.Date; + @EntityReference(value = Backup.class) public class BackupResponse extends BaseResponse { @@ -50,7 +52,7 @@ public class BackupResponse extends BaseResponse { @SerializedName(ApiConstants.CREATED) @Param(description = "backup date") - private String date; + private Date date; @SerializedName(ApiConstants.SIZE) @Param(description = "backup size in bytes") @@ -140,11 +142,11 @@ public class BackupResponse extends BaseResponse { this.type = type; } - public String getDate() { - return date; + public Date getDate() { + return this.date; } - public void setDate(String date) { + public void setDate(Date date) { this.date = date; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java index afb3e9ffc5f..22bb099b1b0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupRestorePointResponse.java @@ -25,6 +25,8 @@ import org.apache.cloudstack.backup.Backup; import com.cloud.serializer.Param; import com.google.gson.annotations.SerializedName; +import java.util.Date; + @EntityReference(value = Backup.RestorePoint.class) public class BackupRestorePointResponse extends BaseResponse { @@ -34,7 +36,7 @@ public class BackupRestorePointResponse extends BaseResponse { @SerializedName(ApiConstants.CREATED) @Param(description = "created time") - private String created; + private Date created; @SerializedName(ApiConstants.TYPE) @Param(description = "restore point type") @@ -48,11 +50,11 @@ public class BackupRestorePointResponse extends BaseResponse { this.id = id; } - public String getCreated() { - return created; + public Date getCreated() { + return this.created; } - public void setCreated(String created) { + public void setCreated(Date created) { this.created = created; } diff --git a/api/src/main/java/org/apache/cloudstack/backup/Backup.java b/api/src/main/java/org/apache/cloudstack/backup/Backup.java index 75c7ab4cca5..f369367957d 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.backup; +import java.util.Date; + import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; @@ -58,10 +60,10 @@ public interface Backup extends ControlledEntity, InternalIdentity, Identity { class RestorePoint { private String id; - private String created; + private Date created; private String type; - public RestorePoint(String id, String created, String type) { + public RestorePoint(String id, Date created, String type) { this.id = id; this.created = created; this.type = type; @@ -75,11 +77,11 @@ public interface Backup extends ControlledEntity, InternalIdentity, Identity { this.id = id; } - public String getCreated() { - return created; + public Date getCreated() { + return this.created; } - public void setCreated(String created) { + public void setCreated(Date created) { this.created = created; } @@ -134,7 +136,7 @@ public interface Backup extends ControlledEntity, InternalIdentity, Identity { long getVmId(); String getExternalId(); String getType(); - String getDate(); + Date getDate(); Backup.Status getStatus(); Long getSize(); Long getProtectedSize(); diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java index c9ac468dc53..57f87d1f2cc 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java @@ -17,16 +17,27 @@ package com.cloud.upgrade.dao; import com.cloud.upgrade.SystemVmTemplateRegistration; +import com.cloud.utils.DateUtil; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.log4j.Logger; import java.io.InputStream; import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; public class Upgrade41810to41900 implements DbUpgrade, DbUpgradeSystemVmTemplate { final static Logger LOG = Logger.getLogger(Upgrade41810to41900.class); private SystemVmTemplateRegistration systemVmTemplateRegistration; + private final SimpleDateFormat[] formats = { + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"), new SimpleDateFormat("MM/dd/yyyy HH:mm:ss"), new SimpleDateFormat("dd/MM/yyyy HH:mm:ss"), + new SimpleDateFormat("EEE MMM dd HH:mm:ss z yyyy")}; + @Override public String[] getUpgradableVersionRange() { return new String[] {"4.18.1.0", "4.19.0.0"}; @@ -55,6 +66,7 @@ public class Upgrade41810to41900 implements DbUpgrade, DbUpgradeSystemVmTemplate @Override public void performDataMigration(Connection conn) { + migrateBackupDates(conn); } @Override @@ -82,4 +94,103 @@ public class Upgrade41810to41900 implements DbUpgrade, DbUpgradeSystemVmTemplate throw new CloudRuntimeException("Failed to find / register SystemVM template(s)"); } } + + public void migrateBackupDates(Connection conn) { + LOG.info("Trying to convert backups' date column from varchar(255) to datetime type."); + + modifyDateColumnNameAndCreateNewOne(conn); + fetchDatesAndMigrateToNewColumn(conn); + dropOldColumn(conn); + + LOG.info("Finished converting backups' date column from varchar(255) to datetime."); + } + + private void modifyDateColumnNameAndCreateNewOne(Connection conn) { + String alterColumnName = "ALTER TABLE `cloud`.`backups` CHANGE COLUMN `date` `old_date` varchar(255);"; + try (PreparedStatement pstmt = conn.prepareStatement(alterColumnName)) { + pstmt.execute(); + } catch (SQLException e) { + String message = String.format("Unable to alter backups' date column name due to [%s].", e.getMessage()); + LOG.error(message, e); + throw new CloudRuntimeException(message, e); + } + + String createNewColumn = "ALTER TABLE `cloud`.`backups` ADD COLUMN `date` DATETIME;"; + try (PreparedStatement pstmt = conn.prepareStatement(createNewColumn)) { + pstmt.execute(); + } catch (SQLException e) { + String message = String.format("Unable to crate new backups' column date due to [%s].", e.getMessage()); + LOG.error(message, e); + throw new CloudRuntimeException(message, e); + } + } + + private void fetchDatesAndMigrateToNewColumn(Connection conn) { + String selectBackupDates = "SELECT `id`, `old_date` FROM `cloud`.`backups` WHERE 1;"; + String date; + java.sql.Date reformatedDate; + + try (PreparedStatement pstmt = conn.prepareStatement(selectBackupDates)) { + try (ResultSet result = pstmt.executeQuery()) { + while (result.next()) { + date = result.getString("old_date"); + reformatedDate = tryToTransformStringToDate(date); + updateBackupDate(conn, result.getLong("id"), reformatedDate); + } + } + } catch (SQLException e) { + String message = String.format("Unable to retrieve backup dates due to [%s].", e.getMessage()); + LOG.error(message, e); + throw new CloudRuntimeException(message, e); + } + } + + private java.sql.Date tryToTransformStringToDate(String date) { + Date parsedDate = null; + try { + parsedDate = DateUtil.parseTZDateString(date); + } catch (ParseException e) { + for (SimpleDateFormat sdf: formats) { + try { + parsedDate = sdf.parse(date); + } catch (ParseException ex) { + continue; + } + break; + } + } + if (parsedDate == null) { + String msg = String.format("Unable to parse date [%s]. Will change backup date to null.", date); + LOG.error(msg); + return null; + } + + return new java.sql.Date(parsedDate.getTime()); + } + + private void updateBackupDate(Connection conn, long id, java.sql.Date date) { + String updateBackupDate = "UPDATE `cloud`.`backups` SET `date` = ? WHERE `id` = ?;"; + try (PreparedStatement pstmt = conn.prepareStatement(updateBackupDate)) { + pstmt.setDate(1, date); + pstmt.setLong(2, id); + + pstmt.executeUpdate(); + } catch (SQLException e) { + String message = String.format("Unable to update backup date with id [%s] to date [%s] due to [%s].", id, date, e.getMessage()); + LOG.error(message, e); + throw new CloudRuntimeException(message, e); + } + } + + private void dropOldColumn(Connection conn) { + String dropOldColumn = "ALTER TABLE `cloud`.`backups` DROP COLUMN `old_date`;"; + try (PreparedStatement pstmt = conn.prepareStatement(dropOldColumn)) { + pstmt.execute(); + } catch (SQLException e) { + String message = String.format("Unable to drop old_date column due to [%s].", e.getMessage()); + LOG.error(message, e); + throw new CloudRuntimeException(message, e); + } + } + } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index dc47fcb6bb3..e5582609d68 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.backup; +import java.util.Date; import java.util.UUID; import javax.persistence.Column; @@ -27,6 +28,8 @@ import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; @Entity @Table(name = "backups") @@ -49,7 +52,8 @@ public class BackupVO implements Backup { private String backupType; @Column(name = "date") - private String date; + @Temporal(value = TemporalType.DATE) + private Date date; @Column(name = "size") private Long size; @@ -114,11 +118,11 @@ public class BackupVO implements Backup { } @Override - public String getDate() { - return date; + public Date getDate() { + return this.date; } - public void setDate(String date) { + public void setDate(Date date) { this.date = date; } diff --git a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java index 0297ce82f5e..fabc9821fd3 100644 --- a/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java +++ b/plugins/backup/dummy/src/main/java/org/apache/cloudstack/backup/DummyBackupProvider.java @@ -117,7 +117,7 @@ public class DummyBackupProvider extends AdapterBase implements BackupProvider { backup.setVmId(vm.getId()); backup.setExternalId("dummy-external-id"); backup.setType("FULL"); - backup.setDate(new Date().toString()); + backup.setDate(new Date()); backup.setSize(1024L); backup.setProtectedSize(1024000L); backup.setStatus(Backup.Status.BackedUp); diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java index 37051f9b5ee..9703203108a 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/NetworkerBackupProvider.java @@ -51,6 +51,8 @@ import javax.inject.Inject; import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.text.ParseException; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -605,7 +607,14 @@ public class NetworkerBackupProvider extends AdapterBase implements BackupProvid strayBackup.setVmId(vm.getId()); strayBackup.setExternalId(strayNetworkerBackup.getId()); strayBackup.setType(strayNetworkerBackup.getType()); - strayBackup.setDate(strayNetworkerBackup.getSaveTime()); + SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ"); + try { + strayBackup.setDate(formatterDateTime.parse(strayNetworkerBackup.getSaveTime())); + } catch (ParseException e) { + String msg = String.format("Unable to parse date [%s].", strayNetworkerBackup.getSaveTime()); + LOG.error(msg, e); + throw new CloudRuntimeException(msg, e); + } strayBackup.setStatus(Backup.Status.BackedUp); for ( Backup.VolumeInfo thisVMVol : vm.getBackupVolumeList()) { vmBackupSize += (thisVMVol.getSize() / 1024L /1024L); diff --git a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java index 939543df387..8bb89b635e9 100644 --- a/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java +++ b/plugins/backup/networker/src/main/java/org/apache/cloudstack/backup/networker/NetworkerClient.java @@ -211,7 +211,7 @@ public class NetworkerClient { SimpleDateFormat formatterDate = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat formatterTime = new SimpleDateFormat("HH:mm:ss"); - SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyy-MM-dd'T'HH:mm:ss"); + SimpleDateFormat formatterDateTime = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); String startDate = formatterDate.format(backupJobStart); String startTime = formatterTime.format(backupJobStart); @@ -252,7 +252,13 @@ public class NetworkerClient { backup.setVmId(vm.getId()); backup.setExternalId(networkerLatestBackup.getId()); backup.setType(networkerLatestBackup.getType()); - backup.setDate(networkerLatestBackup.getCreationTime()); + try { + backup.setDate(formatterDateTime.parse(networkerLatestBackup.getCreationTime())); + } catch (ParseException e) { + String msg = String.format("Unable to parse date [%s].", networkerLatestBackup.getCreationTime()); + LOG.error(msg, e); + throw new CloudRuntimeException(msg, e); + } backup.setSize(networkerLatestBackup.getSize().getValue()); backup.setProtectedSize(networkerLatestBackup.getSize().getValue()); backup.setStatus(org.apache.cloudstack.backup.Backup.Status.BackedUp); diff --git a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java index 40fbe97028a..1438dca4838 100644 --- a/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java +++ b/plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/veeam/VeeamClient.java @@ -34,6 +34,8 @@ import java.util.List; import java.util.Map; import java.util.StringJoiner; import java.util.UUID; +import java.util.Date; +import java.util.Calendar; import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; @@ -660,7 +662,7 @@ public class VeeamClient { private Backup.RestorePoint getRestorePointFromBlock(String[] parts) { LOG.debug(String.format("Processing block of restore points: [%s].", StringUtils.join(parts, ", "))); String id = null; - String created = null; + Date created = null; String type = null; for (String part : parts) { if (part.matches("Id(\\s)+:(.)*")) { @@ -668,7 +670,11 @@ public class VeeamClient { id = split[1].trim(); } else if (part.matches("CreationTime(\\s)+:(.)*")) { String [] split = part.split(":", 2); - created = split[1].trim(); + split[1] = StringUtils.trim(split[1]); + String [] time = split[1].split("[:/ ]"); + Calendar cal = Calendar.getInstance(); + cal.set(Integer.parseInt(time[2]), Integer.parseInt(time[0]) - 1, Integer.parseInt(time[1]), Integer.parseInt(time[3]), Integer.parseInt(time[4]), Integer.parseInt(time[5])); + created = cal.getTime(); } else if (part.matches("Type(\\s)+:(.)*")) { String [] split = part.split(":"); type = split[1].trim(); diff --git a/ui/src/components/view/ListResourceTable.vue b/ui/src/components/view/ListResourceTable.vue index c97aa0fb17e..16c1b388c5d 100644 --- a/ui/src/components/view/ListResourceTable.vue +++ b/ui/src/components/view/ListResourceTable.vue @@ -51,6 +51,11 @@ {{ text }} + + +