Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-02-23 19:09:26 +05:30
parent 0ff4dc5513
commit f9070985d5
23 changed files with 1894 additions and 281 deletions

View File

@ -60,4 +60,6 @@ public interface ResourceTagDao extends GenericDao<ResourceTagVO, Long> {
void removeByResourceIdAndKey(long resourceId, ResourceObjectType resourceType, String key);
List<? extends ResourceTag> listByResourceUuid(String resourceUuid);
List<ResourceTagVO> listByResourceType(ResourceObjectType resourceType);
}

View File

@ -120,4 +120,11 @@ public class ResourceTagsDaoImpl extends GenericDaoBase<ResourceTagVO, Long> imp
sc.setParameters("resourceUuid", resourceUuid);
return listBy(sc);
}
@Override
public List<ResourceTagVO> listByResourceType(ResourceObjectType resourceType) {
SearchCriteria<ResourceTagVO> sc = AllFieldsSearch.create();
sc.setParameters("resourceType", resourceType);
return listBy(sc);
}
}

View File

@ -18,6 +18,7 @@
package org.apache.cloudstack.veeam.adapter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Collections;
@ -75,6 +76,8 @@ import org.apache.cloudstack.jobs.JobInfo;
import org.apache.cloudstack.query.QueryService;
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.TagsRouteHandler;
import org.apache.cloudstack.veeam.api.converter.AsyncJobJoinVOToJobConverter;
import org.apache.cloudstack.veeam.api.converter.BackupVOToBackupConverter;
import org.apache.cloudstack.veeam.api.converter.ClusterVOToClusterConverter;
@ -84,12 +87,14 @@ import org.apache.cloudstack.veeam.api.converter.ImageTransferVOToImageTransferC
import org.apache.cloudstack.veeam.api.converter.NetworkVOToNetworkConverter;
import org.apache.cloudstack.veeam.api.converter.NetworkVOToVnicProfileConverter;
import org.apache.cloudstack.veeam.api.converter.NicVOToNicConverter;
import org.apache.cloudstack.veeam.api.converter.ResourceTagVOToTagConverter;
import org.apache.cloudstack.veeam.api.converter.StoreVOToStorageDomainConverter;
import org.apache.cloudstack.veeam.api.converter.UserVmJoinVOToVmConverter;
import org.apache.cloudstack.veeam.api.converter.UserVmVOToCheckpointConverter;
import org.apache.cloudstack.veeam.api.converter.VmSnapshotVOToSnapshotConverter;
import org.apache.cloudstack.veeam.api.converter.VolumeJoinVOToDiskConverter;
import org.apache.cloudstack.veeam.api.dto.Backup;
import org.apache.cloudstack.veeam.api.dto.BaseDto;
import org.apache.cloudstack.veeam.api.dto.Checkpoint;
import org.apache.cloudstack.veeam.api.dto.Cluster;
import org.apache.cloudstack.veeam.api.dto.DataCenter;
@ -100,9 +105,11 @@ import org.apache.cloudstack.veeam.api.dto.ImageTransfer;
import org.apache.cloudstack.veeam.api.dto.Job;
import org.apache.cloudstack.veeam.api.dto.Network;
import org.apache.cloudstack.veeam.api.dto.Nic;
import org.apache.cloudstack.veeam.api.dto.OvfXmlUtil;
import org.apache.cloudstack.veeam.api.dto.ResourceAction;
import org.apache.cloudstack.veeam.api.dto.Snapshot;
import org.apache.cloudstack.veeam.api.dto.StorageDomain;
import org.apache.cloudstack.veeam.api.dto.Tag;
import org.apache.cloudstack.veeam.api.dto.Vm;
import org.apache.cloudstack.veeam.api.dto.VmAction;
import org.apache.cloudstack.veeam.api.dto.VnicProfile;
@ -138,12 +145,15 @@ import com.cloud.network.dao.NetworkDao;
import com.cloud.network.dao.NetworkVO;
import com.cloud.offering.ServiceOffering;
import com.cloud.org.Grouping;
import com.cloud.server.ResourceTag;
import com.cloud.service.dao.ServiceOfferingDao;
import com.cloud.storage.Volume;
import com.cloud.storage.VolumeApiService;
import com.cloud.storage.VolumeVO;
import com.cloud.storage.dao.VolumeDao;
import com.cloud.storage.dao.VolumeDetailsDao;
import com.cloud.tags.ResourceTagVO;
import com.cloud.tags.dao.ResourceTagDao;
import com.cloud.user.Account;
import com.cloud.user.AccountService;
import com.cloud.user.User;
@ -266,8 +276,31 @@ public class ServerAdapter extends ManagerBase {
@Inject
BackupDao backupDao;
@Inject
ResourceTagDao resourceTagDao;
//ToDo: check access on objects
protected static Tag getDummyTagByName(String name) {
Tag tag = new Tag();
String id = UUID.nameUUIDFromBytes(String.format("veeam:%s", name.toLowerCase()).getBytes()).toString();
tag.setId(id);
tag.setName(name);
tag.setDescription(String.format("Default %s tag", name.toLowerCase()));
tag.setHref(VeeamControlService.ContextPath.value() + TagsRouteHandler.BASE_ROUTE + "/" + id);
tag.setParent(ResourceTagVOToTagConverter.getRootTagRef());
return tag;
}
protected static Map<String, Tag> getDummyTags() {
Map<String, Tag> tags = new HashMap<>();
Tag tag1 = getDummyTagByName("Automatic");
tags.put(tag1.getId(), tag1);
Tag tag2 = getDummyTagByName("Manual");
tags.put(tag2.getId(), tag2);
return tags;
}
protected Role createServiceAccountRole() {
Role role = roleService.createRole(SERVICE_ACCOUNT_ROLE_NAME, RoleType.User,
SERVICE_ACCOUNT_ROLE_NAME, false);
@ -417,20 +450,30 @@ public class ServerAdapter extends ManagerBase {
return UserVmJoinVOToVmConverter.toVmList(vms, this::getHostById);
}
public Vm getInstance(String uuid) {
public Vm getInstance(String uuid, boolean includeDisks, boolean includeNics, boolean allContent) {
UserVmJoinVO vo = userVmJoinDao.findByUuid(uuid);
if (vo == null) {
throw new InvalidParameterValueException("VM with ID " + uuid + " not found");
}
return UserVmJoinVOToVmConverter.toVm(vo, this::getHostById, this::listDiskAttachmentsByInstanceId,
this::listNicsByInstance);
return UserVmJoinVOToVmConverter.toVm(vo, this::getHostById,
includeDisks ? this::listDiskAttachmentsByInstanceId : null,
includeNics ? this::listNicsByInstance : null,
allContent);
}
public Vm createInstance(Vm request) {
if (request == null) {
throw new InvalidParameterValueException("Request disk data is empty");
}
OvfXmlUtil.updateFromConfiguration(request);
String name = request.getName();
if (StringUtils.isBlank(name)) {
throw new InvalidParameterValueException("Invalid name specified for the VM");
}
String displayName = name;
if (name.endsWith("_restored")) {
name = name.replace("_restored", "-restored");
}
Long zoneId = null;
Long clusterId = null;
if (request.getCluster() != null && StringUtils.isNotEmpty(request.getCluster().getId())) {
@ -446,14 +489,16 @@ public class ServerAdapter extends ManagerBase {
Integer cpu = null;
try {
cpu = Integer.valueOf(request.getCpu().getTopology().getSockets());
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
if (cpu == null) {
throw new InvalidParameterValueException("CPU topology sockets must be specified");
}
Long memory = null;
try {
memory = Long.valueOf(request.getMemory());
} catch (Exception ignored) {}
} catch (Exception ignored) {
}
if (memory == null) {
throw new InvalidParameterValueException("Memory must be specified");
}
@ -470,7 +515,7 @@ public class ServerAdapter extends ManagerBase {
Pair<User, Account> serviceUserAccount = createServiceAccountIfNeeded();
CallContext ctx = CallContext.register(serviceUserAccount.first(), serviceUserAccount.second());
try {
return createInstance(zoneId, clusterId, name, cpu, memory, userdata, bootType, bootMode);
return createInstance(zoneId, clusterId, name, displayName, cpu, memory, userdata, bootType, bootMode);
} finally {
CallContext.unregister();
}
@ -491,8 +536,8 @@ public class ServerAdapter extends ManagerBase {
return serviceOfferingDao.findByUuid(uuid);
}
protected Vm createInstance(Long zoneId, Long clusterId, String name, int cpu, long memory, String userdata,
ApiConstants.BootType bootType, ApiConstants.BootMode bootMode) {
protected Vm createInstance(Long zoneId, Long clusterId, String name, String displayName, int cpu, long memory,
String userdata, ApiConstants.BootType bootType, ApiConstants.BootMode bootMode) {
ServiceOffering serviceOffering = getServiceOfferingIdForVmCreation(zoneId, cpu, memory);
if (serviceOffering == null) {
throw new CloudRuntimeException("No service offering found for VM creation with specified CPU and memory");
@ -503,6 +548,9 @@ public class ServerAdapter extends ManagerBase {
cmd.setZoneId(zoneId);
cmd.setClusterId(clusterId);
cmd.setName(name);
if (displayName != null) {
cmd.setDisplayName(displayName);
}
cmd.setServiceOfferingId(serviceOffering.getId());
if (StringUtils.isNotEmpty(userdata)) {
cmd.setUserData(Base64.getEncoder().encodeToString(userdata.getBytes(StandardCharsets.UTF_8)));
@ -526,7 +574,7 @@ public class ServerAdapter extends ManagerBase {
vm = userVmService.finalizeCreateVirtualMachine(vm.getId());
UserVmJoinVO vo = userVmJoinDao.findById(vm.getId());
return UserVmJoinVOToVmConverter.toVm(vo, this::getHostById, this::listDiskAttachmentsByInstanceId,
this::listNicsByInstance);
this::listNicsByInstance, false);
} catch (InsufficientCapacityException | ResourceUnavailableException | ResourceAllocationException | CloudRuntimeException e) {
throw new CloudRuntimeException("Failed to create VM: " + e.getMessage(), e);
}
@ -534,7 +582,7 @@ public class ServerAdapter extends ManagerBase {
public Vm updateInstance(String uuid, Vm request) {
// ToDo: what to do?!
return getInstance(uuid);
return getInstance(uuid, false, false, false);
}
public void deleteInstance(String uuid) {
@ -1236,4 +1284,30 @@ public class ServerAdapter extends ManagerBase {
CallContext.unregister();
}
}
public List<Tag> listAllTags() {
List<Tag> tags = new ArrayList<>(getDummyTags().values());
List<ResourceTagVO> vmResourceTags = resourceTagDao.listByResourceType(ResourceTag.ResourceObjectType.UserVm);
if (CollectionUtils.isNotEmpty(vmResourceTags)) {
tags.addAll(ResourceTagVOToTagConverter.toTags(vmResourceTags));
}
return tags;
}
public Tag getTag(String uuid) {
if (BaseDto.ZERO_UUID.equals(uuid)) {
return ResourceTagVOToTagConverter.getRootTag();
}
Tag tag = getDummyTags().get(uuid);
if (tag == null) {
ResourceTagVO resourceTagVO = resourceTagDao.findByUuid(uuid);
if (resourceTagVO != null) {
tag = ResourceTagVOToTagConverter.toTag(resourceTagVO);
}
}
if (tag == null) {
throw new InvalidParameterValueException("Tag with ID " + uuid + " not found");
}
return tag;
}
}

View File

@ -0,0 +1,102 @@
// 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.veeam.api;
import java.io.IOException;
import java.util.List;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.cloudstack.veeam.RouteHandler;
import org.apache.cloudstack.veeam.VeeamControlServlet;
import org.apache.cloudstack.veeam.adapter.ServerAdapter;
import org.apache.cloudstack.veeam.api.dto.NamedList;
import org.apache.cloudstack.veeam.api.dto.Tag;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.apache.cloudstack.veeam.utils.PathUtil;
import org.apache.commons.collections.CollectionUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.component.ManagerBase;
public class TagsRouteHandler extends ManagerBase implements RouteHandler {
public static final String BASE_ROUTE = "/api/tags";
@Inject
ServerAdapter serverAdapter;
@Override
public boolean start() {
return true;
}
@Override
public int priority() {
return 5;
}
@Override
public boolean canHandle(String method, String path) {
return getSanitizedPath(path).startsWith(BASE_ROUTE);
}
@Override
public void handle(HttpServletRequest req, HttpServletResponse resp, String path, Negotiation.OutFormat outFormat,
VeeamControlServlet io) throws IOException {
final String method = req.getMethod();
if (!"GET".equalsIgnoreCase(method)) {
io.methodNotAllowed(resp, "GET", outFormat);
return;
}
final String sanitizedPath = getSanitizedPath(path);
if (sanitizedPath.equals(BASE_ROUTE)) {
handleGet(req, resp, outFormat, io);
return;
}
List<String> idAndSubPath = PathUtil.extractIdAndSubPath(sanitizedPath, BASE_ROUTE);
if (CollectionUtils.isNotEmpty(idAndSubPath)) {
String id = idAndSubPath.get(0);
if (idAndSubPath.size() == 1) {
handleGetById(id, resp, outFormat, io);
return;
}
}
io.notFound(resp, null, outFormat);
}
protected void handleGet(final HttpServletRequest req, final HttpServletResponse resp,
Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
final List<Tag> result = serverAdapter.listAllTags();
NamedList<Tag> response = NamedList.of("tag", result);
io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat);
}
protected void handleGetById(final String id, final HttpServletResponse resp, final Negotiation.OutFormat outFormat,
final VeeamControlServlet io) throws IOException {
try {
Tag response = serverAdapter.getTag(id);
io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat);
} catch (InvalidParameterValueException e) {
io.notFound(resp, e.getMessage(), outFormat);
}
}
}

View File

@ -34,7 +34,6 @@ import org.apache.cloudstack.veeam.api.dto.Disk;
import org.apache.cloudstack.veeam.api.dto.DiskAttachment;
import org.apache.cloudstack.veeam.api.dto.NamedList;
import org.apache.cloudstack.veeam.api.dto.Nic;
import org.apache.cloudstack.veeam.api.dto.Nics;
import org.apache.cloudstack.veeam.api.dto.ResourceAction;
import org.apache.cloudstack.veeam.api.dto.Snapshot;
import org.apache.cloudstack.veeam.api.dto.Vm;
@ -46,6 +45,7 @@ import org.apache.cloudstack.veeam.api.request.VmSearchParser;
import org.apache.cloudstack.veeam.utils.Negotiation;
import org.apache.cloudstack.veeam.utils.PathUtil;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import com.cloud.exception.InvalidParameterValueException;
import com.cloud.utils.component.ManagerBase;
@ -108,7 +108,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
if (!"GET".equalsIgnoreCase(method) && !"PUT".equalsIgnoreCase(method) && !"DELETE".equalsIgnoreCase(method)) {
io.methodNotAllowed(resp, "GET, PUT, DELETE", outFormat);
} else if ("GET".equalsIgnoreCase(method)) {
handleGetById(id, resp, outFormat, io);
handleGetById(id, req, resp, outFormat, io);
} else if ("PUT".equalsIgnoreCase(method)) {
handleUpdateById(id, req, resp, outFormat, io);
} else if ("DELETE".equalsIgnoreCase(method)) {
@ -308,10 +308,22 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
}
}
protected void handleGetById(final String id, final HttpServletResponse resp, final Negotiation.OutFormat outFormat,
final VeeamControlServlet io) throws IOException {
protected void handleGetById(final String id, final HttpServletRequest req, final HttpServletResponse resp,
final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException {
String followStr = req.getParameter("follow");
boolean includeDisks = false;
boolean includeNics = false;
if (StringUtils.isNotBlank(followStr)) {
Set<String> followParts = java.util.Arrays.stream(followStr.split(","))
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(java.util.stream.Collectors.toSet());
includeDisks = followParts.contains("disk_attachments.disk");
includeNics = followParts.contains("nics.reporteddevices");
}
boolean allContent = Boolean.parseBoolean(req.getParameter("all_content"));
try {
Vm response = serverAdapter.getInstance(id);
Vm response = serverAdapter.getInstance(id, includeDisks, includeNics, allContent);
io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat);
} catch (InvalidParameterValueException e) {
io.notFound(resp, e.getMessage(), outFormat);
@ -399,7 +411,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
final VeeamControlServlet io) throws IOException {
try {
List<Nic> nics = serverAdapter.listNicsByInstanceUuid(id);
Nics response = new Nics(nics);
NamedList<Nic> response = NamedList.of("nic", nics);
io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat);
} catch (InvalidParameterValueException e) {
io.notFound(resp, e.getMessage(), outFormat);

View File

@ -92,7 +92,7 @@ public class AsyncJobJoinVOToJobConverter {
public static VmAction toVmAction(final AsyncJobJoinVO vo, final UserVmJoinVO vm) {
VmAction action = new VmAction();
fillAction(action, vo);
action.setVm(UserVmJoinVOToVmConverter.toVm(vm, null, null, null));
action.setVm(UserVmJoinVOToVmConverter.toVm(vm, null, null, null, false));
return action;
}

View File

@ -0,0 +1,67 @@
// 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.veeam.api.converter;
import java.util.List;
import java.util.stream.Collectors;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.TagsRouteHandler;
import org.apache.cloudstack.veeam.api.VmsRouteHandler;
import org.apache.cloudstack.veeam.api.dto.BaseDto;
import org.apache.cloudstack.veeam.api.dto.Ref;
import org.apache.cloudstack.veeam.api.dto.Tag;
import com.cloud.server.ResourceTag;
import com.cloud.tags.ResourceTagVO;
public class ResourceTagVOToTagConverter {
public static Ref getRootTagRef() {
String basePath = VeeamControlService.ContextPath.value();
return Ref.of(basePath + TagsRouteHandler.BASE_ROUTE + "/" + BaseDto.ZERO_UUID, BaseDto.ZERO_UUID);
}
public static Tag getRootTag() {
String basePath = VeeamControlService.ContextPath.value();
Tag tag = new Tag();
tag.setId(BaseDto.ZERO_UUID);
tag.setName("root");
tag.setHref(getRootTagRef().getHref());
return tag;
}
public static Tag toTag(ResourceTagVO vo) {
String basePath = VeeamControlService.ContextPath.value();
Tag tag = new Tag();
tag.setId(vo.getUuid());
tag.setName(vo.getKey());
tag.setDescription(String.format("Tag %s-%s", vo.getKey(), vo.getValue()));
tag.setHref(basePath + TagsRouteHandler.BASE_ROUTE + "/" + vo.getUuid());
if (ResourceTag.ResourceObjectType.UserVm.equals(vo.getResourceType())) {
tag.setVm(Ref.of(basePath + VmsRouteHandler.BASE_ROUTE + "/" + vo.getResourceUuid(),
vo.getResourceUuid()));
}
tag.setParent(getRootTagRef());
return tag;
}
public static List<Tag> toTags(List<ResourceTagVO> vos) {
return vos.stream().map(ResourceTagVOToTagConverter::toTag).collect(Collectors.toList());
}
}

View File

@ -27,14 +27,13 @@ import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.ApiService;
import org.apache.cloudstack.veeam.api.VmsRouteHandler;
import org.apache.cloudstack.veeam.api.dto.BaseDto;
import org.apache.cloudstack.veeam.api.dto.Bios;
import org.apache.cloudstack.veeam.api.dto.Cpu;
import org.apache.cloudstack.veeam.api.dto.DiskAttachment;
import org.apache.cloudstack.veeam.api.dto.EmptyElement;
import org.apache.cloudstack.veeam.api.dto.NamedList;
import org.apache.cloudstack.veeam.api.dto.Nic;
import org.apache.cloudstack.veeam.api.dto.Nics;
import org.apache.cloudstack.veeam.api.dto.Os;
import org.apache.cloudstack.veeam.api.dto.OvfXmlUtil;
import org.apache.cloudstack.veeam.api.dto.Ref;
import org.apache.cloudstack.veeam.api.dto.Topology;
import org.apache.cloudstack.veeam.api.dto.Vm;
@ -55,7 +54,9 @@ public final class UserVmJoinVOToVmConverter {
* @param src UserVmJoinVO
*/
public static Vm toVm(final UserVmJoinVO src, final Function<Long, HostJoinVO> hostResolver,
final Function<Long, List<DiskAttachment>> disksResolver, final Function<UserVmJoinVO, List<Nic>> nicsResolver) {
final Function<Long, List<DiskAttachment>> disksResolver,
final Function<UserVmJoinVO, List<Nic>> nicsResolver,
final boolean allContent) {
if (src == null) {
return null;
}
@ -104,7 +105,13 @@ public final class UserVmJoinVOToVmConverter {
}
}
dst.setMemory(String.valueOf(src.getRamSize() * 1024L * 1024L));
String memory = String.valueOf(src.getRamSize() * 1024L * 1024L);
dst.setMemory(memory);
Vm.MemoryPolicy memoryPolicy = new Vm.MemoryPolicy();
memoryPolicy.setGuaranteed(memory);
memoryPolicy.setMax(memory);
memoryPolicy.setBallooning("false");
dst.setMemoryPolicy(memoryPolicy);
Cpu cpu = new Cpu();
cpu.setArchitecture(src.getArch());
cpu.setTopology(new Topology(src.getCpu(), 1, 1));
@ -113,9 +120,15 @@ public final class UserVmJoinVOToVmConverter {
os.setType(src.getGuestOsId() % 2 == 0
? "windows"
: "linux");
Os.Boot boot = new Os.Boot();
boot.setDevices(NamedList.of("device", List.of("hd")));
os.setBoot(boot);
dst.setOs(os);
Bios bios = new Bios();
Vm.Bios bios = new Vm.Bios();
bios.setType("q35_secure_boot");
Vm.Bios.BootMenu bootMenu = new Vm.Bios.BootMenu();
bootMenu.setEnabled("false");
bios.setBootMenu(bootMenu);
dst.setBios(bios);
dst.setType("desktop");
dst.setOrigin("ovirt");
@ -126,9 +139,9 @@ public final class UserVmJoinVOToVmConverter {
dst.setDiskAttachments(NamedList.of("disk_attachment", diskAttachments));
}
if (disksResolver != null) {
if (nicsResolver != null) {
List<Nic> nics = nicsResolver.apply(src);
dst.setNics(new Nics(nics));
dst.setNics(NamedList.of("nic", nics));
}
dst.setActions(NamedList.of("link", List.of(
@ -143,13 +156,29 @@ public final class UserVmJoinVOToVmConverter {
BaseDto.getActionLink("snapshots", dst.getHref())
));
dst.setTags(new EmptyElement());
dst.setCpuProfile(Ref.of(
basePath + ApiService.BASE_ROUTE + "/cpuprofiles/" + src.getServiceOfferingUuid(),
src.getServiceOfferingUuid()));
if (allContent) {
dst.setInitialization(getOvfInitialization(dst));
}
return dst;
}
private static Vm.Initialization getOvfInitialization(Vm vm) {
final Vm.Initialization.Configuration configuration = new Vm.Initialization.Configuration();
configuration.setType("ovf");
configuration.setData(OvfXmlUtil.toXml(vm));
final Vm.Initialization initialization = new Vm.Initialization();
initialization.setConfiguration(configuration);
return initialization;
}
public static List<Vm> toVmList(final List<UserVmJoinVO> srcList, final Function<Long, HostJoinVO> hostResolver) {
return srcList.stream()
.map(v -> toVm(v, hostResolver, null, null))
.map(v -> toVm(v, hostResolver, null, null, false))
.collect(Collectors.toList());
}

View File

@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BaseDto {
public static final String ZERO_UUID = "00000000-0000-0000-0000-000000000000";
private String href;
private String id;

View File

@ -1,34 +0,0 @@
// 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.veeam.api.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class BootMenu {
private String enabled = "false";
public String getEnabled() {
return enabled;
}
public void setEnabled(String enabled) {
this.enabled = enabled;
}
}

View File

@ -1,69 +0,0 @@
// 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.veeam.api.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class HardwareInformation {
private String manufacturer;
private String productName;
private String serialNumber;
private String uuid;
private String version;
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}

View File

@ -259,4 +259,53 @@ public class Host extends BaseDto {
public void setLink(List<Link> link) {
this.link = link;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class HardwareInformation {
private String manufacturer;
private String productName;
private String serialNumber;
private String uuid;
private String version;
public String getManufacturer() {
return manufacturer;
}
public void setManufacturer(String manufacturer) {
this.manufacturer = manufacturer;
}
public String getProductName() {
return productName;
}
public void setProductName(String productName) {
this.productName = productName;
}
public String getSerialNumber() {
return serialNumber;
}
public void setSerialNumber(String serialNumber) {
this.serialNumber = serialNumber;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
}
}

View File

@ -1,57 +0,0 @@
// 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.veeam.api.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class HostSummary {
@JsonProperty("active")
private String active;
@JsonProperty("migrating")
private String migrating;
@JsonProperty("total")
private String total;
public String getActive() {
return active;
}
public void setActive(String active) {
this.active = active;
}
public String getMigrating() {
return migrating;
}
public void setMigrating(String migrating) {
this.migrating = migrating;
}
public String getTotal() {
return total;
}
public void setTotal(String total) {
this.total = total;
}
}

View File

@ -1,41 +0,0 @@
// 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.veeam.api.dto;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JacksonXmlRootElement(localName = "nics")
public final class Nics {
@JsonProperty("nic")
@JacksonXmlElementWrapper(useWrapping = false)
public List<Nic> nic;
public Nics() {
}
public Nics(final List<Nic> nic) {
this.nic = nic;
}
}

View File

@ -22,6 +22,8 @@ import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public final class Os {
private String type;
private String version;
private Boot boot;
public String getType() {
return type;
@ -30,4 +32,24 @@ public final class Os {
public void setType(String type) {
this.type = type;
}
public Boot getBoot() {
return boot;
}
public void setBoot(Boot boot) {
this.boot = boot;
}
public final static class Boot {
private NamedList<String> devices;
public NamedList<String> getDevices() {
return devices;
}
public void setDevices(NamedList<String> devices) {
this.devices = devices;
}
}
}

View File

@ -0,0 +1,671 @@
// 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.veeam.api.dto;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
import java.util.UUID;
import javax.xml.XMLConstants;
import javax.xml.namespace.NamespaceContext;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public class OvfXmlUtil {
private static final String NS_OVF = "http://schemas.dmtf.org/ovf/envelope/1/";
private static final String NS_RASD = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_ResourceAllocationSettingData";
private static final String NS_VSSD = "http://schemas.dmtf.org/wbem/wscim/1/cim-schema/2/CIM_VirtualSystemSettingData";
private static final String NS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
private static final String ZERO_UUID = "00000000-0000-0000-0000-000000000000";
private static final TimeZone UTC = TimeZone.getTimeZone("Etc/GMT");
private static final ThreadLocal<SimpleDateFormat> OVIRT_DTF = ThreadLocal.withInitial(() -> {
final SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.ROOT);
sdf.setTimeZone(UTC);
return sdf;
});
public static String toXml(final Vm vm) {
final String vmId = vm.getId();
final String vmName = vm.getName();
final String vmDesc = defaultString(vm.getDescription());
final long creationMillis = vm.getCreationTime();
final String creationDate = formatDate(creationMillis);
final String exportDate = formatDate(System.currentTimeMillis());
final String stopTime = vm.getStopTime() != null ? formatDate(vm.getStopTime()) : creationDate;
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));
// CPU: topology cores/sockets/threads. We default sockets=1 threads=1.
final int vcpu = Math.max(1, Integer.parseInt(vm.getCpu().getTopology().getCores()));
final int sockets = Math.max(1, Integer.parseInt(vm.getCpu().getTopology().getSockets()));
final int threads = Math.max(1, Integer.parseInt(vm.getCpu().getTopology().getThreads()));
final int cpuPerSocket = Math.max(1, vcpu / sockets);
final int maxVcpu = vcpu;
// Template
final Ref template = vm.getTemplate();
final String templateId = template != null && StringUtils.isNotBlank(template.getId()) ? template.getId() : ZERO_UUID;
final String templateName = template != null ? defaultString(template.getId()) : "Blank";
// Snapshot id (stable per VM id)
final String snapshotId = UUID.nameUUIDFromBytes(("ovf-snap-" + vmId).getBytes(StandardCharsets.UTF_8)).toString();
final StringBuilder sb = new StringBuilder(16_384);
sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
sb.append("<ovf:Envelope ");
sb.append("xmlns:ovf=\"").append(NS_OVF).append("\" ");
sb.append("xmlns:rasd=\"").append(NS_RASD).append("\" ");
sb.append("xmlns:vssd=\"").append(NS_VSSD).append("\" ");
sb.append("xmlns:xsi=\"").append(NS_XSI).append("\" ovf:version=\"4.4.0.0\">");
// --- References (from disks) ---
sb.append("<References>");
for (DiskAttachment da : diskAttachments(vm)) {
if (da == null || da.getDisk() == null || StringUtils.isBlank(da.getDisk().getId())) {
continue;
}
final String diskId = da.getDisk().getId();
final String storageDomainId = firstStorageDomainId(da.getDisk());
final String href = storageDomainId + "/" + diskId;
sb.append("<File ovf:href=\"").append(escapeAttr(href))
.append("\" ovf:id=\"").append(escapeAttr(diskId))
.append("\" ovf:size=\"4096\" ovf:description=\"Active VM\" ovf:disk_storage_type=\"IMAGE\" ovf:cinder_volume_type=\"\"></File>");
}
sb.append("</References>");
// --- NetworkSection ---
sb.append("<NetworkSection>");
sb.append("<Info>List of networks</Info>");
// oVirt often lists networks, but can also be empty. We'll include known names if we can.
for (Nic nic : nics(vm)) {
if (nic == null) {
continue;
}
final String netName = inferNetworkName(nic);
if (StringUtils.isBlank(netName)) {
continue;
}
sb.append("<Network ovf:name=\"").append(escapeAttr(netName)).append("\">");
sb.append("<Description>").append(escapeText(defaultString(nic.getDescription()))).append("</Description>");
sb.append("</Network>");
}
sb.append("</NetworkSection>");
// --- DiskSection ---
sb.append("<Section xsi:type=\"ovf:DiskSection_Type\">");
sb.append("<Info>List of Virtual Disks</Info>");
for (DiskAttachment da : diskAttachments(vm)) {
if (da == null || da.getDisk() == null || StringUtils.isBlank(da.getDisk().getId())) {
continue;
}
final org.apache.cloudstack.veeam.api.dto.Disk d = da.getDisk();
final String diskId = d.getId();
final String storageDomainId = firstStorageDomainId(d);
final String href = storageDomainId + "/" + diskId;
final long provBytes = parseLong(d.getProvisionedSize(), 0);
final long actualBytes = parseLong(d.getActualSize(), 0);
final long provGiB = bytesToGibCeil(provBytes);
final long actualGiB = bytesToGibCeil(actualBytes);
final String diskInterface = mapDiskInterface(da.getIface());
sb.append("<Disk");
sb.append(" ovf:diskId=\"").append(escapeAttr(diskId)).append("\"");
sb.append(" ovf:size=\"").append(provGiB > 0 ? provGiB : 1).append("\"");
sb.append(" ovf:actual_size=\"").append(actualGiB > 0 ? actualGiB : 1).append("\"");
sb.append(" ovf:vm_snapshot_id=\"").append(escapeAttr(snapshotId)).append("\"");
sb.append(" ovf:parentRef=\"\"");
sb.append(" ovf:fileRef=\"").append(escapeAttr(href)).append("\"");
sb.append(" ovf:format=\"").append(escapeAttr(mapOvfDiskFormat(d.getFormat(), d.getSparse()))).append("\"");
sb.append(" ovf:volume-format=\"").append(escapeAttr(mapVolumeFormat(d.getFormat()))).append("\"");
sb.append(" ovf:volume-type=\"").append(escapeAttr(mapVolumeType(d.getSparse()))).append("\"");
sb.append(" ovf:disk-interface=\"").append(escapeAttr(diskInterface)).append("\"");
sb.append(" ovf:read-only=\"").append(escapeAttr(booleanString(da.getReadOnly(), "false"))).append("\"");
sb.append(" ovf:shareable=\"").append(escapeAttr(booleanString(d.getShareable(), "false"))).append("\"");
sb.append(" ovf:boot=\"").append(escapeAttr(booleanString(da.getBootable(), "false"))).append("\"");
sb.append(" ovf:pass-discard=\"").append(escapeAttr(booleanString(da.getPassDiscard(), "false"))).append("\"");
sb.append(" ovf:incremental-backup=\"false\"");
sb.append(" ovf:disk-alias=\"").append(escapeAttr(defaultString(d.getAlias()))).append("\"");
sb.append(" ovf:disk-description=\"").append(escapeAttr(defaultString(d.getDescription()))).append("\"");
sb.append(" ovf:wipe-after-delete=\"").append(escapeAttr(booleanString(d.getWipeAfterDelete(), "false"))).append("\"");
sb.append("></Disk>");
}
sb.append("</Section>");
// --- Content / VirtualSystem ---
sb.append("<Content ovf:id=\"out\" xsi:type=\"ovf:VirtualSystem_Type\">");
sb.append("<Name>").append(escapeText(vmName)).append("</Name>");
sb.append("<Description>").append(escapeText(vmDesc)).append("</Description>");
sb.append("<Comment></Comment>");
sb.append("<CreationDate>").append(creationDate).append("</CreationDate>");
sb.append("<ExportDate>").append(exportDate).append("</ExportDate>");
sb.append("<DeleteProtected>false</DeleteProtected>");
sb.append("<SsoMethod>guest_agent</SsoMethod>");
sb.append("<IsSmartcardEnabled>false</IsSmartcardEnabled>");
sb.append("<NumOfIoThreads>1</NumOfIoThreads>");
sb.append("<TimeZone>Etc/GMT</TimeZone>");
sb.append("<default_boot_sequence>0</default_boot_sequence>");
sb.append("<Generation>11</Generation>");
sb.append("<ClusterCompatibilityVersion>4.8</ClusterCompatibilityVersion>");
sb.append("<VmType>1</VmType>");
sb.append("<ResumeBehavior>AUTO_RESUME</ResumeBehavior>");
sb.append("<MinAllocatedMem>").append(memMb).append("</MinAllocatedMem>");
sb.append("<IsStateless>").append(escapeText(booleanString(vm.getStateless(), "false"))).append("</IsStateless>");
sb.append("<IsRunAndPause>false</IsRunAndPause>");
sb.append("<AutoStartup>false</AutoStartup>");
sb.append("<Priority>0</Priority>");
sb.append("<CreatedByUserId>").append(ZERO_UUID).append("</CreatedByUserId>");
sb.append("<MigrationSupport>0</MigrationSupport>");
sb.append("<IsBootMenuEnabled>").append(escapeText(booleanString(vm.getBios() != null && vm.getBios().getBootMenu() != null ? vm.getBios().getBootMenu().getEnabled() : null, "false"))).append("</IsBootMenuEnabled>");
sb.append("<IsSpiceFileTransferEnabled>true</IsSpiceFileTransferEnabled>");
sb.append("<IsSpiceCopyPasteEnabled>true</IsSpiceCopyPasteEnabled>");
sb.append("<AllowConsoleReconnect>false</AllowConsoleReconnect>");
sb.append("<ConsoleDisconnectAction>LOCK_SCREEN</ConsoleDisconnectAction>");
sb.append("<ConsoleDisconnectActionDelay>0</ConsoleDisconnectActionDelay>");
sb.append("<CustomEmulatedMachine></CustomEmulatedMachine>");
sb.append("<BiosType>").append(mapBiosType(vm.getBios() != null ? vm.getBios().getType() : null)).append("</BiosType>");
sb.append("<CustomCpuName></CustomCpuName>");
sb.append("<PredefinedProperties></PredefinedProperties>");
sb.append("<UserDefinedProperties></UserDefinedProperties>");
sb.append("<MaxMemorySizeMb>").append(memMb).append("</MaxMemorySizeMb>");
sb.append("<MultiQueuesEnabled>true</MultiQueuesEnabled>");
sb.append("<VirtioScsiMultiQueuesEnabled>false</VirtioScsiMultiQueuesEnabled>");
sb.append("<UseHostCpu>false</UseHostCpu>");
sb.append("<BalloonEnabled>").append(mapBalloonEnabled(vm)).append("</BalloonEnabled>");
sb.append("<CpuPinningPolicy>0</CpuPinningPolicy>");
sb.append("<ClusterName></ClusterName>");
sb.append("<TemplateId>").append(escapeText(templateId)).append("</TemplateId>");
sb.append("<TemplateName>").append(escapeText(templateName)).append("</TemplateName>");
sb.append("<IsInitilized>true</IsInitilized>");
sb.append("<Origin>3</Origin>");
sb.append("<quota_id>").append(ZERO_UUID).append("</quota_id>");
sb.append("<DefaultDisplayType>2</DefaultDisplayType>");
sb.append("<TrustedService>false</TrustedService>");
sb.append("<OriginalTemplateId>").append(escapeText(templateId)).append("</OriginalTemplateId>");
sb.append("<OriginalTemplateName>").append(escapeText(templateName)).append("</OriginalTemplateName>");
sb.append("<UseLatestVersion>false</UseLatestVersion>");
sb.append("<StopTime>").append(stopTime).append("</StopTime>");
sb.append("<BootTime>").append(bootTime).append("</BootTime>");
sb.append("<Downtime>0</Downtime>");
// --- Operating system section ---
sb.append("<Section ovf:id=\"").append(escapeAttr(vmId)).append("\" ovf:required=\"false\" xsi:type=\"ovf:OperatingSystemSection_Type\">");
sb.append("<Info>Guest Operating System</Info>");
sb.append("<Description>").append(escapeText(inferOsDescription(vm))).append("</Description>");
sb.append("</Section>");
// --- Virtual hardware section ---
sb.append("<Section xsi:type=\"ovf:VirtualHardwareSection_Type\">");
sb.append("<Info>").append(vcpu).append(" CPU, ").append(memMb).append(" Memory</Info>");
sb.append("<System>");
sb.append("<vssd:VirtualSystemType>ENGINE 4.4.0.0</vssd:VirtualSystemType>");
sb.append("</System>");
// CPU
sb.append("<Item>");
sb.append("<rasd:Caption>").append(vcpu).append(" virtual cpu</rasd:Caption>");
sb.append("<rasd:Description>Number of virtual CPU</rasd:Description>");
sb.append("<rasd:InstanceId>1</rasd:InstanceId>");
sb.append("<rasd:ResourceType>3</rasd:ResourceType>");
sb.append("<rasd:num_of_sockets>").append(sockets).append("</rasd:num_of_sockets>");
sb.append("<rasd:cpu_per_socket>").append(cpuPerSocket).append("</rasd:cpu_per_socket>");
sb.append("<rasd:threads_per_cpu>").append(threads).append("</rasd:threads_per_cpu>");
sb.append("<rasd:max_num_of_vcpus>").append(maxVcpu).append("</rasd:max_num_of_vcpus>");
sb.append("<rasd:VirtualQuantity>").append(vcpu).append("</rasd:VirtualQuantity>");
sb.append("</Item>");
// Memory
sb.append("<Item>");
sb.append("<rasd:Caption>").append(memMb).append(" MB of memory</rasd:Caption>");
sb.append("<rasd:Description>Memory Size</rasd:Description>");
sb.append("<rasd:InstanceId>2</rasd:InstanceId>");
sb.append("<rasd:ResourceType>4</rasd:ResourceType>");
sb.append("<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>");
sb.append("<rasd:VirtualQuantity>").append(memMb).append("</rasd:VirtualQuantity>");
sb.append("</Item>");
// Disks as Items
int diskUnit = 0;
for (DiskAttachment da : diskAttachments(vm)) {
if (da == null || da.getDisk() == null || StringUtils.isBlank(da.getDisk().getId())) {
continue;
}
final org.apache.cloudstack.veeam.api.dto.Disk d = da.getDisk();
final String diskId = d.getId();
final String storageDomainId = firstStorageDomainId(d);
final String href = storageDomainId + "/" + diskId;
sb.append("<Item>");
sb.append("<rasd:Caption>").append(escapeText(defaultString(d.getAlias()))).append("</rasd:Caption>");
sb.append("<rasd:InstanceId>").append(escapeText(diskId)).append("</rasd:InstanceId>");
sb.append("<rasd:ResourceType>17</rasd:ResourceType>");
sb.append("<rasd:HostResource>").append(escapeText(href)).append("</rasd:HostResource>");
sb.append("<rasd:Parent>").append(ZERO_UUID).append("</rasd:Parent>");
sb.append("<rasd:Template>").append(escapeText(templateId)).append("</rasd:Template>");
sb.append("<rasd:ApplicationList></rasd:ApplicationList>");
sb.append("<rasd:StorageId>").append(escapeText(storageDomainId)).append("</rasd:StorageId>");
sb.append("<rasd:StoragePoolId>").append(ZERO_UUID).append("</rasd:StoragePoolId>");
sb.append("<rasd:CreationDate>").append(creationDate).append("</rasd:CreationDate>");
sb.append("<rasd:LastModified>").append(exportDate).append("</rasd:LastModified>");
sb.append("<rasd:last_modified_date>").append(exportDate).append("</rasd:last_modified_date>");
sb.append("<Type>disk</Type>");
sb.append("<Device>disk</Device>");
sb.append("<rasd:Address>").append(escapeText("{type=drive, bus=0, controller=0, target=0, unit=" + diskUnit + "}")).append("</rasd:Address>");
sb.append("<BootOrder>").append("true".equalsIgnoreCase(da.getBootable()) ? 1 : 0).append("</BootOrder>");
sb.append("<IsPlugged>true</IsPlugged>");
sb.append("<IsReadOnly>").append("true".equalsIgnoreCase(da.getReadOnly())).append("</IsReadOnly>");
sb.append("<Alias>").append(escapeText("ua-" + href)).append("</Alias>");
sb.append("</Item>");
diskUnit++;
}
// NICs as Items
int nicSlot = 0;
for (Nic nic : nics(vm)) {
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()) : "";
sb.append("<Item>");
sb.append("<rasd:Caption>Ethernet adapter on [No Network]</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>");
sb.append("<rasd:ResourceSubType>").append(mapNicResourceSubType(nic.getInterfaceType())).append("</rasd:ResourceSubType>");
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:MACAddress>").append(escapeText(mac)).append("</rasd:MACAddress>");
sb.append("<rasd:speed>10000</rasd:speed>");
sb.append("<Type>interface</Type>");
sb.append("<Device>bridge</Device>");
sb.append("<rasd:Address>").append(escapeText("{type=pci, slot=0x" + String.format("%02x", nicSlot) + ", bus=0x01, domain=0x0000, function=0x0}")).append("</rasd:Address>");
sb.append("<BootOrder>0</BootOrder>");
sb.append("<IsPlugged>").append(escapeText(booleanString(nic.getPlugged(), "true"))).append("</IsPlugged>");
sb.append("<IsReadOnly>false</IsReadOnly>");
sb.append("<Alias>").append(escapeText("ua-" + nicId)).append("</Alias>");
sb.append("</Item>");
nicSlot++;
}
// A few common devices that some consumers expect to exist (kept minimal)
// USB controller
sb.append("<Item>");
sb.append("<rasd:Caption>USB Controller</rasd:Caption>");
sb.append("<rasd:InstanceId>3</rasd:InstanceId>");
sb.append("<rasd:ResourceType>23</rasd:ResourceType>");
sb.append("<rasd:UsbPolicy>DISABLED</rasd:UsbPolicy>");
sb.append("</Item>");
// RNG device
sb.append("<Item>");
sb.append("<rasd:ResourceType>0</rasd:ResourceType>");
sb.append("<rasd:InstanceId>").append(UUID.nameUUIDFromBytes(("rng-" + vmId).getBytes(StandardCharsets.UTF_8))).append("</rasd:InstanceId>");
sb.append("<Type>rng</Type>");
sb.append("<Device>virtio</Device>");
sb.append("<rasd:Address>{type=pci, slot=0x00, bus=0x06, domain=0x0000, function=0x0}</rasd:Address>");
sb.append("<BootOrder>0</BootOrder>");
sb.append("<IsPlugged>true</IsPlugged>");
sb.append("<IsReadOnly>false</IsReadOnly>");
sb.append("<Alias></Alias>");
sb.append("<SpecParams><source>urandom</source></SpecParams>");
sb.append("</Item>");
sb.append("</Content>");
sb.append("</ovf:Envelope>");
return sb.toString();
}
public static void updateFromConfiguration(Vm vm) {
if (ObjectUtils.anyNull(vm.getInitialization(),
vm.getInitialization().getConfiguration(),
vm.getInitialization().getConfiguration().getData())) {
return;
}
OvfXmlUtil.updateFromXml(vm, vm.getInitialization().getConfiguration().getData());
}
protected static void updateFromXml(Vm vm, String ovfXml) {
if (vm == null || StringUtils.isBlank(ovfXml)) {
return;
}
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(ovfXml.getBytes(StandardCharsets.UTF_8)));
XPathFactory xpf = XPathFactory.newInstance();
XPath xpath = xpf.newXPath();
// Register namespace context for XPath
xpath.setNamespaceContext(new OvfNamespaceContext());
Node hwSection = (Node) xpath.evaluate(
"//*[local-name()='Section' and @*[local-name()='type']='ovf:VirtualHardwareSection_Type']",
doc,
XPathConstants.NODE
);
if (hwSection != null) {
// Memory
NodeList memItems = (NodeList) xpath.evaluate(
".//*[local-name()='Item'][*[local-name()='ResourceType' and text()='4']]",
hwSection,
XPathConstants.NODESET
);
if (memItems != null && memItems.getLength() > 0) {
Node memItem = memItems.item(0);
String memStr = childText(memItem, "VirtualQuantity");
if (StringUtils.isNotBlank(memStr)) {
vm.setMemory(memStr);
}
}
// CPU
NodeList cpuItems = (NodeList) xpath.evaluate(
".//*[local-name()='Item'][*[local-name()='ResourceType' and text()='3']]",
hwSection,
XPathConstants.NODESET
);
if (cpuItems != null && cpuItems.getLength() > 0) {
Node cpuItem = cpuItems.item(0);
String socketsStr = childText(cpuItem, "num_of_sockets");
String coresStr = childText(cpuItem, "cpu_per_socket");
String threadsStr = childText(cpuItem, "threads_per_cpu");
if (vm.getCpu() == null) {
vm.setCpu(new Cpu());
}
if (vm.getCpu().getTopology() == null) {
vm.getCpu().setTopology(new Topology());
}
if (StringUtils.isNotBlank(socketsStr)) {
vm.getCpu().getTopology().setSockets(socketsStr);
}
if (StringUtils.isNotBlank(coresStr)) {
vm.getCpu().getTopology().setCores(coresStr);
}
if (StringUtils.isNotBlank(threadsStr)) {
vm.getCpu().getTopology().setThreads(threadsStr);
}
}
}
} catch (Exception e) {
// Ignore parsing errors and keep original VM configuration
}
}
private static String xpathString(XPath xpath, Document doc, String expression) {
try {
String value = (String) xpath.evaluate(expression, doc, XPathConstants.STRING);
return StringUtils.isBlank(value) ? null : value.trim();
} catch (XPathExpressionException e) {
return null;
}
}
private static String childText(Node parent, String localName) {
if (parent == null || StringUtils.isBlank(localName)) {
return null;
}
NodeList children = parent.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
if (child.getNodeType() != Node.ELEMENT_NODE) {
continue;
}
String ln = child.getLocalName();
if (StringUtils.isBlank(ln)) {
ln = child.getNodeName();
}
if (localName.equalsIgnoreCase(ln)) {
return StringUtils.trim(child.getTextContent());
}
}
return null;
}
private static List<DiskAttachment> diskAttachments(Vm vm) {
if (vm.getDiskAttachments() == null) {
return List.of();
}
return vm.getDiskAttachments().getItems();
}
private static List<Nic> nics(Vm vm) {
if (vm.getNics() == null) {
return List.of();
}
return vm.getNics().getItems();
}
private static String inferOsDescription(Vm vm) {
if (vm.getOs() == null) {
return "other";
}
String t = vm.getOs().getType();
if (StringUtils.isBlank(t)) {
return "other";
}
if (t.toLowerCase(Locale.ROOT).contains("win")) {
return "windows";
}
if (t.toLowerCase(Locale.ROOT).contains("linux")) {
return "linux";
}
return t;
}
private static String inferNetworkName(Nic nic) {
return "Network-" + nic.getId();
}
private static String firstStorageDomainId(Disk d) {
if (ObjectUtils.allNotNull(d, d.getStorageDomains()) && CollectionUtils.isNotEmpty(d.getStorageDomains().getItems())) {
return d.getStorageDomains().getItems().get(0).getId();
}
return UUID.randomUUID().toString();
}
private static String mapDiskInterface(String iface) {
if (StringUtils.isBlank(iface)) {
return "VirtIO_SCSI";
}
String v = iface.toLowerCase(Locale.ROOT);
if (v.contains("virtio") && v.contains("scsi")) {
return "VirtIO_SCSI";
}
if (v.contains("virtio")) {
return "VirtIO";
}
if (v.contains("ide")) {
return "IDE";
}
if (v.contains("sata")) {
return "SATA";
}
return iface;
}
private static String mapOvfDiskFormat(String format, String sparse) {
if ("true".equalsIgnoreCase(sparse)) {
return "http://www.vmware.com/specifications/vmdk.html#sparse";
}
return "http://www.vmware.com/specifications/vmdk.html#sparse";
}
private static String mapVolumeFormat(String format) {
if (StringUtils.isBlank(format)) {
return "RAW";
}
String f = format.toLowerCase(Locale.ROOT);
if (f.contains("cow") || f.contains("qcow")) {
return "COW";
}
if (f.contains("raw")) {
return "RAW";
}
return format.toUpperCase(Locale.ROOT);
}
private static String mapVolumeType(String sparse) {
return "true".equalsIgnoreCase(sparse) ? "Sparse" : "Preallocated";
}
private static int mapBiosType(String biosType) {
if (StringUtils.isBlank(biosType)) {
return 2;
}
String t = biosType.toLowerCase(Locale.ROOT);
if (t.contains("uefi") || t.contains("secure")) {
return 2;
}
return 0;
}
private static String mapBalloonEnabled(Vm vm) {
if (vm.getMemoryPolicy() == null || vm.getMemoryPolicy().getBallooning() == null) {
return "true";
}
return "true".equalsIgnoreCase(vm.getMemoryPolicy().getBallooning()) ? "true" : "false";
}
private static int mapNicResourceSubType(String iface) {
if (StringUtils.isBlank(iface)) {
return 3;
}
String v = iface.toLowerCase(Locale.ROOT);
if (v.contains("virtio")) {
return 3;
}
return 3;
}
private static String booleanString(String v, String def) {
if (StringUtils.isBlank(v)) {
return def;
}
if ("true".equalsIgnoreCase(v)) {
return "true";
}
if ("false".equalsIgnoreCase(v)) {
return "false";
}
return def;
}
private static String firstNonBlank(String... vals) {
for (String v : vals) {
if (StringUtils.isNotBlank(v)) {
return v;
}
}
return "";
}
private static String defaultString(String s) {
return s == null ? "" : s;
}
private static long parseLong(String s, long def) {
if (StringUtils.isBlank(s)) {
return def;
}
try {
return Long.parseLong(s);
} catch (Exception ignored) {
return def;
}
}
private static long bytesToGibCeil(long bytes) {
if (bytes <= 0) {
return 0;
}
final long gib = 1024L * 1024L * 1024L;
return (bytes + gib - 1) / gib;
}
private static String formatDate(long epochMillis) {
return OVIRT_DTF.get().format(new Date(epochMillis));
}
private static String escapeText(String s) {
if (s == null) {
return "";
}
return s.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&apos;");
}
private static String escapeAttr(String s) {
return escapeText(s);
}
protected static class OvfNamespaceContext implements NamespaceContext {
@Override
public String getNamespaceURI(String prefix) {
if ("ovf".equals(prefix)) return NS_OVF;
if ("rasd".equals(prefix)) return NS_RASD;
if ("vssd".equals(prefix)) return NS_VSSD;
if ("xsi".equals(prefix)) return NS_XSI;
return XMLConstants.NULL_NS_URI;
}
@Override
public String getPrefix(String namespaceURI) {
return null;
}
@Override
public java.util.Iterator<String> getPrefixes(String namespaceURI) {
return null;
}
}
}

View File

@ -17,27 +17,41 @@
package org.apache.cloudstack.veeam.api.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
public class Tag extends BaseDto {
private String name;
private String description;
private Ref parent;
private Ref vm;
@JsonInclude(JsonInclude.Include.NON_NULL)
public final class Bios {
private String type; // "uefi" or "bios" or whatever mapping you choose
private BootMenu bootMenu = new BootMenu();
public String getType() {
return type;
public String getName() {
return name;
}
public void setType(String type) {
this.type = type;
public void setName(String name) {
this.name = name;
}
public BootMenu getBootMenu() {
return bootMenu;
public String getDescription() {
return description;
}
public void setBootMenu(BootMenu bootMenu) {
this.bootMenu = bootMenu;
public void setDescription(String description) {
this.description = description;
}
public Ref getParent() {
return parent;
}
public void setParent(Ref parent) {
this.parent = parent;
}
public Ref getVm() {
return vm;
}
public void setVm(Ref vm) {
this.vm = vm;
}
}

View File

@ -42,6 +42,7 @@ public final class Vm extends BaseDto {
private Ref cluster;
private Ref host;
private String memory; // bytes
private MemoryPolicy memoryPolicy;
private Cpu cpu;
private Os os;
private Bios bios;
@ -53,8 +54,22 @@ public final class Vm extends BaseDto {
private List<Link> link; // related resources
private EmptyElement tags; // empty <tags/>
private NamedList<DiskAttachment> diskAttachments;
private Nics nics;
private VmInitialization initialization;
private NamedList<Nic> nics;
private Initialization initialization;
private Ref cpuProfile;
public EmptyElement io = new EmptyElement();
public EmptyElement migration = new EmptyElement();
public EmptyElement sso = new EmptyElement();
public EmptyElement usb = new EmptyElement();
public EmptyElement quota = new EmptyElement();
public EmptyElement highAvailability = new EmptyElement();
public EmptyElement largeIcon = new EmptyElement();
public EmptyElement smallIcon = new EmptyElement();
public EmptyElement placementPolicy = new EmptyElement();
public EmptyElement timeZone = new EmptyElement();
public EmptyElement display = new EmptyElement();
public String getName() {
return name;
@ -152,6 +167,14 @@ public final class Vm extends BaseDto {
this.memory = memory;
}
public MemoryPolicy getMemoryPolicy() {
return memoryPolicy;
}
public void setMemoryPolicy(MemoryPolicy memoryPolicy) {
this.memoryPolicy = memoryPolicy;
}
public Cpu getCpu() {
return cpu;
}
@ -232,22 +255,145 @@ public final class Vm extends BaseDto {
this.diskAttachments = diskAttachments;
}
public Nics getNics() {
public NamedList<Nic> getNics() {
return nics;
}
public void setNics(Nics nics) {
public void setNics(NamedList<Nic> nics) {
this.nics = nics;
}
public VmInitialization getInitialization() {
public Initialization getInitialization() {
return initialization;
}
public void setInitialization(VmInitialization initialization) {
public void setInitialization(Initialization initialization) {
this.initialization = initialization;
}
public Ref getCpuProfile() {
return cpuProfile;
}
public void setCpuProfile(Ref cpuProfile) {
this.cpuProfile = cpuProfile;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static final class Bios {
private String type; // "uefi" or "bios" or whatever mapping you choose
private BootMenu bootMenu = new BootMenu();
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public BootMenu getBootMenu() {
return bootMenu;
}
public void setBootMenu(BootMenu bootMenu) {
this.bootMenu = bootMenu;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class BootMenu {
private String enabled;
public String getEnabled() {
return enabled;
}
public void setEnabled(String enabled) {
this.enabled = enabled;
}
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static final class MemoryPolicy {
private String guaranteed;
private String max;
private String ballooning;
public String getGuaranteed() {
return guaranteed;
}
public void setGuaranteed(String guaranteed) {
this.guaranteed = guaranteed;
}
public String getMax() {
return max;
}
public void setMax(String max) {
this.max = max;
}
public String getBallooning() {
return ballooning;
}
public void setBallooning(String ballooning) {
this.ballooning = ballooning;
}
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Initialization {
private String customScript;
private Configuration configuration;
public String getCustomScript() {
return customScript;
}
public void setCustomScript(String customScript) {
this.customScript = customScript;
}
public Configuration getConfiguration() {
return configuration;
}
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
@JsonInclude(JsonInclude.Include.NON_NULL)
public static class Configuration {
private String data;
private String type;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
}
public static Vm of(String href, String id) {
Vm vm = new Vm();
vm.setHref(href);

View File

@ -1,34 +0,0 @@
// 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.veeam.api.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
public class VmInitialization {
private String customScript;
public String getCustomScript() {
return customScript;
}
public void setCustomScript(String customScript) {
this.customScript = customScript;
}
}

View File

@ -43,6 +43,7 @@
<bean id="disksRouteHandler" class="org.apache.cloudstack.veeam.api.DisksRouteHandler"/>
<bean id="imageTransfersRouteHandler" class="org.apache.cloudstack.veeam.api.ImageTransfersRouteHandler"/>
<bean id="jobsRouteHandler" class="org.apache.cloudstack.veeam.api.JobsRouteHandler"/>
<bean id="tagsRouteHandler" class="org.apache.cloudstack.veeam.api.TagsRouteHandler"/>
<bean id="veeamControlService" class="org.apache.cloudstack.veeam.VeeamControlServiceImpl" >
<property name="routeHandlers" value="#{routeHandlerRegistry.registered}" />

View File

@ -0,0 +1,618 @@
<?xml version="1.0" encoding="UTF-8"?>
<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="ddf18375-4c69-4ec5-8371-6dabc94e4e60/5cbc2ed5-de89-44a4-aa58-b7161f8afaf8" ovf:id="5cbc2ed5-de89-44a4-aa58-b7161f8afaf8" ovf:size="4096" ovf:description="Active VM" ovf:disk_storage_type="IMAGE" ovf:cinder_volume_type=""></File>
</References>
<NetworkSection>
<Info>List of networks</Info>
</NetworkSection>
<Section xsi:type="ovf:DiskSection_Type">
<Info>List of Virtual Disks</Info>
<Disk ovf:diskId="5cbc2ed5-de89-44a4-aa58-b7161f8afaf8" ovf:size="1" ovf:actual_size="1" ovf:vm_snapshot_id="3d298647-57d5-4a2b-90e7-226bdb4f1800" ovf:parentRef="" ovf:fileRef="ddf18375-4c69-4ec5-8371-6dabc94e4e60/5cbc2ed5-de89-44a4-aa58-b7161f8afaf8" ovf:format="http://www.vmware.com/specifications/vmdk.html#sparse" ovf:volume-format="RAW" 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="test-vm-abhisar_Disk1" ovf:disk-description="" ovf:wipe-after-delete="false"></Disk>
</Section>
<Content ovf:id="out" xsi:type="ovf:VirtualSystem_Type">
<Name>test-vm-abhisar</Name>
<Description></Description>
<Comment></Comment>
<CreationDate>2026/01/07 13:37:09</CreationDate>
<ExportDate>2026/01/08 04:07:00</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>1024</MinAllocatedMem>
<IsStateless>false</IsStateless>
<IsRunAndPause>false</IsRunAndPause>
<AutoStartup>false</AutoStartup>
<Priority>0</Priority>
<CreatedByUserId>c067a148-e4d5-11f0-98ce-00163e6c35f4</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>2</BiosType>
<CustomCpuName></CustomCpuName>
<PredefinedProperties></PredefinedProperties>
<UserDefinedProperties></UserDefinedProperties>
<MaxMemorySizeMb>4096</MaxMemorySizeMb>
<MultiQueuesEnabled>true</MultiQueuesEnabled>
<VirtioScsiMultiQueuesEnabled>false</VirtioScsiMultiQueuesEnabled>
<UseHostCpu>false</UseHostCpu>
<BalloonEnabled>true</BalloonEnabled>
<CpuPinningPolicy>0</CpuPinningPolicy>
<ClusterName>Default</ClusterName>
<TemplateId>00000000-0000-0000-0000-000000000000</TemplateId>
<TemplateName>Blank</TemplateName>
<IsInitilized>true</IsInitilized>
<Origin>3</Origin>
<quota_id>95e46398-e4d5-11f0-bb71-00163e6c35f4</quota_id>
<DefaultDisplayType>2</DefaultDisplayType>
<TrustedService>false</TrustedService>
<OriginalTemplateId>00000000-0000-0000-0000-000000000000</OriginalTemplateId>
<OriginalTemplateName>Blank</OriginalTemplateName>
<UseLatestVersion>false</UseLatestVersion>
<StopTime>2026/01/07 13:37:09</StopTime>
<BootTime>2026/01/07 13:38:03</BootTime>
<Downtime>0</Downtime>
<Section ovf:id="9ce5bba0-6e01-4467-95a1-c4e28ad3ea43" ovf:required="false" xsi:type="ovf:OperatingSystemSection_Type">
<Info>Guest Operating System</Info>
<Description>other</Description>
</Section>
<Section xsi:type="ovf:VirtualHardwareSection_Type">
<Info>1 CPU, 1024 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>16</rasd:max_num_of_vcpus>
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Caption>1024 MB of memory</rasd:Caption>
<rasd:Description>Memory Size</rasd:Description>
<rasd:InstanceId>2</rasd:InstanceId>
<rasd:ResourceType>4</rasd:ResourceType>
<rasd:AllocationUnits>MegaBytes</rasd:AllocationUnits>
<rasd:VirtualQuantity>1024</rasd:VirtualQuantity>
</Item>
<Item>
<rasd:Caption>test-vm-abhisar_Disk1</rasd:Caption>
<rasd:InstanceId>5cbc2ed5-de89-44a4-aa58-b7161f8afaf8</rasd:InstanceId>
<rasd:ResourceType>17</rasd:ResourceType>
<rasd:HostResource>ddf18375-4c69-4ec5-8371-6dabc94e4e60/5cbc2ed5-de89-44a4-aa58-b7161f8afaf8</rasd:HostResource>
<rasd:Parent>00000000-0000-0000-0000-000000000000</rasd:Parent>
<rasd:Template>00000000-0000-0000-0000-000000000000</rasd:Template>
<rasd:ApplicationList></rasd:ApplicationList>
<rasd:StorageId>41609681-c92a-410a-bcc2-5b5e1305cdd1</rasd:StorageId>
<rasd:StoragePoolId>91f4d826-e4d5-11f0-bd93-00163e6c35f4</rasd:StoragePoolId>
<rasd:CreationDate>2026/01/07 13:36:59</rasd:CreationDate>
<rasd:LastModified>2026/01/07 13:53:36</rasd:LastModified>
<rasd:last_modified_date>2026/01/08 04:07:00</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-ddf18375-4c69-4ec5-8371-6dabc94e4e60</Alias>
</Item>
<Item>
<rasd:Caption>Ethernet adapter on [No Network]</rasd:Caption>
<rasd:InstanceId>9a6f804d-b305-41db-b1b4-bdfd82c4b446</rasd:InstanceId>
<rasd:ResourceType>10</rasd:ResourceType>
<rasd:OtherResourceType></rasd:OtherResourceType>
<rasd:ResourceSubType>3</rasd:ResourceSubType>
<rasd:Connection></rasd:Connection>
<rasd:Linked>true</rasd:Linked>
<rasd:Name>nic1</rasd:Name>
<rasd:ElementName>nic1</rasd:ElementName>
<rasd:MACAddress>56:6f:9f:c0:00:07</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-9a6f804d-b305-41db-b1b4-bdfd82c4b446</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:Caption>Graphical Controller</rasd:Caption>
<rasd:InstanceId>0d4a490c-f9d7-45dd-8686-69d5bae218d6</rasd:InstanceId>
<rasd:ResourceType>20</rasd:ResourceType>
<rasd:VirtualQuantity>1</rasd:VirtualQuantity>
<rasd:SinglePciQxl>false</rasd:SinglePciQxl>
<Type>video</Type>
<Device>vga</Device>
<rasd:Address>{type=pci, slot=0x01, bus=0x00, domain=0x0000, function=0x0}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>ua-0d4a490c-f9d7-45dd-8686-69d5bae218d6</Alias>
<SpecParams>
<vram>16384</vram>
</SpecParams>
</Item>
<Item>
<rasd:Caption>Graphical Framebuffer</rasd:Caption>
<rasd:InstanceId>f62554f1-05fe-472e-a34b-9e6b980ad59f</rasd:InstanceId>
<rasd:ResourceType>26</rasd:ResourceType>
<Type>graphics</Type>
<Device>vnc</Device>
<rasd:Address></rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias></Alias>
</Item>
<Item>
<rasd:Caption>CDROM</rasd:Caption>
<rasd:InstanceId>9c38cc6a-9def-46f3-bf1c-2b3f4aa6b764</rasd:InstanceId>
<rasd:ResourceType>15</rasd:ResourceType>
<Type>disk</Type>
<Device>cdrom</Device>
<rasd:Address>{type=drive, bus=0, controller=0, target=0, unit=2}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>true</IsReadOnly>
<Alias>ua-9c38cc6a-9def-46f3-bf1c-2b3f4aa6b764</Alias>
<SpecParams>
<path></path>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>a737450e-20b5-427e-a18b-85ec20683e31</rasd:InstanceId>
<Type>channel</Type>
<Device>unix</Device>
<rasd:Address>{type=virtio-serial, bus=0, controller=0, port=1}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>channel0</Alias>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>1d3ba276-9e8d-4a16-9cdf-dfd25180b7bc</rasd:InstanceId>
<Type>channel</Type>
<Device>unix</Device>
<rasd:Address>{type=virtio-serial, bus=0, controller=0, port=2}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>channel1</Alias>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>8f21ce42-9499-4ded-88d4-04dff2fdc3ff</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x0, multifunction=on}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.1</Alias>
<SpecParams>
<index>1</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>d1b9d421-1a57-469d-97fe-0682ad4594c3</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x1}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.2</Alias>
<SpecParams>
<index>2</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>768c4772-eb7a-4f0f-85a7-2b94e20fe78c</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x2}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.3</Alias>
<SpecParams>
<index>3</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>d20bae3b-f5d7-4131-b00a-3cf66f390434</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x3}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.4</Alias>
<SpecParams>
<index>4</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>5887f3ad-c575-488e-9138-fca9c7064ae5</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x4}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.5</Alias>
<SpecParams>
<index>5</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>f880f086-227e-4e25-b2fc-8a3d13d1f1bd</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x5}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.6</Alias>
<SpecParams>
<index>6</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>d64f62a0-6176-482b-8d24-f82fb32b8f12</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x6}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.7</Alias>
<SpecParams>
<index>7</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>1544f32e-1e94-4e10-b198-7c5e95ab280d</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x7}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.8</Alias>
<SpecParams>
<index>8</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>7dd5080f-8c04-4593-8c6a-1dc5cd6c3e3e</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x0, multifunction=on}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.9</Alias>
<SpecParams>
<index>9</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>4dab4257-2729-482c-b4e1-6a3c05161153</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x1}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.10</Alias>
<SpecParams>
<index>10</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>99effa2f-2963-4abd-9eab-1cbe8e913ca4</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x2}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.11</Alias>
<SpecParams>
<index>11</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>2a376983-897b-4396-be32-89f2a9ca7d22</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x3}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.12</Alias>
<SpecParams>
<index>12</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>2e763d82-4475-4268-bc0a-07c915ec19c8</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x4}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.13</Alias>
<SpecParams>
<index>13</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>ef39155f-760e-4374-afb9-ff05cc8b9609</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x5}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.14</Alias>
<SpecParams>
<index>14</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>74be06f0-84b6-472e-a054-486343f66084</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x6}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.15</Alias>
<SpecParams>
<index>15</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>c68db43a-fa3a-4689-941d-b477d2676d27</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x7}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.16</Alias>
<SpecParams>
<index>16</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>d11cbe26-ee82-4e15-b8eb-2aa7b285d00d</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x0, multifunction=on}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.17</Alias>
<SpecParams>
<index>17</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>c2ef6c73-f633-41c1-8736-7e9c8d748ac2</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x1}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.18</Alias>
<SpecParams>
<index>18</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>5944d260-08c3-4f12-aa22-1e9ac76ae6c0</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x2}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.19</Alias>
<SpecParams>
<index>19</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>8c7ad6aa-ac22-4d98-86b7-45f3a13c98da</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x3}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.20</Alias>
<SpecParams>
<index>20</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>dc1cfae5-682d-4bb5-a53e-d604852e62cd</rasd:InstanceId>
<Type>controller</Type>
<Device>pci</Device>
<rasd:Address>{type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x4}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>pci.21</Alias>
<SpecParams>
<index>21</index>
<model>pcie-root-port</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>6117753b-8ce6-4568-8e09-c8b686396334</rasd:InstanceId>
<Type>controller</Type>
<Device>sata</Device>
<rasd:Address>{type=pci, slot=0x1f, bus=0x00, domain=0x0000, function=0x2}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>ide</Alias>
<SpecParams>
<index>0</index>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>17976687-41f8-4f7c-97f5-a76a282c40e4</rasd:InstanceId>
<Type>controller</Type>
<Device>virtio-serial</Device>
<rasd:Address>{type=pci, slot=0x00, bus=0x03, domain=0x0000, function=0x0}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>ua-17976687-41f8-4f7c-97f5-a76a282c40e4</Alias>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>97f6991c-e4d5-11f0-9b4a-00163e6c35f4</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>ua-97f6991c-e4d5-11f0-9b4a-00163e6c35f4</Alias>
<SpecParams>
<source>urandom</source>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>0eb75625-9891-4b03-9541-c58c43c323b2</rasd:InstanceId>
<Type>controller</Type>
<Device>virtio-scsi</Device>
<rasd:Address>{type=pci, slot=0x00, bus=0x02, domain=0x0000, function=0x0}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>ua-0eb75625-9891-4b03-9541-c58c43c323b2</Alias>
<SpecParams>
<ioThreadId></ioThreadId>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>59536909-bac6-4202-b2ad-d84a22a41013</rasd:InstanceId>
<Type>balloon</Type>
<Device>memballoon</Device>
<rasd:Address>{type=pci, slot=0x00, bus=0x05, domain=0x0000, function=0x0}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>true</IsReadOnly>
<Alias>ua-59536909-bac6-4202-b2ad-d84a22a41013</Alias>
<SpecParams>
<model>virtio</model>
</SpecParams>
</Item>
<Item>
<rasd:ResourceType>0</rasd:ResourceType>
<rasd:InstanceId>e95647b0-4bb2-4ccb-b867-cbde06311038</rasd:InstanceId>
<Type>controller</Type>
<Device>usb</Device>
<rasd:Address>{type=pci, slot=0x00, bus=0x04, domain=0x0000, function=0x0}</rasd:Address>
<BootOrder>0</BootOrder>
<IsPlugged>true</IsPlugged>
<IsReadOnly>false</IsReadOnly>
<Alias>ua-e95647b0-4bb2-4ccb-b867-cbde06311038</Alias>
<SpecParams>
<index>0</index>
<model>qemu-xhci</model>
</SpecParams>
</Item>
</Section>
<Section xsi:type="ovf:SnapshotsSection_Type">
<Snapshot ovf:id="3d298647-57d5-4a2b-90e7-226bdb4f1800">
<Type>ACTIVE</Type>
<Description>Active VM</Description>
<CreationDate>2026/01/07 13:37:09</CreationDate>
</Snapshot>
</Section>
</Content>
</ovf:Envelope>

View File

@ -0,0 +1,28 @@
package org.apache.cloudstack.veeam.api.dto;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class OvfXmlUtilTest {
String configuration = "<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\">" +
"<Content ovf:id=\"out\" xsi:type=\"ovf:VirtualSystem_Type\"><Name>adm-v9</Name><Description>adm-v9</Description>"+
"<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>MegaBytes</rasd:AllocationUnits><rasd:VirtualQuantity>512</rasd:VirtualQuantity></Item>" +
"</Section></Content></ovf:Envelope>";
@Test
public void updateFromXml_parsesDetails() {
Vm vm = new Vm();
OvfXmlUtil.updateFromXml(vm, configuration);
assertEquals(String.valueOf(512L), vm.getMemory());
assertEquals("1", vm.getCpu().getTopology().getSockets());
assertEquals("1", vm.getCpu().getTopology().getCores());
assertEquals("1", vm.getCpu().getTopology().getThreads());
}
}

View File

@ -137,6 +137,10 @@ public class IncrementalBackupServiceImpl extends ManagerBase implements Increme
throw new CloudRuntimeException("VM must be running or stopped to start backup");
}
if (vm.getBackupOfferingId() == null) {
throw new CloudRuntimeException("VM not assigned a backup offering");
}
Backup existingBackup = backupDao.findByVmId(vmId);
if (existingBackup != null && existingBackup.getStatus() == Backup.Status.BackingUp) {
throw new CloudRuntimeException("Backup already in progress for VM: " + vmId);