Bug fix + new features for backroll

This commit is contained in:
Pierre Charton 2023-11-14 17:28:00 +01:00
parent 3ec3007635
commit 1f518227ce
42 changed files with 1945 additions and 89 deletions

1
.gitignore vendored
View File

@ -104,3 +104,4 @@ scripts/.pydevproject
venv
node_modules
.vscode
*.bz2

View File

@ -547,6 +547,11 @@
<artifactId>cloud-plugin-integrations-prometheus-exporter</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-backup-backroll</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-backup-dummy</artifactId>
@ -1001,6 +1006,11 @@
<artifactId>cloud-plugin-backup-veeam</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-plugin-backup-backroll</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
</profile>
<profile>

View File

@ -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

View File

@ -0,0 +1,45 @@
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>cloud-plugin-backup-backroll</artifactId>
<name>Apache CloudStack Plugin - BackRoll Backup and Recovery Plugin by DIMSI</name>
<parent>
<artifactId>cloudstack-plugins</artifactId>
<groupId>org.apache.cloudstack</groupId>
<version>4.19.0.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<dependencies>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.cloudstack</groupId>
<artifactId>cloud-utils</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
</dependency>
</dependencies>
</project>

View File

@ -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<String> 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<String> 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<String> 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<BackupOffering> 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<BackupOffering>();
}
@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<Boolean, String> 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<VirtualMachine, Backup.Metric> getBackupMetrics(Long zoneId, List<VirtualMachine> vms) {
final Map<VirtualMachine, Backup.Metric> 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<String> 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<Backup> 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<Backup> 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<BackrollVmBackup> 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);
}
}

View File

@ -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<BackupOffering> 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<BackupOffering> 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<BackupOffering>();
}
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<BackrollVmBackup> getAllBackupsfromVirtualMachine(String vmId) {
LOG.info(String.format("Trying to retrieve all backups for vm %s", vmId));
List<BackrollVmBackup> backups = new ArrayList<BackrollVmBackup>();
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<BackrollBackup> 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<BackrollBackup> 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<BackrollBackup>();
}
}

View File

@ -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);
} */
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<BackrollArchiveResponse> archives;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<BackupInfos> 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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<BackrollBackupPolicyResponse> backupPolicies;
}

View File

@ -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

View File

@ -0,0 +1,27 @@
<!--
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.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
>
<bean id="backrollBackupRecoveryDriver" class="org.apache.cloudstack.backup.BackrollBackupProvider">
<property name="name" value="backroll" />
</bean>
</beans>

View File

@ -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<BackupOffering> results = backupProvider.listBackupOfferings(0L);
assertEquals("Backroll Backup Plugin", "Backroll Backup Plugin");
}*/
}

View File

@ -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 {
}

View File

@ -60,6 +60,7 @@
<module>api/rate-limit</module>
<module>api/solidfire-intg-test</module>
<module>backup/backroll</module>
<module>backup/dummy</module>
<module>backup/networker</module>
@ -194,6 +195,7 @@
</activation>
<modules>
<module>api/vmware-sioc</module>
<module>backup/backroll</module>
<module>backup/veeam</module>
<module>hypervisors/vmware</module>
<module>network-elements/cisco-vnmc</module>

194
ui/package-lock.json generated
View File

@ -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": {

View File

@ -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",