From 22cd00ffb172f633b62db22831a0f800d8f1bc82 Mon Sep 17 00:00:00 2001 From: Wei Zhou Date: Wed, 26 Jun 2024 14:32:25 +0200 Subject: [PATCH] veeam: fix issues with PreSetup and DVS and Solidfire (#9256) * Veeam: find storage pool by path for PreSetup and VMFS * Veeam: support VMware distributed virtual switch * Veeam: sync volumes on Solidfire after backup restoration user faced the issue that backup is restored but the DATA disk is gone (ROOT disk is ok) ``` 2024-05-03 12:00:32,868 ERROR [o.a.c.b.BackupManagerImpl] (API-Job-Executor-13:ctx-aa8a1d85 job-149661 ctx-73328567) (logid:6510cf06) Failed to import VM [vmInternalName: i-169-9679-VM] from backup restoration [{"backupType":"Full","externalId":"821ca400-a5da-4282-bf3f-7c7e38a6cdb4","id":257,"uuid":"69399101-5cbd-461c-8a48-f0c70eac0b24","vmId":9679}] with hypervisor [type: VMware] due to: [Couldn't find storage pool -iqn.2010-01.com.solidfire:3p53.data-9679.221-0]. ``` On managed storage, the datastore name of DATA disk is determined by the iscsi_name of the volume. * Veeam: set correct path for DATA disks on solidfire --- .../java/com/cloud/storage/dao/VolumeDao.java | 2 + .../com/cloud/storage/dao/VolumeDaoImpl.java | 7 + .../datastore/db/PrimaryDataStoreDao.java | 2 + .../datastore/db/PrimaryDataStoreDaoImpl.java | 10 ++ .../com/cloud/hypervisor/guru/VMwareGuru.java | 138 +++++++++++++++--- 5 files changed, 136 insertions(+), 23 deletions(-) diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java index 7aec8f02c76..8e3bbf68dea 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java @@ -156,4 +156,6 @@ public interface VolumeDao extends GenericDao, StateDao listByIds(List ids); + + VolumeVO findOneByIScsiName(String iScsiName); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index be1a7e01b4e..3a3c02d83b4 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -388,6 +388,7 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol AllFieldsSearch.and("updatedCount", AllFieldsSearch.entity().getUpdatedCount(), Op.EQ); AllFieldsSearch.and("name", AllFieldsSearch.entity().getName(), Op.EQ); AllFieldsSearch.and("passphraseId", AllFieldsSearch.entity().getPassphraseId(), Op.EQ); + AllFieldsSearch.and("iScsiName", AllFieldsSearch.entity().get_iScsiName(), Op.EQ); AllFieldsSearch.done(); RootDiskStateSearch = createSearchBuilder(); @@ -840,4 +841,10 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol sc.setParameters("idIN", ids.toArray()); return listBy(sc, null); } + + public VolumeVO findOneByIScsiName(String iScsiName) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("iScsiName", iScsiName); + return findOneIncludingRemovedBy(sc); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java index e97f4638b54..cf2cad65492 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java @@ -140,6 +140,8 @@ public interface PrimaryDataStoreDao extends GenericDao { List findPoolsByStorageType(String storageType); + StoragePoolVO findPoolByZoneAndPath(long zoneId, String datastorePath); + List listStoragePoolsWithActiveVolumesByOfferingId(long offeringid); Pair, Integer> searchForIdsAndCount(Long storagePoolId, String storagePoolName, Long zoneId, diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java index 90a692489a1..ce42f9f4d16 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java @@ -659,6 +659,16 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase return listBy(sc); } + @Override + public StoragePoolVO findPoolByZoneAndPath(long zoneId, String datastorePath) { + SearchCriteria sc = AllFieldSearch.create(); + sc.setParameters("datacenterId", zoneId); + if (datastorePath != null) { + sc.addAnd("path", Op.LIKE, "%/" + datastorePath); + } + return findOneBy(sc); + } + @Override public List listStoragePoolsWithActiveVolumesByOfferingId(long offeringId) { TransactionLegacy txn = TransactionLegacy.currentTxn(); diff --git a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java index fd4d9159edd..c6a4d68bb9b 100644 --- a/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java +++ b/plugins/hypervisors/vmware/src/main/java/com/cloud/hypervisor/guru/VMwareGuru.java @@ -18,6 +18,7 @@ package com.cloud.hypervisor.guru; import static com.cloud.utils.NumbersUtil.toHumanReadableSize; +import java.net.URI; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; @@ -149,16 +150,22 @@ import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.google.gson.Gson; +import com.vmware.vim25.DistributedVirtualPort; +import com.vmware.vim25.DistributedVirtualSwitchPortConnection; +import com.vmware.vim25.DistributedVirtualSwitchPortCriteria; import com.vmware.vim25.ManagedObjectReference; +import com.vmware.vim25.VMwareDVSPortSetting; import com.vmware.vim25.VirtualDevice; import com.vmware.vim25.VirtualDeviceBackingInfo; import com.vmware.vim25.VirtualDeviceConnectInfo; import com.vmware.vim25.VirtualDisk; import com.vmware.vim25.VirtualDiskFlatVer2BackingInfo; import com.vmware.vim25.VirtualEthernetCard; +import com.vmware.vim25.VirtualEthernetCardDistributedVirtualPortBackingInfo; import com.vmware.vim25.VirtualEthernetCardNetworkBackingInfo; import com.vmware.vim25.VirtualMachineConfigSummary; import com.vmware.vim25.VirtualMachineRuntimeInfo; +import com.vmware.vim25.VmwareDistributedVirtualSwitchVlanIdSpec; public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Configurable { private static final Logger s_logger = Logger.getLogger(VMwareGuru.class); @@ -533,11 +540,29 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co /** * Get pool ID from datastore UUID */ - private Long getPoolIdFromDatastoreUuid(String datastoreUuid) { - String poolUuid = UuidUtils.normalize(datastoreUuid); - StoragePoolVO pool = _storagePoolDao.findByUuid(poolUuid); + private Long getPoolIdFromDatastoreUuid(long zoneId, String datastoreUuid) { + StoragePoolVO pool = null; + try { + String poolUuid = UuidUtils.normalize(datastoreUuid); + s_logger.info("Trying to find pool by UUID: " + poolUuid); + pool = _storagePoolDao.findByUuid(poolUuid); + } catch (CloudRuntimeException ex) { + s_logger.warn("Unable to get pool by datastore UUID: " + ex.getMessage()); + } if (pool == null) { - throw new CloudRuntimeException("Couldn't find storage pool " + poolUuid); + s_logger.info("Trying to find pool by path: " + datastoreUuid); + pool = _storagePoolDao.findPoolByZoneAndPath(zoneId, datastoreUuid); + } + if (pool == null && datastoreUuid.startsWith("-iqn") && datastoreUuid.endsWith("-0")) { + String iScsiName = "/iqn" + datastoreUuid.substring(4, datastoreUuid.length() - 2) + "/0"; + s_logger.info("Trying to find volume by iScsi name: " + iScsiName); + VolumeVO volumeVO = _volumeDao.findOneByIScsiName(iScsiName); + if (volumeVO != null) { + pool = _storagePoolDao.findById(volumeVO.getPoolId()); + } + } + if (pool == null) { + throw new CloudRuntimeException("Couldn't find storage pool " + datastoreUuid); } return pool.getId(); } @@ -545,13 +570,13 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co /** * Get pool ID for disk */ - private Long getPoolId(VirtualDisk disk) { + private Long getPoolId(long zoneId, VirtualDisk disk) { VirtualDeviceBackingInfo backing = disk.getBacking(); checkBackingInfo(backing); VirtualDiskFlatVer2BackingInfo info = (VirtualDiskFlatVer2BackingInfo)backing; String[] fileNameParts = info.getFileName().split(" "); String datastoreUuid = StringUtils.substringBetween(fileNameParts[0], "[", "]"); - return getPoolIdFromDatastoreUuid(datastoreUuid); + return getPoolIdFromDatastoreUuid(zoneId, datastoreUuid); } /** @@ -588,12 +613,12 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co /** * Get template pool ID */ - private Long getTemplatePoolId(VirtualMachineMO template) throws Exception { + private Long getTemplatePoolId(long zoneId, VirtualMachineMO template) throws Exception { VirtualMachineConfigSummary configSummary = template.getConfigSummary(); String vmPathName = configSummary.getVmPathName(); String[] pathParts = vmPathName.split(" "); String dataStoreUuid = pathParts[0].replace("[", "").replace("]", ""); - return getPoolIdFromDatastoreUuid(dataStoreUuid); + return getPoolIdFromDatastoreUuid(zoneId, dataStoreUuid); } /** @@ -643,14 +668,14 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co /** * Get template ID for VM being imported. If it is not found, it is created */ - private Long getImportingVMTemplate(List virtualDisks, DatacenterMO dcMo, String vmInternalName, Long guestOsId, long accountId, Map disksMapping, Backup backup) throws Exception { + private Long getImportingVMTemplate(List virtualDisks, long zoneId, DatacenterMO dcMo, String vmInternalName, Long guestOsId, long accountId, Map disksMapping, Backup backup) throws Exception { for (VirtualDisk disk : virtualDisks) { if (isRootDisk(disk, disksMapping, backup)) { VolumeVO volumeVO = disksMapping.get(disk); if (volumeVO == null) { String templatePath = getRootDiskTemplatePath(disk); VirtualMachineMO template = getTemplate(dcMo, templatePath); - Long poolId = getTemplatePoolId(template); + Long poolId = getTemplatePoolId(zoneId, template); Long templateSize = getTemplateSize(template, vmInternalName, disksMapping, backup); long templateId = getTemplateId(templatePath, vmInternalName, guestOsId, accountId); updateTemplateRef(templateId, poolId, templatePath, templateSize); @@ -744,7 +769,11 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co protected VolumeVO updateVolume(VirtualDisk disk, Map disksMapping, VirtualMachineMO vmToImport, Long poolId, VirtualMachine vm) throws Exception { VolumeVO volume = disksMapping.get(disk); String volumeName = getVolumeName(disk, vmToImport); - volume.setPath(volumeName); + if (volume.get_iScsiName() != null) { + volume.setPath(String.format("[%s] %s.vmdk", volumeName, volumeName)); + } else { + volume.setPath(volumeName); + } volume.setPoolId(poolId); VirtualMachineDiskInfo diskInfo = getDiskInfo(vmToImport, poolId, volumeName); volume.setChainInfo(GSON.toJson(diskInfo)); @@ -779,7 +808,7 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co String operation = ""; for (VirtualDisk disk : virtualDisks) { - Long poolId = getPoolId(disk); + Long poolId = getPoolId(zoneId, disk); Volume volume = null; if (disksMapping.containsKey(disk) && disksMapping.get(disk) != null) { volume = updateVolume(disk, disksMapping, vmToImport, poolId, vmInstanceVO); @@ -903,8 +932,13 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co Map mapping = new HashMap<>(); for (String networkName : vmNetworkNames) { NetworkVO networkVO = getGuestNetworkFromNetworkMorName(networkName, accountId, zoneId, domainId); - s_logger.debug(String.format("Mapping network name [%s] to networkVO [id: %s].", networkName, networkVO.getUuid())); - mapping.put(networkName, networkVO); + URI broadcastUri = networkVO.getBroadcastUri(); + if (broadcastUri == null) { + continue; + } + String vlan = broadcastUri.getHost(); + s_logger.debug(String.format("Mapping network vlan [%s] to networkVO [id: %s].", vlan, networkVO.getUuid())); + mapping.put(vlan, networkVO); } return mapping; } @@ -914,7 +948,7 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co */ private NetworkMO getNetworkMO(VirtualEthernetCard nic, VmwareContext context) { VirtualDeviceConnectInfo connectable = nic.getConnectable(); - VirtualEthernetCardNetworkBackingInfo info = (VirtualEthernetCardNetworkBackingInfo)nic.getBacking(); + VirtualEthernetCardNetworkBackingInfo info = (VirtualEthernetCardNetworkBackingInfo) nic.getBacking(); ManagedObjectReference networkMor = info.getNetwork(); if (networkMor == null) { throw new CloudRuntimeException("Could not find network for NIC on: " + nic.getMacAddress()); @@ -922,22 +956,80 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co return new NetworkMO(context, networkMor); } - private Pair getNicMacAddressAndNetworkName(VirtualDevice nicDevice, VmwareContext context) throws Exception { + private Pair getNicMacAddressAndVlan(VirtualDevice nicDevice, VmwareContext context) throws Exception { VirtualEthernetCard nic = (VirtualEthernetCard)nicDevice; String macAddress = nic.getMacAddress(); - NetworkMO networkMO = getNetworkMO(nic, context); - String networkName = networkMO.getName(); - return new Pair<>(macAddress, networkName); + VirtualDeviceBackingInfo backing = nic.getBacking(); + if (backing instanceof VirtualEthernetCardNetworkBackingInfo) { + VirtualEthernetCardNetworkBackingInfo backingInfo = (VirtualEthernetCardNetworkBackingInfo) backing; + String deviceName = backingInfo.getDeviceName(); + String vlan = getVlanFromDeviceName(deviceName); + return new Pair<>(macAddress, vlan); + } else if (backing instanceof VirtualEthernetCardDistributedVirtualPortBackingInfo) { + VirtualEthernetCardDistributedVirtualPortBackingInfo portInfo = (VirtualEthernetCardDistributedVirtualPortBackingInfo) backing; + DistributedVirtualSwitchPortConnection port = portInfo.getPort(); + String portKey = port.getPortKey(); + String portGroupKey = port.getPortgroupKey(); + String dvSwitchUuid = port.getSwitchUuid(); + String vlan = getVlanFromDvsPort(context, dvSwitchUuid, portGroupKey, portKey); + return new Pair<>(macAddress, vlan); + } + return new Pair<>(macAddress, null); + } + + private String getVlanFromDeviceName(String networkName) { + String prefix = "cloud.guest."; + if (!networkName.startsWith(prefix)) { + return null; + } + String nameWithoutPrefix = networkName.replace(prefix, ""); + String[] parts = nameWithoutPrefix.split("\\."); + String vlan = parts[0]; + return vlan; + } + + private String getVlanFromDvsPort(VmwareContext context, String dvSwitchUuid, String portGroupKey, String portKey) { + try { + ManagedObjectReference dvSwitchManager = context.getVimClient().getServiceContent().getDvSwitchManager(); + ManagedObjectReference dvSwitch = context.getVimClient().getService().queryDvsByUuid(dvSwitchManager, dvSwitchUuid); + + // Get all ports + DistributedVirtualSwitchPortCriteria criteria = new DistributedVirtualSwitchPortCriteria(); + criteria.setInside(true); + criteria.getPortgroupKey().add(portGroupKey); + List dvPorts = context.getVimClient().getService().fetchDVPorts(dvSwitch, criteria); + + for (DistributedVirtualPort dvPort : dvPorts) { + if (!portKey.equals(dvPort.getKey())) { + continue; + } + VMwareDVSPortSetting settings = (VMwareDVSPortSetting) dvPort.getConfig().getSetting(); + VmwareDistributedVirtualSwitchVlanIdSpec vlanId = (VmwareDistributedVirtualSwitchVlanIdSpec) settings.getVlan(); + s_logger.debug("Found port " + dvPort.getKey() + " with vlan " + vlanId.getVlanId()); + return String.valueOf(vlanId.getVlanId()); + } + } catch (Exception ex) { + s_logger.error("Got exception while get vlan from DVS port: " + ex.getMessage()); + } + return null; } private void syncVMNics(VirtualDevice[] nicDevices, DatacenterMO dcMo, Map networksMapping, VMInstanceVO vm) throws Exception { VmwareContext context = dcMo.getContext(); List allNics = nicDao.listByVmId(vm.getId()); for (VirtualDevice nicDevice : nicDevices) { - Pair pair = getNicMacAddressAndNetworkName(nicDevice, context); + Pair pair = getNicMacAddressAndVlan(nicDevice, context); String macAddress = pair.first(); - String networkName = pair.second(); - NetworkVO networkVO = networksMapping.get(networkName); + String vlanId = pair.second(); + if (vlanId == null) { + s_logger.warn(String.format("vlanId for MAC address [%s] is null", macAddress)); + continue; + } + NetworkVO networkVO = networksMapping.get(vlanId); + if (networkVO == null) { + s_logger.warn(String.format("Cannot find network for MAC address [%s] and vlanId [%s]", macAddress, vlanId)); + continue; + } NicVO nicVO = nicDao.findByNetworkIdAndMacAddressIncludingRemoved(networkVO.getId(), macAddress); if (nicVO != null) { s_logger.warn(String.format("Find NIC in DB with networkId [%s] and MAC Address [%s], so this NIC will be removed from list of unmapped NICs of VM [id: %s, name: %s].", @@ -1068,7 +1160,7 @@ public class VMwareGuru extends HypervisorGuruBase implements HypervisorGuru, Co long guestOsId = getImportingVMGuestOs(configSummary); long serviceOfferingId = getImportingVMServiceOffering(configSummary, runtimeInfo); - long templateId = getImportingVMTemplate(virtualDisks, dcMo, vmInternalName, guestOsId, accountId, disksMapping, backup); + long templateId = getImportingVMTemplate(virtualDisks, zoneId, dcMo, vmInternalName, guestOsId, accountId, disksMapping, backup); VMInstanceVO vm = getVM(vmInternalName, templateId, guestOsId, serviceOfferingId, zoneId, accountId, userId, domainId); syncVMVolumes(vm, virtualDisks, disksMapping, vmToImport, backup);