diff --git a/client/pom.xml b/client/pom.xml index d8fa433d5be3..6ef335e94e4a 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -597,6 +597,11 @@ cloud-plugin-integrations-prometheus-exporter ${project.version} + + org.apache.cloudstack + cloud-plugin-backup-backroll + ${project.version} + org.apache.cloudstack cloud-plugin-backup-dummy @@ -1122,6 +1127,11 @@ cloud-plugin-backup-veeam ${project.version} + + org.apache.cloudstack + cloud-plugin-backup-backroll + ${project.version} + diff --git a/core/src/main/resources/META-INF/cloudstack/bootstrap/default-config-resources.xml b/core/src/main/resources/META-INF/cloudstack/bootstrap/default-config-resources.xml new file mode 100644 index 000000000000..d2895050b9e3 --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/bootstrap/default-config-resources.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/core/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context-inheritable.xml.ori b/core/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context-inheritable.xml.ori new file mode 100644 index 000000000000..fd1b68f24637 --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/bootstrap/spring-bootstrap-context-inheritable.xml.ori @@ -0,0 +1,44 @@ +!-- + + 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. + +--> + + + + + + + + + + + + + + + diff --git a/plugins/backup/backroll/pom.xml b/plugins/backup/backroll/pom.xml new file mode 100644 index 000000000000..0c00cb580d6b --- /dev/null +++ b/plugins/backup/backroll/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + cloud-plugin-backup-backroll + Apache CloudStack Plugin - BackRoll Backup and Recovery Plugin by DIMSI + + cloudstack-plugins + org.apache.cloudstack + 4.21.0.0-SNAPSHOT + ../../pom.xml + + + + org.apache.cloudstack + cloud-api + ${project.version} + + + org.apache.cloudstack + cloud-utils + ${project.version} + + + org.json + json + + + com.github.tomakehurst + wiremock-standalone + ${cs.wiremock.version} + test + + + 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 000000000000..018686a2083c --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/BackrollBackupProvider.java @@ -0,0 +1,443 @@ +// 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.io.IOException; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +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.Backup.RestorePoint; +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.utils.BackrollApiException; +import org.apache.cloudstack.backup.backroll.utils.BackrollHttpClientProvider; +import org.apache.cloudstack.backup.dao.BackupDao; +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; +import org.apache.http.ParseException; +import org.apache.logging.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; + +public class BackrollBackupProvider extends AdapterBase implements BackupProvider, Configurable { + + 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.", true, ConfigKey.Scope.Zone); + + public ConfigKey BackrollAppNameConfigKey = new ConfigKey<>("Advanced", String.class, + "backup.plugin.backroll.config.appname", + "backroll-api", + "App Name for backroll plugin.", true, ConfigKey.Scope.Zone); + + public ConfigKey BackrollPasswordConfigKey = new ConfigKey<>("Advanced", String.class, + "backup.plugin.backroll.config.password", + "VviX8dALauSyYJMqVYJqf3UyZOpO3joS", + "Password for backroll plugin.", true, ConfigKey.Scope.Zone); + + private BackrollClient backrollClient; + + @Inject + private BackupDao backupDao; + @Inject + private VMInstanceDao vmInstanceDao; + + public BackrollBackupProvider(BackupDao backupDao, VMInstanceDao vmInstanceDao, BackrollClient client, Logger logger){ + this.backupDao = backupDao; + this.vmInstanceDao = vmInstanceDao; + this.backrollClient = client; + this.logger = logger; + } + + public BackrollBackupProvider(){} + + @Override + public String getName() { + return "backroll"; + } + + @Override + public String getDescription() { + return "Backroll Backup Plugin"; + } + + @Override + public List listBackupOfferings(Long zoneId) { + logger.debug("Listing backup policies on backroll B&R Plugin"); + BackrollClient client = getClient(zoneId); + try{ + String urlToRequest = client.getBackupOfferingUrl(); + logger.info("BackrollProvider: urlToRequest: " + urlToRequest); + if (!StringUtils.isEmpty(urlToRequest)){ + List results = new ArrayList(); + // return client.getBackupOfferings(urlToRequest); + results = client.getBackupOfferings(urlToRequest); + if(results.size()>0) { + logger.info("BackrollProvider: results > 0"); + } else { + logger.info("BackrollProvider: results <= 0"); + } + return results; + } + } catch (ParseException | BackrollApiException | IOException e) { + logger.info("BackrollProvider: catch erreur: {}", e); + throw new CloudRuntimeException("Failed to load backup offerings"); + } + return new ArrayList(); + } + + @Override + public boolean isValidProviderOffering(Long zoneId, String uuid) { + logger.info("Checking if backup offering exists on the Backroll Backup Provider"); + return true; + } + + @Override + public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering) { + logger.info("Creating VM backup for VM {} from backup offering {}", vm.getInstanceName(), backupOffering.getName()); + if(vm instanceof VMInstanceVO) { + ((VMInstanceVO) vm).setBackupExternalId(backupOffering.getUuid()); + return true; + } + return false; + } + + @Override + public boolean restoreVMFromBackup(VirtualMachine vm, Backup backup) { + logger.debug("Restoring vm {} from backup {} on the backroll Backup Provider", vm.getUuid(), backup.getUuid()); + boolean isSuccess; + try { + isSuccess = getClient(vm.getDataCenterId()).restoreVMFromBackup(vm.getUuid(), getBackupName(backup)); + } catch (ParseException | BackrollApiException | IOException e) { + throw new CloudRuntimeException("Failed to restore VM from Backup"); + } + return isSuccess; + } + + @Override + public Map getBackupMetrics(Long zoneId, List vms) { + final Map metrics = new HashMap<>(); + if (CollectionUtils.isEmpty(vms)) { + 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()); + logger.debug("Get Backup Metrics for VMs: {}.", String.join(", ", vmUuids)); + + BackrollClient client = getClient(zoneId); + for (final VirtualMachine vm : vms) { + if (vm == null) { + continue; + } + try { + // get backups from database + List backupsInDb = backupDao.listByVmId(zoneId, vm.getId()); + + // check backing up task + for (Backup backup : backupsInDb) { + if (backup.getStatus().equals(Backup.Status.BackingUp)) { + BackrollTaskStatus response; + try { + response = client.checkBackupTaskStatus(backup.getExternalId()); + } catch (ParseException | BackrollApiException | IOException e) { + logger.error(e); + throw new CloudRuntimeException("Failed to sync backups"); + } + + if (response != null) { + logger.debug("backroll backup id: {}", backup.getExternalId()); + logger.debug("backroll backup status: {}", response.getState()); + + BackupVO backupToUpdate = ((BackupVO) backup); + + 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(response.getInfo()); + + BackrollBackupMetrics backupMetrics = null; + try { + backupMetrics = client.getBackupMetrics(vm.getUuid() , response.getInfo()); + if (backupMetrics != null) { + backupToUpdate.setProtectedSize(backupMetrics.getDeduplicated()); + backupToUpdate.setSize(backupMetrics.getSize()); + } + } catch (BackrollApiException | IOException e) { + logger.error(e); + throw new CloudRuntimeException("Failed to get backup metrics"); + } + } else { + backupToUpdate.setStatus(Backup.Status.BackingUp); + } + + if (backupDao.persist(backupToUpdate) != null) { + logger.info("Backroll backup updated"); + } + } + } else { + if(backup.getExternalId().contains(",")) { + String backupId = backup.getExternalId().split(",")[1]; + BackupVO backupToUpdate = ((BackupVO) backup); + backupToUpdate.setExternalId(backupId); + try { + BackrollBackupMetrics backupMetrics = client.getBackupMetrics(vm.getUuid() , backupId); + if (backupMetrics != null) { + backupToUpdate.setProtectedSize(backupMetrics.getDeduplicated()); + backupToUpdate.setSize(backupMetrics.getSize()); + } + } catch (BackrollApiException | IOException e) { + logger.error(e); + throw new CloudRuntimeException("Failed to get backup metrics"); + } + if (backupDao.persist(backupToUpdate) != null) { + logger.info("Backroll backup updated"); + } + } + } + } + + // refresh backup in database list + backupsInDb = backupDao.listByVmId(zoneId, vm.getId()); + + Long usedSize = 0L; + Long dataSize = 0L; + List backups = client.listRestorePoints(vm.getUuid()); + for (RestorePoint backup : backups) { + + BackrollBackupMetrics backupMetrics = client.getBackupMetrics(vm.getUuid() , getBackupName(backup.getId())); + if (backupMetrics != null) { + usedSize += Long.valueOf(backupMetrics.getDeduplicated()); + dataSize += Long.valueOf(backupMetrics.getSize()); + + // update backup metrics + Backup backupToFind = backupsInDb.stream() + .filter(backupInDb -> backupInDb.getExternalId().contains(backup.getId())) + .findAny() + .orElse(null); + + if (backupToFind != null) { + BackupVO backupToUpdate = ((BackupVO) backupToFind); + backupToUpdate.setProtectedSize(usedSize); + backupToUpdate.setSize(dataSize); + backupDao.persist(backupToUpdate); + } + + } + } + Metric metric = new Metric(dataSize, usedSize); + logger.debug("Metrics for VM [uuid: {}, name: {}] is [backup size: {}, data size: {}].", vm.getUuid(), + vm.getInstanceName(), metric.getBackupSize(), metric.getDataSize()); + metrics.put(vm, metric); + } catch (BackrollApiException | IOException e) { + throw new CloudRuntimeException("Failed to retrieve backup metrics"); + } + } + return metrics; + } + + @Override + public boolean removeVMFromBackupOffering(VirtualMachine vm) { + return true; + } + + @Override + public boolean willDeleteBackupsOnOfferingRemoval() { + return false; + } + + @Override + public Pair takeBackup(VirtualMachine vm) { + logger.info("Starting backup for VM ID {} on backroll provider", vm.getUuid()); + final BackrollClient client = getClient(vm.getDataCenterId()); + + try { + String urlToRequest = client.startBackupJob(vm.getUuid()); + logger.info("BackrollProvider: urlToRequest: " + urlToRequest); + String backupJob = urlToRequest.replace("/status/", ""); + if (!StringUtils.isEmpty(backupJob)) { + BackupVO backup = new BackupVO(); + backup.setVmId(vm.getId()); + backup.setExternalId(backupJob); + backup.setType("INCREMENTAL"); + 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()); + Boolean result = backupDao.persist(backup) != null; + return new Pair(result, backup); + } + } catch (ParseException | BackrollApiException | IOException e) { + logger.debug(e.getMessage()); + throw new CloudRuntimeException("Failed to take backup"); + } + return new Pair(false, null); + } + + @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) { + 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 { + logger.debug("BACKROLL: try delete backup"); + + if (backup.getStatus().equals(Backup.Status.Removed) || backup.getStatus().equals(Backup.Status.Failed)){ + return deleteBackupInDb(backup); + } else { + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + try { + if (getClient(backup.getZoneId()).deleteBackup(vm.getUuid(), getBackupName(backup))) { + logger.debug("BACKROLL: Backup deletion for backup {} complete on backroll side.", backup.getUuid()); + return deleteBackupInDb(backup); + } + } catch (BackrollApiException | IOException e) { + logger.error(e); + throw new CloudRuntimeException("BACKROLL: Failed to delete backup"); + } + } + } + return false; + } + + private boolean deleteBackupInDb(Backup backup) { + BackupVO backupToUpdate = ((BackupVO) backup); + backupToUpdate.setStatus(Backup.Status.Removed); + if (backupDao.persist(backupToUpdate) != null) { + logger.debug("BACKROLL: Backroll backup {} deleted in database.", backup.getUuid()); + VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); + return true; + } + return false; + } + + protected BackrollClient getClient(final Long zoneId) { + logger.debug("Backroll Provider GetClient with zone id {}", zoneId); + try { + if (backrollClient == null) { + logger.debug("backroll client null - instantiation of new one "); + BackrollHttpClientProvider provider = BackrollHttpClientProvider.createProvider(new BackrollHttpClientProvider(), BackrollUrlConfigKey.valueIn(zoneId), BackrollAppNameConfigKey.valueIn(zoneId), BackrollPasswordConfigKey.valueIn(zoneId), true, 300, 600); + backrollClient = new BackrollClient(provider); + } + return backrollClient; + } catch (URISyntaxException e) { + logger.error(e); + throw new CloudRuntimeException("Failed to parse Backroll API URL: " + e.getMessage()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + logger.error(e); + throw new CloudRuntimeException("Failed to build Backroll API client"); + } + } + + private String getBackupName(Backup backup) { + return getBackupName(backup.getExternalId()); + } + + private String getBackupName(String externalId) { + return externalId.substring(externalId.indexOf(",") + 1); + } + + @Override + public Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid, Pair vmNameAndState) { + logger.debug("Restoring volume {} from backup {} on the Backroll Backup Provider", volumeUuid, backup.getUuid()); + throw new CloudRuntimeException("Backroll plugin does not support this feature"); + } + + @Override + public List listRestorePoints(VirtualMachine vm) { + try { + final BackrollClient client = getClient(vm.getDataCenterId()); + return client.listRestorePoints(vm.getUuid()); + } catch (BackrollApiException | IOException e) { + logger.error(e); + throw new CloudRuntimeException("Error while listing restore points"); + } + } + + @Override + public Backup createNewBackupEntryForRestorePoint(RestorePoint restorePoint, VirtualMachine vm, Metric metric) { + final BackrollClient client = getClient(vm.getDataCenterId()); + BackupVO backupToInsert = new BackupVO(); + backupToInsert.setVmId(vm.getId()); + backupToInsert.setExternalId(restorePoint.getId()); + backupToInsert.setType("INCREMENTAL"); + backupToInsert.setDate(restorePoint.getCreated()); + backupToInsert.setStatus(Backup.Status.BackedUp); + backupToInsert.setBackupOfferingId(vm.getBackupOfferingId()); + backupToInsert.setAccountId(vm.getAccountId()); + backupToInsert.setDomainId(vm.getDomainId()); + backupToInsert.setZoneId(vm.getDataCenterId()); + + try { + BackrollBackupMetrics backupMetrics = client.getBackupMetrics(vm.getUuid() , getBackupName(restorePoint.getId())); + if (backupMetrics != null) { + backupToInsert.setProtectedSize(backupMetrics.getDeduplicated()); + backupToInsert.setSize(backupMetrics.getSize()); + } + } catch (IOException | BackrollApiException e) { + logger.error(e); + } + + backupDao.persist(backupToInsert); + return backupToInsert; + } +} 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 000000000000..bd5717fb049c --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/BackrollClient.java @@ -0,0 +1,263 @@ +// 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; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.Backup.Metric; +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.TaskState; +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.backup.backroll.utils.BackrollApiException; +import org.apache.cloudstack.backup.backroll.utils.BackrollHttpClientProvider; +import org.apache.commons.lang3.StringUtils; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import org.joda.time.DateTime; + +import org.json.JSONException; +import org.json.JSONObject; +import com.fasterxml.jackson.databind.ObjectMapper; + +public class BackrollClient { + + protected Logger logger = LogManager.getLogger(BackrollClient.class); + + private BackrollHttpClientProvider httpProvider; + + public BackrollClient(BackrollHttpClientProvider httpProvider) + throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { + + this.httpProvider = httpProvider; + } + + public String startBackupJob(final String jobId) throws IOException, BackrollApiException { + logger.info("startBackupJob : Trying to start backup for Backroll job: {}", jobId); + String backupJob = ""; + BackrollTaskRequestResponse requestResponse = httpProvider.post(String.format("/tasks/singlebackup/%s", jobId), + null, BackrollTaskRequestResponse.class); + logger.info("startBackupJob : BackupJob status link: {}", requestResponse.location); + backupJob = requestResponse.location.replace("/api/v1", ""); + return StringUtils.isEmpty(backupJob) ? null : backupJob; + } + + public String getBackupOfferingUrl() throws IOException, BackrollApiException { + logger.info("Trying to get backroll backup policies url"); + String url = ""; + BackrollTaskRequestResponse requestResponse = httpProvider.get("/backup_policies", + BackrollTaskRequestResponse.class); + logger.info("BackrollClient:getBackupOfferingUrl:Apres Parse: " + requestResponse.location); + url = requestResponse.location.replace("/api/v1", ""); + return StringUtils.isEmpty(url) ? null : url; + } + + public List getBackupOfferings(String idTask) throws BackrollApiException, IOException { + logger.info("Trying to list backroll backup policies"); + final List policies = new ArrayList<>(); + BackupPoliciesResponse backupPoliciesResponse = httpProvider.waitGet(idTask, BackupPoliciesResponse.class); + logger.info( + "BackrollClient:getBackupOfferings:Apres Parse: " + backupPoliciesResponse.backupPolicies.get(0).name); + for (final BackrollBackupPolicyResponse policy : backupPoliciesResponse.backupPolicies) { + policies.add(new BackrollOffering(policy.name, policy.id)); + } + + return policies; + } + + public boolean restoreVMFromBackup(final String vmId, final String backupName) + throws IOException, BackrollApiException { + logger.info("Start restore backup with backroll with backup {} for vm {}", backupName, vmId); + + boolean isRestoreOk = false; + + 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) { + logger.error("Backroll Error: {}", e.getMessage()); + } + + BackrollTaskRequestResponse requestResponse = httpProvider.post(String.format("/tasks/restore/%s", vmId), + jsonBody, BackrollTaskRequestResponse.class); + String urlToRequest = requestResponse.location.replace("/api/v1", ""); + + String result = httpProvider.waitGetWithoutParseResponse(urlToRequest); + if (result.contains("SUCCESS")) { + logger.debug("RESTORE SUCCESS content : " + result); + logger.debug("RESTORE SUCCESS"); + isRestoreOk = true; + } + + return isRestoreOk; + } + + public BackrollTaskStatus checkBackupTaskStatus(String taskId) throws IOException, BackrollApiException { + logger.info("Trying to get backup status for Backroll task: {}", taskId); + + BackrollTaskStatus status = new BackrollTaskStatus(); + + String backupResponse = httpProvider.getWithoutParseResponse("/status/" + taskId); + + if (backupResponse.contains(TaskState.FAILURE) || backupResponse.contains(TaskState.PENDING)) { + BackrollBackupStatusResponse backupStatusRequestResponse = new ObjectMapper().readValue(backupResponse, + BackrollBackupStatusResponse.class); + status.setState(backupStatusRequestResponse.state); + } else { + BackrollBackupStatusSuccessResponse backupStatusSuccessRequestResponse = new ObjectMapper() + .readValue(backupResponse, BackrollBackupStatusSuccessResponse.class); + status.setState(backupStatusSuccessRequestResponse.state); + status.setInfo(backupStatusSuccessRequestResponse.info); + } + + return StringUtils.isEmpty(status.getState()) ? null : status; + } + + public boolean deleteBackup(final String vmId, final String backupName) throws IOException, BackrollApiException { + logger.info("BACKROLL: Trying to delete backup {} for vm {} using Backroll", vmId, backupName); + boolean isBackupDeleted = false; + + BackrollTaskRequestResponse requestResponse = httpProvider.delete( + String.format("/virtualmachines/%s/backups/%s", vmId, backupName), BackrollTaskRequestResponse.class); + String urlToRequest = requestResponse.location.replace("/api/v1", ""); + + BackrollBackupsFromVMResponse backrollBackupsFromVMResponse = httpProvider.waitGet(urlToRequest, + BackrollBackupsFromVMResponse.class); + logger.debug(backrollBackupsFromVMResponse.state); + isBackupDeleted = backrollBackupsFromVMResponse.state.equals(TaskState.SUCCESS); + + return isBackupDeleted; + } + + public Metric getVirtualMachineMetrics(final String vmId) throws IOException, BackrollApiException { + logger.info("Trying to retrieve virtual machine metric from Backroll for vm {}", vmId); + + Metric metric = new Metric(0L, 0L); + + BackrollTaskRequestResponse requestResponse = httpProvider + .get(String.format("/virtualmachines/%s/repository", vmId), BackrollTaskRequestResponse.class); + + String urlToRequest = requestResponse.location.replace("/api/v1", ""); + + BackrollVmMetricsResponse vmMetricsResponse = httpProvider.waitGet(urlToRequest, + BackrollVmMetricsResponse.class); + + if (vmMetricsResponse != null && vmMetricsResponse.state.equals(TaskState.SUCCESS)) { + logger.debug("SUCCESS ok"); + CacheStats stats = null; + try { + stats = vmMetricsResponse.infos.cache.stats; + } catch (NullPointerException e) { + } + if (stats != null) { + long size = Long.parseLong(stats.totalSize); + metric = new Metric(size, size); + } + } + + return metric; + } + + public BackrollBackupMetrics getBackupMetrics(String vmId, String backupId) + throws IOException, BackrollApiException { + logger.info("Trying to get backup metrics for VM: {}, and backup: {}", vmId, backupId); + + BackrollBackupMetrics metrics = null; + + BackrollTaskRequestResponse requestResponse = httpProvider.get( + String.format("/virtualmachines/%s/backups/%s", vmId, backupId), BackrollTaskRequestResponse.class); + + String urlToRequest = requestResponse.location.replace("/api/v1", ""); + + logger.debug(urlToRequest); + + BackrollBackupMetricsResponse metricsResponse = httpProvider.waitGet(urlToRequest, + BackrollBackupMetricsResponse.class); + if (metricsResponse.info != null) { + metrics = new BackrollBackupMetrics(Long.parseLong(metricsResponse.info.originalSize), + Long.parseLong(metricsResponse.info.deduplicatedSize)); + } + return metrics; + } + + public List getAllBackupsfromVirtualMachine(String vmId) + throws BackrollApiException, IOException { + List backups = new ArrayList(); + List backupInfos = getBackupInfosFromVm(vmId); + if (backupInfos != null && backupInfos.size() > 0) { + for (BackupInfos infos : backupInfos) { + var dateStart = new DateTime(infos.start); + backups.add(new BackrollVmBackup(infos.id, infos.name, dateStart.toDate())); + } + } + return backups; + } + + public List getBackupInfosFromVm(String vmId) + throws BackrollApiException, IOException { + logger.info("Trying to retrieve all backups for vm {}", vmId); + BackrollTaskRequestResponse requestResponse = httpProvider + .get(String.format("/virtualmachines/%s/backups", vmId), BackrollTaskRequestResponse.class); + + String urlToRequest = requestResponse.location.replace("/api/v1", ""); + logger.debug(urlToRequest); + VirtualMachineBackupsResponse virtualMachineBackupsResponse = httpProvider.waitGet(urlToRequest, + VirtualMachineBackupsResponse.class); + if (virtualMachineBackupsResponse.state.equals(TaskState.SUCCESS)) { + return virtualMachineBackupsResponse.info.archives; + } + return null; + + } + + public List listRestorePoints(String vmId) throws BackrollApiException, IOException { + List backups = new ArrayList(); + List backupInfos = getBackupInfosFromVm(vmId); + if (backupInfos != null && backupInfos.size() > 0) { + for (BackupInfos infos : backupInfos) { + var dateStart = new DateTime(infos.start); + backups.add(new Backup.RestorePoint(infos.name, dateStart.toDate(), "INCREMENTAL")); + } + } + return backups; + } +} 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 000000000000..a0d5af0b5c6e --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackup.java @@ -0,0 +1,29 @@ +// 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.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 000000000000..44a9a0357b4d --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollBackupMetrics.java @@ -0,0 +1,35 @@ +// 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.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 000000000000..9a58ff127c08 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollOffering.java @@ -0,0 +1,77 @@ +// 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.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 000000000000..54a08c341a65 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollTaskStatus.java @@ -0,0 +1,47 @@ +// 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.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 000000000000..c338f74dafc2 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/BackrollVmBackup.java @@ -0,0 +1,43 @@ +// 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.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; + } +} diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollAsyncResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollAsyncResponse.java new file mode 100644 index 000000000000..84c94b5fbbcd --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollAsyncResponse.java @@ -0,0 +1,24 @@ +// 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.model.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public abstract class BackrollAsyncResponse { + @JsonProperty("state") + public String state; +} 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 000000000000..decc191318df --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/BackrollTaskRequestResponse.java @@ -0,0 +1,24 @@ +// 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.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/TaskState.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/TaskState.java new file mode 100644 index 000000000000..a9abd43fd8b1 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/TaskState.java @@ -0,0 +1,23 @@ +// 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.model.response; + +public class TaskState { + public static final String PENDING = "PENDING"; + public static final String FAILURE = "FAILURE"; + public static final String SUCCESS = "SUCCESS"; +} diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/TaskStateResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/TaskStateResponse.java new file mode 100644 index 000000000000..10bd25d98039 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/TaskStateResponse.java @@ -0,0 +1,24 @@ +// 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.model.response; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TaskStateResponse { + @JsonProperty("state") + public String state; +} 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 000000000000..7fbb5ea18ee2 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/api/LoginApiResponse.java @@ -0,0 +1,39 @@ +// 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.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 000000000000..92c2f1c927ba --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchiveResponse.java @@ -0,0 +1,42 @@ +// 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.model.response.archive; + +import org.joda.time.DateTime; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BackrollArchiveResponse { + @JsonProperty("archive") + public String archive; + + // TODO Merged typo ? + @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 000000000000..5cb8634d5d80 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollArchivesResponse.java @@ -0,0 +1,28 @@ +// 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.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 000000000000..bde2f3645dbd --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollBackupsFromVMResponse.java @@ -0,0 +1,26 @@ +// 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.model.response.archive; + +import org.apache.cloudstack.backup.backroll.model.response.BackrollAsyncResponse; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BackrollBackupsFromVMResponse extends BackrollAsyncResponse { + @JsonProperty("info") + public BackrollArchivesResponse archives; +} diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupResponse.java new file mode 100644 index 000000000000..f4ce131eb8c4 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupResponse.java @@ -0,0 +1,23 @@ +// 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.model.response.backup; + +import org.apache.cloudstack.backup.backroll.model.response.BackrollAsyncResponse; + +public class BackrollBackupResponse extends BackrollAsyncResponse{ + +} 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 000000000000..5edf97380128 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusResponse.java @@ -0,0 +1,33 @@ +// 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.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; +} 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 000000000000..b60e5fad495e --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/backup/BackrollBackupStatusSuccessResponse.java @@ -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. +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 000000000000..1582c0133f2f --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackrollBackupMetricsResponse.java @@ -0,0 +1,26 @@ +// 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.model.response.metrics.backup; + +import org.apache.cloudstack.backup.backroll.model.response.BackrollAsyncResponse; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BackrollBackupMetricsResponse extends BackrollAsyncResponse { + @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 000000000000..e762debc7ca8 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/backup/BackupMetricsInfo.java @@ -0,0 +1,33 @@ +// 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.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 000000000000..74bbca646ae6 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/BackrollVmMetricsResponse.java @@ -0,0 +1,26 @@ +// 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.model.response.metrics.virtualMachine; + +import org.apache.cloudstack.backup.backroll.model.response.BackrollAsyncResponse; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BackrollVmMetricsResponse extends BackrollAsyncResponse{ + @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 000000000000..179b8bd78ff1 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/CacheStats.java @@ -0,0 +1,39 @@ +// 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.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 000000000000..1d9f09d36494 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosCache.java @@ -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. +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 000000000000..39d90ee04928 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosEncryption.java @@ -0,0 +1,24 @@ +// 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.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 000000000000..80949c0ad781 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/InfosRepository.java @@ -0,0 +1,30 @@ +// 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.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 000000000000..bf2109c1d2a0 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/MetricsInfos.java @@ -0,0 +1,33 @@ +// 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.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 000000000000..f4e9067aef69 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/Archives.java @@ -0,0 +1,35 @@ +// 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.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 000000000000..87d9a7269b18 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/BackupInfos.java @@ -0,0 +1,40 @@ +// 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.model.response.metrics.virtualMachineBackups; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BackupInfos { + @JsonProperty("archive") + public String archive; + + // TODO Merged typo ? + @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 000000000000..ba6418c9985f --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosEncryption.java @@ -0,0 +1,24 @@ +// 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.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 000000000000..90302899bccc --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/InfosRepository.java @@ -0,0 +1,30 @@ +// 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.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 000000000000..11ed8267be6f --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/VirtualMachineBackupsResponse.java @@ -0,0 +1,26 @@ +// 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.model.response.metrics.virtualMachineBackups; + +import org.apache.cloudstack.backup.backroll.model.response.BackrollAsyncResponse; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public class VirtualMachineBackupsResponse extends BackrollAsyncResponse { + @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 000000000000..125c60ac94ac --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackrollBackupPolicyResponse.java @@ -0,0 +1,54 @@ +// 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.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 000000000000..f9512520386d --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackupPoliciesResponse.java @@ -0,0 +1,31 @@ +// 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.model.response.policy; + +import java.util.List; + +import org.apache.cloudstack.backup.backroll.model.response.BackrollAsyncResponse; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BackupPoliciesResponse extends BackrollAsyncResponse { + + @JsonProperty("info") + @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + public List backupPolicies; +} diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/restore/BackrollRestoreResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/restore/BackrollRestoreResponse.java new file mode 100644 index 000000000000..8f831f49e2f3 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/restore/BackrollRestoreResponse.java @@ -0,0 +1,28 @@ +// 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.model.response.restore; + +import org.apache.cloudstack.backup.backroll.model.response.BackrollAsyncResponse; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +@JsonInclude(JsonInclude.Include.NON_NULL) +public class BackrollRestoreResponse extends BackrollAsyncResponse { + @JsonProperty("info") + public String info; +} diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/utils/BackrollApiException.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/utils/BackrollApiException.java new file mode 100644 index 000000000000..a07f9e9863da --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/utils/BackrollApiException.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.utils; + + +public class BackrollApiException extends Exception { + +} diff --git a/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/utils/BackrollHttpClientProvider.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/utils/BackrollHttpClientProvider.java new file mode 100644 index 000000000000..496f6c4d7095 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/utils/BackrollHttpClientProvider.java @@ -0,0 +1,403 @@ +// 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.utils; + +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.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.BackrollClient; +import org.apache.cloudstack.backup.backroll.model.response.TaskState; +import org.apache.cloudstack.backup.backroll.model.response.api.LoginApiResponse; +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.HttpStatus; +import org.apache.http.ParseException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +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.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +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; + +public class BackrollHttpClientProvider { + private URI apiURI;; + private String backrollToken = null; + private String appname = null; + private String password = null; + private RequestConfig config = null; + private boolean validateCertificate = false; + + private Logger logger = LogManager.getLogger(BackrollClient.class); + + public static BackrollHttpClientProvider createProvider(final BackrollHttpClientProvider backrollHttpClientProvider, final String url, final String appname, final String password, + final boolean validateCertificate, final int timeout, + final int restoreTimeout) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { + //BackrollHttpClientProvider backrollHttpClientProvider = new BackrollHttpClientProvider(); + backrollHttpClientProvider.apiURI = new URI(url); + backrollHttpClientProvider.appname = appname; + backrollHttpClientProvider.password = password; + backrollHttpClientProvider.validateCertificate = validateCertificate; + + backrollHttpClientProvider.config = RequestConfig.custom() + .setConnectTimeout(timeout * 1000) + .setConnectionRequestTimeout(timeout * 1000) + .setSocketTimeout(timeout * 1000) + .build(); + + return backrollHttpClientProvider; + } + + protected CloseableHttpClient createHttpClient() throws BackrollApiException { + if(!validateCertificate) { + SSLContext sslContext; + try { + sslContext = SSLUtils.getSSLContext(); + sslContext.init(null, new X509TrustManager[] { new TrustAllManager() }, new SecureRandom()); + final SSLConnectionSocketFactory factory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE); + return HttpClientBuilder.create() + .setDefaultRequestConfig(config) + .setSSLSocketFactory(factory) + .build(); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } else { + return HttpClientBuilder.create() + .setDefaultRequestConfig(config) + .build(); + } + } + + public T post(final String path, final JSONObject json, Class classOfT) throws BackrollApiException, IOException { + + loginIfAuthenticationFailed(); + + try (CloseableHttpClient httpClient = createHttpClient()) { + + String xml = null; + StringEntity params = null; + if (json != null) { + logger.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 CloseableHttpResponse response = httpClient.execute(request); + + logger.debug("Response received in POST request with body {} is: {} for URL {}.", xml, response.toString(), url); + + String result = okBody(response); + + T requestResponse = new ObjectMapper().readValue(result, classOfT); + //response.close(); + + return requestResponse; + } catch (ParseException | NotOkBodyException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } + + public T get(String path, Class classOfT) throws IOException, BackrollApiException{ + logger.debug("Backroll Get Auth "); + loginIfAuthenticationFailed(); + logger.debug("Backroll Get Auth ok"); + try (CloseableHttpClient httpClient = createHttpClient()) { + + String url = apiURI.toString() + path; + logger.debug("Backroll URL {}", url); + + HttpGet request = new HttpGet(url); + request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + backrollToken); + request.setHeader("Content-type", "application/json"); + CloseableHttpResponse response = httpClient.execute(request); + logger.debug("Response received in GET request is: {} for URL: {}.", response.toString(), url); + + String result = okBody(response); + T requestResponse = new ObjectMapper().readValue(result, classOfT); + //response.close(); + + return requestResponse; + } catch (NotOkBodyException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } + + public String getWithoutParseResponse(String path) throws IOException, BackrollApiException{ + logger.debug("Backroll Get Auth "); + loginIfAuthenticationFailed(); + logger.debug("Backroll Get Auth ok"); + try (CloseableHttpClient httpClient = createHttpClient()) { + + String url = apiURI.toString() + path; + logger.debug("Backroll URL {}", url); + + HttpGet request = new HttpGet(url); + request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + backrollToken); + request.setHeader("Content-type", "application/json"); + CloseableHttpResponse response = httpClient.execute(request); + logger.debug("Response received in GET request is: {} for URL: {}.", response.toString(), url); + + String result = okBody(response); + //response.close(); + + return result; + } catch (NotOkBodyException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } + + public T delete(String path, Class classOfT) throws IOException, BackrollApiException { + + loginIfAuthenticationFailed(); + + try (CloseableHttpClient httpClient = createHttpClient()) { + + String url = apiURI.toString() + path; + final HttpDelete request = new HttpDelete(url); + request.setHeader(HttpHeaders.AUTHORIZATION, "Bearer " + backrollToken); + request.setHeader("Content-type", "application/json"); + final CloseableHttpResponse response = httpClient.execute(request); + logger.debug("Response received in GET request is: {} for URL: {}.", response.toString(), url); + + String result = okBody(response); + T requestResponse = new ObjectMapper().readValue(result, classOfT); + //response.close(); + + return requestResponse; + } catch (NotOkBodyException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } + + public class NotOkBodyException extends Exception { + public NotOkBodyException() { + super(); + } + } + + public String okBody(final CloseableHttpResponse response) throws NotOkBodyException { + String result = ""; + switch (response.getStatusLine().getStatusCode()) { + case HttpStatus.SC_OK: + case HttpStatus.SC_ACCEPTED: + HttpEntity bodyEntity = response.getEntity(); + try { + logger.debug("bodyentity : {}", bodyEntity); + result = EntityUtils.toString(bodyEntity); + logger.debug("bodyentity : result {}", result); + EntityUtils.consumeQuietly(bodyEntity); + return result; + } catch (ParseException | IOException e) { + e.printStackTrace(); + throw new NotOkBodyException(); + } finally { + EntityUtils.consumeQuietly(bodyEntity); + } + default: + try { + closeConnection(response); + } catch (IOException e) { + e.printStackTrace(); + } + + throw new NotOkBodyException(); + } + } + + public T waitGet(String url, Class classOfT) throws IOException, BackrollApiException { + // int threshold = 30; // 5 minutes + int maxAttempts = 12; // 2 minutes + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + String body = getWithoutParseResponse(url); + if(!StringUtils.isEmpty(body)){ + if (!body.contains(TaskState.PENDING)) { + logger.debug("METRICS waitGetWithoutParseResponse : result {}", body); + T result = new ObjectMapper().readValue(body, classOfT); + return result; + } + } + + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } + + return null; + } + + public String waitGetWithoutParseResponse(String url) + throws IOException, BackrollApiException { + // int threshold = 30; // 5 minutes + int maxAttempts = 12; // 2 minutes + + for (int attempt = 1; attempt <= maxAttempts; attempt++) { + + String body = getWithoutParseResponse(url); + if (!body.contains(TaskState.PENDING)) { + logger.debug("waitGetWithoutParseResponse : result {}", body); + return body; + } + + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } + return null; + } + + protected boolean isAuthenticated() throws BackrollApiException, IOException { + boolean result = false; + + if(StringUtils.isEmpty(backrollToken)) { + logger.debug("Backroll Tocken empty : " + backrollToken); + return result; + } + + try (CloseableHttpClient httpClient = createHttpClient()) { + final HttpPost request = new HttpPost(apiURI.toString() + "/auth"); + CloseableHttpResponse httpResponse = httpClient.execute(request); + logger.debug("Backroll Auth response : " + EntityUtils.toString(httpResponse.getEntity())); + logger.debug("Backroll Auth response status : " + httpResponse.getStatusLine().getStatusCode()); + String response = okBody(httpResponse); + logger.debug("Backroll Auth response 2 : " + response); + logger.debug("Backroll Auth ok"); + result = true; + }catch (NotOkBodyException e) { + logger.error(e); + e.printStackTrace(); + result = false; + } + return result; + } + + private void closeConnection(CloseableHttpResponse closeableHttpResponse) throws IOException { + closeableHttpResponse.close(); + } + + public void loginIfAuthenticationFailed() throws BackrollApiException, IOException { + if (!isAuthenticated()) { + login(); + } + } + + protected void login() throws BackrollApiException, IOException { + logger.debug("Backroll client - start login"); + + CloseableHttpClient httpClient = createHttpClient(); + CloseableHttpResponse httpResponse = null; + + 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", password); + params = new StringEntity(jsonBody.toString()); + request.setEntity(params); + + httpResponse = httpClient.execute(request); + try { + String response = okBody(httpResponse); + ObjectMapper objectMapper = new ObjectMapper(); + logger.info("BACKROLL: " + response); + LoginApiResponse loginResponse = objectMapper.readValue(response, LoginApiResponse.class); + logger.info("ok"); + backrollToken = loginResponse.accessToken; + logger.debug("Backroll client - Token : {}", backrollToken); + + if (StringUtils.isEmpty(loginResponse.accessToken)) { + throw new CloudRuntimeException("Backroll token is not available to perform API requests"); + } + } catch (final NotOkBodyException e) { + logger.error(e); + if (httpResponse.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) { + logger.error(e); + throw new CloudRuntimeException("Failed to authenticate Backroll API service due to:" + e.getMessage()); + } catch (JSONException e) { + e.printStackTrace(); + logger.error(e); + throw new CloudRuntimeException("Failed to authenticate Backroll API service due to:" + e.getMessage()); + } + finally { + closeConnection(httpResponse); + } + logger.debug("Backroll client - end login"); + } +} 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 000000000000..a181dadfb203 --- /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 000000000000..231c694ab8b1 --- /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 000000000000..a2b3f3db647f --- /dev/null +++ b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/BackrollBackupProviderTest.java @@ -0,0 +1,394 @@ +// 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.UUID; + +import org.apache.cloudstack.backup.backroll.BackrollClient; +import org.apache.cloudstack.backup.backroll.utils.BackrollApiException; +import org.apache.cloudstack.backup.backroll.model.BackrollBackupMetrics; +import org.apache.cloudstack.backup.backroll.model.BackrollOffering; +import org.apache.cloudstack.backup.Backup.RestorePoint; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.logging.log4j.Logger; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; + +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.framework.config.ConfigKey; + +public class BackrollBackupProviderTest { + @Mock + BackrollClient clientMock; + + @Spy + @InjectMocks + BackrollBackupProvider backupProvider; + + @Mock + BackrollBackupProvider backupProviderMock; + + @Mock + VMInstanceDao vmInstanceDao; + + @Mock + private ConfigKey BackrollUrlConfigKey; + + @Mock + private ConfigKey BackrollAppNameConfigKey; + + @Mock + private ConfigKey BackrollPasswordConfigKey; + + @Mock + BackupDao backupDao; + + @Before + public void setUp() throws Exception { + vmInstanceDao = mock(VMInstanceDao.class); + clientMock = mock(BackrollClient.class); + backupDao = mock(BackupDao.class); + backupProvider = new BackrollBackupProvider(backupDao, vmInstanceDao, clientMock, Mockito.mock(Logger.class)); + backupProvider.BackrollAppNameConfigKey = BackrollAppNameConfigKey; + backupProvider.BackrollPasswordConfigKey = BackrollPasswordConfigKey; + backupProvider.BackrollUrlConfigKey = BackrollUrlConfigKey; + } + + @Test + public void listBackupOfferings_Test() throws BackrollApiException, IOException { + Mockito.doReturn("dummyUrlToRequest").when(clientMock).getBackupOfferingUrl(); + Mockito.doReturn(Arrays.asList(new BackrollOffering("dummyName", "dummyId"))).when(clientMock) + .getBackupOfferings(Mockito.anyString()); + List results = backupProvider.listBackupOfferings(2L); + assertTrue(results.size() == 1); + } + + @Test + public void takeBackup_Test() throws BackrollApiException, IOException { + + VMInstanceVO vmInstanceVO = new VMInstanceVO(); + vmInstanceVO.setInstanceName("test"); + vmInstanceVO.setDataCenterId(2l); + vmInstanceVO.setUuid(UUID.randomUUID().toString()); + vmInstanceVO.setBackupOfferingId(2L); + + Mockito.doReturn("/status/f32092e4-3e8a-461b-8733-ed93e23fa782").when(clientMock) + .startBackupJob(Mockito.anyString()); + Mockito.doReturn(new BackupVO()).when(backupDao).persist(Mockito.any(BackupVO.class)); + Pair result = backupProvider.takeBackup(vmInstanceVO); + assertTrue(result.first()); + } + + @Test + public void restoreBackedUpVolume_Test() { + try { + backupProvider.restoreBackedUpVolume(new BackupVO(), "dummyString", "dummyString", "dummyString", + new Pair("dummyString", VirtualMachine.State.Shutdown)); + } catch (Exception e) { + assertEquals(CloudRuntimeException.class, e.getClass()); + String expected = String.format("Backroll plugin does not support this feature"); + assertEquals(expected, e.getMessage()); + } + } + + @Test + public void getConfigKeys_Test() { + assertEquals(3, backupProvider.getConfigKeys().length); + } + + @Test + public void getConfigComponentName_Test() { + assertEquals(BackupService.class.getSimpleName(), backupProvider.getConfigComponentName()); + } + + @Test + public void getBackupMetricsEmpty_Test() { + assertEquals(backupProvider.getBackupMetrics(2L, Arrays.asList()).size(), 0); + } + + @Test + public void getBackupMetrics_Test() throws BackrollApiException, IOException{ + + VMInstanceVO vmInstanceVO = new VMInstanceVO(); + vmInstanceVO.setInstanceName("test"); + vmInstanceVO.setDataCenterId(1l); + vmInstanceVO.setBackupOfferingId(1l); + + VMInstanceVO vmInstanceVO2 = new VMInstanceVO(); + vmInstanceVO2.setInstanceName("test2"); + vmInstanceVO2.setDataCenterId(2l); + vmInstanceVO2.setBackupOfferingId(2l); + + VMInstanceVO vmInstanceVO3 = new VMInstanceVO(); + vmInstanceVO3.setInstanceName("test3"); + vmInstanceVO3.setDataCenterId(3l); + vmInstanceVO3.setBackupOfferingId(3l); + + BackrollBackupMetrics metrics = new BackrollBackupMetrics(5L, 6L); + + Mockito.doReturn(metrics).when(clientMock).getBackupMetrics(Mockito.anyString(), Mockito.anyString()); + assertEquals(backupProvider.getBackupMetrics(2L, Arrays.asList(vmInstanceVO, vmInstanceVO2, vmInstanceVO3)).size(), 1); + + // Mockito.verify(clientMock, times(3)).getBackupMetrics(Mockito.anyString(), Mockito.anyString()); + } + + @Test + public void restoreVMFromBackupTrue_Test() throws BackrollApiException, IOException { + VMInstanceVO vmInstanceVO = new VMInstanceVO(); + vmInstanceVO.setDataCenterId(2l); + + BackupVO backupVo = new BackupVO(); + backupVo.setExternalId("abc,defgh"); + + Mockito.doReturn(true).when(clientMock).restoreVMFromBackup(Mockito.anyString(), Mockito.anyString()); + + Boolean result = backupProvider.restoreVMFromBackup(vmInstanceVO, backupVo); + + assertTrue(result); + } + + @Test + public void restoreVMFromBackupFalse_Test() throws BackrollApiException, IOException { + VMInstanceVO vmInstanceVO = new VMInstanceVO(); + vmInstanceVO.setDataCenterId(2l); + + BackupVO backupVo = new BackupVO(); + backupVo.setExternalId("abc,defgh"); + + Mockito.doReturn(false).when(clientMock).restoreVMFromBackup(Mockito.anyString(), Mockito.anyString()); + + try { + backupProvider.restoreVMFromBackup(vmInstanceVO, backupVo); + } catch (Exception e) { + assertEquals(CloudRuntimeException.class, e.getClass()); + String expected = String.format("Failed to restore VM from Backup"); + assertEquals(expected, e.getMessage()); + } + } + + @Test + public void getDescription_Test() { + assertEquals("Backroll Backup Plugin", backupProvider.getDescription()); + } + + @Test + public void isValidProviderOffering_Test() { + assertTrue(backupProvider.isValidProviderOffering(2L, "dummyString")); + } + + @Test + public void getName_Test() { + assertEquals("backroll", backupProvider.getName()); + } + + @Test + public void assignVMToBackupOffering_Test() { + VMInstanceVO vmInstanceVO = new VMInstanceVO(); + BackrollOffering backrollOf = new BackrollOffering("dummyName", UUID.randomUUID().toString()); + assertTrue(backupProvider.assignVMToBackupOffering(vmInstanceVO, backrollOf)); + } + + @Test + public void removeVMFromBackupOffering_Test() { + assertTrue(backupProvider.removeVMFromBackupOffering(new VMInstanceVO())); + } + + @Test + public void willDeleteBackupsOnOfferingRemoval_Test() { + assertFalse(backupProvider.willDeleteBackupsOnOfferingRemoval()); + } + + @Test + public void getClient_Test() { + BackrollClient client = backupProvider.getClient(2L); + assertEquals(client, clientMock); + } + + @Test + public void deleteBackupTestSuccess_Test() throws BackrollApiException, IOException { + VMInstanceVO vmInstanceVO = new VMInstanceVO(); + vmInstanceVO.setInstanceName("test"); + vmInstanceVO.setDataCenterId(2l); + vmInstanceVO.setUuid(UUID.randomUUID().toString()); + BackupVO backup = new BackupVO(); + backup.setVmId(1l); + backup.setExternalId("abc,defgh"); + backup.setType("Full"); + backup.setZoneId(2l); + backup.setStatus(Backup.Status.Removed); + Boolean deleteBackupResponse = true; + Mockito.doReturn(vmInstanceVO).when(vmInstanceDao).findByIdIncludingRemoved(Mockito.anyLong()); + Mockito.doReturn(deleteBackupResponse).when(clientMock).deleteBackup(Mockito.anyString(), Mockito.anyString()); + Mockito.doReturn(new BackupVO()).when(backupDao).persist(Mockito.any(BackupVO.class)); + boolean result = backupProvider.deleteBackup(backup, true); + assertEquals(true, result); + } + + @Test + public void deleteBackupBackinUp_Test() { + BackupVO backup = new BackupVO(); + backup.setStatus(Backup.Status.BackingUp); + try { + backupProvider.deleteBackup(backup, false); + } catch (Exception e) { + assertEquals(CloudRuntimeException.class, e.getClass()); + String expected = String.format("You can't delete a backup while it still BackingUp"); + assertEquals(expected, e.getMessage()); + } + } + + @Test + public void listRestorePoints_Test() throws BackrollApiException, IOException { + List rps = Arrays.asList(new RestorePoint("rp1", new Date(), "incremental"), + new RestorePoint("rp2", new Date(), "incremental"), + new RestorePoint("rp3", new Date(), "incremental"), + new RestorePoint("rp4", new Date(), "incremental")); + + VMInstanceVO vmInstanceVO3 = new VMInstanceVO(); + vmInstanceVO3.setInstanceName("test3"); + vmInstanceVO3.setDataCenterId(3l); + vmInstanceVO3.setBackupOfferingId(3l); + + Mockito.doReturn(rps).when(clientMock).listRestorePoints(Mockito.anyString()); + + List rPoints = backupProvider.listRestorePoints(vmInstanceVO3); + + assertEquals(rPoints.size(), rps.size()); + + } + + @Test + public void createNewBackupEntryForRestorePoint_Test() throws BackrollApiException, IOException { + RestorePoint restorePoint = new RestorePoint("restore-123", new Date(), "INCREMENTAL"); + + VMInstanceVO vm = new VMInstanceVO(); + vm.setUuid("vm-uuid-456"); + vm.setDataCenterId(2L); + vm.setBackupOfferingId(3L); + + Backup.Metric metric = null; + + BackrollBackupMetrics backupMetrics = new BackrollBackupMetrics(100L, 200L); + Mockito.doReturn(backupMetrics).when(clientMock).getBackupMetrics(vm.getUuid(), restorePoint.getId()); + + BackupVO savedBackup = new BackupVO(); + Mockito.doReturn(savedBackup).when(backupDao).persist(Mockito.any(BackupVO.class)); + + Backup result = backupProvider.createNewBackupEntryForRestorePoint(restorePoint, vm, metric); + + assertNotNull(result); + assertEquals(vm.getId(), result.getVmId()); + assertEquals(restorePoint.getId(), result.getExternalId()); + assertEquals("INCREMENTAL", result.getType()); + assertEquals(restorePoint.getCreated(), result.getDate()); + assertEquals(Backup.Status.BackedUp, result.getStatus()); + assertEquals(vm.getBackupOfferingId(), (Long)result.getBackupOfferingId()); + assertEquals(vm.getAccountId(), result.getAccountId()); + assertEquals(vm.getDomainId(), result.getDomainId()); + assertEquals(vm.getDataCenterId(), result.getZoneId()); + assertEquals((Long)backupMetrics.getSize(), result.getSize()); + assertEquals((Long)backupMetrics.getDeduplicated(), result.getProtectedSize()); + + Mockito.verify(clientMock).getBackupMetrics(vm.getUuid(), restorePoint.getId()); + Mockito.verify(backupDao).persist(Mockito.any(BackupVO.class)); + } + + @Test + public void createNewBackupEntryForRestorePoint_WithMetric_Test() throws BackrollApiException, IOException { + RestorePoint restorePoint = new RestorePoint("restore-789", new Date(), "INCREMENTAL"); + + VMInstanceVO vm = new VMInstanceVO(); + vm.setUuid("vm-uuid-789"); + vm.setDataCenterId(3L); + vm.setBackupOfferingId(4L); + + Backup.Metric metric = new Backup.Metric(150L, 250L); + + BackupVO savedBackup = new BackupVO(); + Mockito.doReturn(savedBackup).when(backupDao).persist(Mockito.any(BackupVO.class)); + + Backup result = backupProvider.createNewBackupEntryForRestorePoint(restorePoint, vm, metric); + + assertNotNull(result); + assertEquals(vm.getId(), result.getVmId()); + assertEquals(restorePoint.getId(), result.getExternalId()); + assertEquals("INCREMENTAL", result.getType()); + assertEquals(restorePoint.getCreated(), result.getDate()); + assertEquals(Backup.Status.BackedUp, result.getStatus()); + assertEquals(vm.getBackupOfferingId(), (Long)result.getBackupOfferingId()); + assertEquals(vm.getAccountId(), result.getAccountId()); + assertEquals(vm.getDomainId(), result.getDomainId()); + assertEquals(vm.getDataCenterId(), result.getZoneId()); + + Mockito.verify(backupDao).persist(Mockito.any(BackupVO.class)); + } + + @Test + public void createNewBackupEntryForRestorePoint_BackrollApiException_Test() + throws BackrollApiException, IOException { + + RestorePoint restorePoint =new RestorePoint("restore-404", new Date(), "INCREMENTAL"); + + VMInstanceVO vm = new VMInstanceVO(); + vm.setUuid("vm-uuid-404"); + vm.setDataCenterId(4L); + vm.setBackupOfferingId(5L); + + Backup.Metric metric = null; + + Mockito.doThrow(new BackrollApiException()).when(clientMock).getBackupMetrics(vm.getUuid(), + restorePoint.getId()); + + backupProvider.createNewBackupEntryForRestorePoint(restorePoint, vm, metric); + } + + @Test + public void createNewBackupEntryForRestorePoint_IOException_Test() throws BackrollApiException, IOException { + + RestorePoint restorePoint = new RestorePoint("restore-500", new Date(), "INCREMENTAL"); + + VMInstanceVO vm = new VMInstanceVO(); + vm.setUuid("vm-uuid-500"); + vm.setDataCenterId(5L); + vm.setBackupOfferingId(6L); + + Backup.Metric metric = null; + + Mockito.doThrow(new IOException("IO Error")).when(clientMock).getBackupMetrics(vm.getUuid(), + restorePoint.getId()); + + backupProvider.createNewBackupEntryForRestorePoint(restorePoint, vm, metric); + } +} 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 000000000000..cada5de72d99 --- /dev/null +++ b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/BackrollClientTest.java @@ -0,0 +1,291 @@ +// 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; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.Backup.Metric; +import org.apache.cloudstack.backup.BackupOffering; +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.backroll.model.response.BackrollTaskRequestResponse; +import org.apache.cloudstack.backup.backroll.model.response.TaskState; +import org.apache.cloudstack.backup.backroll.model.response.archive.BackrollArchiveResponse; +import org.apache.cloudstack.backup.backroll.model.response.archive.BackrollArchivesResponse; +import org.apache.cloudstack.backup.backroll.model.response.archive.BackrollBackupsFromVMResponse; +import org.apache.cloudstack.backup.backroll.model.response.metrics.backup.BackrollBackupMetricsResponse; +import org.apache.cloudstack.backup.backroll.model.response.metrics.backup.BackupMetricsInfo; +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.virtualMachine.InfosCache; +import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachine.MetricsInfos; +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.backup.backroll.utils.BackrollApiException; +import org.apache.cloudstack.backup.backroll.utils.BackrollHttpClientProvider; +import org.apache.logging.log4j.Logger; +import org.joda.time.DateTime; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import org.mockito.Mock; +import org.mockito.Mockito; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.tomakehurst.wiremock.junit.WireMockRule; + +public class BackrollClientTest { + private BackrollClient client; + + @Mock + private BackrollHttpClientProvider backrollHttpClientProviderMock; + + @Rule + public WireMockRule wireMockRule = new WireMockRule(9399); + + @Before + public void setUp() throws Exception { + backrollHttpClientProviderMock = mock(BackrollHttpClientProvider.class); + client = new BackrollClient(backrollHttpClientProviderMock); + client.logger = Mockito.mock(Logger.class); + } + + @Test + public void getAllBackupsfromVirtualMachine_test() throws Exception { + String vmId = "TEST-vm_uuid"; + String virtualMachineResponseString = "{ \"state\": \"SUCCESS\", \"info\": { \"archives\": [ { \"archive\": \"ROOT-00000\", \"barchive\": \"ROOT-00000\", \"id\": \"25d55ad283aa400af464c76d713c07ad7d163abdd3b8fbcdbdc46b827e5e0457\", \"name\": \"ROOT-00000\", \"start\": \"2024-11-08T18:24:48.000000\", \"time\": \"2024-11-08T18:24:48.000000\" } ], \"encryption\": { \"mode\": \"none\" }, \"repository\": { \"id\": \"36a11ebc0775a097c927735cc7015d19be7309be69fc15b896c5b1fd87fcbd79\", \"last_modified\": \"2024-11-29T09:53:09.000000\", \"location\": \"/mnt/backup/backup1\" } } }"; + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + VirtualMachineBackupsResponse virtualMachineBackupsResponseMock = new ObjectMapper() + .readValue(virtualMachineResponseString, VirtualMachineBackupsResponse.class); + + // Mocking client responses + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .get(Mockito.matches(".*/virtualmachines/.*"), Mockito.any()); + doReturn(virtualMachineBackupsResponseMock).when(backrollHttpClientProviderMock).waitGet(Mockito.anyString(), + Mockito.any()); + // Run the method under test + List backupsTestList = client.getAllBackupsfromVirtualMachine(vmId); + + // Check results + assertEquals(1, backupsTestList.size()); // Should be 1 based on provided mock data + } + + @Test + public void getBackupMetrics_Test() throws IOException, BackrollApiException { + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + BackrollBackupMetricsResponse mockResponse = new BackrollBackupMetricsResponse(); + mockResponse.info = new BackupMetricsInfo(); + mockResponse.info.originalSize = "1000"; + mockResponse.info.deduplicatedSize = "800"; + + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .get(Mockito.matches(".*/virtualmachines/.*"), Mockito.any()); + doReturn(mockResponse).when(backrollHttpClientProviderMock).waitGet(Mockito.anyString(), Mockito.any()); + + BackrollBackupMetrics metrics = client.getBackupMetrics("dummyVMId", "dummyBackupId"); + + assertEquals(1000L, metrics.getSize()); + assertEquals(800L, metrics.getDeduplicated()); + } + + @Test + public void getVirtualMachineMetrics_Test() throws IOException, BackrollApiException { + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + + BackrollVmMetricsResponse mockResponse = new BackrollVmMetricsResponse(); + mockResponse.state = TaskState.SUCCESS; + mockResponse.infos = new MetricsInfos(); + mockResponse.infos.cache = new InfosCache(); + mockResponse.infos.cache.stats = new CacheStats(); + mockResponse.infos.cache.stats.totalSize = "10000"; + + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .get(Mockito.matches(".*/virtualmachines/.*"), Mockito.any()); + doReturn(mockResponse).when(backrollHttpClientProviderMock).waitGet(Mockito.anyString(), Mockito.any()); + + Metric metrics = client.getVirtualMachineMetrics("dummyVMId"); + + assertEquals(10000L, (long) metrics.getBackupSize()); + assertEquals(10000L, (long) metrics.getDataSize()); + } + + @Test + public void deleteBackup_Test() throws IOException, BackrollApiException { + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + + BackrollBackupsFromVMResponse mockResponse = new BackrollBackupsFromVMResponse(); + mockResponse.state = TaskState.SUCCESS; + mockResponse.archives = new BackrollArchivesResponse(); + mockResponse.archives.archives = Arrays.asList(new BackrollArchiveResponse()); + + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .delete(Mockito.matches(".*/virtualmachines/.*"), Mockito.any()); + doReturn(mockResponse).when(backrollHttpClientProviderMock).waitGet(Mockito.anyString(), Mockito.any()); + + Boolean isBackupDeleted = client.deleteBackup("dummyVMId", "dummyBackUpName"); + + assertTrue(isBackupDeleted); + } + + @Test + public void checkBackupTaskStatusSuccess_Test() throws IOException, BackrollApiException { + String backupResponse = "{\"state\":\"SUCCESS\",\"info\":\"test\"}"; + doReturn(backupResponse).when(backrollHttpClientProviderMock) + .getWithoutParseResponse(Mockito.matches(".*status/.*")); + + BackrollTaskStatus status = client.checkBackupTaskStatus("dummytaskid"); + + assertEquals(TaskState.SUCCESS, status.getState()); + assertEquals("test", status.getInfo()); + } + + @Test + public void checkBackupTaskStatus_Test() throws IOException, BackrollApiException { + String backupResponse = "{\"state\":\"PENDING\",\"current\":0,\"total\":1,\"status\":\"Pending...\"}"; + doReturn(backupResponse).when(backrollHttpClientProviderMock) + .getWithoutParseResponse(Mockito.matches(".*/status/.*")); + + BackrollTaskStatus status = client.checkBackupTaskStatus("dummytaskid"); + + assertEquals(TaskState.PENDING, status.getState()); + } + + @Test + public void restoreVMFromBackup_Test() throws IOException, BackrollApiException { + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + String resultMock = "SUCCESS WOW YOUHOU"; + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .post(Mockito.matches(".*/tasks/restore/.*"), Mockito.any(JSONObject.class), Mockito.any()); + doReturn(resultMock).when(backrollHttpClientProviderMock).waitGetWithoutParseResponse(Mockito.anyString()); + + Boolean isRestoreOk = client.restoreVMFromBackup("dummyVMId", "dummyBackUpName"); + + assertTrue(isRestoreOk); + } + + @Test + public void startBackupJob_Test() throws IOException, BackrollApiException { + + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .post(Mockito.matches(".*/tasks/singlebackup/.*"), Mockito.nullable(JSONObject.class), Mockito.any()); + + String response = client.startBackupJob("dummyJobId"); + + assertEquals("/status/f32092e4-3e8a-461b-8733-ed93e23fa782", response); + } + + @Test + public void getBackupOfferingUrl_Test() throws IOException, BackrollApiException { + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .get(Mockito.matches(".*/backup_policies.*"), Mockito.any()); + + String response = client.getBackupOfferingUrl(); + + assertEquals("/status/f32092e4-3e8a-461b-8733-ed93e23fa782", response); + } + + @Test + public void getBackupOfferings_Test() throws BackrollApiException, IOException { + + BackrollBackupPolicyResponse policy1 = new BackrollBackupPolicyResponse(); + policy1.name = "User-Policy-1"; + policy1.retentionDay = 6; + policy1.schedule = "0 0 * * 1"; + policy1.retentionMonth = 0; + policy1.storage = "f32092e4-3e8a-461b-8733-ed93e23fa782"; + policy1.enabled = false; + policy1.description = "User's policy 1 description"; + policy1.id = "f32092e4-3e8a-461b-8733-ed93e23fa782"; + policy1.retentionWeek = 0; + policy1.retentionYear = 0; + policy1.externalHook = null; + + BackrollBackupPolicyResponse policy2 = new BackrollBackupPolicyResponse(); + policy1.name = "User-Policy-2"; + policy1.retentionDay = 6; + policy1.schedule = "0 0 * * 1"; + policy1.retentionMonth = 0; + policy1.storage = "f32092e4-3e8a-461b-8733-ed93e23fa782"; + policy1.enabled = false; + policy1.description = "User's policy 2 description"; + policy1.id = "f32092e4-3e8a-461b-8733-ed93e23fa782"; + policy1.retentionWeek = 0; + policy1.retentionYear = 0; + policy1.externalHook = null; + BackupPoliciesResponse backupPoliciesResponseMock = new BackupPoliciesResponse(); + backupPoliciesResponseMock.backupPolicies = Arrays.asList(policy1, policy2); + + doReturn(backupPoliciesResponseMock).when(backrollHttpClientProviderMock) + .waitGet(Mockito.matches("/status/f32092e4-3e8a-461b-8733-ed93e23fa782"), Mockito.any()); + + List response = client.getBackupOfferings("/status/f32092e4-3e8a-461b-8733-ed93e23fa782"); + + assertEquals(response.size(), 2); + + } + + @Test + public void listRestorePoints_Test() throws BackrollApiException, IOException { + String vmId = "vm-123"; + + String virtualMachineResponseString = "{ \"state\": \"SUCCESS\", \"info\": { \"archives\": [ { \"archive\": \"ROOT-00000\", \"barchive\": \"ROOT-00000\", \"id\": \"25d55ad283aa400af464c76d713c07ad7d163abdd3b8fbcdbdc46b827e5e0457\", \"name\": \"ROOT-00000\", \"start\": \"2024-11-08T18:24:48.000000\", \"time\": \"2024-11-08T18:24:48.000000\" } ], \"encryption\": { \"mode\": \"none\" }, \"repository\": { \"id\": \"36a11ebc0775a097c927735cc7015d19be7309be69fc15b896c5b1fd87fcbd79\", \"last_modified\": \"2024-11-29T09:53:09.000000\", \"location\": \"/mnt/backup/backup1\" } } }"; + BackrollTaskRequestResponse backrollTaskReqResponseMock = new BackrollTaskRequestResponse(); + backrollTaskReqResponseMock.location = "/api/v1/status/f32092e4-3e8a-461b-8733-ed93e23fa782"; + VirtualMachineBackupsResponse virtualMachineBackupsResponseMock = new ObjectMapper() + .readValue(virtualMachineResponseString, VirtualMachineBackupsResponse.class); + + doReturn(backrollTaskReqResponseMock).when(backrollHttpClientProviderMock) + .get(Mockito.matches(".*/virtualmachines/.*"), Mockito.any()); + doReturn(virtualMachineBackupsResponseMock).when(backrollHttpClientProviderMock).waitGet(Mockito.anyString(), + Mockito.any()); + + List restorePoints = client.listRestorePoints(vmId); + + assertNotNull(restorePoints); + assertEquals(1, restorePoints.size()); + + Backup.RestorePoint rp1 = restorePoints.get(0); + assertEquals("ROOT-00000", rp1.getId()); + assertEquals(new DateTime("2024-11-08T18:24:48.000000").toDate(), rp1.getCreated()); + assertEquals("INCREMENTAL", rp1.getType()); + + Mockito.verify(backrollHttpClientProviderMock).get(Mockito.matches(".*/virtualmachines/.*"), Mockito.any()); + Mockito.verify(backrollHttpClientProviderMock).waitGet(anyString(), Mockito.any()); + } +} diff --git a/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/utils/BackrollHttpClientProviderTest.java b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/utils/BackrollHttpClientProviderTest.java new file mode 100644 index 000000000000..877c7107f27e --- /dev/null +++ b/plugins/backup/backroll/src/test/java/org/apache/cloudstack/backup/backroll/utils/BackrollHttpClientProviderTest.java @@ -0,0 +1,315 @@ +// 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.utils; + +import static org.junit.Assert.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; + +import javax.net.ssl.SSLContext; + +import org.apache.cloudstack.backup.backroll.model.response.api.LoginApiResponse; +import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups.VirtualMachineBackupsResponse; +import org.apache.cloudstack.backup.backroll.utils.BackrollHttpClientProvider.NotOkBodyException; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +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.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.json.JSONObject; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; +import org.apache.cloudstack.utils.security.SSLUtils; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + + +public class BackrollHttpClientProviderTest { + + @Spy + @InjectMocks + BackrollHttpClientProvider backupHttpClientProvider; + + @Mock + private CloseableHttpClient httpClient; + + @Mock + private CloseableHttpResponse response; + + @Mock + private RequestConfig config; + + @Spy + private SSLUtils sslUtils; + + @Spy + private HttpClientBuilder httpClientBuilder; + + @Mock + private SSLContext sslContext; + + @Mock + private SSLConnectionSocketFactory sslConnectionSocketFactory; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + backupHttpClientProvider = BackrollHttpClientProvider.createProvider(backupHttpClientProvider, + "http://api.backup.demo.ccc:5050/api/v1", "backroll-api", "VviX8dALauSyYJMqVYJqf3UyZOpO3joS", true, + 300, 600); + } + + private void defaultTestHttpClient(String path) + throws BackrollApiException, ClientProtocolException, IOException, NotOkBodyException { + + LoginApiResponse responseLogin = new LoginApiResponse(); + responseLogin.accessToken = "dummyToken"; + responseLogin.expiresIn = 3600; + responseLogin.notBeforePolicy = "dummyNotBeforePolicy"; + responseLogin.refreshExpiresIn = "7200"; + responseLogin.scope = "dummyScope"; + + String virtualMachineResponseString = "{ \"state\": \"SUCCESS\", \"info\": { \"archives\": [ { \"archive\": \"ROOT-00000\", \"barchive\": \"ROOT-00000\", \"id\": \"25d55ad283aa400af464c76d713c07ad7d163abdd3b8fbcdbdc46b827e5e0457\", \"name\": \"ROOT-00000\", \"start\": \"2024-11-08T18:24:48.000000\", \"time\": \"2024-11-08T18:24:48.000000\" } ], \"encryption\": { \"mode\": \"none\" }, \"repository\": { \"id\": \"36a11ebc0775a097c927735cc7015d19be7309be69fc15b896c5b1fd87fcbd79\", \"last_modified\": \"2024-11-29T09:53:09.000000\", \"location\": \"/mnt/backup/backup1\" } } }"; + + CloseableHttpResponse response2 = mock(CloseableHttpResponse.class); + StatusLine statusLine = mock(StatusLine.class); + + doReturn(httpClient).when(backupHttpClientProvider).createHttpClient(); + doReturn(response).when(httpClient) + .execute(argThat(argument -> argument != null && argument.getURI().toString().contains("login"))); + + doReturn(response2).when(httpClient) + .execute(argThat(argument -> argument != null && argument.getURI().toString().contains(path))); + + doReturn(new ObjectMapper().writeValueAsString(responseLogin)).when(backupHttpClientProvider).okBody(response); + doReturn(virtualMachineResponseString).when(backupHttpClientProvider).okBody(response2); + + doReturn(statusLine).when(response).getStatusLine(); + doReturn(HttpStatus.SC_OK).when(statusLine).getStatusCode(); + + doNothing().when(response).close(); + + doReturn(new StringEntity("{\"mockKey\": \"mockValue\"}", ContentType.APPLICATION_JSON)).when(response) + .getEntity(); + } + + @Test + public void testCreateHttpClient_WithValidateCertificateTrue() + throws KeyManagementException, NoSuchAlgorithmException, URISyntaxException, BackrollApiException { + backupHttpClientProvider = BackrollHttpClientProvider.createProvider(backupHttpClientProvider, + "http://api.backup.demo.ccc:5050/api/v1", "backroll-api", "VviX8dALauSyYJMqVYJqf3UyZOpO3joS", true, + 300, 600); + + // Mock HttpClientBuilder + HttpClientBuilder mockBuilder = mock(HttpClientBuilder.class); + try (MockedStatic utilities = Mockito.mockStatic(HttpClientBuilder.class)) { + utilities.when(HttpClientBuilder::create).thenReturn(mockBuilder); + when(mockBuilder.setDefaultRequestConfig(config)).thenReturn(mockBuilder); + when(mockBuilder.build()).thenReturn(httpClient); + } + + // Test the method + CloseableHttpClient client = backupHttpClientProvider.createHttpClient(); + + // Verify and assert + assertNotNull(client); + } + + @Test + public void testCreateHttpClient_WithValidateCertificateFalse() + throws KeyManagementException, NoSuchAlgorithmException, URISyntaxException, BackrollApiException { + backupHttpClientProvider = BackrollHttpClientProvider.createProvider(backupHttpClientProvider, + "http://api.backup.demo.ccc:5050/api/v1", "backroll-api", "VviX8dALauSyYJMqVYJqf3UyZOpO3joS", false, + 300, 600); + + // Mock HttpClientBuilder + HttpClientBuilder mockBuilder = mock(HttpClientBuilder.class); + try (MockedStatic utilities = Mockito.mockStatic(HttpClientBuilder.class)) { + utilities.when(HttpClientBuilder::create).thenReturn(mockBuilder); + when(mockBuilder.setDefaultRequestConfig(config)).thenReturn(mockBuilder); + when(mockBuilder.setSSLSocketFactory(any(SSLConnectionSocketFactory.class))).thenReturn(mockBuilder); + when(mockBuilder.build()).thenReturn(httpClient); + } + + // Test the method + CloseableHttpClient client = backupHttpClientProvider.createHttpClient(); + + // Verify and assert + assertNotNull(client); + } + + @Test + public void NotOkBodyException_Test(){ + BackrollHttpClientProvider.NotOkBodyException exception = backupHttpClientProvider.new NotOkBodyException(); + assertNotNull(exception); + } + + @Test + public void get_Test_success() throws Exception { + // Arrange + String path = "/test"; + defaultTestHttpClient(path); + + // Act + VirtualMachineBackupsResponse result = backupHttpClientProvider.get(path, + VirtualMachineBackupsResponse.class); + + // Assert + assertNotNull(result); + verify(backupHttpClientProvider, times(2)).okBody(Mockito.any(CloseableHttpResponse.class)); + verify(httpClient, times(1)).execute(Mockito.any(HttpPost.class)); + verify(httpClient, times(1)).execute(Mockito.any(HttpGet.class)); + verify(response, times(1)).close(); + } + + @Test + public void delete_Test_success() throws Exception { + // Arrange + String path = "/test"; + defaultTestHttpClient(path); + + // Act + VirtualMachineBackupsResponse result = backupHttpClientProvider.delete(path, + VirtualMachineBackupsResponse.class); + + // Assert + assertNotNull(result); + verify(backupHttpClientProvider, times(2)).okBody(Mockito.any(CloseableHttpResponse.class)); + verify(httpClient, times(1)).execute(Mockito.any(HttpPost.class)); + verify(httpClient, times(1)).execute(Mockito.any(HttpDelete.class)); + verify(response, times(1)).close(); + } + + @Test + public void okBody_Test_success() throws BackrollApiException, IOException, NotOkBodyException { + StatusLine statusLine = mock(StatusLine.class); + doReturn(statusLine).when(response).getStatusLine(); + doReturn(HttpStatus.SC_OK).when(statusLine).getStatusCode(); + doReturn(new StringEntity("{\"mockKey\": \"mockValue\"}", ContentType.APPLICATION_JSON)).when(response) + .getEntity(); + doNothing().when(response).close(); + + String result = backupHttpClientProvider.okBody(response); + assertNotNull(result); + } + + @Test(expected = BackrollHttpClientProvider.NotOkBodyException.class) + public void okBody_Test_Error() throws BackrollApiException, IOException, NotOkBodyException { + StatusLine statusLine = mock(StatusLine.class); + doReturn(statusLine).when(response).getStatusLine(); + doReturn(HttpStatus.SC_INTERNAL_SERVER_ERROR).when(statusLine).getStatusCode(); + + backupHttpClientProvider.okBody(response); + } + + @Test + public void waitGet_Test() throws Exception { + String path = "/test"; + defaultTestHttpClient(path); + + // Act + VirtualMachineBackupsResponse result = backupHttpClientProvider.waitGet(path, + VirtualMachineBackupsResponse.class); + + // Assert + assertNotNull(result); + verify(backupHttpClientProvider, times(2)).okBody(Mockito.any(CloseableHttpResponse.class)); + verify(httpClient, times(1)).execute(Mockito.any(HttpGet.class)); + verify(response, times(1)).close(); + } + + @Test + public void waitGetWithoutParseResponse_Test() throws Exception { + String path = "/test"; + defaultTestHttpClient(path); + + // Act + String result = backupHttpClientProvider.waitGetWithoutParseResponse(path); + + // Assert + assertNotNull(result); + verify(backupHttpClientProvider, times(2)).okBody(Mockito.any(CloseableHttpResponse.class)); + verify(httpClient, times(1)).execute(Mockito.any(HttpGet.class)); + verify(response, times(1)).close(); + } + + @Test + public void testPost_success() throws Exception { + // Arrange + String path = "/test"; + JSONObject json = new JSONObject(); + defaultTestHttpClient(path); + + // Act + VirtualMachineBackupsResponse result = backupHttpClientProvider.post(path, json, + VirtualMachineBackupsResponse.class); + + // Assert + assertNotNull(result); + verify(backupHttpClientProvider, times(2)).okBody(Mockito.any(CloseableHttpResponse.class)); + verify(httpClient, times(2)).execute(Mockito.any(HttpPost.class)); + verify(response, times(1)).close(); + } + + @Test + public void testAuthenticationFailure() throws Exception { + doReturn(false).when(backupHttpClientProvider).isAuthenticated(); + doNothing().when(backupHttpClientProvider).login(); + backupHttpClientProvider.loginIfAuthenticationFailed(); + verify(backupHttpClientProvider).login(); + } + + @Test(expected = BackrollApiException.class) + public void testLoginFailure() throws Exception { + doReturn(false).when(backupHttpClientProvider).isAuthenticated(); + doThrow(BackrollApiException.class).when(backupHttpClientProvider).login(); + backupHttpClientProvider.loginIfAuthenticationFailed(); + } + + @Test + public void testLoginSuccess() throws Exception { + doReturn(true).when(backupHttpClientProvider).isAuthenticated(); + backupHttpClientProvider.loginIfAuthenticationFailed(); + verify(backupHttpClientProvider, times(0)).login(); + } +} diff --git a/plugins/pom.xml b/plugins/pom.xml index e7d13871285e..df48ec6a03a7 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 backup/nas @@ -229,6 +230,7 @@ api/vmware-sioc + backup/backroll backup/veeam hypervisors/vmware network-elements/cisco-vnmc diff --git a/root@169.254.109.99 b/root@169.254.109.99 new file mode 100644 index 000000000000..5d1bf6e36850 Binary files /dev/null and b/root@169.254.109.99 differ