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 index f8fd1c59c88..8feafe22c4f 100644 --- 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 @@ -34,6 +34,8 @@ import org.apache.cloudstack.backup.backroll.BackrollClient; import org.apache.cloudstack.backup.backroll.model.BackrollBackupMetrics; import org.apache.cloudstack.backup.backroll.model.BackrollTaskStatus; import org.apache.cloudstack.backup.backroll.model.BackrollVmBackup; +import org.apache.cloudstack.backup.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; @@ -89,7 +91,6 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide @Override public List listBackupOfferings(Long zoneId) { logger.debug("Listing backup policies on backroll B&R Plugin"); - logger.info("Listing backup policies on backroll B&R Plugin"); BackrollClient client = getClient(zoneId); try{ String urlToRequest = client.getBackupOfferingUrl(); @@ -105,8 +106,8 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide } return results; } - } catch (KeyManagementException | ParseException | NoSuchAlgorithmException | IOException e) { - logger.info("BackrollProvider: catch erreur!!!!!!!!!!!!!!!!!!!!!!!!"); + } catch (ParseException | BackrollApiException | IOException e) { + logger.info("BackrollProvider: catch erreur: " + e); throw new CloudRuntimeException("Failed to load backup offerings"); } return new ArrayList(); @@ -121,8 +122,11 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide @Override public boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering) { logger.info("Creating VM backup for VM {} from backup offering {}", vm.getInstanceName(), backupOffering.getName()); - ((VMInstanceVO) vm).setBackupExternalId(backupOffering.getUuid()); - return true; + if(vm instanceof VMInstanceVO) { + ((VMInstanceVO) vm).setBackupExternalId(backupOffering.getUuid()); + return true; + } + return false; } @Override @@ -131,7 +135,7 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide boolean isSuccess; try { isSuccess = getClient(vm.getDataCenterId()).restoreVMFromBackup(vm.getUuid(), getBackupName(backup)); - } catch (KeyManagementException | ParseException | NoSuchAlgorithmException | IOException e) { + } catch (ParseException | BackrollApiException | IOException e) { throw new CloudRuntimeException("Failed to restore VM from Backrup"); } return isSuccess; @@ -148,6 +152,7 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide 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; @@ -155,8 +160,8 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide Metric metric; try { - metric = getClient(zoneId).getVirtualMachineMetrics(vm.getUuid()); - } catch (KeyManagementException | NoSuchAlgorithmException | IOException e) { + metric = client.getVirtualMachineMetrics(vm.getUuid()); + } catch (BackrollApiException | IOException e) { throw new CloudRuntimeException("Failed to retrieve backup metrics"); } logger.debug("Metrics for VM [uuid: {}, name: {}] is [backup size: {}, data size: {}].", vm.getUuid(), @@ -184,7 +189,8 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide String idBackupTask; try { idBackupTask = client.startBackupJob(vm.getUuid()); - } catch (KeyManagementException | NoSuchAlgorithmException | IOException e) { + } catch (IOException | BackrollApiException e) { + logger.error(e); throw new CloudRuntimeException("Failed to take backup"); } if (!StringUtils.isEmpty(idBackupTask)) { @@ -210,14 +216,15 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide logger.info("Starting sync backup for VM ID " + vm.getUuid() + " on backroll provider"); final BackrollClient client = getClient(vm.getDataCenterId()); - List backupsInDb = backupDao.listByVmId(null, vm.getId()); + List backupsInDb = backupDao.listByVmId(vm.getDataCenterId(), vm.getId()); for (Backup backup : backupsInDb) { if (backup.getStatus().equals(Backup.Status.BackingUp)) { BackrollTaskStatus response; try { response = client.checkBackupTaskStatus(backup.getExternalId()); - } catch (KeyManagementException | ParseException | NoSuchAlgorithmException | IOException e) { + } catch (ParseException | BackrollApiException | IOException e) { + logger.error(e); throw new CloudRuntimeException("Failed to sync backups"); } @@ -225,17 +232,7 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide logger.debug("backroll backup id: {}", backup.getExternalId()); logger.debug("backroll backup status: {}", response.getState()); - BackupVO backupToUpdate = new BackupVO(); - backupToUpdate.setVmId(backup.getVmId()); - backupToUpdate.setExternalId(backup.getExternalId()); - backupToUpdate.setType(backup.getType()); - backupToUpdate.setDate(backup.getDate()); - backupToUpdate.setSize(backup.getSize()); - backupToUpdate.setProtectedSize(backup.getProtectedSize()); - backupToUpdate.setBackupOfferingId(vm.getBackupOfferingId()); - backupToUpdate.setAccountId(backup.getAccountId()); - backupToUpdate.setDomainId(backup.getDomainId()); - backupToUpdate.setZoneId(backup.getZoneId()); + BackupVO backupToUpdate = ((BackupVO) backup); if (response.getState().equals("PENDING")) { backupToUpdate.setStatus(Backup.Status.BackingUp); @@ -248,7 +245,8 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide BackrollBackupMetrics backupMetrics = null; try { backupMetrics = client.getBackupMetrics(vm.getUuid() , response.getInfo()); - } catch (KeyManagementException | NoSuchAlgorithmException | IOException e) { + } catch (BackrollApiException | IOException e) { + logger.error(e); throw new CloudRuntimeException("Failed to get backup metrics"); } @@ -262,7 +260,6 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide if (backupDao.persist(backupToUpdate) != null) { logger.info("Backroll mise à jour enregistrée"); - backupDao.remove(backup.getId()); } } } else if (backup.getStatus().equals(Backup.Status.BackedUp) && backup.getSize().equals(0L)) { @@ -270,7 +267,8 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide BackrollBackupMetrics backupMetrics; try { backupMetrics = client.getBackupMetrics(vm.getUuid() , backupId); - } catch (KeyManagementException | NoSuchAlgorithmException | IOException e) { + } catch (BackrollApiException | IOException e) { + logger.error(e); throw new CloudRuntimeException("Failed to get backup metrics"); } @@ -284,57 +282,63 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide } // Backups synchronisation between Backroll ad CS Db - List backupsFromBackroll = client.getAllBackupsfromVirtualMachine(vm.getUuid()); - backupsInDb = backupDao.listByVmId(null, vm.getId()); + List backupsFromBackroll; + try { + backupsFromBackroll = client.getAllBackupsfromVirtualMachine(vm.getUuid()); - // insert new backroll backup in CS - for (BackrollVmBackup backupInBackroll : backupsFromBackroll) { - Backup backupToFind = backupsInDb.stream() - .filter(backupInDb -> backupInDb.getExternalId().contains(backupInBackroll.getName())) - .findAny() - .orElse(null); + backupsInDb = backupDao.listByVmId(null, vm.getId()); - if (backupToFind == null) { - BackupVO backupToInsert = new BackupVO(); - backupToInsert.setVmId(vm.getId()); - backupToInsert.setExternalId(backupInBackroll.getId() + "," + backupInBackroll.getName()); - backupToInsert.setType("INCREMENTAL"); - backupToInsert.setDate(backupInBackroll.getDate()); - backupToInsert.setSize(0L); - backupToInsert.setProtectedSize(0L); - backupToInsert.setStatus(Backup.Status.BackedUp); - backupToInsert.setBackupOfferingId(vm.getBackupOfferingId()); - backupToInsert.setAccountId(vm.getAccountId()); - backupToInsert.setDomainId(vm.getDomainId()); - backupToInsert.setZoneId(vm.getDataCenterId()); - backupDao.persist(backupToInsert); - } - if (backupToFind != null && backupToFind.getStatus() == Backup.Status.Removed) { - BackupVO backupToUpdate = ((BackupVO) backupToFind); - backupToUpdate.setStatus(Backup.Status.BackedUp); - if (backupDao.persist(backupToUpdate) != null) { - logger.info("Backroll update saved"); - backupDao.remove(backupToFind.getId()); + // insert new backroll backup in CS + for (BackrollVmBackup backupInBackroll : backupsFromBackroll) { + Backup backupToFind = backupsInDb.stream() + .filter(backupInDb -> backupInDb.getExternalId().contains(backupInBackroll.getName())) + .findAny() + .orElse(null); + + if (backupToFind == null) { + BackupVO backupToInsert = new BackupVO(); + backupToInsert.setVmId(vm.getId()); + backupToInsert.setExternalId(backupInBackroll.getId() + "," + backupInBackroll.getName()); + backupToInsert.setType("INCREMENTAL"); + backupToInsert.setDate(backupInBackroll.getDate()); + backupToInsert.setSize(0L); + backupToInsert.setProtectedSize(0L); + backupToInsert.setStatus(Backup.Status.BackedUp); + backupToInsert.setBackupOfferingId(vm.getBackupOfferingId()); + backupToInsert.setAccountId(vm.getAccountId()); + backupToInsert.setDomainId(vm.getDomainId()); + backupToInsert.setZoneId(vm.getDataCenterId()); + backupDao.persist(backupToInsert); + } + if (backupToFind != null && backupToFind.getStatus() == Backup.Status.Removed) { + BackupVO backupToUpdate = ((BackupVO) backupToFind); + backupToUpdate.setStatus(Backup.Status.BackedUp); + if (backupDao.persist(backupToUpdate) != null) { + logger.info("Backroll update saved"); + backupDao.remove(backupToFind.getId()); + } } } - } - // delete deleted backroll backup in CS - backupsInDb = backupDao.listByVmId(null, vm.getId()); - for (Backup backup : backupsInDb) { - String backupName = backup.getExternalId().contains(",") ? backup.getExternalId().split(",")[1] : backup.getExternalId(); - BackrollVmBackup backupToFind = backupsFromBackroll.stream() - .filter(backupInBackroll -> backupInBackroll.getName().contains(backupName)) - .findAny() - .orElse(null); + // delete deleted backroll backup in CS + backupsInDb = backupDao.listByVmId(null, vm.getId()); + for (Backup backup : backupsInDb) { + String backupName = backup.getExternalId().contains(",") ? backup.getExternalId().split(",")[1] : backup.getExternalId(); + BackrollVmBackup backupToFind = backupsFromBackroll.stream() + .filter(backupInBackroll -> backupInBackroll.getName().contains(backupName)) + .findAny() + .orElse(null); - if (backupToFind == null) { - BackupVO backupToUpdate = ((BackupVO) backup); - backupToUpdate.setStatus(Backup.Status.Removed); - if (backupDao.persist(backupToUpdate) != null) { - logger.debug("Backroll delete saved (sync)"); + if (backupToFind == null) { + BackupVO backupToUpdate = ((BackupVO) backup); + backupToUpdate.setStatus(Backup.Status.Removed); + if (backupDao.persist(backupToUpdate) != null) { + logger.debug("Backroll delete saved (sync)"); + } } } + } catch (BackrollApiException | IOException e) { + logger.error(e); } } @@ -369,7 +373,8 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide logger.debug("Backup deletion for backup {} complete on backroll side.", backup.getUuid()); return deleteBackupInDb(backup); } - } catch (KeyManagementException | NoSuchAlgorithmException | IOException e) { + } catch (BackrollApiException | IOException e) { + logger.error(e); throw new CloudRuntimeException("Failed to delete backup"); } } @@ -392,15 +397,17 @@ public class BackrollBackupProvider extends AdapterBase implements BackupProvide try { if (backrollClient == null) { logger.debug("backroll client null - instanciation of new one "); - backrollClient = new BackrollClient(BackrollUrlConfigKey.valueIn(zoneId), BackrollAppNameConfigKey.valueIn(zoneId), BackrollPasswordConfigKey.valueIn(zoneId), true, 300, 600); + BackrollHttpClientProvider provider = 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.info("Failed to build Backroll API client due to: ", e); + logger.error(e); + throw new CloudRuntimeException("Failed to build Backroll API client"); } - throw new CloudRuntimeException("Failed to build Backroll API client"); } private String getBackupName(Backup backup) { 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 index 469a354a2f2..a5978b81541 100644 --- 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 @@ -17,31 +17,19 @@ package org.apache.cloudstack.backup.backroll; import java.io.IOException; -import java.net.URI; import java.net.URISyntaxException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.TimeUnit; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509TrustManager; - -import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.Backup.Metric; -import org.apache.cloudstack.backup.backroll.model.BackrollBackup; import org.apache.cloudstack.backup.backroll.model.BackrollBackupMetrics; import org.apache.cloudstack.backup.backroll.model.BackrollOffering; import org.apache.cloudstack.backup.backroll.model.BackrollTaskStatus; import org.apache.cloudstack.backup.backroll.model.BackrollVmBackup; import org.apache.cloudstack.backup.backroll.model.response.BackrollTaskRequestResponse; import org.apache.cloudstack.backup.backroll.model.response.TaskState; -import org.apache.cloudstack.backup.backroll.model.response.api.LoginApiResponse; -import org.apache.cloudstack.backup.backroll.model.response.archive.BackrollArchiveResponse; import org.apache.cloudstack.backup.backroll.model.response.archive.BackrollBackupsFromVMResponse; import org.apache.cloudstack.backup.backroll.model.response.backup.BackrollBackupStatusResponse; import org.apache.cloudstack.backup.backroll.model.response.backup.BackrollBackupStatusSuccessResponse; @@ -52,28 +40,11 @@ import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachi import org.apache.cloudstack.backup.backroll.model.response.metrics.virtualMachineBackups.VirtualMachineBackupsResponse; import org.apache.cloudstack.backup.backroll.model.response.policy.BackrollBackupPolicyResponse; import org.apache.cloudstack.backup.backroll.model.response.policy.BackupPoliciesResponse; -import org.apache.cloudstack.utils.security.SSLUtils; +import org.apache.cloudstack.backup.backroll.utils.BackrollApiException; +import org.apache.cloudstack.backup.backroll.utils.BackrollHttpClientProvider; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpEntity; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.HttpStatus; -import org.apache.http.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; @@ -81,585 +52,177 @@ import org.joda.time.DateTime; import org.json.JSONException; import org.json.JSONObject; - -import com.cloud.utils.exception.CloudRuntimeException; -import com.cloud.utils.nio.TrustAllManager; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; public class BackrollClient { private Logger logger = LogManager.getLogger(BackrollClient.class); - private final URI apiURI; + private BackrollHttpClientProvider httpProvider; - private String backrollToken = null; - private String appname = null; - private String password = null; - private boolean validateCertificate = false; - private RequestConfig config = null; - - private int restoreTimeout; - - public BackrollClient(final String url, final String appname, final String password, - final boolean validateCertificate, final int timeout, - final int restoreTimeout) + public BackrollClient(BackrollHttpClientProvider httpProvider) throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { - this.apiURI = new URI(url); - this.restoreTimeout = restoreTimeout; - this.appname = appname; - this.password = password; - this.config = RequestConfig.custom() - .setConnectTimeout(timeout * 1000) - .setConnectionRequestTimeout(timeout * 1000) - .setSocketTimeout(timeout * 1000) - .build(); + this.httpProvider = httpProvider; } - private CloseableHttpClient createHttpClient() throws KeyManagementException, NoSuchAlgorithmException { - if(!validateCertificate) { - SSLContext 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(); - } else { - return HttpClientBuilder.create() - .setDefaultRequestConfig(config) - .build(); - } - } - - private void closeConnection(CloseableHttpResponse closeableHttpResponse) throws IOException { - closeableHttpResponse.close(); - } - - public String startBackupJob(final String jobId) throws KeyManagementException, NoSuchAlgorithmException, IOException { + public String startBackupJob(final String jobId) throws IOException, BackrollApiException { logger.info("Trying to start backup for Backroll job: {}", jobId); String backupJob = ""; - - loginIfAuthenticationFailed(); - - try { - CloseableHttpResponse response = post(String.format("/tasks/singlebackup/%s", jobId), null); - String result = okBody(response); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - response.close(); - backupJob = requestResponse.location.replace("/api/v1/status/", ""); - } catch (final Exception e) { - logger.error("Failed to start Backroll backup job due to: {}", e.getMessage()); - } + BackrollTaskRequestResponse requestResponse = httpProvider.post(String.format("/tasks/singlebackup/%s", jobId), null, BackrollTaskRequestResponse.class); + backupJob = requestResponse.location.replace("/api/v1/status/", ""); return StringUtils.isEmpty(backupJob) ? null : backupJob; } - public String getBackupOfferingUrl() throws KeyManagementException, NoSuchAlgorithmException, IOException { - logger.info("Trying to list backroll backup policies"); - - loginIfAuthenticationFailed(); + public String getBackupOfferingUrl() throws IOException, BackrollApiException { + logger.info("Trying to get backroll backup policies url"); String url = ""; - - try { - CloseableHttpResponse response = get("/backup_policies"); - String result = okBody(response); - logger.info("BackrollClient:getBackupOfferingUrl:result: " + result); - //BackrollTaskRequestResponse requestResponse = parse(result); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - logger.info("BackrollClient:getBackupOfferingUrl:Apres PArse: " + requestResponse.location); - response.close(); - url = requestResponse.location.replace("/api/v1", ""); - } catch (final Exception e) { - logger.info("Failed to list Backroll jobs due to: {}", e.getMessage()); - logger.error("Failed to list Backroll jobs due to: {}", e.getMessage()); - } + 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 KeyManagementException, ParseException, NoSuchAlgorithmException, IOException { + public List getBackupOfferings(String idTask) throws BackrollApiException, IOException { logger.info("Trying to list backroll backup policies"); - - loginIfAuthenticationFailed(); final List policies = new ArrayList<>(); - - try { - String results = waitGet(idTask); - BackupPoliciesResponse backupPoliciesResponse = new ObjectMapper().readValue(results, BackupPoliciesResponse.class); - - for (final BackrollBackupPolicyResponse policy : backupPoliciesResponse.backupPolicies) { - policies.add(new BackrollOffering(policy.name, policy.id)); - } - } catch (final IOException | InterruptedException e) { - logger.error("Failed to list Backroll jobs due to: {}", e.getMessage()); + 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 KeyManagementException, ParseException, NoSuchAlgorithmException, IOException { + 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); - loginIfAuthenticationFailed(); boolean isRestoreOk = false; + JSONObject jsonBody = new JSONObject(); try { - JSONObject jsonBody = new JSONObject(); - try { - jsonBody.put("virtual_machine_id", vmId); - jsonBody.put("backup_name", backupName); - jsonBody.put("storage", ""); - jsonBody.put("mode", "single"); + 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()); - } - - CloseableHttpResponse response = post(String.format("/tasks/restore/%s", vmId), jsonBody); - String result = okBody(response); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - response.close(); - String urlToRequest = requestResponse.location.replace("/api/v1", ""); - result = waitGet(urlToRequest); - - if(result.contains("SUCCESS")) { - logger.debug("RESTORE SUCCESS"); - isRestoreOk = true; - } - - } catch (final NotOkBodyException e) { - return false; - } catch (final IOException | InterruptedException e) { - logger.error("Ouch! Failed to restore VM with Backroll due to: {}", e.getMessage()); - throw new CloudRuntimeException("Ouch! Failed to restore VM with Backroll due to: {}" + e.getMessage()); + } 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 KeyManagementException, ParseException, NoSuchAlgorithmException, IOException { + public BackrollTaskStatus checkBackupTaskStatus(String taskId) throws IOException, BackrollApiException { logger.info("Trying to get backup status for Backroll task: {}", taskId); - loginIfAuthenticationFailed(); - BackrollTaskStatus status = new BackrollTaskStatus(); - try { + String backupResponse = httpProvider.getWithoutParseResponse("/status/" + taskId); - String body = okBody(get("/status/" + taskId)); - - if (body.contains(TaskState.FAILURE) || body.contains(TaskState.PENDING)) { - BackrollBackupStatusResponse backupStatusRequestResponse = new ObjectMapper().readValue(body, BackrollBackupStatusResponse.class); - status.setState(backupStatusRequestResponse.state); - } else { - BackrollBackupStatusSuccessResponse backupStatusSuccessRequestResponse = new ObjectMapper().readValue(body, BackrollBackupStatusSuccessResponse.class); - status.setState(backupStatusSuccessRequestResponse.state); - status.setInfo(backupStatusSuccessRequestResponse.info); - } - - } catch (final NotOkBodyException e) { - // throw new CloudRuntimeException("Failed to retrieve backups status for this - // VM via Backroll"); - logger.error("Failed to retrieve backups status for this VM via Backroll"); - - } catch (final IOException e) { - logger.error("Failed to check backups status due to: {}", e.getMessage()); + 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 KeyManagementException, NoSuchAlgorithmException, IOException { + public boolean deleteBackup(final String vmId, final String backupName) throws IOException, BackrollApiException{ logger.info("Trying to delete backup {} for vm {} using Backroll", vmId, backupName); - - loginIfAuthenticationFailed(); - boolean isBackupDeleted = false; - try { + BackrollTaskRequestResponse requestResponse = httpProvider.delete(String.format("/virtualmachines/%s/backups/%s", vmId, backupName), BackrollTaskRequestResponse.class); + String urlToRequest = requestResponse.location.replace("/api/v1", ""); - CloseableHttpResponse response = delete(String.format("/virtualmachines/%s/backups/%s", vmId, backupName)); - String result = okBody(response); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - response.close(); - String urlToRequest = requestResponse.location.replace("/api/v1", ""); + BackrollBackupsFromVMResponse backrollBackupsFromVMResponse = httpProvider.waitGet(urlToRequest, BackrollBackupsFromVMResponse.class); + logger.debug(backrollBackupsFromVMResponse.state); + isBackupDeleted = backrollBackupsFromVMResponse.state.equals(TaskState.SUCCESS); - result = waitGet(urlToRequest); - BackrollBackupsFromVMResponse backrollBackupsFromVMResponse = new ObjectMapper().readValue(result, BackrollBackupsFromVMResponse.class); - logger.debug(backrollBackupsFromVMResponse.state); - isBackupDeleted = backrollBackupsFromVMResponse.state.equals(TaskState.SUCCESS); - } catch (final NotOkBodyException e) { - isBackupDeleted = false; - } catch (final Exception e) { - isBackupDeleted = false; - logger.error("Failed to delete backup using Backroll due to: {}", e.getMessage()); - } return isBackupDeleted; } - public Metric getVirtualMachineMetrics(final String vmId) throws KeyManagementException, NoSuchAlgorithmException, IOException { + public Metric getVirtualMachineMetrics(final String vmId) throws IOException, BackrollApiException { logger.info("Trying to retrieve virtual machine metric from Backroll for vm {}", vmId); - loginIfAuthenticationFailed(); - Metric metric = new Metric(0L, 0L); - try { + BackrollTaskRequestResponse requestResponse = httpProvider.get(String.format("/virtualmachines/%s/repository", vmId), BackrollTaskRequestResponse.class); - CloseableHttpResponse response = get(String.format("/virtualmachines/%s/repository", vmId)); - String result = okBody(response); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - response.close(); - String urlToRequest = requestResponse.location.replace("/api/v1", ""); + String urlToRequest = requestResponse.location.replace("/api/v1", ""); - result = waitGet(urlToRequest); - BackrollVmMetricsResponse vmMetricsResponse =new ObjectMapper().readValue(result, BackrollVmMetricsResponse.class); + 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); - } + 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); } - } catch (final Exception e) { - logger.error("Failed to retrieve virtual machine metrics with Backroll due to: {}", e.getMessage()); } return metric; } - public BackrollBackupMetrics getBackupMetrics(String vmId, String backupId) throws KeyManagementException, NoSuchAlgorithmException, IOException { + public BackrollBackupMetrics getBackupMetrics(String vmId, String backupId) throws IOException, BackrollApiException { logger.info("Trying to get backup metrics for VM: {}, and backup: {}", vmId, backupId); - loginIfAuthenticationFailed(); - BackrollBackupMetrics metrics = null; - try { + BackrollTaskRequestResponse requestResponse = httpProvider.get(String.format("/virtualmachines/%s/backups/%s", vmId, backupId), BackrollTaskRequestResponse.class); - CloseableHttpResponse response = get(String.format("/virtualmachines/%s/backups/%s", vmId, backupId)); - String result = okBody(response); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - response.close(); - String urlToRequest = requestResponse.location.replace("/api/v1", ""); + String urlToRequest = requestResponse.location.replace("/api/v1", ""); - logger.debug(urlToRequest); + logger.debug(urlToRequest); - result = waitGet(urlToRequest); - BackrollBackupMetricsResponse metricsResponse = new ObjectMapper().readValue(result, BackrollBackupMetricsResponse.class); - if (metricsResponse.info != null) { - metrics = new BackrollBackupMetrics(Long.parseLong(metricsResponse.info.originalSize), - Long.parseLong(metricsResponse.info.deduplicatedSize)); - } - } catch (final NotOkBodyException e) { - throw new CloudRuntimeException("Failed to retrieve backups status for this VM via Backroll"); - } catch (final IOException | InterruptedException e) { - logger.error("Failed to check backups status due to: {}", e.getMessage()); + 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) { + public List getAllBackupsfromVirtualMachine(String vmId) throws BackrollApiException, IOException { logger.info("Trying to retrieve all backups for vm {}", vmId); List backups = new ArrayList(); - try { + BackrollTaskRequestResponse requestResponse = httpProvider.get(String.format("/virtualmachines/%s/backups", vmId), BackrollTaskRequestResponse.class); - CloseableHttpResponse response = get(String.format("/virtualmachines/%s/backups", vmId)); - String result = okBody(response); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - response.close(); - String urlToRequest = requestResponse.location.replace("/api/v1", ""); + String urlToRequest = requestResponse.location.replace("/api/v1", ""); + logger.debug(urlToRequest); + VirtualMachineBackupsResponse virtualMachineBackupsResponse = httpProvider.waitGet(urlToRequest, VirtualMachineBackupsResponse.class); - logger.debug(urlToRequest); - - result = waitGet(urlToRequest); - VirtualMachineBackupsResponse virtualMachineBackupsResponse = new ObjectMapper().readValue(result, VirtualMachineBackupsResponse.class); - - if (virtualMachineBackupsResponse.state.equals(TaskState.SUCCESS)) { - if (virtualMachineBackupsResponse.info.archives.size() > 0) { - for (BackupInfos infos : virtualMachineBackupsResponse.info.archives) { - var dateStart = new DateTime(infos.start); - backups.add(new BackrollVmBackup(infos.id, infos.name, dateStart.toDate())); - } + if (virtualMachineBackupsResponse.state.equals(TaskState.SUCCESS)) { + if (virtualMachineBackupsResponse.info.archives.size() > 0) { + for (BackupInfos infos : virtualMachineBackupsResponse.info.archives) { + var dateStart = new DateTime(infos.start); + backups.add(new BackrollVmBackup(infos.id, infos.name, dateStart.toDate())); } } - - } catch (Exception e) { - e.printStackTrace(); } + return backups; } - - private CloseableHttpResponse post(final String path, final JSONObject json) throws IOException, KeyManagementException, NoSuchAlgorithmException { - 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); - - return response; - } - - protected CloseableHttpResponse get(String path) throws IOException, KeyManagementException, NoSuchAlgorithmException { - 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); - return response; - } - - protected CloseableHttpResponse delete(String path) throws IOException, KeyManagementException, NoSuchAlgorithmException { - 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); - return response; - } - - private boolean isResponseAuthorized(final HttpResponse response) { - return response.getStatusLine().getStatusCode() == HttpStatus.SC_OK - || response.getStatusLine().getStatusCode() == HttpStatus.SC_ACCEPTED; - } - - private class NotOkBodyException extends Exception { - } - - private String okBody(final CloseableHttpResponse response) - throws ParseException, IOException, NotOkBodyException { - String result = ""; - switch (response.getStatusLine().getStatusCode()) { - case HttpStatus.SC_OK: - case HttpStatus.SC_ACCEPTED: - HttpEntity bodyEntity = response.getEntity(); - try { - result = EntityUtils.toString(bodyEntity); - EntityUtils.consumeQuietly(bodyEntity); - closeConnection(response); - return result; - } finally { - EntityUtils.consumeQuietly(bodyEntity); - } - default: - closeConnection(response); - throw new NotOkBodyException(); - } - } - - private T parse(final String json) - throws JsonMappingException, JsonProcessingException { - return new ObjectMapper().readValue(json, new TypeReference() { - }); - } - - private String waitGet(String url) - throws IOException, InterruptedException, KeyManagementException, ParseException, NoSuchAlgorithmException { - // int threshold = 30; // 5 minutes - int maxAttempts = 12; // 2 minutes - - for (int attempt = 1; attempt <= maxAttempts; attempt++) { - try { - String body = okBody(get(url)); - if (!body.contains(TaskState.PENDING)) { - return body; - } - } catch (final NotOkBodyException e) { - throw new CloudRuntimeException("An error occured with Backroll"); - } - TimeUnit.SECONDS.sleep(10); - } - - return null; - } - - private boolean isAuthenticated() throws KeyManagementException, NoSuchAlgorithmException { - boolean result = false; - - if(StringUtils.isEmpty(backrollToken)) { - return result; - } - - try { - CloseableHttpResponse response = post("/auth", null); - result = isResponseAuthorized(response); - EntityUtils.consumeQuietly(response.getEntity()); - closeConnection(response); - } catch (IOException e) { - logger.error("Failed to authenticate to Backroll due to: {}", e.getMessage()); - } - return result; - } - - private void loginIfAuthenticationFailed() throws KeyManagementException, NoSuchAlgorithmException, IOException { - if (!isAuthenticated()) { - login(appname, password); - } - } - - // private void login(final String appname, final String appsecret) throws IOException, KeyManagementException, NoSuchAlgorithmException { - // logger.info("Backroll client - start login"); - // CloseableHttpClient httpClient = createHttpClient(); - // CloseableHttpResponse response = 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", appsecret); - // params = new StringEntity(jsonBody.toString()); - // request.setEntity(params); - - // response = httpClient.execute(request); - - // try { - // HttpEntity body = response.getEntity(); - // String bodyStr = EntityUtils.toString(body); - - // LoginApiResponse loginResponse = new ObjectMapper().readValue(bodyStr, LoginApiResponse.class); - // backrollToken = loginResponse.accessToken; - // logger.debug(String.format("Backroll client - Token : %s", backrollToken)); - - // EntityUtils.consumeQuietly(response.getEntity()); - - // if (StringUtils.isEmpty(loginResponse.accessToken)) { - // throw new CloudRuntimeException("Backroll token is not available to perform API requests"); - // } - // } catch (final Exception e) { - // EntityUtils.consumeQuietly(response.getEntity()); - // if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { - // throw new CloudRuntimeException( - // "Failed to create and authenticate Backroll client, please check the settings."); - // } else { - // throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, - // "Backroll API call unauthorized, please ask your administrator to fix integration issues."); - // } - // } - - // } catch (final IOException e) { - // throw new CloudRuntimeException("Failed to authenticate Backroll API service due to:" + e.getMessage()); - // } catch (JSONException e) { - // e.printStackTrace(); - // } - // finally { - // closeConnection(response); - // } - // logger.info("Backroll client - end login"); - // } - - private void login(final String appname, final String appsecret) throws IOException, KeyManagementException, NoSuchAlgorithmException { - logger.debug("Backroll client - start login"); - - CloseableHttpClient httpClient = createHttpClient(); - CloseableHttpResponse response = 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", appsecret); - params = new StringEntity(jsonBody.toString()); - request.setEntity(params); - - response = httpClient.execute(request); - try { - String toto = okBody(response); - ObjectMapper objectMapper = new ObjectMapper(); - logger.info("BACKROLL: " + toto); - LoginApiResponse loginResponse = objectMapper.readValue(toto, 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) { - if (response.getStatusLine().getStatusCode() != HttpStatus.SC_CREATED) { - throw new CloudRuntimeException( - "Failed to create and authenticate Backroll client, please check the settings."); - } else { - throw new ServerApiException(ApiErrorCode.UNAUTHORIZED, - "Backroll API call unauthorized, please ask your administrator to fix integration issues."); - } - } - } catch (final IOException e) { - throw new CloudRuntimeException("Failed to authenticate Backroll API service due to:" + e.getMessage()); - } catch (JSONException e) { - e.printStackTrace(); - } - finally { - closeConnection(response); - } - logger.debug("Backroll client - end login"); - } - - private List getBackrollBackups(final String vmId) throws KeyManagementException, ParseException, NoSuchAlgorithmException { - try { - logger.info("start to list Backroll backups for vm {}", vmId); - - CloseableHttpResponse response = get("/virtualmachines/" + vmId + "/backups"); - String result = okBody(response); - BackrollTaskRequestResponse requestResponse = new ObjectMapper().readValue(result, BackrollTaskRequestResponse.class); - response.close(); - String urlToRequest = requestResponse.location.replace("/api/v1", ""); - - logger.debug(urlToRequest); - result = waitGet(urlToRequest); - BackrollBackupsFromVMResponse backrollBackupsFromVMResponse = new ObjectMapper().readValue(result, BackrollBackupsFromVMResponse.class); - - final List backups = new ArrayList<>(); - for (final BackrollArchiveResponse archive : backrollBackupsFromVMResponse.archives.archives) { - backups.add(new BackrollBackup(archive.name)); - logger.debug(archive.name); - } - return backups; - } catch (final NotOkBodyException e) { - throw new CloudRuntimeException("Failed to retrieve backups for this VM via Backroll"); - } catch (final IOException e) { - logger.error("Failed to list backup form vm with Backroll due to: {}", e); - } catch (InterruptedException e) { - logger.error("Backroll Error: {}", e); - } - return new ArrayList(); - } } \ No newline at end of file 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 00000000000..84c94b5fbbc --- /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/archive/BackrollBackupsFromVMResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/archive/BackrollBackupsFromVMResponse.java index 6e066dce7f6..0fa3398bda8 100644 --- 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 @@ -1,27 +1,10 @@ -// 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 { - @JsonProperty("state") - public String state; - +public class BackrollBackupsFromVMResponse extends BackrollAsyncResponse { @JsonProperty("info") public BackrollArchivesResponse archives; -} +} \ No newline at end of file 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 00000000000..a8eb0b14f58 --- /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{ + +} \ No newline at end of file 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 index f696ac5f20a..1582c0133f2 100644 --- 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 @@ -16,12 +16,11 @@ // 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 { - @JsonProperty("state") - public String state; - +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/virtualMachine/BackrollVmMetricsResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachine/BackrollVmMetricsResponse.java index 2a9fd490e7d..74bbca646ae 100644 --- 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 @@ -16,12 +16,11 @@ // 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 { - @JsonProperty("state") - public String state; - +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/virtualMachineBackups/VirtualMachineBackupsResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/metrics/virtualMachineBackups/VirtualMachineBackupsResponse.java index 5f70a91466c..11ed8267be6 100644 --- 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 @@ -16,12 +16,11 @@ // 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 { - @JsonProperty("state") - public String state; - +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/BackupPoliciesResponse.java b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/model/response/policy/BackupPoliciesResponse.java index a125144b96d..f9512520386 100644 --- 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 @@ -18,12 +18,12 @@ 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 { - @JsonProperty("state") - public String state; +public class BackupPoliciesResponse extends BackrollAsyncResponse { @JsonProperty("info") @JsonFormat(with = JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY) 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 00000000000..8f831f49e2f --- /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 00000000000..a07f9e9863d --- /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 00000000000..823e7ffb972 --- /dev/null +++ b/plugins/backup/backroll/src/main/java/org/apache/cloudstack/backup/backroll/utils/BackrollHttpClientProvider.java @@ -0,0 +1,395 @@ +// 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 final 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 BackrollHttpClientProvider(final String url, final String appname, final String password, + final boolean validateCertificate, final int timeout, + final int restoreTimeout) + throws URISyntaxException, NoSuchAlgorithmException, KeyManagementException { + this.apiURI = new URI(url); + this.appname = appname; + this.password = password; + this.validateCertificate = validateCertificate; + + this.config = RequestConfig.custom() + .setConnectTimeout(timeout * 1000) + .setConnectionRequestTimeout(timeout * 1000) + .setSocketTimeout(timeout * 1000) + .build(); + } + + private 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 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 { + result = EntityUtils.toString(bodyEntity); + 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 { + loginIfAuthenticationFailed(); + + // 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)) { + 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)) { + return body; + } + + try { + TimeUnit.SECONDS.sleep(10); + } catch (InterruptedException e) { + logger.error(e); + e.printStackTrace(); + throw new BackrollApiException(); + } + } + + return null; + } + + private 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(appname, password); + } + } + + private void login(final String appname, final String appsecret) 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", appsecret); + 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"); + this.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"); + } +}