fix for preserving nic mac and ip

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-05-07 00:54:07 +05:30
parent e8cf62a0c4
commit 9ea3364b10
9 changed files with 767 additions and 27 deletions

View File

@ -92,6 +92,8 @@ public interface AccountService {
Account getAccount(long accountId);
Account getAccountByUuid(String accountUuid);
User getActiveUser(long userId);
User getOneActiveUserForAccount(Account account);

View File

@ -127,6 +127,7 @@ import org.apache.cloudstack.veeam.api.dto.VmAction;
import org.apache.cloudstack.veeam.api.dto.VnicProfile;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@ -192,6 +193,7 @@ import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VMInstanceDetailVO;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
@ -207,6 +209,7 @@ public class ServerAdapter extends ManagerBase {
);
private static final String VM_TA_KEY = "veeam_tag";
private static final String WORKER_VM_GUEST_CPU_MODE = "host-passthrough";
private static final String RESTORE_CONFIG = "restore.config";
@Inject
AccountService accountService;
@ -512,7 +515,7 @@ public class ServerAdapter extends ManagerBase {
return template;
}
protected Vm createInstance(com.cloud.dc.DataCenter zone, Long clusterId, Account owner, Long domainId,
protected Pair<Vm, UserVm> createInstance(com.cloud.dc.DataCenter zone, Long clusterId, Account owner, Long domainId,
String accountName, Long projectId, String name, String displayName, String serviceOfferingUuid,
int cpu, int memory, String templateUuid, String userdata, ApiConstants.BootType bootType,
ApiConstants.BootMode bootMode, String affinityGroupId, String userDataId, Map<String, String> details) {
@ -582,8 +585,10 @@ public class ServerAdapter extends ManagerBase {
UserVm vm = userVmManager.createVirtualMachine(cmd);
vm = userVmManager.finalizeCreateVirtualMachine(vm.getId());
UserVmJoinVO vo = userVmJoinDao.findById(vm.getId());
return UserVmJoinVOToVmConverter.toVm(vo, this::getHostById, this::getDetailsByInstanceId,
this::listTagsByInstanceId, this::listDiskAttachmentsByInstanceId, this::listNicsByInstance, false);
Vm vmObj = UserVmJoinVOToVmConverter.toVm(vo, this::getHostById, this::getDetailsByInstanceId,
this::listTagsByInstanceId, this::listDiskAttachmentsByInstanceId, this::listNicsByInstance,
false);
return new Pair<>(vmObj, vm);
} catch (InsufficientCapacityException | ResourceUnavailableException | ResourceAllocationException | CloudRuntimeException e) {
throw new CloudRuntimeException("Failed to create VM: " + e.getMessage(), e);
}
@ -618,6 +623,63 @@ public class ServerAdapter extends ManagerBase {
return details;
}
protected void saveInstanceRestoreConfig(Vm request, UserVm vm) {
if (StringUtils.isBlank(request.getAccountId())) {
return;
}
if (accountService.getAccountByUuid(request.getAccountId()) == null) {
return;
}
String restoreConfig = OvfXmlUtil.getConfigMetadataXml(request, logger);
if (StringUtils.isBlank(restoreConfig)) {
return;
}
vmInstanceDetailsDao.addDetail(vm.getId(), RESTORE_CONFIG, restoreConfig, false);
}
protected void removeInstanceRestoreConfig(UserVm vm) {
vmInstanceDetailsDao.removeDetail(vm.getId(), RESTORE_CONFIG);
}
protected Pair<String, String> getValidatedInstanceNicDetails(final UserVmVO vm, final NetworkVO network) {
if (ObjectUtils.anyNull(vm, network)) {
return new Pair<>(null, null);
}
VMInstanceDetailVO detail = vmInstanceDetailsDao.findDetail(vm.getId(), RESTORE_CONFIG);
if (detail == null || StringUtils.isBlank(detail.getValue())) {
return new Pair<>(null, null);
}
Pair<String, String> result = OvfXmlUtil.getVmNicDetailFromStoredConfig(detail.getValue(), network.getUuid(), logger);
String mac = StringUtils.trimToNull(result.first());
String ip4Address = StringUtils.trimToNull(result.second());
NicVO nic = null;
if (mac != null) {
nic = nicDao.findByNetworkIdAndMacAddress(network.getId(), mac);
if (nic != null) {
logger.warn("MAC address {} specified in the restore config for {} is already in use by {}, ignoring it",
mac, network, nic);
mac = null;
if (!Objects.equals(ip4Address, nic.getIPv4Address())) {
nic = null;
}
}
}
if (ip4Address != null) {
if (nic == null) {
nic = nicDao.findNonPlaceHolderByIp4AddressAndNetworkId(ip4Address, network.getId());
}
if (nic != null) {
logger.warn("IPv4 address {} specified in the restore config for {} is already in use by {}, ignoring it",
ip4Address, network, nic);
mac = null;
if (Objects.equals(ip4Address, nic.getIPv4Address())) {
ip4Address = null;
}
}
}
return new Pair<>(mac, ip4Address);
}
protected static long getProvisionedSizeInGb(String sizeStr) {
long provisionedSizeInGb;
try {
@ -968,10 +1030,12 @@ public class ServerAdapter extends ManagerBase {
if (request.getTemplate() != null && StringUtils.isNotEmpty(request.getTemplate().getId())) {
templateUuid = request.getTemplate().getId();
}
return createInstance(zone, clusterId, owner, ownerDetails.first(), ownerDetails.second(),
Pair<Vm, UserVm> result = createInstance(zone, clusterId, owner, ownerDetails.first(), ownerDetails.second(),
ownerDetails.third(), name, displayName, serviceOfferingUuid, cpu, memoryMB, templateUuid,
userdata, bootOptions.first(), bootOptions.second(), request.getAffinityGroupId(),
request.getUserDataId(), request.getDetails());
saveInstanceRestoreConfig(request, result.second());
return result.first();
}
@ApiAccess(command = UpdateVMCmd.class)
@ -1175,6 +1239,7 @@ public class ServerAdapter extends ManagerBase {
}
accountService.checkAccess(CallContext.current().getCallingAccount(), SecurityChecker.AccessType.OperateEntry,
false, vmVo);
removeInstanceRestoreConfig(vmVo);
if (vmVo.getAccountId() != volumeVO.getAccountId()) {
if (VeeamControlService.InstanceRestoreAssignOwner.value()) {
assignVolumeToAccount(volumeVO, vmVo.getAccountId());
@ -1296,10 +1361,13 @@ public class ServerAdapter extends ManagerBase {
accountCannotAccessNetwork(networkVO, vmVo.getAccountId())) {
assignVmToAccount(vmVo, networkVO.getAccountId());
}
Pair<String, String> nicDetails = getValidatedInstanceNicDetails(vmVo, networkVO);
AddNicToVMCmd cmd = new AddNicToVMCmd();
ComponentContext.inject(cmd);
cmd.setVmId(vmVo.getId());
cmd.setNetworkId(networkVO.getId());
cmd.setMacAddress(nicDetails.first());
cmd.setIpaddr(nicDetails.second());
if (request.getMac() != null && StringUtils.isNotBlank(request.getMac().getAddress())) {
cmd.setMacAddress(request.getMac().getAddress());
}

View File

@ -17,6 +17,7 @@
package org.apache.cloudstack.veeam.api.converter;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.stream.Collectors;
@ -79,23 +80,34 @@ public class NicVOToNicConverter {
device.setDescription(String.format("%s device", vo.getReserver()));
device.setMac(mac);
if (ObjectUtils.anyNotNull(vo.getIPv4Address(), vo.getIPv6Address())) {
Ip ip = new Ip();
if (vo.getIPv4Address() != null) {
ip.setAddress(vo.getIPv4Address());
ip.setGateway(vo.getIPv4Gateway());
ip.setVersion("v4");
} else if (vo.getIPv6Address() != null) {
ip.setAddress(vo.getIPv6Address());
ip.setGateway(vo.getIPv6Gateway());
ip.setVersion("v6");
}
device.setIps(NamedList.of("ip", List.of(ip)));
List<Ip> ips = getIps(vo);
device.setIps(NamedList.of("ip", ips));
}
device.setHref(vm.getHref() + "/reporteddevices/" + vo.getUuid());
device.setVm(vm);
return device;
}
@NotNull
private static List<Ip> getIps(NicVO vo) {
List<Ip> ips = new ArrayList<>();
if (vo.getIPv4Address() != null) {
Ip ip = new Ip();
ip.setAddress(vo.getIPv4Address());
ip.setGateway(vo.getIPv4Gateway());
ip.setVersion("v4");
ips.add(ip);
}
if (vo.getIPv6Address() != null) {
Ip ip6 = new Ip();
ip6.setAddress(vo.getIPv6Address());
ip6.setGateway(vo.getIPv6Gateway());
ip6.setVersion("v6");
ips.add(ip6);
}
return ips;
}
public static List<Nic> toNicList(final List<NicVO> vos, final String vmUuid, final Function<Long, NetworkVO> networkResolver) {
return vos.stream()
.map(vo -> toNic(vo, vmUuid, networkResolver))

View File

@ -18,6 +18,7 @@
package org.apache.cloudstack.veeam.api.dto;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
@ -32,6 +33,7 @@ import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
@ -41,11 +43,15 @@ import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import com.cloud.api.query.vo.UserVmJoinVO;
import com.cloud.utils.Pair;
public class OvfXmlUtil {
@ -108,8 +114,8 @@ public class OvfXmlUtil {
final String bootTime = vm.getStartTime() != null ? formatDate(vm.getStartTime()) : creationDate;
// Memory: Vm.memory is bytes (string)
final long memBytes = parseLong(vm.getMemory(), 1024L * 1024L * 1024L);
final long memMb = Math.max(128, memBytes / (1024L * 1024L));
final long memBytes = parseLong(vm.getMemory(), MemoryAllocationUnit.Gigabytes.getBytesMultiplier());
final long memMb = Math.max(128, memBytes / MemoryAllocationUnit.Megabytes.getBytesMultiplier());
// CPU: topology cores/sockets/threads. We default sockets=1 threads=1.
final int vcpu = Math.max(1, Integer.parseInt(vm.getCpu().getTopology().getCores()));
@ -126,6 +132,8 @@ public class OvfXmlUtil {
// Snapshot id (stable per VM id)
final String snapshotId = UUID.nameUUIDFromBytes(("ovf-snap-" + vmId).getBytes(StandardCharsets.UTF_8)).toString();
final List<Nic> nics = nics(vm);
final StringBuilder sb = new StringBuilder(16_384);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<ovf:Envelope ");
@ -174,7 +182,7 @@ public class OvfXmlUtil {
if (da == null || da.getDisk() == null || StringUtils.isBlank(da.getDisk().getId())) {
continue;
}
final org.apache.cloudstack.veeam.api.dto.Disk d = da.getDisk();
final Disk d = da.getDisk();
final String diskId = d.getId();
final String storageDomainId = firstStorageDomainId(d);
final String href = storageDomainId + "/" + diskId;
@ -247,6 +255,26 @@ public class OvfXmlUtil {
if (vo.getAffinityGroupId() != null) {
sb.append("<AffinityGroupId>").append(escapeText(vo.getAffinityGroupUuid())).append("</AffinityGroupId>");
}
if (vm.getNics() != null && CollectionUtils.isNotEmpty(vm.getNics().getItems())) {
sb.append("<Nics>");
for (Nic nic : nics(vm)) {
if (nic == null || StringUtils.isBlank(nic.getId())) {
continue;
}
String networkId = nicNetworkId(nic);
if (networkId == null) {
continue;
}
sb.append("<Nic>");
sb.append("<Id>").append(escapeText(nic.getId())).append("</Id>");
sb.append("<NetworkId>").append(escapeText(networkId)).append("</NetworkId>");
sb.append("<MACAddress>").append(escapeText(nicMac(nic))).append("</MACAddress>");
sb.append("<Ip4Address>").append(escapeText(nicIp(nic, "v4"))).append("</Ip4Address>");
sb.append("<Ip6Address>").append(escapeText(nicIp(nic, "v6"))).append("</Ip6Address>");
sb.append("</Nic>");
}
sb.append("</Nics>");
}
sb.append("</CloudStack>");
sb.append("</Section>");
}
@ -349,7 +377,7 @@ public class OvfXmlUtil {
if (da == null || da.getDisk() == null || StringUtils.isBlank(da.getDisk().getId())) {
continue;
}
final org.apache.cloudstack.veeam.api.dto.Disk d = da.getDisk();
final Disk d = da.getDisk();
final String diskId = d.getId();
final String storageDomainId = firstStorageDomainId(d);
final String href = storageDomainId + "/" + diskId;
@ -380,16 +408,17 @@ public class OvfXmlUtil {
// NICs as Items
int nicSlot = 0;
for (Nic nic : nics(vm)) {
for (Nic nic : nics) {
if (nic == null) {
continue;
}
final String nicId = firstNonBlank(nic.getId(), UUID.nameUUIDFromBytes(("nic-" + vmId + "-" + nicSlot).getBytes(StandardCharsets.UTF_8)).toString());
final String nicName = firstNonBlank(nic.getName(), "nic" + (nicSlot + 1));
final String mac = nic.getMac() != null ? defaultString(nic.getMac().getAddress()) : "";
final String elementName = nic.getVnicProfile() != null ? defaultString(nic.getVnicProfile().getId()) : nicName;
sb.append("<Item>");
sb.append("<rasd:Caption>Ethernet adapter on [No Network]</rasd:Caption>");
sb.append("<rasd:Caption>Ethernet adapter - ").append(nic.getName()).append("</rasd:Caption>");
sb.append("<rasd:InstanceId>").append(escapeText(nicId)).append("</rasd:InstanceId>");
sb.append("<rasd:ResourceType>10</rasd:ResourceType>");
sb.append("<rasd:OtherResourceType></rasd:OtherResourceType>");
@ -397,7 +426,7 @@ public class OvfXmlUtil {
sb.append("<rasd:Connection>").append(escapeText(defaultString(inferNetworkName(nic)))).append("</rasd:Connection>");
sb.append("<rasd:Linked>").append(escapeText(booleanString(nic.getLinked(), "true"))).append("</rasd:Linked>");
sb.append("<rasd:Name>").append(escapeText(nicName)).append("</rasd:Name>");
sb.append("<rasd:ElementName>").append(escapeText(nicName)).append("</rasd:ElementName>");
sb.append("<rasd:ElementName>").append(escapeText(elementName)).append("</rasd:ElementName>");
sb.append("<rasd:MACAddress>").append(escapeText(mac)).append("</rasd:MACAddress>");
sb.append("<rasd:speed>10000</rasd:speed>");
sb.append("<Type>interface</Type>");
@ -441,16 +470,153 @@ public class OvfXmlUtil {
return sb.toString();
}
public static void updateFromConfiguration(Vm vm) {
protected static String getVmConfigurationData(Vm vm) {
Vm.Initialization initialization = vm.getInitialization();
if (initialization == null) {
return;
return null;
}
Vm.Initialization.Configuration configuration = vm.getInitialization().getConfiguration();
if (configuration == null) {
return null;
}
return configuration.getData();
}
public static void updateFromConfiguration(Vm vm) {
String configurationData = getVmConfigurationData(vm);
OvfXmlUtil.updateFromXml(vm, configurationData);
}
public static String getConfigMetadataXml(Vm vm, Logger logger) {
String configurationData = getVmConfigurationData(vm);
if (StringUtils.isBlank(configurationData)) {
return null;
}
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new ByteArrayInputStream(configurationData.getBytes(StandardCharsets.UTF_8)));
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
// Persist only the CloudStack metadata section from the source OVF.
Node metadataSection = (Node) xpath.evaluate(
"//*[local-name()='Section' and @*[local-name()='type']='ovf:CloudStackMetadata_Type']",
doc,
XPathConstants.NODE
);
if (metadataSection == null) {
return null;
}
// Wrap section payload so it remains standalone XML with namespace declarations.
StringBuilder sb = new StringBuilder(2048);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<Sections")
.append(" xmlns:rasd=\"").append(NS_RASD).append("\"")
.append(" xmlns:ovf=\"").append(NS_OVF).append("\"")
.append(" xmlns:xsi=\"").append(NS_XSI).append("\"")
.append(">");
sb.append(nodeToString(metadataSection));
sb.append("</Sections>");
return sb.toString();
} catch (ParserConfigurationException | XPathExpressionException | IOException | SAXException e) {
logger.error("Failed to parse VM configuration data for VM id {}: {}", vm.getId(), e.getMessage());
return null;
}
}
public static Pair<String, String> getVmNicDetailFromStoredConfig(String xmlConfig, String networkId, Logger logger) {
if (StringUtils.isAnyBlank(xmlConfig, networkId)) {
return new Pair<>(null, null);
}
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
dbf.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(new ByteArrayInputStream(xmlConfig.getBytes(StandardCharsets.UTF_8)));
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
// Preferred format: CloudStack metadata section with CloudStack/Nics/Nic records.
NodeList nicNodes = (NodeList) xpath.evaluate(
"//*[local-name()='Section' and @*[local-name()='type']='ovf:CloudStackMetadata_Type']/*[local-name()='CloudStack']/*[local-name()='Nics']/*[local-name()='Nic']",
doc,
XPathConstants.NODESET
);
if (nicNodes != null && nicNodes.getLength() > 0) {
for (int i = 0; i < nicNodes.getLength(); i++) {
Node nicNode = nicNodes.item(i);
String nicNetworkId = xpathString(xpath, nicNode, "./*[local-name()='NetworkId']/text()");
if (StringUtils.equals(nicNetworkId, networkId)) {
return new Pair<>(
xpathString(xpath, nicNode, "./*[local-name()='MACAddress' or local-name()='MACAddress']/text()"),
xpathString(xpath, nicNode, "./*[local-name()='Ip4Address' or local-name()='Ip4Address']/text()")
);
}
}
}
} catch (ParserConfigurationException | XPathExpressionException | IOException | SAXException e) {
logger.error("Failed to parse VM configuration XML to retrieve details for NIC for network ID {}: {}",
networkId, e.getMessage());
}
return new Pair<>(null, null);
}
private static String nodeToString(Node node) {
try {
// Implementation using string manipulation
StringBuilder sb = new StringBuilder();
serializeNodeToString(node, sb);
return sb.toString();
} catch (Exception e) {
return "";
}
}
private static void serializeNodeToString(Node node, StringBuilder sb) {
if (node == null) {
return;
}
OvfXmlUtil.updateFromXml(vm, configuration.getData());
short nodeType = node.getNodeType();
switch (nodeType) {
case Node.ELEMENT_NODE:
sb.append("<").append(node.getNodeName());
NamedNodeMap attrs = node.getAttributes();
if (attrs != null) {
for (int i = 0; i < attrs.getLength(); i++) {
Node attr = attrs.item(i);
sb.append(" ").append(attr.getNodeName()).append("=\"")
.append(escapeAttr(attr.getNodeValue())).append("\"");
}
}
sb.append(">");
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
serializeNodeToString(children.item(i), sb);
}
sb.append("</").append(node.getNodeName()).append(">");
break;
case Node.TEXT_NODE:
String text = node.getNodeValue();
if (StringUtils.isNotBlank(text)) {
sb.append(escapeText(text));
}
break;
case Node.CDATA_SECTION_NODE:
sb.append("<![CDATA[").append(node.getNodeValue()).append("]]>");
break;
default:
break;
}
}
protected static void updateFromXml(Vm vm, String ovfXml) {
@ -663,6 +829,40 @@ public class OvfXmlUtil {
return vm.getNics().getItems();
}
private static String nicNetworkId(Nic nic) {
if (nic == null || nic.getVnicProfile() == null || StringUtils.isEmpty(nic.getVnicProfile().getId())) {
return null;
}
return nic.getVnicProfile().getId();
}
private static ReportedDevice getNicReportedDevice(Nic nic) {
if (nic == null || nic.getReportedDevices() == null || CollectionUtils.isEmpty(nic.getReportedDevices().getItems())) {
return null;
}
return nic.getReportedDevices().getItems().get(0);
}
private static String nicMac(Nic nic) {
if (nic == null || nic.getMac() == null || StringUtils.isBlank(nic.getMac().getAddress())) {
return "";
}
return nic.getMac().getAddress();
}
private static String nicIp(Nic nic, String version) {
ReportedDevice device = getNicReportedDevice(nic);
if (device == null || device.getIps() == null || CollectionUtils.isEmpty(device.getIps().getItems())) {
return "";
}
for (Ip ip : device.getIps().getItems()) {
if (version.equalsIgnoreCase(ip.getVersion())) {
return ip.getAddress();
}
}
return "";
}
private static String inferOsDescription(Vm vm) {
if (vm.getOs() == null) {
return "other";
@ -740,7 +940,7 @@ public class OvfXmlUtil {
if (vm.getMemoryPolicy() == null || vm.getMemoryPolicy().getBallooning() == null) {
return "true";
}
return "true".equalsIgnoreCase(vm.getMemoryPolicy().getBallooning()) ? "true" : "false";
return Boolean.toString("true".equalsIgnoreCase(vm.getMemoryPolicy().getBallooning()));
}
private static int mapNicResourceSubType(String iface) {
@ -795,7 +995,7 @@ public class OvfXmlUtil {
if (bytes <= 0) {
return 0;
}
final long gib = 1024L * 1024L * 1024L;
final long gib = MemoryAllocationUnit.Gigabytes.getBytesMultiplier();
return (bytes + gib - 1) / gib;
}

View File

@ -48,14 +48,17 @@ import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.veeam.api.dto.DataCenter;
import org.apache.cloudstack.veeam.api.dto.Disk;
import org.apache.cloudstack.veeam.api.dto.OvfXmlUtil;
import org.apache.cloudstack.veeam.api.dto.Tag;
import org.apache.cloudstack.veeam.api.dto.Vm;
import org.apache.logging.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
@ -96,10 +99,14 @@ import com.cloud.user.User;
import com.cloud.utils.Pair;
import com.cloud.utils.Ternary;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.vm.NicVO;
import com.cloud.vm.UserVmManager;
import com.cloud.vm.VMInstanceDetailVO;
import com.cloud.vm.UserVmVO;
import com.cloud.vm.VmDetailConstants;
import com.cloud.vm.dao.NicDao;
import com.cloud.vm.dao.UserVmDao;
import com.cloud.vm.dao.VMInstanceDetailsDao;
import com.cloud.vm.snapshot.dao.VMSnapshotDao;
@RunWith(MockitoJUnitRunner.class)
@ -117,6 +124,7 @@ public class ServerAdapterTest {
@Mock NetworkDao networkDao;
@Mock UserVmDao userVmDao;
@Mock UserVmJoinDao userVmJoinDao;
@Mock VMInstanceDetailsDao vmInstanceDetailsDao;
@Mock VolumeDao volumeDao;
@Mock VolumeJoinDao volumeJoinDao;
// kept minimal: only mocks used directly by tests
@ -126,6 +134,7 @@ public class ServerAdapterTest {
@Mock ServiceOfferingDao serviceOfferingDao;
@Mock VMTemplateDao templateDao;
@Mock UserVmManager userVmManager;
@Mock NicDao nicDao;
@Mock AsyncJobDao asyncJobDao;
@Mock AsyncJobJoinDao asyncJobJoinDao;
@Mock VMSnapshotDao vmSnapshotDao;
@ -266,6 +275,185 @@ public class ServerAdapterTest {
assertEquals("3000", result.get(VmDetailConstants.CPU_SPEED));
}
@Test
public void testGetValidatedInstanceNicDetails_NullVm_ReturnsNullPair() {
NetworkVO network = mock(NetworkVO.class);
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(null, network);
assertNull(result.first());
assertNull(result.second());
}
@Test
public void testGetValidatedInstanceNicDetails_NullNetwork_ReturnsNullPair() {
UserVmVO vm = mock(UserVmVO.class);
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, null);
assertNull(result.first());
assertNull(result.second());
}
@Test
public void testGetValidatedInstanceNicDetails_NoRestoreConfig_ReturnsNullPair() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(10L);
when(vmInstanceDetailsDao.findDetail(10L, "restore.config")).thenReturn(null);
NetworkVO network = mock(NetworkVO.class);
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, network);
assertNull(result.first());
assertNull(result.second());
}
@Test
public void testGetValidatedInstanceNicDetails_BlankRestoreConfig_ReturnsNullPair() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(10L);
VMInstanceDetailVO detail = mock(VMInstanceDetailVO.class);
when(detail.getValue()).thenReturn(" ");
when(vmInstanceDetailsDao.findDetail(10L, "restore.config")).thenReturn(detail);
NetworkVO network = mock(NetworkVO.class);
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, network);
assertNull(result.first());
assertNull(result.second());
}
@Test
public void testGetValidatedInstanceNicDetails_BlankMacAndIpFromConfig_ReturnsNullPair() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(11L);
VMInstanceDetailVO detail = mock(VMInstanceDetailVO.class);
when(detail.getValue()).thenReturn("restore-xml");
when(vmInstanceDetailsDao.findDetail(11L, "restore.config")).thenReturn(detail);
NetworkVO network = mock(NetworkVO.class);
when(network.getUuid()).thenReturn("network-uuid");
try (MockedStatic<OvfXmlUtil> ovfXmlUtil = Mockito.mockStatic(OvfXmlUtil.class)) {
ovfXmlUtil.when(() -> OvfXmlUtil.getVmNicDetailFromStoredConfig(eq("restore-xml"), eq("network-uuid"), any(Logger.class)))
.thenReturn(new Pair<>(" ", "\t"));
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, network);
assertNull(result.first());
assertNull(result.second());
}
}
@Test
public void testGetValidatedInstanceNicDetails_NoConflicts_ReturnsMacAndIp() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(20L);
VMInstanceDetailVO detail = mock(VMInstanceDetailVO.class);
when(detail.getValue()).thenReturn("restore-xml");
when(vmInstanceDetailsDao.findDetail(20L, "restore.config")).thenReturn(detail);
NetworkVO network = mock(NetworkVO.class);
when(network.getId()).thenReturn(30L);
when(network.getUuid()).thenReturn("network-uuid");
when(nicDao.findByNetworkIdAndMacAddress(30L, "02:00:00:00:00:01")).thenReturn(null);
when(nicDao.findNonPlaceHolderByIp4AddressAndNetworkId("10.0.0.10", 30L)).thenReturn(null);
try (MockedStatic<OvfXmlUtil> ovfXmlUtil = Mockito.mockStatic(OvfXmlUtil.class)) {
ovfXmlUtil.when(() -> OvfXmlUtil.getVmNicDetailFromStoredConfig(eq("restore-xml"), eq("network-uuid"), any(Logger.class)))
.thenReturn(new Pair<>("02:00:00:00:00:01", "10.0.0.10"));
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, network);
assertEquals("02:00:00:00:00:01", result.first());
assertEquals("10.0.0.10", result.second());
}
}
@Test
public void testGetValidatedInstanceNicDetails_MacConflictWithSameIp_ClearsBoth() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(21L);
VMInstanceDetailVO detail = mock(VMInstanceDetailVO.class);
when(detail.getValue()).thenReturn("restore-xml");
when(vmInstanceDetailsDao.findDetail(21L, "restore.config")).thenReturn(detail);
NetworkVO network = mock(NetworkVO.class);
when(network.getId()).thenReturn(31L);
when(network.getUuid()).thenReturn("network-uuid");
NicVO conflictingNic = mock(NicVO.class);
when(conflictingNic.getIPv4Address()).thenReturn("10.0.0.11");
when(nicDao.findByNetworkIdAndMacAddress(31L, "02:00:00:00:00:02")).thenReturn(conflictingNic);
try (MockedStatic<OvfXmlUtil> ovfXmlUtil = Mockito.mockStatic(OvfXmlUtil.class)) {
ovfXmlUtil.when(() -> OvfXmlUtil.getVmNicDetailFromStoredConfig(eq("restore-xml"), eq("network-uuid"), any(Logger.class)))
.thenReturn(new Pair<>("02:00:00:00:00:02", "10.0.0.11"));
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, network);
assertNull(result.first());
assertNull(result.second());
}
}
@Test
public void testGetValidatedInstanceNicDetails_MacConflictWithDifferentIp_ClearsOnlyMac() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(22L);
VMInstanceDetailVO detail = mock(VMInstanceDetailVO.class);
when(detail.getValue()).thenReturn("restore-xml");
when(vmInstanceDetailsDao.findDetail(22L, "restore.config")).thenReturn(detail);
NetworkVO network = mock(NetworkVO.class);
when(network.getId()).thenReturn(32L);
when(network.getUuid()).thenReturn("network-uuid");
NicVO conflictingNic = mock(NicVO.class);
when(conflictingNic.getIPv4Address()).thenReturn("10.0.0.99");
when(nicDao.findByNetworkIdAndMacAddress(32L, "02:00:00:00:00:03")).thenReturn(conflictingNic);
when(nicDao.findNonPlaceHolderByIp4AddressAndNetworkId("10.0.0.12", 32L)).thenReturn(null);
try (MockedStatic<OvfXmlUtil> ovfXmlUtil = Mockito.mockStatic(OvfXmlUtil.class)) {
ovfXmlUtil.when(() -> OvfXmlUtil.getVmNicDetailFromStoredConfig(eq("restore-xml"), eq("network-uuid"), any(Logger.class)))
.thenReturn(new Pair<>("02:00:00:00:00:03", "10.0.0.12"));
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, network);
assertNull(result.first());
assertEquals("10.0.0.12", result.second());
}
}
@Test
public void testGetValidatedInstanceNicDetails_IpConflict_ClearsIpAndMac() {
UserVmVO vm = mock(UserVmVO.class);
when(vm.getId()).thenReturn(23L);
VMInstanceDetailVO detail = mock(VMInstanceDetailVO.class);
when(detail.getValue()).thenReturn("restore-xml");
when(vmInstanceDetailsDao.findDetail(23L, "restore.config")).thenReturn(detail);
NetworkVO network = mock(NetworkVO.class);
when(network.getId()).thenReturn(33L);
when(network.getUuid()).thenReturn("network-uuid");
when(nicDao.findByNetworkIdAndMacAddress(33L, "02:00:00:00:00:04")).thenReturn(null);
NicVO conflictingIpNic = mock(NicVO.class);
when(conflictingIpNic.getIPv4Address()).thenReturn("10.0.0.13");
when(nicDao.findNonPlaceHolderByIp4AddressAndNetworkId("10.0.0.13", 33L)).thenReturn(conflictingIpNic);
try (MockedStatic<OvfXmlUtil> ovfXmlUtil = Mockito.mockStatic(OvfXmlUtil.class)) {
ovfXmlUtil.when(() -> OvfXmlUtil.getVmNicDetailFromStoredConfig(eq("restore-xml"), eq("network-uuid"), any(Logger.class)))
.thenReturn(new Pair<>("02:00:00:00:00:04", "10.0.0.13"));
Pair<String, String> result = serverAdapter.getValidatedInstanceNicDetails(vm, network);
assertNull(result.first());
assertNull(result.second());
}
}
@Test
public void testGetDummyTags_ContainsRootTag() {

View File

@ -18,11 +18,21 @@
package org.apache.cloudstack.veeam.api.dto;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import org.apache.logging.log4j.Logger;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import com.cloud.utils.Pair;
@RunWith(MockitoJUnitRunner.class)
public class OvfXmlUtilTest {
@ -42,4 +52,29 @@ public class OvfXmlUtilTest {
assertEquals("1", vm.getCpu().getTopology().getCores());
assertEquals("1", vm.getCpu().getTopology().getThreads());
}
@Test
public void test_restoreConfig_parse() throws Exception {
Vm vm = mock(Vm.class);
Vm.Initialization initialization = mock(Vm.Initialization.class);
Vm.Initialization.Configuration configMock = mock(Vm.Initialization.Configuration.class);
when(initialization.getConfiguration()).thenReturn(configMock);
when(vm.getInitialization()).thenReturn(initialization);
String ovfXml;
try (InputStream is = getClass().getClassLoader().getResourceAsStream("test-ovf.xml")) {
assertNotNull(is);
ovfXml = new String(is.readAllBytes(), StandardCharsets.UTF_8);
}
when(configMock.getData()).thenReturn(ovfXml);
String instanceConfig = OvfXmlUtil.getConfigMetadataXml(vm, mock(Logger.class));
assertNotNull(instanceConfig);
assertTrue(instanceConfig.contains("ovf:CloudStackMetadata_Type"));
assertTrue(instanceConfig.contains("<NetworkId>6965c1cf-8d44-4622-82e2-4dbbe4a58355</NetworkId>"));
Pair<String, String> result = OvfXmlUtil.getVmNicDetailFromStoredConfig(instanceConfig, "6965c1cf-8d44-4622-82e2-4dbbe4a58355", mock(Logger.class));
assertNotNull(result);
assertEquals("1e:01:50:00:00:fd", result.first());
assertEquals("10.1.1.103", result.second());
}
}

View File

@ -0,0 +1,225 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<ovf:Envelope
xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1/"
xmlns:rasd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData"
xmlns:vssd="http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ovf:version="4.4.0.0">
<References>
<File ovf:href="6af2dd24-1af2-3610-b7b8-de38c98ec958/2efdaae2-6c38-4ac8-ac75-562cf47adba1" ovf:id="2efdaae2-6c38-4ac8-ac75-562cf47adba1" ovf:size="4096" ovf:description="Active VM" ovf:disk_storage_type="IMAGE" ovf:cinder_volume_type=""></File>
</References>
<NetworkSection>
<Info>List of networks</Info>
<Network ovf:name="Network-85990692-f734-4695-afe4-aca34a21f459">
<Description></Description>
</Network>
</NetworkSection>
<Section xsi:type="ovf:DiskSection_Type">
<Info>List of Virtual Disks</Info>
<Disk ovf:diskId="2efdaae2-6c38-4ac8-ac75-562cf47adba1" ovf:size="1" ovf:actual_size="1" ovf:vm_snapshot_id="dfa11f00-f92d-353b-8dbe-a653a0a4315b" ovf:parentRef="" ovf:fileRef="6af2dd24-1af2-3610-b7b8-de38c98ec958/2efdaae2-6c38-4ac8-ac75-562cf47adba1" ovf:format="http://www.vmware.com/specifications/vmdk.html#sparse" ovf:volume-format="COW" ovf:volume-type="Sparse" ovf:disk-interface="VirtIO_SCSI" ovf:read-only="false" ovf:shareable="false" ovf:boot="true" ovf:pass-discard="false" ovf:incremental-backup="false" ovf:disk-alias="ROOT-27" ovf:disk-description="ROOT-27" ovf:wipe-after-delete="false"></Disk>
</Section>
<Section xsi:type="ovf:CloudStackMetadata_Type">
<Info>CloudStack specific metadata</Info>
<CloudStack>
<AccountId>048531b6-4386-11f1-930c-525400580bd0</AccountId>
<DomainId>e0afbb58-4385-11f1-930c-525400580bd0</DomainId>
<ProjectId></ProjectId>
<ServiceOfferingId>ac3edee0-e2fb-4cd1-b6d4-d3b4555203ba</ServiceOfferingId>
<DataDiskOfferingIdMap>
<Entry>
<DiskId>2efdaae2-6c38-4ac8-ac75-562cf47adba1</DiskId>
<OfferingId>4de70249-b89d-4f08-9ca2-05ddb4ad1b2a</OfferingId>
</Entry>
</DataDiskOfferingIdMap>
<Details>
<Detail>
<Key>keyboard</Key>
<Value>us</Value>
</Detail>
<Detail>
<Key>skip.force.disk.controller</Key>
<Value>true</Value>
</Detail>
<Detail>
<Key>nicAdapter</Key>
<Value>virtio</Value>
</Detail>
<Detail>
<Key>rootDiskController</Key>
<Value>osdefault</Value>
</Detail>
</Details>
<Nics>
<Nic>
<Id>85990692-f734-4695-afe4-aca34a21f459</Id>
<NetworkId>6aff2178-a323-4148-a592-edbd47b93229</NetworkId>
<MACAddress>02:01:00:cf:00:05</MACAddress>
<Ip4Address>10.1.1.40</Ip4Address>
<Ip6Address></Ip6Address>
</Nic>
</Nics>
</CloudStack>
</Section>
<Content ovf:id="out" xsi:type="ovf:VirtualSystem_Type">
<Name>test-vm1</Name>
<Description>test-vm1</Description>
<Comment></Comment>
<CreationDate>2026/05/06 18:29:58</CreationDate>
<ExportDate>2026/05/06 18:47:55</ExportDate>
<DeleteProtected>false</DeleteProtected>
<SsoMethod>guest_agent</SsoMethod>
<IsSmartcardEnabled>false</IsSmartcardEnabled>
<NumOfIoThreads>1</NumOfIoThreads>
<TimeZone>Etc/GMT</TimeZone>
<default_boot_sequence>0</default_boot_sequence>
<Generation>11</Generation>
<ClusterCompatibilityVersion>4.8</ClusterCompatibilityVersion>
<VmType>1</VmType>
<ResumeBehavior>AUTO_RESUME</ResumeBehavior>
<MinAllocatedMem>512</MinAllocatedMem>
<IsStateless>false</IsStateless>
<IsRunAndPause>false</IsRunAndPause>
<AutoStartup>false</AutoStartup>
<Priority>0</Priority>
<CreatedByUserId>048531b6-4386-11f1-930c-525400580bd0</CreatedByUserId>
<MigrationSupport>0</MigrationSupport>
<IsBootMenuEnabled>false</IsBootMenuEnabled>
<IsSpiceFileTransferEnabled>true</IsSpiceFileTransferEnabled>
<IsSpiceCopyPasteEnabled>true</IsSpiceCopyPasteEnabled>
<AllowConsoleReconnect>false</AllowConsoleReconnect>
<ConsoleDisconnectAction>LOCK_SCREEN</ConsoleDisconnectAction>
<ConsoleDisconnectActionDelay>0</ConsoleDisconnectActionDelay>
<CustomEmulatedMachine></CustomEmulatedMachine>
<BiosType>3</BiosType>
<CustomCpuName></CustomCpuName>
<PredefinedProperties></PredefinedProperties>
<UserDefinedProperties></UserDefinedProperties>
<MaxMemorySizeMb>512</MaxMemorySizeMb>
<MultiQueuesEnabled>true</MultiQueuesEnabled>
<VirtioScsiMultiQueuesEnabled>false</VirtioScsiMultiQueuesEnabled>
<UseHostCpu>false</UseHostCpu>
<BalloonEnabled>false</BalloonEnabled>
<CpuPinningPolicy>0</CpuPinningPolicy>
<ClusterName></ClusterName>
<TemplateId>8c367ed8-03d9-4c02-a4a4-f20b796f1b56</TemplateId>
<TemplateName>8c367ed8-03d9-4c02-a4a4-f20b796f1b56</TemplateName>
<IsInitilized>true</IsInitilized>
<Origin>3</Origin>
<quota_id>00000000-0000-0000-0000-000000000000</quota_id>
<DefaultDisplayType>2</DefaultDisplayType>
<TrustedService>false</TrustedService>
<OriginalTemplateId>8c367ed8-03d9-4c02-a4a4-f20b796f1b56</OriginalTemplateId>
<OriginalTemplateName>8c367ed8-03d9-4c02-a4a4-f20b796f1b56</OriginalTemplateName>
<UseLatestVersion>false</UseLatestVersion>
<StopTime>2026/05/06 18:29:58</StopTime>
<BootTime>2026/05/06 18:29:58</BootTime>
<Downtime>0</Downtime>
<Section ovf:id="649d4d91-7cad-4541-a0a4-3930472f95ec" ovf:required="false" xsi:type="ovf:OperatingSystemSection_Type">
<Info>Guest Operating System</Info>
<Description>linux</Description>
</Section>
<Section xsi:type="ovf:VirtualHardwareSection_Type">
<Info>1 CPU, 512 Memory</Info>
<System>
<vssd:VirtualSystemType>ENGINE 4.4.0.0</vssd:VirtualSystemType>
</System>
<Item>
<rasd:Caption>1 virtual cpu</rasd:Caption>
<rasd:Description>Number of virtual CPU</rasd:Description>
<rasd:InstanceId>1</rasd:InstanceId>
<rasd:ResourceType>3</rasd:ResourceType>
<rasd:num_of_sockets>1</rasd:num_of_sockets>
<rasd:cpu_per_socket>1</rasd:cpu_per_socket>
<rasd:threads_per_cpu>1</rasd:threads_per_cpu>
<rasd:max_num_of_vcpus>1</rasd:max_num_of_vcpus>
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Caption>512 MB of memory</rasd:Caption>
<rasd:Description>Memory Size</rasd:Description>
<rasd:InstanceId>2</rasd:InstanceId>
<rasd:ResourceType>4</rasd:ResourceType>
<rasd:AllocationUnits>byte * 2^20</rasd:AllocationUnits>
<rasd:VirtualQuantity>512</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Caption>ROOT-27</rasd:Caption>
<rasd:InstanceId>2efdaae2-6c38-4ac8-ac75-562cf47adba1</rasd:InstanceId>
<rasd:ResourceType>17</rasd:ResourceType>
<rasd:HostResource>6af2dd24-1af2-3610-b7b8-de38c98ec958/2efdaae2-6c38-4ac8-ac75-562cf47adba1</rasd:HostResource>
<rasd:Parent>00000000-0000-0000-0000-000000000000</rasd:Parent>
<rasd:Template>8c367ed8-03d9-4c02-a4a4-f20b796f1b56</rasd:Template>
<rasd:ApplicationList></rasd:ApplicationList>
<rasd:StorageId>6af2dd24-1af2-3610-b7b8-de38c98ec958</rasd:StorageId>
<rasd:StoragePoolId>00000000-0000-0000-0000-000000000000</rasd:StoragePoolId>
<rasd:CreationDate>2026/05/06 18:29:58</rasd:CreationDate>
<rasd:LastModified>2026/05/06 18:47:55</rasd:LastModified>
<rasd:last_modified_date>2026/05/06 18:47:55</rasd:last_modified_date>
<Type>disk</Type>
<Device>disk</Device>
<rasd:Address>{type=drive, bus=0, controller=0, target=0, unit=0}</rasd:Address>
<BootOrder>1</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>ua-6af2dd24-1af2-3610-b7b8-de38c98ec958/2efdaae2-6c38-4ac8-ac75-562cf47adba1</Alias>
</Item>
<Item>
<rasd:Caption>Ethernet adapter - ExternalGuestNetworkGuru</rasd:Caption>
<rasd:InstanceId>85990692-f734-4695-afe4-aca34a21f459</rasd:InstanceId>
<rasd:ResourceType>10</rasd:ResourceType>
<rasd:OtherResourceType></rasd:OtherResourceType>
<rasd:ResourceSubType>3</rasd:ResourceSubType>
<rasd:Connection>Network-85990692-f734-4695-afe4-aca34a21f459</rasd:Connection>
<rasd:Linked>true</rasd:Linked>
<rasd:Name>ExternalGuestNetworkGuru</rasd:Name>
<rasd:ElementName>6aff2178-a323-4148-a592-edbd47b93229</rasd:ElementName>
<rasd:MACAddress>02:01:00:cf:00:05</rasd:MACAddress>
<rasd:speed>10000</rasd:speed>
<Type>interface</Type>
<Device>bridge</Device>
<rasd:Address>{type=pci, slot=0x00, bus=0x01, domain=0x0000, function=0x0}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>ua-85990692-f734-4695-afe4-aca34a21f459</Alias>
</Item>
<Item>
<rasd:Caption>USB Controller</rasd:Caption>
<rasd:InstanceId>3</rasd:InstanceId>
<rasd:ResourceType>23</rasd:ResourceType>
<rasd:UsbPolicy>DISABLED</rasd:UsbPolicy>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>373a1bbf-b292-31c9-a29c-afeb9ba84c21</rasd:InstanceId>
<Type>rng</Type>
<Device>virtio</Device>
<rasd:Address>{type=pci, slot=0x00, bus=0x06, domain=0x0000, function=0x0}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias></Alias>
<SpecParams>
<source>urandom</source>
</SpecParams>
</Item>
</Section>
</Content>
</ovf:Envelope>

View File

@ -627,4 +627,9 @@ public class MockAccountManager extends ManagerBase implements AccountManager {
public User getOneActiveUserForAccount(Account account) {
return null;
}
@Override
public Account getAccountByUuid(String accountUuid) {
return null;
}
}

View File

@ -2789,6 +2789,11 @@ public class AccountManagerImpl extends ManagerBase implements AccountManager, M
return _accountDao.findByIdIncludingRemoved(accountId);
}
@Override
public Account getAccountByUuid(String accountUuid) {
return _accountDao.findByUuidIncludingRemoved(accountUuid);
}
@Override
public RoleType getRoleType(Account account) {
if (account == null) {