nvazquez commented on a change in pull request #3553: [WIP] [DO NOT MERGE] CloudStack Backup & Recovery Framework URL: https://github.com/apache/cloudstack/pull/3553#discussion_r357928028
########## File path: plugins/backup/veeam/src/main/java/org/apache/cloudstack/backup/VeeamBackupProvider.java ########## @@ -0,0 +1,319 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import java.net.URISyntaxException; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.backup.dao.BackupDao; +import org.apache.cloudstack.backup.veeam.VeeamClient; +import org.apache.cloudstack.backup.veeam.api.Job; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.collections.CollectionUtils; +import org.apache.log4j.Logger; + +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.vmware.VmwareDatacenter; +import com.cloud.hypervisor.vmware.VmwareDatacenterZoneMap; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterDao; +import com.cloud.hypervisor.vmware.dao.VmwareDatacenterZoneMapDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.AdapterBase; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallbackNoReturn; +import com.cloud.utils.db.TransactionStatus; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; + +public class VeeamBackupProvider extends AdapterBase implements BackupProvider, Configurable { + + private static final Logger LOG = Logger.getLogger(VeeamBackupProvider.class); + public static final String BACKUP_IDENTIFIER = "-CSBKP-"; + + public ConfigKey<String> VeeamUrl = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.url", + "http://localhost:9399/api/", + "The Veeam backup and recovery URL.", true, ConfigKey.Scope.Zone); + + private ConfigKey<String> VeeamUsername = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.username", + "administrator", + "The Veeam backup and recovery username.", true, ConfigKey.Scope.Zone); + + private ConfigKey<String> VeeamPassword = new ConfigKey<>("Advanced", String.class, + "backup.plugin.veeam.password", + "P@ssword123", + "The Veeam backup and recovery password.", true, ConfigKey.Scope.Zone); + + private ConfigKey<Boolean> VeeamValidateSSLSecurity = new ConfigKey<>("Advanced", Boolean.class, "backup.plugin.veeam.validate.ssl", "false", + "When set to true, this will validate the SSL certificate when connecting to https/ssl enabled Veeam API service.", true, ConfigKey.Scope.Zone); + + private ConfigKey<Integer> VeeamApiRequestTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.plugin.veeam.request.timeout", "300", + "The Veeam B&R API request timeout in seconds.", true, ConfigKey.Scope.Zone); + + @Inject + private VmwareDatacenterZoneMapDao vmwareDatacenterZoneMapDao; + @Inject + private VmwareDatacenterDao vmwareDatacenterDao; + @Inject + private BackupDao backupDao; + + private VeeamClient getClient(final Long zoneId) { + try { + return new VeeamClient(VeeamUrl.valueIn(zoneId), VeeamUsername.valueIn(zoneId), VeeamPassword.valueIn(zoneId), + VeeamValidateSSLSecurity.valueIn(zoneId), VeeamApiRequestTimeout.valueIn(zoneId)); + } catch (URISyntaxException e) { + throw new CloudRuntimeException("Failed to parse Veeam API URL: " + e.getMessage()); + } catch (NoSuchAlgorithmException | KeyManagementException e) { + LOG.error("Failed to build Veeam API client due to: ", e); + } + throw new CloudRuntimeException("Failed to build Veeam API client"); + } + + public List<BackupOffering> listBackupOfferings(final Long zoneId) { + List<BackupOffering> policies = new ArrayList<>(); + for (final BackupOffering policy : getClient(zoneId).listJobs()) { + if (!policy.getName().contains(BACKUP_IDENTIFIER)) { + policies.add(policy); + } + } + return policies; + } + + @Override + public boolean isBackupOffering(final Long zoneId, final String uuid) { + List<BackupOffering> policies = listBackupOfferings(zoneId); + if (CollectionUtils.isEmpty(policies)) { + return false; + } + for (final BackupOffering policy : policies) { + if (policy.getExternalId().equals(uuid)) { + return true; + } + } + return false; + } + + private VmwareDatacenter findVmwareDatacenterForVM(final VirtualMachine vm) { + if (vm == null || vm.getHypervisorType() != Hypervisor.HypervisorType.VMware) { + throw new CloudRuntimeException("The Veeam backup provider is only applicable for VMware VMs"); + } + final VmwareDatacenterZoneMap zoneMap = vmwareDatacenterZoneMapDao.findByZoneId(vm.getDataCenterId()); + if (zoneMap == null) { + throw new CloudRuntimeException("Failed to find a mapped VMware datacenter for zone id:" + vm.getDataCenterId()); + } + final VmwareDatacenter vmwareDatacenter = vmwareDatacenterDao.findById(zoneMap.getVmwareDcId()); + if (vmwareDatacenter == null) { + throw new CloudRuntimeException("Failed to find a valid VMware datacenter mapped for zone id:" + vm.getDataCenterId()); + } + return vmwareDatacenter; + } + + public boolean addVMToBackupOffering(final BackupOffering policy, final VirtualMachine vm) { + final VmwareDatacenter vmwareDatacenter = findVmwareDatacenterForVM(vm); + return getClient(vm.getDataCenterId()).removeVMFromVeeamJob(policy.getExternalId(), vm.getInstanceName(), vmwareDatacenter.getVcenterHost()); + } + + private String getGuestBackupName(final String instanceName, final String uuid) { + return String.format("%s%s%s", instanceName, BACKUP_IDENTIFIER, uuid); + } + + @Override + public boolean assignVMToBackupOffering(final VirtualMachine vm, final BackupOffering backupOffering) { + final VeeamClient client = getClient(vm.getDataCenterId()); + final Job parentJob = client.listJob(backupOffering.getExternalId()); + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), vm.getUuid()); + if (client.cloneVeeamJob(parentJob, clonedJobName)) { + for (BackupOffering job : client.listJobs()) { + if (job.getName().equals(clonedJobName)) { + final Job clonedJob = client.listJob(job.getExternalId()); + if (clonedJob.getScheduleConfigured() && !clonedJob.getScheduleEnabled()) { + client.toggleJobSchedule(clonedJob.getId()); + } + final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); + if (client.addVMToVeeamJob(job.getExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { + ((VMInstanceVO) vm).setBackupExternalId(job.getExternalId()); + return true; + } + } + } + } else { + LOG.error("Failed to clone pre-defined Veeam job (backup offering) for backup offering ID: " + backupOffering.getExternalId()); + } + return false; + } + + @Override + public boolean removeVMFromBackupOffering(final VirtualMachine vm) { + final VeeamClient client = getClient(vm.getDataCenterId()); + final VmwareDatacenter vmwareDC = findVmwareDatacenterForVM(vm); + try { + if (!client.removeVMFromVeeamJob(vm.getBackupExternalId(), vm.getInstanceName(), vmwareDC.getVcenterHost())) { + LOG.warn("Failed to remove VM from Veeam Job id: " + vm.getBackupExternalId()); + } + } catch (CloudRuntimeException e) { + LOG.debug("VM was removed from the job so could not remove again, trying to delete the veeam job now.", e); + } + + final String clonedJobName = getGuestBackupName(vm.getInstanceName(), vm.getUuid()); + if (!client.deleteJobAndBackup(clonedJobName)) { + LOG.warn("Failed to remove Veeam job and backup for job: " + clonedJobName); + throw new CloudRuntimeException("Failed to delete Veeam B&R job and backup, an operation may be in progress. Please try again after some time."); + } + for (final Backup backup: backupDao.listByVmId(null, vm.getId())) { Review comment: Can this also be moved to the BackupManagerImpl on successful operation? ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org With regards, Apache Git Services