From 1f518227ce77615dbdd5dee3d8f9c8fe59d5a0ca Mon Sep 17 00:00:00 2001
From: Pierre Charton
Date: Tue, 14 Nov 2023 17:28:00 +0100
Subject: [PATCH] Bug fix + new features for backroll
---
.gitignore | 1 +
client/pom.xml | 10 +
developer/utils/conf/db.properties | 76 +++
plugins/backup/backroll/pom.xml | 45 ++
.../backup/BackrollBackupProvider.java | 390 +++++++++++
.../backup/backroll/BackrollClient.java | 612 ++++++++++++++++++
.../api/command/GetBackrollHello.java | 56 ++
.../api/response/ApiHelloResponse.java | 32 +
.../backup/backroll/model/BackrollBackup.java | 13 +
.../backroll/model/BackrollBackupMetrics.java | 19 +
.../backroll/model/BackrollOffering.java | 61 ++
.../backroll/model/BackrollTaskStatus.java | 31 +
.../backroll/model/BackrollVmBackup.java | 27 +
.../response/BackrollTaskRequestResponse.java | 8 +
.../model/response/api/LoginApiResponse.java | 23 +
.../archive/BackrollArchiveResponse.java | 25 +
.../archive/BackrollArchivesResponse.java | 12 +
.../BackrollBackupsFromVMResponse.java | 11 +
.../backup/BackrollBackupStatusResponse.java | 17 +
.../BackrollBackupStatusSuccessResponse.java | 11 +
.../backup/BackrollBackupMetricsResponse.java | 11 +
.../metrics/backup/BackupMetricsInfo.java | 17 +
.../BackrollVmMetricsResponse.java | 11 +
.../metrics/virtualMachine/CacheStats.java | 23 +
.../metrics/virtualMachine/InfosCache.java | 11 +
.../virtualMachine/InfosEncryption.java | 8 +
.../virtualMachine/InfosRepository.java | 14 +
.../metrics/virtualMachine/MetricsInfos.java | 17 +
.../virtualMachineBackups/Archives.java | 19 +
.../virtualMachineBackups/BackupInfos.java | 23 +
.../InfosEncryption.java | 8 +
.../InfosRepository.java | 14 +
.../VirtualMachineBackupsResponse.java | 11 +
.../policy/BackrollBackupPolicyResponse.java | 38 ++
.../policy/BackupPoliciesResponse.java | 15 +
.../backroll-backup/module.properties | 18 +
.../spring-backup-backroll-context.xml | 27 +
.../backup/BackrollBackupProviderTest.java | 49 ++
.../backup/backroll/BackrollClientTest.java | 22 +
plugins/pom.xml | 2 +
ui/package-lock.json | 194 +++---
ui/package.json | 2 +-
42 files changed, 1945 insertions(+), 89 deletions(-)
create mode 100644 developer/utils/conf/db.properties
create mode 100644 plugins/backup/backroll/pom.xml
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/BackrollBackupProvider.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/BackrollClient.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/command/GetBackrollHello.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/response/ApiHelloResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackup.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackupMetrics.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollOffering.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollTaskStatus.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollVmBackup.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollTaskRequestResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/api/LoginApiResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchiveResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchivesResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollBackupsFromVMResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusSuccessResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackrollBackupMetricsResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackupMetricsInfo.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/BackrollVmMetricsResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/CacheStats.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosCache.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosEncryption.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosRepository.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/MetricsInfos.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/Archives.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/BackupInfos.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosEncryption.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosRepository.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/VirtualMachineBackupsResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackrollBackupPolicyResponse.java
create mode 100644 plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackupPoliciesResponse.java
create mode 100644 plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/module.properties
create mode 100644 plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/spring-backup-backroll-context.xml
create mode 100644 plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/BackrollBackupProviderTest.java
create mode 100644 plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/BackrollClientTest.java
diff --git a/.gitignore b/.gitignore
index e638a09ae21..87b56ab5868 100644
--- a/.gitignore
+++ b/.gitignore
@@ -104,3 +104,4 @@ scripts/.pydevproject
venv
node_modules
.vscode
+*.bz2
diff --git a/client/pom.xml b/client/pom.xml
index cbe45d88b6c..502f889eec6 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -547,6 +547,11 @@
cloud-plugin-integrations-prometheus-exporter
${project.version}
+
+ org.apache.cloudstack
+ cloud-plugin-backup-backroll
+ ${project.version}
+
org.apache.cloudstack
cloud-plugin-backup-dummy
@@ -1001,6 +1006,11 @@
cloud-plugin-backup-veeam
${project.version}
+
+ org.apache.cloudstack
+ cloud-plugin-backup-backroll
+ ${project.version}
+
diff --git a/developer/utils/conf/db.properties b/developer/utils/conf/db.properties
new file mode 100644
index 00000000000..ad9b71ef319
--- /dev/null
+++ b/developer/utils/conf/db.properties
@@ -0,0 +1,76 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+
+# management server clustering parameters, change cluster.node.IP to the machine IP address
+# in which the management server is running
+cluster.node.IP=127.0.0.1
+cluster.servlet.port=9090
+region.id=1
+
+# CloudStack database settings
+db.cloud.username=cloud
+db.cloud.password=cloud
+db.root.password=12345
+db.cloud.host=localhost
+db.cloud.driver=jdbc:mysql
+db.cloud.port=3306
+db.cloud.name=cloud
+
+# CloudStack database tuning parameters
+db.cloud.maxActive=250
+db.cloud.maxIdle=30
+db.cloud.maxWait=10000
+db.cloud.autoReconnect=true
+db.cloud.validationQuery=SELECT 1
+db.cloud.testOnBorrow=true
+db.cloud.testWhileIdle=true
+db.cloud.timeBetweenEvictionRunsMillis=40000
+db.cloud.minEvictableIdleTimeMillis=240000
+db.cloud.poolPreparedStatements=false
+db.cloud.url.params=prepStmtCacheSize=517&cachePrepStmts=true&prepStmtCacheSqlLimit=4096&sessionVariables=sql_mode='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION'&serverTimezone=UTC
+
+# usage database settings
+db.usage.username=cloud
+db.usage.password=cloud
+db.usage.host=localhost
+# It's not guaranteed that using a different DB provider than the one from the regular cloud DB will work
+db.usage.driver=jdbc:mysql
+db.usage.port=3306
+db.usage.name=cloud_usage
+
+# usage database tuning parameters
+db.usage.maxActive=100
+db.usage.maxIdle=30
+db.usage.maxWait=10000
+db.usage.autoReconnect=true
+db.usage.url.params=serverTimezone=UTC
+
+# Simulator database settings
+db.simulator.username=cloud
+db.simulator.password=cloud
+db.simulator.host=localhost
+# It's not guaranteed that using a different DB provider than the one from the regular cloud DB will work
+db.simulator.driver=jdbc:mysql
+db.simulator.port=3306
+db.simulator.name=simulator
+db.simulator.maxActive=250
+db.simulator.maxIdle=30
+db.simulator.maxWait=10000
+db.simulator.autoReconnect=true
diff --git a/plugins/backup/backroll/pom.xml b/plugins/backup/backroll/pom.xml
new file mode 100644
index 00000000000..14bbd8c48e4
--- /dev/null
+++ b/plugins/backup/backroll/pom.xml
@@ -0,0 +1,45 @@
+
+
+ 4.0.0
+ cloud-plugin-backup-backroll
+ Apache CloudStack Plugin - BackRoll Backup and Recovery Plugin by DIMSI
+
+ cloudstack-plugins
+ org.apache.cloudstack
+ 4.19.0.0-SNAPSHOT
+ ../../pom.xml
+
+
+
+ org.apache.cloudstack
+ cloud-api
+ ${project.version}
+
+
+ org.apache.cloudstack
+ cloud-utils
+ ${project.version}
+
+
+ org.json
+ json
+
+
+
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/BackrollBackupProvider.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/BackrollBackupProvider.java
new file mode 100644
index 00000000000..522cb9e24ea
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/BackrollBackupProvider.java
@@ -0,0 +1,390 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.backup;
+
+import java.net.URISyntaxException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import javax.inject.Inject;
+
+import org.apache.cloudstack.backup.Backup.Metric;
+import org.apache.cloudstack.backup.backroll.BackrollClient;
+import org.apache.cloudstack.backup.backroll.model.BackrollBackupMetrics;
+import org.apache.cloudstack.backup.backroll.model.BackrollTaskStatus;
+import org.apache.cloudstack.backup.backroll.model.BackrollVmBackup;
+import org.apache.cloudstack.backup.dao.BackupDao;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VMInstanceVO;
+import com.cloud.vm.VirtualMachine;
+import com.cloud.vm.dao.VMInstanceDao;
+
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+
+public class BackrollBackupProvider extends AdapterBase implements BackupProvider, Configurable {
+
+ private static final Logger s_logger = Logger.getLogger(BackrollBackupProvider.class);
+ public static final String BACKUP_IDENTIFIER = "-CSBKP-";
+
+ public ConfigKey BackrollUrlConfigKey = new ConfigKey<>("Advanced", String.class,
+ "backup.plugin.backroll.config.url",
+ "http://api.backup.demo.ccc:5050/api/v1",
+ "Url for backroll plugin by DIMSI", true, ConfigKey.Scope.Zone);
+
+ public ConfigKey BackrollAppNameConfigKey = new ConfigKey<>("Advanced", String.class,
+ "backup.plugin.backroll.config.appname",
+ "backroll_api",
+ "App Name for backroll plugin by DIMSI", true, ConfigKey.Scope.Zone);
+
+ public ConfigKey BackrollPasswordConfigKey = new ConfigKey<>("Advanced", String.class,
+ "backup.plugin.backroll.config.password",
+ "VviX8dALauSyYJMqVYJqf3UyZOpO3joS",
+ "Password for backroll plugin by DIMSI", true, ConfigKey.Scope.Zone);
+
+ private BackrollClient backrollClient;
+
+ @Inject
+ private BackupDao backupDao;
+ @Inject
+ private VMInstanceDao vmInstanceDao;
+
+ @Override
+ public String getName() {
+ return "backroll";
+ }
+
+ @Override
+ public String getDescription() {
+ return "Backroll Backup Plugin";
+ }
+
+ @Override
+ public List listBackupOfferings(Long zoneId) {
+ s_logger.debug("Listing backup policies on backroll B&R Plugin");
+ BackrollClient client = getClient(zoneId);
+ String urlToRequest = client.getBackupOfferingUrl();
+ if(!StringUtils.isEmpty(urlToRequest)){
+ return client.getBackupOfferings(urlToRequest);
+ }
+ return new ArrayList();
+ }
+
+ @Override
+ public boolean isValidProviderOffering(Long zoneId, String uuid) {
+ s_logger.info("Checking if backup offering exists on the Backroll Backup Provider");
+ return true;
+ }
+
+ @Override
+ public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering) {
+ s_logger.info("Creating VM backup for VM " + vm.getInstanceName() + " from backup offering " + backupOffering.getName());
+ ((VMInstanceVO) vm).setBackupExternalId(backupOffering.getUuid());
+ return true;
+ }
+
+ @Override
+ public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) {
+ s_logger.debug("Restoring vm " + vm.getUuid() + "from backup " + backup.getUuid() + " on the backroll Backup Provider");
+ boolean isSuccess = getClient(vm.getDataCenterId()).restoreVMFromBackup(vm.getUuid(), getBackupName(backup));
+ return isSuccess;
+ }
+
+ @Override
+ public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid) {
+ s_logger.debug("Restoring volume " + volumeUuid + "from backup " + backup.getUuid() + " on the backroll Backup Provider");
+ throw new CloudRuntimeException("Backroll plugin does not support this feature");
+ }
+
+ @Override
+ public Map getBackupMetrics(Long zoneId, List vms) {
+ final Map metrics = new HashMap<>();
+ if (CollectionUtils.isEmpty(vms)) {
+ s_logger.warn("Unable to get VM Backup Metrics because the list of VMs is empty.");
+ return metrics;
+ }
+
+ List vmUuids = vms.stream().filter(Objects::nonNull).map(VirtualMachine::getUuid).collect(Collectors.toList());
+ s_logger.debug(String.format("Get Backup Metrics for VMs: [%s].", String.join(", ", vmUuids)));
+
+ for (final VirtualMachine vm : vms) {
+ if (vm == null) {
+ continue;
+ }
+
+ Metric metric = getClient(zoneId).getVirtualMachineMetrics(vm.getUuid());
+ s_logger.debug(String.format("Metrics for VM [uuid: %s, name: %s] is [backup size: %s, data size: %s].", vm.getUuid(),
+ vm.getInstanceName(), metric.getBackupSize(), metric.getDataSize()));
+ metrics.put(vm, metric);
+ }
+ return metrics;
+ }
+
+ @Override
+ public boolean removeVMFromBackupOffering(VirtualMachine vm) {
+ s_logger.info("Removing VM ID " + vm.getUuid() + " from Backrool backup offering ");
+
+ boolean isAnyProblemWhileRemovingBackups = false;
+
+ List backupsInCs = backupDao.listByVmId(null, vm.getId());
+
+ for (Backup backup : backupsInCs) {
+ s_logger.debug("Trying to remove backup with id" + backup.getId());
+
+ if (getClient(backup.getZoneId()).deleteBackup(vm.getUuid(), getBackupName(backup))) {
+ var message = MessageFormat.format("Backup {0} deleted in Backroll for virtual machine {1}", backup.getId(), vm.getName());
+ s_logger.info(message);
+ if(!backupDao.remove(backup.getId())){
+ isAnyProblemWhileRemovingBackups = true;
+ }
+ message = MessageFormat.format("Backup {0} deleted in CS for virtual machine {1}", backup.getId(), vm.getName());
+ s_logger.info(message);
+ } else {
+ isAnyProblemWhileRemovingBackups = false;
+ }
+ }
+
+ if(isAnyProblemWhileRemovingBackups) {
+ var message = MessageFormat.format("Problems occured while removing some backups for virtual machine {0}", vm.getName());
+ s_logger.info(message);
+ }
+ return isAnyProblemWhileRemovingBackups;
+ }
+
+ @Override
+ public boolean willDeleteBackupsOnOfferingRemoval() {
+ return false;
+ }
+
+ @Override
+ public boolean takeBackup(VirtualMachine vm) {
+ s_logger.info("Starting backup for VM ID " + vm.getUuid() + " on backroll provider");
+ final BackrollClient client = getClient(vm.getDataCenterId());
+
+ String idBackupTask = client.startBackupJob(vm.getUuid());
+ if(!StringUtils.isEmpty(idBackupTask)) {
+ BackupVO backup = new BackupVO();
+ backup.setVmId(vm.getId());
+ backup.setExternalId(idBackupTask);
+ backup.setType("INCREMENTIAL");
+ backup.setDate(new DateTime().toDate());
+ backup.setSize(0L);
+ backup.setProtectedSize(0L);
+ backup.setStatus(Backup.Status.BackingUp);
+ backup.setBackupOfferingId(vm.getBackupOfferingId());
+ backup.setAccountId(vm.getAccountId());
+ backup.setDomainId(vm.getDomainId());
+ backup.setZoneId(vm.getDataCenterId());
+ return backupDao.persist(backup) != null;
+ }
+ return false;
+ }
+
+ @Override
+ public void syncBackups(VirtualMachine vm, Backup.Metric metric) {
+ s_logger.info("Starting sync backup for VM ID " + vm.getUuid() + " on backroll provider");
+
+ final BackrollClient client = getClient(vm.getDataCenterId());
+ List backupsInDb = backupDao.listByVmId(null, vm.getId());
+
+ for (Backup backup : backupsInDb) {
+ if(backup.getStatus().equals(Backup.Status.BackingUp)) {
+ BackrollTaskStatus response = client.checkBackupTaskStatus(backup.getExternalId());
+ if(response != null) {
+ s_logger.debug("backroll backup id: " + backup.getExternalId());
+ s_logger.debug("backroll backup status: " + response.getState());
+
+ BackupVO backupToUpdate = new BackupVO();
+ backupToUpdate.setVmId(backup.getVmId());
+ backupToUpdate.setExternalId(backup.getExternalId());
+ backupToUpdate.setType(backup.getType());
+ backupToUpdate.setDate(backup.getDate());
+ backupToUpdate.setSize(backup.getSize());
+ backupToUpdate.setProtectedSize(backup.getProtectedSize());
+ backupToUpdate.setBackupOfferingId(vm.getBackupOfferingId());
+ backupToUpdate.setAccountId(backup.getAccountId());
+ backupToUpdate.setDomainId(backup.getDomainId());
+ backupToUpdate.setZoneId(backup.getZoneId());
+
+ if(response.getState().equals("PENDING")) {
+ backupToUpdate.setStatus(Backup.Status.BackingUp);
+ } else if(response.getState().equals("FAILURE")) {
+ backupToUpdate.setStatus(Backup.Status.Failed);
+ } else if(response.getState().equals("SUCCESS")) {
+ backupToUpdate.setStatus(Backup.Status.BackedUp);
+ backupToUpdate.setExternalId(backup.getExternalId() + "," + response.getInfo());
+
+ BackrollBackupMetrics backupMetrics = client.getBackupMetrics(vm.getUuid() , response.getInfo());
+ if(backupMetrics != null) {
+ backupToUpdate.setSize(backupMetrics.getDeduplicated()); // real size
+ backupToUpdate.setProtectedSize(backupMetrics.getSize()); // total size
+ }
+ } else {
+ backupToUpdate.setStatus(Backup.Status.BackingUp);
+ }
+
+ if(backupDao.persist(backupToUpdate) != null) {
+ s_logger.info("Backroll mise à jour enregistrée");
+ backupDao.remove(backup.getId());
+ }
+ }
+ } else if(backup.getStatus().equals(Backup.Status.BackedUp) && backup.getSize().equals(0L)) {
+ String backupId = backup.getExternalId().contains(",") ? backup.getExternalId().split(",")[1] : backup.getExternalId();
+ BackrollBackupMetrics backupMetrics = client.getBackupMetrics(vm.getUuid() , backupId);
+ if(backupMetrics != null) {
+ BackupVO backupToUpdate = ((BackupVO) backup);
+ backupToUpdate.setSize(backupMetrics.getDeduplicated()); // real size
+ backupToUpdate.setProtectedSize(backupMetrics.getSize()); // total size
+ backupDao.persist(backupToUpdate);
+ }
+ }
+ }
+
+ // Backups synchronisation between Backroll ad CS Db
+ List backupsFromBackroll = client.getAllBackupsfromVirtualMachine(vm.getUuid());
+ backupsInDb = backupDao.listByVmId(null, vm.getId());
+
+ // insert new backroll backup in CS
+ for (BackrollVmBackup backupInBackroll : backupsFromBackroll) {
+ Backup backupToFind = backupsInDb.stream()
+ .filter(backupInDb -> backupInDb.getExternalId().contains(backupInBackroll.getName()))
+ .findAny()
+ .orElse(null);
+
+ if(backupToFind == null) {
+ BackupVO backupToInsert = new BackupVO();
+ backupToInsert.setVmId(vm.getId());
+ backupToInsert.setExternalId(backupInBackroll.getId() + "," + backupInBackroll.getName());
+ backupToInsert.setType("INCREMENTIAL");
+ backupToInsert.setDate(backupInBackroll.getDate());
+ backupToInsert.setSize(0L);
+ backupToInsert.setProtectedSize(0L);
+ backupToInsert.setStatus(Backup.Status.BackedUp);
+ backupToInsert.setBackupOfferingId(vm.getBackupOfferingId());
+ backupToInsert.setAccountId(vm.getAccountId());
+ backupToInsert.setDomainId(vm.getDomainId());
+ backupToInsert.setZoneId(vm.getDataCenterId());
+ backupDao.persist(backupToInsert);
+ }
+ if(backupToFind != null && backupToFind.getStatus() == Backup.Status.Removed) {
+ BackupVO backupToUpdate = ((BackupVO) backupToFind);
+ backupToUpdate.setStatus(Backup.Status.BackedUp);
+ if(backupDao.persist(backupToUpdate) != null) {
+ s_logger.info("Backroll mise à jour enregistrée");
+ backupDao.remove(backupToFind.getId());
+ }
+ }
+ }
+
+ // delete deleted backroll backup in CS
+ backupsInDb = backupDao.listByVmId(null, vm.getId());
+ for (Backup backup : backupsInDb) {
+ String backupName = backup.getExternalId().contains(",") ? backup.getExternalId().split(",")[1] : backup.getExternalId();
+ BackrollVmBackup backupToFind = backupsFromBackroll.stream()
+ .filter(backupInBackroll -> backupInBackroll.getName().contains(backupName))
+ .findAny()
+ .orElse(null);
+
+ if(backupToFind == null) {
+ BackupVO backupToUpdate = ((BackupVO) backup);
+ backupToUpdate.setStatus(Backup.Status.Removed);
+ if(backupDao.persist(backupToUpdate) != null) {
+ s_logger.debug("Backroll suppression enregistrée (sync)");
+ }
+ }
+ }
+ }
+
+ @Override
+ public String getConfigComponentName() {
+ return BackupService.class.getSimpleName();
+ }
+
+ @Override
+ public ConfigKey>[] getConfigKeys() {
+ return new ConfigKey[]{
+ BackrollUrlConfigKey,
+ BackrollAppNameConfigKey,
+ BackrollPasswordConfigKey
+ };
+ }
+
+ @Override
+ public boolean deleteBackup(Backup backup, boolean forced) {
+ s_logger.info("backroll delete backup id: " + backup.getExternalId());
+ if(backup.getStatus().equals(Backup.Status.BackingUp)) {
+ throw new CloudRuntimeException("You can't delete a backup while it still BackingUp");
+ } else {
+ s_logger.debug("backroll - try delete backup");
+ VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId());
+
+ if(backup.getStatus().equals(Backup.Status.Removed) || backup.getStatus().equals(Backup.Status.Failed)){
+ return deleteBackupInDb(backup);
+ } else {
+ if (getClient(backup.getZoneId()).deleteBackup(vm.getUuid(), getBackupName(backup))) {
+ s_logger.debug("backroll delete backup ok on backroll side");
+ return deleteBackupInDb(backup);
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean deleteBackupInDb(Backup backup) {
+ BackupVO backupToUpdate = ((BackupVO) backup);
+ backupToUpdate.setStatus(Backup.Status.Removed);
+ if(backupDao.persist(backupToUpdate) != null) {
+ s_logger.debug("Backroll backup delete in database");
+ return true;
+ }
+ return false;
+ }
+
+ protected BackrollClient getClient(final Long zoneId) {
+ s_logger.debug("Backroll Provider GetClient");
+ try {
+ if(backrollClient == null){
+ s_logger.debug("backroll client null - instanciation of new one ");
+ backrollClient = new BackrollClient(BackrollUrlConfigKey.valueIn(zoneId), BackrollAppNameConfigKey.valueIn(zoneId), BackrollPasswordConfigKey.valueIn(zoneId), true, 300, 600);
+ }
+ return backrollClient;
+ } catch (URISyntaxException e) {
+ throw new CloudRuntimeException("Failed to parse Backroll API URL: " + e.getMessage());
+ } catch (NoSuchAlgorithmException | KeyManagementException e) {
+ s_logger.info("Failed to build Backroll API client due to: ", e);
+ }
+ throw new CloudRuntimeException("Failed to build Backroll API client");
+ }
+
+ private String getBackupName(Backup backup) {
+ return backup.getExternalId().substring(backup.getExternalId().indexOf(",") + 1);
+ }
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/BackrollClient.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/BackrollClient.java
new file mode 100644
index 00000000000..2abf5c2522a
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/BackrollClient.java
@@ -0,0 +1,612 @@
+package org.apache.cloudstack.backup.backroll;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.cloudstack.api.ApiErrorCode;
+import org.apache.cloudstack.api.ServerApiException;
+import org.apache.cloudstack.backup.backroll.model.BackrollBackup;
+import org.apache.cloudstack.backup.backroll.model.BackrollBackupMetrics;
+import org.apache.cloudstack.backup.backroll.model.BackrollOffering;
+import org.apache.cloudstack.backup.backroll.model.BackrollTaskStatus;
+import org.apache.cloudstack.backup.backroll.model.BackrollVmBackup;
+import org.apache.cloudstack.backup.backroll.model.response.BackrollTaskRequestResponse;
+import org.apache.cloudstack.backup.backroll.model.response.api.LoginApiResponse;
+import org.apache.cloudstack.backup.backroll.model.response.archive.BackrollArchiveResponse;
+import org.apache.cloudstack.backup.backroll.model.response.archive.BackrollBackupsFromVMResponse;
+import org.apache.cloudstack.backup.backroll.model.response.backup.BackrollBackupStatusResponse;
+import org.apache.cloudstack.backup.backroll.model.response.backup.BackrollBackupStatusSuccessResponse;
+import org.apache.cloudstack.backup.backroll.model.response.metrics.backup.BackrollBackupMetricsResponse;
+import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine.BackrollVmMetricsResponse;
+import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine.CacheStats;
+import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups.BackupInfos;
+import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups.VirtualMachineBackupsResponse;
+import org.apache.cloudstack.backup.backroll.model.response.policy.BackrollBackupPolicyResponse;
+import org.apache.cloudstack.backup.backroll.model.response.policy.BackupPoliciesResponse;
+import org.apache.cloudstack.utils.security.SSLUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.util.EntityUtils;
+import org.apache.log4j.Logger;
+import org.joda.time.DateTime;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.nio.TrustAllManager;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.cloudstack.backup.BackupOffering;
+import org.apache.cloudstack.backup.Backup.Metric;
+
+public class BackrollClient {
+
+ private static final Logger LOG = Logger.getLogger(BackrollClient.class);
+
+ private int restoreTimeout;
+
+ private final URI apiURI;
+
+ private final HttpClient httpClient;
+
+ private String backrollToken = null;
+ private String appname = null;
+ private String password = null;
+
+ public BackrollClient(final String url, final String appname, final String password,
+ final boolean validateCertificate, final int timeout,
+ final int restoreTimeout)
+ throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException {
+ this.apiURI = new URI(url);
+ this.restoreTimeout = restoreTimeout;
+ this.appname = appname;
+ this.password = password;
+
+ final RequestConfig config = RequestConfig.custom()
+ .setConnectTimeout(timeout * 1000)
+ .setConnectionRequestTimeout(timeout * 1000)
+ .setSocketTimeout(timeout * 1000)
+ .build();
+
+ if (!validateCertificate) {
+ final SSLContext sslcontext = SSLUtils.getSSLContext();
+ sslcontext.init(null, new X509TrustManager[] { new TrustAllManager() }, new SecureRandom());
+ final SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslcontext,
+ NoopHostnameVerifier.INSTANCE);
+ this.httpClient = HttpClientBuilder.create()
+ .setDefaultRequestConfig(config)
+ .setSSLSocketFactory(factory)
+ .build();
+ } else {
+ this.httpClient = HttpClientBuilder.create()
+ .setDefaultRequestConfig(config)
+ .build();
+ }
+
+ if (StringUtils.isEmpty(backrollToken) || !isAuthentificated()) {
+ login(appname, password);
+ }
+ }
+
+ public String startBackupJob(final String jobId) {
+ LOG.info(String.format("Trying to start backup for Backroll job: %s", jobId));
+
+ try {
+ loginIfAuthentificationFailed();
+
+ final HttpResponse response = post(String.format("/tasks/singlebackup/%s", jobId), null);
+ if(isResponseOk(response)) {
+ HttpEntity body = response.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ String idBackupTask = backupTaskRequestResponse.location.replace("/api/v1/status/", "");
+ EntityUtils.consumeQuietly(response.getEntity());
+ return idBackupTask;
+ }
+ } catch (final IOException e) {
+ LOG.error(String.format("Failed to start Backroll backup job due to: %s", e.getMessage()));
+ }
+ return null;
+ }
+
+ public String getBackupOfferingUrl(){
+ LOG.info("Trying to list backroll backup policies");
+
+ loginIfAuthentificationFailed();
+
+ try {
+
+ final HttpResponse backupPoliciesRequestResponse = get("/backup_policies");
+
+ if(isResponseOk(backupPoliciesRequestResponse)) {
+ HttpEntity body = backupPoliciesRequestResponse.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ EntityUtils.consumeQuietly(backupPoliciesRequestResponse.getEntity());
+ return backupTaskRequestResponse.location.replace("/api/v1", "");
+ }
+ } catch (final IOException e) {
+ LOG.error(String.format("Failed to list Backroll jobs due to: %s", e.getMessage()));
+ }
+ return null;
+ }
+
+ public List getBackupOfferings(String idTask) {
+ LOG.info("Trying to list backroll backup policies");
+
+ loginIfAuthentificationFailed();
+
+ try {
+ String bodyStr = waitForGetRequestResponse(idTask);
+
+ BackupPoliciesResponse backupPoliciesResponse = new ObjectMapper().readValue(bodyStr, BackupPoliciesResponse.class);
+
+ final List policies = new ArrayList<>();
+ for (final BackrollBackupPolicyResponse policy : backupPoliciesResponse.backupPolicies) {
+ policies.add(new BackrollOffering(policy.name, policy.id));
+ }
+ return policies;
+ } catch (final IOException e) {
+ LOG.error(String.format("Failed to list Backroll jobs due to: %s", e.getMessage()));
+ } catch (InterruptedException e) {
+ LOG.error(String.format("Failed to list Backroll jobs due to: %s", e.getMessage()));
+ }
+ return new ArrayList();
+ }
+
+ public boolean restoreVMFromBackup(final String vmId, final String backupName) {
+ LOG.info(String.format("Start restore backup with backroll with backup %s for vm %s", backupName, vmId));
+
+ loginIfAuthentificationFailed();
+
+ try {
+ JSONObject jsonBody = new JSONObject();
+ try {
+ jsonBody.put("virtual_machine_id", vmId);
+ jsonBody.put("backup_name", backupName);
+ jsonBody.put("storage", "");
+ jsonBody.put("mode", "single");
+
+ } catch (JSONException e) {
+ LOG.error(String.format("Backroll Error: %s", e.getMessage()));
+ }
+
+ final HttpResponse response = post(String.format("/tasks/restore/%s", vmId), jsonBody);
+ LOG.debug(response.toString());
+
+ if(isResponseOk(response)) {
+ HttpEntity body = response.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+ LOG.debug(bodyStr);
+
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ String urlToRequest = backupTaskRequestResponse.location.replace("/api/v1", "");
+ EntityUtils.consumeQuietly(response.getEntity());
+
+ String responseStatus = waitForGetRequestResponse(urlToRequest);
+ LOG.debug("RESTORE " + responseStatus);
+ if(responseStatus.contains("SUCCESS")) {
+ LOG.debug("RESTORE SUCCESS");
+ return true;
+ } else {
+ return false;
+ }
+ }
+ } catch (final IOException | InterruptedException e) {
+ LOG.error(String.format("Ouch! Failed to restore VM with Backroll due to: %s", e.getMessage()));
+ throw new CloudRuntimeException(String.format("Ouch! Failed to restore VM with Backroll due to: %s", e.getMessage()));
+ }
+ return false;
+ }
+
+ public BackrollTaskStatus checkBackupTaskStatus(String taskId) {
+ LOG.info(String.format("Trying to get backup status for Backroll task: %s", taskId));
+
+ loginIfAuthentificationFailed();
+
+ try {
+ final HttpResponse backupStatusResponse = get("/status/" + taskId);
+
+ if(isResponseOk(backupStatusResponse)) {
+ BackrollTaskStatus status = new BackrollTaskStatus();
+
+ HttpEntity body = backupStatusResponse.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+
+ if(bodyStr.contains("FAILURE") || bodyStr.contains("PENDING")) {
+ BackrollBackupStatusResponse backupStatusRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollBackupStatusResponse.class);
+ EntityUtils.consumeQuietly(backupStatusResponse.getEntity());
+ status.setState(backupStatusRequestResponse.state);
+ } else {
+ BackrollBackupStatusSuccessResponse backupStatusSuccessRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollBackupStatusSuccessResponse.class);
+ EntityUtils.consumeQuietly(backupStatusResponse.getEntity());
+ status.setState(backupStatusSuccessRequestResponse.state);
+ status.setInfo(backupStatusSuccessRequestResponse.info);
+ }
+
+ return status;
+ } else {
+ EntityUtils.consumeQuietly(backupStatusResponse.getEntity());
+ // throw new CloudRuntimeException("Failed to retrieve backups status for this VM via Backroll");
+ LOG.error(String.format("Failed to retrieve backups status for this VM via Backroll"));
+ }
+ } catch (final IOException e) {
+ LOG.error(String.format("Failed to check backups status due to: %s", e.getMessage()));
+ }
+ return null;
+ }
+
+ public boolean deleteBackup(final String vmId, final String backupName) {
+ LOG.info(String.format("Trying to delete backup %s for vm %s using Backroll", vmId, backupName));
+
+ loginIfAuthentificationFailed();
+
+ try {
+ final HttpResponse response = delete(String.format("/virtualmachines/%s/backups/%s", vmId, backupName));
+ LOG.debug(response.toString());
+ if(isResponseOk(response)) {
+ HttpEntity body = response.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+ LOG.debug(bodyStr);
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ String urlToRequest = backupTaskRequestResponse.location.replace("/api/v1", "");
+ EntityUtils.consumeQuietly(response.getEntity());
+
+ String responseStatus = waitForGetRequestResponse(urlToRequest);
+ LOG.debug(responseStatus);
+ BackrollBackupsFromVMResponse backrollBackupsFromVMResponse = new ObjectMapper().readValue(responseStatus, BackrollBackupsFromVMResponse.class);
+ if(backrollBackupsFromVMResponse.state.equals("SUCCESS")) {
+ EntityUtils.consumeQuietly(response.getEntity());
+ return true;
+ } else {
+ EntityUtils.consumeQuietly(response.getEntity());
+ return false;
+ }
+ }
+ EntityUtils.consumeQuietly(response.getEntity());
+ return false;
+ } catch (final IOException | InterruptedException e) {
+ LOG.error(String.format("Failed to delete backup using Backroll due to: %s", e.getMessage()));
+ }
+ return false;
+ }
+
+ public Metric getVirtualMachineMetrics(final String vmId){
+ LOG.info(String.format("Trying to retrieve virtual machine metric from Backroll for vm %s", vmId));
+
+ loginIfAuthentificationFailed();
+
+ Metric metric = new Metric(0L,0L);
+
+ try {
+
+ final HttpResponse metricsRequestResponse = get(String.format("/virtualmachines/%s/repository", vmId));
+
+ if(isResponseOk(metricsRequestResponse)) {
+ HttpEntity body = metricsRequestResponse.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+ LOG.debug(bodyStr);
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ String urlToRequest = backupTaskRequestResponse.location.replace("/api/v1", "");
+ LOG.debug(urlToRequest);
+ EntityUtils.consumeQuietly(metricsRequestResponse.getEntity());
+ bodyStr = waitForGetRequestResponse(urlToRequest);
+ LOG.debug(bodyStr);
+
+ BackrollVmMetricsResponse vmMetricsResponse = new ObjectMapper().readValue(bodyStr, BackrollVmMetricsResponse.class);
+
+ if(vmMetricsResponse != null) {
+
+ if(vmMetricsResponse.state.equals("SUCCESS")) {
+ LOG.debug("SUCCESS ok");
+ if(vmMetricsResponse.infos != null) {
+ if(vmMetricsResponse.infos.cache != null) {
+ if(vmMetricsResponse.infos.cache.stats != null) {
+ CacheStats stats = vmMetricsResponse.infos.cache.stats;
+ long size = Long.parseLong(stats.totalSize);
+ return new Metric(size,size);
+ }
+ }
+ }
+ }
+ }
+
+ EntityUtils.consumeQuietly(metricsRequestResponse.getEntity());
+ }
+ } catch (final IOException | InterruptedException e) {
+ LOG.error(String.format("Failed to retrieve virtual machine metrics with Backroll due to: %s", e.getMessage()));
+ }
+
+ return metric;
+ }
+
+ public BackrollBackupMetrics getBackupMetrics(String vmId, String backupId) {
+ LOG.info(String.format("Trying to get backup metrics for VM: %s, and backup: %s", vmId, backupId));
+
+ loginIfAuthentificationFailed();
+
+ try {
+ final HttpResponse backupMetricsResponse = get(String.format("/virtualmachines/%s/backups/%s", vmId, backupId));
+
+ if(isResponseOk(backupMetricsResponse)) {
+ HttpEntity body = backupMetricsResponse.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ String urlToRequest = backupTaskRequestResponse.location.replace("/api/v1", "");
+ LOG.debug(urlToRequest);
+ EntityUtils.consumeQuietly(backupMetricsResponse.getEntity());
+ bodyStr = waitForGetRequestResponse(urlToRequest);
+ LOG.debug(bodyStr);
+
+ BackrollBackupMetricsResponse metrics = new ObjectMapper().readValue(bodyStr, BackrollBackupMetricsResponse.class);
+ EntityUtils.consumeQuietly(backupMetricsResponse.getEntity());
+ if(metrics.info != null) {
+ return new BackrollBackupMetrics(Long.parseLong(metrics.info.originalSize), Long.parseLong(metrics.info.deduplicatedSize));
+ }
+ } else {
+ EntityUtils.consumeQuietly(backupMetricsResponse.getEntity());
+ throw new CloudRuntimeException("Failed to retrieve backups status for this VM via Backroll");
+ }
+ } catch (final IOException | InterruptedException e) {
+ LOG.error(String.format("Failed to check backups status due to: %s", e.getMessage()));
+ }
+ return null;
+ }
+
+ public List getAllBackupsfromVirtualMachine(String vmId) {
+ LOG.info(String.format("Trying to retrieve all backups for vm %s", vmId));
+
+ List backups = new ArrayList();
+
+ try {
+ HttpResponse backupsResponse = get(String.format("/virtualmachines/%s/backups", vmId));
+
+ if(isResponseOk(backupsResponse)) {
+ HttpEntity body = backupsResponse.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ String urlToRequest = backupTaskRequestResponse.location.replace("/api/v1", "");
+ LOG.debug(urlToRequest);
+ EntityUtils.consumeQuietly(backupsResponse.getEntity());
+
+ bodyStr = waitForGetRequestResponse(urlToRequest);
+
+ VirtualMachineBackupsResponse response = new ObjectMapper().readValue(bodyStr, VirtualMachineBackupsResponse.class);
+ EntityUtils.consumeQuietly(backupsResponse.getEntity());
+
+ if(response.state.equals("SUCCESS")) {
+ if(response.info.archives.size() > 0) {
+ for (BackupInfos infos : response.info.archives) {
+ var dateStart = new DateTime(infos.start);
+ backups.add(new BackrollVmBackup(infos.id, infos.name, dateStart.toDate()));
+ }
+ }
+ }
+ }
+
+ } catch (IOException | InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ return backups;
+ }
+
+ private HttpResponse post(final String path, final JSONObject json) throws IOException {
+ String xml = null;
+ StringEntity params = null;
+ if (json != null) {
+ LOG.debug("JSON " + json.toString());
+ params = new StringEntity(json.toString(), ContentType.APPLICATION_JSON);
+ }
+
+ String url = apiURI.toString() + path;
+ final HttpPost request = new HttpPost(url);
+
+ if (params != null) {
+ request.setEntity(params);
+ }
+
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + backrollToken);
+ request.setHeader("Content-type", "application/json");
+
+ final HttpResponse response = httpClient.execute(request);
+
+ LOG.info(String.format("Response received in POST request with body [%s] is: [%s] for URL [%s].", xml, response.toString(), url));
+
+ return response;
+ }
+
+ protected HttpResponse get(final String path) throws IOException {
+ String url = apiURI.toString() + path;
+ LOG.debug(String.format("Backroll URL %s", url));
+ final HttpGet request = new HttpGet(url);
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + backrollToken);
+ request.setHeader("Content-type", "application/json");
+ final HttpResponse response = httpClient.execute(request);
+ LOG.debug(String.format("Response received in GET request is: [%s] for URL: [%s].", response.toString(), url));
+ return response;
+ }
+
+ protected HttpResponse delete(final String path) throws IOException {
+ String url = apiURI.toString() + path;
+ final HttpDelete request = new HttpDelete(url);
+ request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + backrollToken);
+ request.setHeader("Content-type", "application/json");
+ final HttpResponse response = httpClient.execute(request);
+ LOG.debug(String.format("Response received in GET request is: [%s] for URL: [%s].", response.toString(), url));
+ return response;
+ }
+
+ private boolean isResponseOk(final HttpResponse response) {
+ if((response.getStatusLine().getStatusCode() == HttpStatus.SC_OK ||
+ response.getStatusLine().getStatusCode() == HttpStatus.SC_ACCEPTED)) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean isResponseAuthorized(final HttpResponse response) {
+ return (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK || response.getStatusLine().getStatusCode() == HttpStatus.SC_ACCEPTED) ? true : false;
+ }
+
+ private String waitForGetRequestResponse(String urlToRequest) throws IOException, InterruptedException {
+ HttpEntity body;
+ String bodyStr = "";
+ int cpt = 0;
+ //int threshold = 30; // 5 minutes
+ int threshold = 12; // 2 minutes
+
+ do {
+ if(cpt == threshold){
+ break;
+ } else {
+ final HttpResponse response = get(urlToRequest);
+ LOG.debug("Backroll!!!" + response);
+ if (isResponseOk(response)) {
+
+ body = response.getEntity();
+ bodyStr = EntityUtils.toString(body);
+ LOG.debug(bodyStr);
+ EntityUtils.consumeQuietly(response.getEntity());
+ } else {
+ throw new CloudRuntimeException("An error occured with Backroll");
+ }
+ cpt++;
+ TimeUnit.SECONDS.sleep(10);
+ }
+
+ } while (bodyStr.contains("PENDING"));
+
+ if(cpt == threshold){
+ bodyStr = "ERROR";
+ }
+
+ return bodyStr;
+ }
+
+ private boolean isAuthentificated() {
+ boolean result = false;
+ try {
+ final HttpResponse response = post("/auth", null);
+ result = isResponseAuthorized(response);
+ EntityUtils.consumeQuietly(response.getEntity());
+ } catch (IOException e) {
+ LOG.error(String.format("Failed to authentificate to Backroll due to: %s", e.getMessage()));
+ }
+ return result;
+ }
+
+ private void loginIfAuthentificationFailed() {
+ if(!isAuthentificated()) {
+ login(appname, password);
+ }
+ }
+
+ private void login(final String appname, final String appsecret) {
+ LOG.info("Backroll client - start login");
+ final HttpPost request = new HttpPost(apiURI.toString() + "/login");
+
+ request.addHeader("content-type", "application/json");
+
+ JSONObject jsonBody = new JSONObject();
+ StringEntity params;
+
+ try {
+ jsonBody.put("app_id", appname);
+ jsonBody.put("app_secret", appsecret);
+ params = new StringEntity(jsonBody.toString());
+ request.setEntity(params);
+
+ final HttpResponse response = httpClient.execute(request);
+
+ if (isResponseOk(response)) {
+ HttpEntity body = response.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+
+ LoginApiResponse loginResponse = new ObjectMapper().readValue(bodyStr, LoginApiResponse.class);
+ backrollToken = loginResponse.accessToken;
+ LOG.debug(String.format("Backroll client - Token : %s", backrollToken));
+
+ EntityUtils.consumeQuietly(response.getEntity());
+
+ if (StringUtils.isEmpty(loginResponse.accessToken)) {
+ throw new CloudRuntimeException("Backroll token is not available to perform API requests");
+ }
+ } else {
+ EntityUtils.consumeQuietly(response.getEntity());
+ if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) {
+ throw new CloudRuntimeException(
+ "Failed to create and authenticate Backroll client, please check the settings.");
+ } else {
+ throw new ServerApiException(ApiErrorCode.UNAUTHORIZED,
+ "Backroll API call unauthorized, please ask your administrator to fix integration issues.");
+ }
+ }
+
+ } catch (final IOException e) {
+ throw new CloudRuntimeException("Failed to authenticate Backroll API service due to:" + e.getMessage());
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ LOG.info("Backroll client - end login");
+ }
+
+ private List getBackrollBackups(final String vmId) {
+ try {
+ LOG.info("start to list Backroll backups for vm " + vmId);
+ final HttpResponse backupsAskingResponse = get("/virtualmachines/" + vmId + "/backups");
+
+ if(isResponseOk(backupsAskingResponse)) {
+ HttpEntity body = backupsAskingResponse.getEntity();
+ String bodyStr = EntityUtils.toString(body);
+ BackrollTaskRequestResponse backupTaskRequestResponse = new ObjectMapper().readValue(bodyStr, BackrollTaskRequestResponse.class);
+ String urlToRequest = backupTaskRequestResponse.location.replace("/api/v1", "");
+ LOG.debug(urlToRequest);
+ EntityUtils.consumeQuietly(backupsAskingResponse.getEntity());
+ bodyStr = waitForGetRequestResponse(urlToRequest);
+ LOG.debug(bodyStr);
+ BackrollBackupsFromVMResponse backrollBackupsFromVMResponse = new ObjectMapper().readValue(bodyStr, BackrollBackupsFromVMResponse.class);
+
+ final List backups = new ArrayList<>();
+ for (final BackrollArchiveResponse archive : backrollBackupsFromVMResponse.archives.archives) {
+ backups.add(new BackrollBackup(archive.name));
+ LOG.debug(archive.name);
+ }
+ return backups;
+ } else {
+ throw new CloudRuntimeException(
+ "Failed to retrieve backups for this VM via Backroll");
+ }
+ } catch (final IOException e) {
+ LOG.error("Failed to list backup form vm with Backroll due to:", e);
+ } catch (InterruptedException e) {
+ LOG.error("Backroll Error:", e);
+ }
+ return new ArrayList();
+ }
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/command/GetBackrollHello.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/command/GetBackrollHello.java
new file mode 100644
index 00000000000..6e5128f2d96
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/command/GetBackrollHello.java
@@ -0,0 +1,56 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.backup.backroll.api.command;
+
+/* import org.apache.log4j.Logger;
+import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.BaseCmd;
+import org.apache.cloudstack.backup.backroll.api.response.ApiHelloResponse; */
+
+/* @APICommand(name = "getBackrollHello", responseObject = ApiHelloResponse.class, description = "Get Hello",
+ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) */
+public class GetBackrollHello {//extends BaseCmd {
+ /* private static final Logger LOGGER = Logger.getLogger(GetBackrollHello.class.getName());
+ private static final String NAME = "gethelloresponse";
+
+
+ /////////////////////////////////////////////////////
+ /////////////// API Implementation///////////////////
+ /////////////////////////////////////////////////////
+
+ @Override
+ public String getCommandName() {
+ return NAME;
+ }
+
+ @Override
+ public long getEntityOwnerId() {
+ return 0;
+ }
+
+ @Override
+ public void execute() {
+ LOGGER.info("'getBackrollHello.execute' method invoked");
+
+ ApiHelloResponse response = new ApiHelloResponse("Hello");
+
+ response.setResponseName(getCommandName());
+ response.setObjectName("msg");
+
+ setResponseObject(response);
+ } */
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/response/ApiHelloResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/response/ApiHelloResponse.java
new file mode 100644
index 00000000000..ef71e264db2
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/api/response/ApiHelloResponse.java
@@ -0,0 +1,32 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.backup.backroll.api.response;
+
+import org.apache.cloudstack.api.BaseResponse;
+
+import com.cloud.serializer.Param;
+import com.google.gson.annotations.SerializedName;
+
+public class ApiHelloResponse extends BaseResponse {
+ @SerializedName("TEXT")
+ @Param(description = "Text")
+ private String msg;
+
+ public ApiHelloResponse(String msg) {
+ this.msg = msg;
+ }
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackup.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackup.java
new file mode 100644
index 00000000000..ddaadc3643c
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackup.java
@@ -0,0 +1,13 @@
+package org.apache.cloudstack.backup.backroll.model;
+
+public class BackrollBackup {
+ private String archive;
+
+ public String getArchive() {
+ return archive;
+ }
+
+ public BackrollBackup(String archive) {
+ this.archive = archive;
+ }
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackupMetrics.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackupMetrics.java
new file mode 100644
index 00000000000..e275f0fb2a9
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackupMetrics.java
@@ -0,0 +1,19 @@
+package org.apache.cloudstack.backup.backroll.model;
+
+public class BackrollBackupMetrics {
+ private long size;
+ private long deduplicated;
+
+ public long getSize() {
+ return size;
+ }
+
+ public long getDeduplicated() {
+ return deduplicated;
+ }
+
+ public BackrollBackupMetrics(long size, long deduplicated) {
+ this.size = size;
+ this.deduplicated = deduplicated;
+ }
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollOffering.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollOffering.java
new file mode 100644
index 00000000000..7d329aaa405
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollOffering.java
@@ -0,0 +1,61 @@
+package org.apache.cloudstack.backup.backroll.model;
+
+import java.util.Date;
+
+import org.apache.cloudstack.backup.BackupOffering;
+
+public class BackrollOffering implements BackupOffering {
+
+ private String name;
+ private String uid;
+
+ public BackrollOffering(String name, String uid) {
+ this.name = name;
+ this.uid = uid;
+ }
+
+ @Override
+ public String getExternalId() {
+ return uid;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ @Override
+ public String getDescription() {
+ return "Backroll Backup Offering (Job)";
+ }
+
+ @Override
+ public long getZoneId() {
+ return -1;
+ }
+
+ @Override
+ public boolean isUserDrivenBackupAllowed() {
+ return false;
+ }
+
+ @Override
+ public String getProvider() {
+ return "backroll";
+ }
+
+ @Override
+ public Date getCreated() {
+ return null;
+ }
+
+ @Override
+ public String getUuid() {
+ return uid;
+ }
+
+ @Override
+ public long getId() {
+ return -1;
+ }
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollTaskStatus.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollTaskStatus.java
new file mode 100644
index 00000000000..6f63f6f5364
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollTaskStatus.java
@@ -0,0 +1,31 @@
+package org.apache.cloudstack.backup.backroll.model;
+
+public class BackrollTaskStatus {
+ private String state;
+ private String error;
+ private String info;
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getError() {
+ return error;
+ }
+
+ public void setError(String error) {
+ this.error = error;
+ }
+
+ public String getInfo() {
+ return info;
+ }
+
+ public void setInfo(String info) {
+ this.info = info;
+ }
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollVmBackup.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollVmBackup.java
new file mode 100644
index 00000000000..1a837405ce5
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollVmBackup.java
@@ -0,0 +1,27 @@
+package org.apache.cloudstack.backup.backroll.model;
+
+import java.util.Date;
+
+public class BackrollVmBackup {
+ private String id;
+ private String name;
+ private Date date;
+
+ public String getId() {
+ return id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Date getDate() {
+ return date;
+ }
+
+ public BackrollVmBackup(String id, String name, Date date) {
+ this.id = id;
+ this.name = name;
+ this.date = date;
+ }
+}
\ No newline at end of file
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollTaskRequestResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollTaskRequestResponse.java
new file mode 100644
index 00000000000..b4ad1ea31f6
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollTaskRequestResponse.java
@@ -0,0 +1,8 @@
+package org.apache.cloudstack.backup.backroll.model.response;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollTaskRequestResponse {
+ @JsonProperty("Location")
+ public String location;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/api/LoginApiResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/api/LoginApiResponse.java
new file mode 100644
index 00000000000..e9906171304
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/api/LoginApiResponse.java
@@ -0,0 +1,23 @@
+package org.apache.cloudstack.backup.backroll.model.response.api;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class LoginApiResponse {
+ @JsonProperty("access_token")
+ public String accessToken;
+
+ @JsonProperty("expires_in")
+ public int expiresIn;
+
+ @JsonProperty("refresh_expires_in")
+ public String refreshExpiresIn;
+
+ @JsonProperty("token_type")
+ public String tokenType;
+
+ @JsonProperty("not-before-policy")
+ public String notBeforePolicy;
+
+ @JsonProperty("scope")
+ public String scope;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchiveResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchiveResponse.java
new file mode 100644
index 00000000000..03b3c3e56ea
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchiveResponse.java
@@ -0,0 +1,25 @@
+package org.apache.cloudstack.backup.backroll.model.response.archive;
+
+import org.joda.time.DateTime;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollArchiveResponse {
+ @JsonProperty("archive")
+ public String archive;
+
+ @JsonProperty("barchive")
+ public String barchive;
+
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("name")
+ public String name;
+
+ @JsonProperty("start")
+ public DateTime start;
+
+ @JsonProperty("time")
+ public DateTime time;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchivesResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchivesResponse.java
new file mode 100644
index 00000000000..28de0ce3114
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchivesResponse.java
@@ -0,0 +1,12 @@
+package org.apache.cloudstack.backup.backroll.model.response.archive;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollArchivesResponse {
+ @JsonProperty("archives")
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public List archives;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollBackupsFromVMResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollBackupsFromVMResponse.java
new file mode 100644
index 00000000000..db5a579a147
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollBackupsFromVMResponse.java
@@ -0,0 +1,11 @@
+package org.apache.cloudstack.backup.backroll.model.response.archive;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollBackupsFromVMResponse {
+ @JsonProperty("state")
+ public String state;
+
+ @JsonProperty("info")
+ public BackrollArchivesResponse archives;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusResponse.java
new file mode 100644
index 00000000000..9f34503d9ba
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusResponse.java
@@ -0,0 +1,17 @@
+package org.apache.cloudstack.backup.backroll.model.response.backup;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollBackupStatusResponse {
+ @JsonProperty("state")
+ public String state;
+
+ @JsonProperty("current")
+ public String current;
+
+ @JsonProperty("total")
+ public String total;
+
+ @JsonProperty("status")
+ public String status;
+}
\ No newline at end of file
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusSuccessResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusSuccessResponse.java
new file mode 100644
index 00000000000..7b72e45c958
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusSuccessResponse.java
@@ -0,0 +1,11 @@
+package org.apache.cloudstack.backup.backroll.model.response.backup;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollBackupStatusSuccessResponse {
+ @JsonProperty("state")
+ public String state;
+
+ @JsonProperty("info")
+ public String info;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackrollBackupMetricsResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackrollBackupMetricsResponse.java
new file mode 100644
index 00000000000..52675b26a80
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackrollBackupMetricsResponse.java
@@ -0,0 +1,11 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.backup;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollBackupMetricsResponse {
+ @JsonProperty("state")
+ public String state;
+
+ @JsonProperty("info")
+ public BackupMetricsInfo info;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackupMetricsInfo.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackupMetricsInfo.java
new file mode 100644
index 00000000000..6d9381f4dcb
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackupMetricsInfo.java
@@ -0,0 +1,17 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.backup;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackupMetricsInfo {
+ @JsonProperty("compressed_size")
+ public String compressedSize;
+
+ @JsonProperty("deduplicated_size")
+ public String deduplicatedSize;
+
+ @JsonProperty("nfiles")
+ public String nFiles;
+
+ @JsonProperty("original_size")
+ public String originalSize;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/BackrollVmMetricsResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/BackrollVmMetricsResponse.java
new file mode 100644
index 00000000000..8a0e4304e85
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/BackrollVmMetricsResponse.java
@@ -0,0 +1,11 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollVmMetricsResponse {
+ @JsonProperty("state")
+ public String state;
+
+ @JsonProperty("info")
+ public MetricsInfos infos;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/CacheStats.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/CacheStats.java
new file mode 100644
index 00000000000..f445e423ada
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/CacheStats.java
@@ -0,0 +1,23 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class CacheStats {
+ @JsonProperty("total_chunks")
+ public String totalChunks;
+
+ @JsonProperty("total_csize")
+ public String totalCsize;
+
+ @JsonProperty("total_size")
+ public String totalSize;
+
+ @JsonProperty("total_unique_chunks")
+ public String totalUniqueChunks;
+
+ @JsonProperty("unique_csize")
+ public String uniqueCsize;
+
+ @JsonProperty("unique_size")
+ public String uniqueSize;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosCache.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosCache.java
new file mode 100644
index 00000000000..5fa64fc5c89
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosCache.java
@@ -0,0 +1,11 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InfosCache {
+ @JsonProperty("path")
+ public String path;
+
+ @JsonProperty("stats")
+ public CacheStats stats;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosEncryption.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosEncryption.java
new file mode 100644
index 00000000000..e0a86d9de29
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosEncryption.java
@@ -0,0 +1,8 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InfosEncryption {
+ @JsonProperty("mode")
+ public String mode;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosRepository.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosRepository.java
new file mode 100644
index 00000000000..695a53ed211
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosRepository.java
@@ -0,0 +1,14 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InfosRepository {
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("last_modified")
+ public String lastModified;
+
+ @JsonProperty("location")
+ public String location;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/MetricsInfos.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/MetricsInfos.java
new file mode 100644
index 00000000000..bf9ff009095
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/MetricsInfos.java
@@ -0,0 +1,17 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class MetricsInfos {
+ @JsonProperty("cache")
+ public InfosCache cache;
+
+ @JsonProperty("encryption")
+ public InfosEncryption encryption;
+
+ @JsonProperty("repository")
+ public InfosRepository repository;
+
+ @JsonProperty("security_dir")
+ public String securityDir;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/Archives.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/Archives.java
new file mode 100644
index 00000000000..f70e0dc183b
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/Archives.java
@@ -0,0 +1,19 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class Archives {
+ @JsonProperty("archives")
+ public List archives;
+
+ @JsonProperty(value = "encryption", required = false)
+ public InfosEncryption encryption;
+
+ @JsonProperty(value = "repository", required = false)
+ public InfosRepository repository;
+
+ @JsonProperty(value = "state", required = false)
+ public String state;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/BackupInfos.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/BackupInfos.java
new file mode 100644
index 00000000000..175b1a664f7
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/BackupInfos.java
@@ -0,0 +1,23 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackupInfos {
+ @JsonProperty("archive")
+ public String archive;
+
+ @JsonProperty("barchive")
+ public String barchive;
+
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("name")
+ public String name;
+
+ @JsonProperty("start")
+ public String start;
+
+ @JsonProperty("time")
+ public String time;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosEncryption.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosEncryption.java
new file mode 100644
index 00000000000..05d40cdd149
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosEncryption.java
@@ -0,0 +1,8 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InfosEncryption {
+ @JsonProperty("mode")
+ public String mode;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosRepository.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosRepository.java
new file mode 100644
index 00000000000..796a37b7164
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosRepository.java
@@ -0,0 +1,14 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class InfosRepository {
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("last_modified")
+ public String lastModified;
+
+ @JsonProperty("location")
+ public String location;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/VirtualMachineBackupsResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/VirtualMachineBackupsResponse.java
new file mode 100644
index 00000000000..79cc6afe3b4
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/VirtualMachineBackupsResponse.java
@@ -0,0 +1,11 @@
+package org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class VirtualMachineBackupsResponse {
+ @JsonProperty("state")
+ public String state;
+
+ @JsonProperty("info")
+ public Archives info;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackrollBackupPolicyResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackrollBackupPolicyResponse.java
new file mode 100644
index 00000000000..d62e043f704
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackrollBackupPolicyResponse.java
@@ -0,0 +1,38 @@
+package org.apache.cloudstack.backup.backroll.model.response.policy;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackrollBackupPolicyResponse {
+ @JsonProperty("name")
+ public String name;
+
+ @JsonProperty("retention_day")
+ public int retentionDay;
+
+ @JsonProperty("schedule")
+ public String schedule;
+
+ @JsonProperty("retention_month")
+ public int retentionMonth;
+
+ @JsonProperty("storage")
+ public String storage;
+
+ @JsonProperty("enabled")
+ public Boolean enabled;
+
+ @JsonProperty("description")
+ public String description;
+
+ @JsonProperty("id")
+ public String id;
+
+ @JsonProperty("retention_week")
+ public int retentionWeek;
+
+ @JsonProperty("retention_year")
+ public int retentionYear;
+
+ @JsonProperty("externalhook")
+ public String externalHook;
+}
diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackupPoliciesResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackupPoliciesResponse.java
new file mode 100644
index 00000000000..5373e884ada
--- /dev/null
+++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackupPoliciesResponse.java
@@ -0,0 +1,15 @@
+package org.apache.cloudstack.backup.backroll.model.response.policy;
+
+import java.util.List;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class BackupPoliciesResponse {
+ @JsonProperty("state")
+ public String state;
+
+ @JsonProperty("info")
+ @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY)
+ public List backupPolicies;
+}
diff --git a/plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/module.properties b/plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/module.properties
new file mode 100644
index 00000000000..a181dadfb20
--- /dev/null
+++ b/plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/module.properties
@@ -0,0 +1,18 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+name=backroll-backup
+parent=backup
diff --git a/plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/spring-backup-backroll-context.xml b/plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/spring-backup-backroll-context.xml
new file mode 100644
index 00000000000..231c694ab8b
--- /dev/null
+++ b/plugins/backup/backroll/src/main/resources/META-INF/cloudstack/backroll-backup/spring-backup-backroll-context.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
diff --git a/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/BackrollBackupProviderTest.java b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/BackrollBackupProviderTest.java
new file mode 100644
index 00000000000..82d8b3d1af9
--- /dev/null
+++ b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/BackrollBackupProviderTest.java
@@ -0,0 +1,49 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.backup;
+
+
+//@RunWith(MockitoJUnitRunner.class)
+public class BackrollBackupProviderTest {
+ /*@Spy
+ @InjectMocks
+ BackrollBackupProvider backupProvider = new BackrollBackupProvider();
+
+ @Mock
+ BackrollClient client;
+
+ @Mock
+ VMInstanceDao vmInstanceDao;
+
+ @Mock
+ BackupDao backupDao;
+
+ @Test
+ public void providerGetDescription() {
+ assertEquals("Backroll Backup Plugin", backupProvider.getDescription());
+
+ }
+
+ @Test
+ public void listBackupOfferingsToBackrollTestOk() {
+
+ List results = backupProvider.listBackupOfferings(0L);
+ assertEquals("Backroll Backup Plugin", "Backroll Backup Plugin");
+
+ }*/
+}
\ No newline at end of file
diff --git a/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/BackrollClientTest.java b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/BackrollClientTest.java
new file mode 100644
index 00000000000..0ba9ebb9772
--- /dev/null
+++ b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/BackrollClientTest.java
@@ -0,0 +1,22 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.backup.backroll;
+
+public class BackrollClientTest {
+
+}
\ No newline at end of file
diff --git a/plugins/pom.xml b/plugins/pom.xml
index d0661c01a2c..2f4c40b2152 100755
--- a/plugins/pom.xml
+++ b/plugins/pom.xml
@@ -60,6 +60,7 @@
api/rate-limit
api/solidfire-intg-test
+ backup/backroll
backup/dummy
backup/networker
@@ -194,6 +195,7 @@
api/vmware-sioc
+ backup/backroll
backup/veeam
hypervisors/vmware
network-elements/cisco-vnmc
diff --git a/ui/package-lock.json b/ui/package-lock.json
index d17a02d3c36..e7e8cce2824 100644
--- a/ui/package-lock.json
+++ b/ui/package-lock.json
@@ -2486,9 +2486,9 @@
}
},
"@types/connect-history-api-fallback": {
- "version": "1.3.5",
- "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.3.5.tgz",
- "integrity": "sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==",
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.1.tgz",
+ "integrity": "sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw==",
"dev": true,
"requires": {
"@types/express-serve-static-core": "*",
@@ -2589,9 +2589,9 @@
"dev": true
},
"@types/http-proxy": {
- "version": "1.17.9",
- "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz",
- "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==",
+ "version": "1.17.12",
+ "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.12.tgz",
+ "integrity": "sha512-kQtujO08dVtQ2wXAuSFfk9ASy3sug4+ogFR8Kd8UgP8PEuc1/G/8yjYRmp//PcDNJEUKOza/MrQu15bouEUCiw==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -2803,9 +2803,9 @@
"dev": true
},
"@types/tapable": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.8.tgz",
- "integrity": "sha512-ipixuVrh2OdNmauvtT51o3d8z12p6LtFW9in7U79der/kwejjdNchQC5UMn5u/KxNoM7VHHOs/l8KS8uHxhODQ==",
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/tapable/-/tapable-1.0.9.tgz",
+ "integrity": "sha512-fOHIwZua0sRltqWzODGUM6b4ffZrf/vzGUmNXdR+4DzuJP42PMbM5dLKcdzlYvv8bMJ3GALOzkk1q7cDm2zPyA==",
"dev": true
},
"@types/through": {
@@ -2818,9 +2818,9 @@
}
},
"@types/uglify-js": {
- "version": "3.16.0",
- "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.16.0.tgz",
- "integrity": "sha512-0yeUr92L3r0GLRnBOvtYK1v2SjqMIqQDHMl7GLb+l2L8+6LSFWEEWEIgVsPdMn5ImLM8qzWT8xFPtQYpp8co0g==",
+ "version": "3.17.2",
+ "resolved": "https://registry.npmjs.org/@types/uglify-js/-/uglify-js-3.17.2.tgz",
+ "integrity": "sha512-9SjrHO54LINgC/6Ehr81NjAxAYvwEZqjUHLjJYvC4Nmr9jbLQCIZbWSvl4vXQkkmR1UAuaKDycau3O1kWGFyXQ==",
"dev": true,
"requires": {
"source-map": "^0.6.1"
@@ -2832,9 +2832,9 @@
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
},
"@types/webpack": {
- "version": "4.41.32",
- "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz",
- "integrity": "sha512-cb+0ioil/7oz5//7tZUSwbrSAN/NWHrQylz5cW8G0dWTcF/g+/dSdMlKVZspBYuMAN1+WnwHrkxiRrLcwd0Heg==",
+ "version": "4.41.33",
+ "resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.33.tgz",
+ "integrity": "sha512-PPajH64Ft2vWevkerISMtnZ8rTs4YmRbs+23c402J0INmxDKCrhZNvwZYtzx96gY2wAtXdrK1BS2fiC8MlLr3g==",
"dev": true,
"requires": {
"@types/node": "*",
@@ -3489,18 +3489,18 @@
"dev": true
},
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -3529,9 +3529,9 @@
}
},
"vue-loader": {
- "version": "15.9.8",
- "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.8.tgz",
- "integrity": "sha512-GwSkxPrihfLR69/dSV3+5CdMQ0D+jXg8Ma1S4nQXKJAznYFX14vHdc/NetQc34Dw+rBbIJyP7JOuVb9Fhprvog==",
+ "version": "15.10.2",
+ "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.2.tgz",
+ "integrity": "sha512-ndeSe/8KQc/nlA7TJ+OBhv2qalmj1s+uBs7yHDRFaAXscFTApBzY9F1jES3bautmgWjDlDct0fw8rPuySDLwxw==",
"dev": true,
"requires": {
"@vue/component-compiler-utils": "^3.1.0",
@@ -4211,9 +4211,9 @@
"dev": true
},
"address": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/address/-/address-1.2.0.tgz",
- "integrity": "sha512-tNEZYz5G/zYunxFm7sfhAxkXEuLj3K6BKwv6ZURlsF6yiUQ65z0Q2wZW9L5cPUl9ocofGvXOdFYbFHp0+6MOig==",
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/address/-/address-1.2.2.tgz",
+ "integrity": "sha512-4B/qKCfeE/ODUaAUpSwfzazo5x29WD4r3vXiWsB7I2mSDAihwEqKO+g8GELZUQSSAo5e1XTYh3ZVfLyxBc12nA==",
"dev": true
},
"agent-base": {
@@ -7866,18 +7866,18 @@
"dev": true
},
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -8179,18 +8179,18 @@
},
"dependencies": {
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -8199,9 +8199,9 @@
}
},
"semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true
}
}
@@ -10813,18 +10813,18 @@
},
"dependencies": {
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -15140,12 +15140,30 @@
}
},
"launch-editor-middleware": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.4.0.tgz",
- "integrity": "sha512-/M7AX/6xktZY60KE7j71XLrj9U6H5TBoP+mJzhYB3fcdAq8rcazit/K0qWiu1jvytUPXP4lJRd1VJFwvdMQ/uw==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/launch-editor-middleware/-/launch-editor-middleware-2.6.0.tgz",
+ "integrity": "sha512-K2yxgljj5TdCeRN1lBtO3/J26+AIDDDw+04y6VAiZbWcTdBwsYN6RrZBnW5DN/QiSIdKNjKdATLUUluWWFYTIA==",
"dev": true,
"requires": {
- "launch-editor": "^2.4.0"
+ "launch-editor": "^2.6.0"
+ },
+ "dependencies": {
+ "launch-editor": {
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.6.0.tgz",
+ "integrity": "sha512-JpDCcQnyAAzZZaZ7vEiSqL690w7dAEyLao+KC96zBplnYbJS7TYNjvM3M7y3dGz+v7aIsJk3hllWuc0kWAjyRQ==",
+ "dev": true,
+ "requires": {
+ "picocolors": "^1.0.0",
+ "shell-quote": "^1.7.3"
+ }
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==",
+ "dev": true
+ }
}
},
"left-pad": {
@@ -15848,18 +15866,18 @@
},
"dependencies": {
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -17637,18 +17655,18 @@
},
"dependencies": {
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -18063,9 +18081,9 @@
}
},
"postcss-selector-parser": {
- "version": "6.0.10",
- "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz",
- "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==",
+ "version": "6.0.13",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz",
+ "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==",
"dev": true,
"requires": {
"cssesc": "^3.0.0",
@@ -18120,9 +18138,9 @@
"integrity": "sha512-ravE6m9Atw9Z/jjttRUZ+clIXogdghyZAuWJ3qEzjT+jI/dL1ifAqhZeC5VHzQp1MSt1+jxKkFNemj/iO7tVUA=="
},
"prettier": {
- "version": "2.7.1",
- "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
- "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
+ "version": "2.8.8",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz",
+ "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==",
"dev": true,
"optional": true
},
@@ -20413,9 +20431,9 @@
},
"dependencies": {
"readable-stream": {
- "version": "3.6.0",
- "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
- "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
@@ -22084,18 +22102,18 @@
},
"dependencies": {
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -22638,18 +22656,18 @@
"dev": true
},
"json5": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz",
- "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==",
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
"dev": true,
"requires": {
"minimist": "^1.2.0"
}
},
"loader-utils": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz",
- "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==",
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.2.tgz",
+ "integrity": "sha512-I5d00Pd/jwMD2QCduo657+YM/6L3KZu++pmX9VFncxaxvHcru9jx1lBaFft+r4Mt2jK0Yhp41XlRAihzPxHNCg==",
"dev": true,
"requires": {
"big.js": "^5.2.2",
@@ -23415,9 +23433,9 @@
}
},
"semver": {
- "version": "6.3.0",
- "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
- "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"dev": true
},
"string-width": {
diff --git a/ui/package.json b/ui/package.json
index b269effb422..326869a1d0c 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -81,7 +81,7 @@
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-unit-jest": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
- "@vue/cli-service": "~4.5.0",
+ "@vue/cli-service": "^4.5.19",
"@vue/compiler-sfc": "^3.2.31",
"@vue/eslint-config-standard": "^5.1.2",
"@vue/test-utils": "^2.0.0-0",