diff --git a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java index bacb09b9879..5f2225c410f 100644 --- a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java +++ b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java @@ -60,4 +60,6 @@ public interface ResourceTagDao extends GenericDao { void removeByResourceIdAndKey(long resourceId, ResourceObjectType resourceType, String key); List listByResourceUuid(String resourceUuid); + + List listByResourceType(ResourceObjectType resourceType); } diff --git a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java index cc9d99e6ab1..6fb7f71b269 100644 --- a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java @@ -120,4 +120,11 @@ public class ResourceTagsDaoImpl extends GenericDaoBase imp sc.setParameters("resourceUuid", resourceUuid); return listBy(sc); } + + @Override + public List listByResourceType(ResourceObjectType resourceType) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("resourceType", resourceType); + return listBy(sc); + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java index 781cb6b94d6..eb2f94628fd 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/adapter/ServerAdapter.java @@ -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 getDummyTags() { + Map 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 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 listAllTags() { + List tags = new ArrayList<>(getDummyTags().values()); + List 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; + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/TagsRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/TagsRouteHandler.java new file mode 100644 index 00000000000..e81709cb212 --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/TagsRouteHandler.java @@ -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 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 result = serverAdapter.listAllTags(); + NamedList 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); + } + } +} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java index 70a34ba08a6..eba432b7879 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/VmsRouteHandler.java @@ -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 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 nics = serverAdapter.listNicsByInstanceUuid(id); - Nics response = new Nics(nics); + NamedList response = NamedList.of("nic", nics); io.getWriter().write(resp, HttpServletResponse.SC_OK, response, outFormat); } catch (InvalidParameterValueException e) { io.notFound(resp, e.getMessage(), outFormat); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java index bdae4983694..49bf1f1caba 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/AsyncJobJoinVOToJobConverter.java @@ -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; } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/ResourceTagVOToTagConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/ResourceTagVOToTagConverter.java new file mode 100644 index 00000000000..d22a234d9e4 --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/ResourceTagVOToTagConverter.java @@ -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 toTags(List vos) { + return vos.stream().map(ResourceTagVOToTagConverter::toTag).collect(Collectors.toList()); + } +} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java index 36e1a04c4b4..6c7c8bddd79 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/UserVmJoinVOToVmConverter.java @@ -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 hostResolver, - final Function> disksResolver, final Function> nicsResolver) { + final Function> disksResolver, + final Function> 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 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 toVmList(final List srcList, final Function hostResolver) { return srcList.stream() - .map(v -> toVm(v, hostResolver, null, null)) + .map(v -> toVm(v, hostResolver, null, null, false)) .collect(Collectors.toList()); } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/BaseDto.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/BaseDto.java index 5ae2eb82422..5f98ca775dc 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/BaseDto.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/BaseDto.java @@ -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; diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/BootMenu.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/BootMenu.java deleted file mode 100644 index 6a354d5e749..00000000000 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/BootMenu.java +++ /dev/null @@ -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; - } -} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/HardwareInformation.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/HardwareInformation.java deleted file mode 100644 index 0ded2f095f3..00000000000 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/HardwareInformation.java +++ /dev/null @@ -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; - } -} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Host.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Host.java index c937cdb564b..8c4dba1d57c 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Host.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Host.java @@ -259,4 +259,53 @@ public class Host extends BaseDto { public void setLink(List 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; + } + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/HostSummary.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/HostSummary.java deleted file mode 100644 index a1d4b4aa734..00000000000 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/HostSummary.java +++ /dev/null @@ -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; - } -} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Nics.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Nics.java deleted file mode 100644 index 1d1a4667501..00000000000 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Nics.java +++ /dev/null @@ -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; - - public Nics() { - } - - public Nics(final List nic) { - this.nic = nic; - } -} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Os.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Os.java index da73ebd9069..af17151d433 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Os.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Os.java @@ -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 devices; + + public NamedList getDevices() { + return devices; + } + + public void setDevices(NamedList devices) { + this.devices = devices; + } + } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtil.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtil.java new file mode 100644 index 00000000000..3b0662b7c6b --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtil.java @@ -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 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(""); + sb.append(""); + + // --- References (from disks) --- + sb.append(""); + 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(""); + } + sb.append(""); + + // --- NetworkSection --- + sb.append(""); + sb.append("List of networks"); + // 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(""); + sb.append("").append(escapeText(defaultString(nic.getDescription()))).append(""); + sb.append(""); + } + sb.append(""); + + // --- DiskSection --- + sb.append("
"); + sb.append("List of Virtual Disks"); + 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(" 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(">"); + } + sb.append("
"); + + // --- Content / VirtualSystem --- + sb.append(""); + sb.append("").append(escapeText(vmName)).append(""); + sb.append("").append(escapeText(vmDesc)).append(""); + sb.append(""); + sb.append("").append(creationDate).append(""); + sb.append("").append(exportDate).append(""); + sb.append("false"); + sb.append("guest_agent"); + sb.append("false"); + sb.append("1"); + sb.append("Etc/GMT"); + sb.append("0"); + sb.append("11"); + sb.append("4.8"); + sb.append("1"); + sb.append("AUTO_RESUME"); + sb.append("").append(memMb).append(""); + sb.append("").append(escapeText(booleanString(vm.getStateless(), "false"))).append(""); + sb.append("false"); + sb.append("false"); + sb.append("0"); + sb.append("").append(ZERO_UUID).append(""); + sb.append("0"); + sb.append("").append(escapeText(booleanString(vm.getBios() != null && vm.getBios().getBootMenu() != null ? vm.getBios().getBootMenu().getEnabled() : null, "false"))).append(""); + sb.append("true"); + sb.append("true"); + sb.append("false"); + sb.append("LOCK_SCREEN"); + sb.append("0"); + sb.append(""); + sb.append("").append(mapBiosType(vm.getBios() != null ? vm.getBios().getType() : null)).append(""); + sb.append(""); + sb.append(""); + sb.append(""); + sb.append("").append(memMb).append(""); + sb.append("true"); + sb.append("false"); + sb.append("false"); + sb.append("").append(mapBalloonEnabled(vm)).append(""); + sb.append("0"); + sb.append(""); + sb.append("").append(escapeText(templateId)).append(""); + sb.append("").append(escapeText(templateName)).append(""); + sb.append("true"); + sb.append("3"); + sb.append("").append(ZERO_UUID).append(""); + sb.append("2"); + sb.append("false"); + sb.append("").append(escapeText(templateId)).append(""); + sb.append("").append(escapeText(templateName)).append(""); + sb.append("false"); + sb.append("").append(stopTime).append(""); + sb.append("").append(bootTime).append(""); + sb.append("0"); + + // --- Operating system section --- + sb.append("
"); + sb.append("Guest Operating System"); + sb.append("").append(escapeText(inferOsDescription(vm))).append(""); + sb.append("
"); + + // --- Virtual hardware section --- + sb.append("
"); + sb.append("").append(vcpu).append(" CPU, ").append(memMb).append(" Memory"); + sb.append(""); + sb.append("ENGINE 4.4.0.0"); + sb.append(""); + + // CPU + sb.append(""); + sb.append("").append(vcpu).append(" virtual cpu"); + sb.append("Number of virtual CPU"); + sb.append("1"); + sb.append("3"); + sb.append("").append(sockets).append(""); + sb.append("").append(cpuPerSocket).append(""); + sb.append("").append(threads).append(""); + sb.append("").append(maxVcpu).append(""); + sb.append("").append(vcpu).append(""); + sb.append(""); + + // Memory + sb.append(""); + sb.append("").append(memMb).append(" MB of memory"); + sb.append("Memory Size"); + sb.append("2"); + sb.append("4"); + sb.append("MegaBytes"); + sb.append("").append(memMb).append(""); + sb.append(""); + + // 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(""); + sb.append("").append(escapeText(defaultString(d.getAlias()))).append(""); + sb.append("").append(escapeText(diskId)).append(""); + sb.append("17"); + sb.append("").append(escapeText(href)).append(""); + sb.append("").append(ZERO_UUID).append(""); + sb.append("").append(escapeText(templateId)).append(""); + sb.append(""); + sb.append("").append(escapeText(storageDomainId)).append(""); + sb.append("").append(ZERO_UUID).append(""); + sb.append("").append(creationDate).append(""); + sb.append("").append(exportDate).append(""); + sb.append("").append(exportDate).append(""); + sb.append("disk"); + sb.append("disk"); + sb.append("").append(escapeText("{type=drive, bus=0, controller=0, target=0, unit=" + diskUnit + "}")).append(""); + sb.append("").append("true".equalsIgnoreCase(da.getBootable()) ? 1 : 0).append(""); + sb.append("true"); + sb.append("").append("true".equalsIgnoreCase(da.getReadOnly())).append(""); + sb.append("").append(escapeText("ua-" + href)).append(""); + sb.append(""); + 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(""); + sb.append("Ethernet adapter on [No Network]"); + sb.append("").append(escapeText(nicId)).append(""); + sb.append("10"); + sb.append(""); + sb.append("").append(mapNicResourceSubType(nic.getInterfaceType())).append(""); + sb.append("").append(escapeText(defaultString(inferNetworkName(nic)))).append(""); + sb.append("").append(escapeText(booleanString(nic.getLinked(), "true"))).append(""); + sb.append("").append(escapeText(nicName)).append(""); + sb.append("").append(escapeText(nicName)).append(""); + sb.append("").append(escapeText(mac)).append(""); + sb.append("10000"); + sb.append("interface"); + sb.append("bridge"); + sb.append("").append(escapeText("{type=pci, slot=0x" + String.format("%02x", nicSlot) + ", bus=0x01, domain=0x0000, function=0x0}")).append(""); + sb.append("0"); + sb.append("").append(escapeText(booleanString(nic.getPlugged(), "true"))).append(""); + sb.append("false"); + sb.append("").append(escapeText("ua-" + nicId)).append(""); + sb.append(""); + nicSlot++; + } + + // A few common devices that some consumers expect to exist (kept minimal) + // USB controller + sb.append(""); + sb.append("USB Controller"); + sb.append("3"); + sb.append("23"); + sb.append("DISABLED"); + sb.append(""); + + // RNG device + sb.append(""); + sb.append("0"); + sb.append("").append(UUID.nameUUIDFromBytes(("rng-" + vmId).getBytes(StandardCharsets.UTF_8))).append(""); + sb.append("rng"); + sb.append("virtio"); + sb.append("{type=pci, slot=0x00, bus=0x06, domain=0x0000, function=0x0}"); + sb.append("0"); + sb.append("true"); + sb.append("false"); + sb.append(""); + sb.append("urandom"); + sb.append(""); + + sb.append(""); + sb.append(""); + + 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 diskAttachments(Vm vm) { + if (vm.getDiskAttachments() == null) { + return List.of(); + } + return vm.getDiskAttachments().getItems(); + } + + private static List 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("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } + + 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 getPrefixes(String namespaceURI) { + return null; + } + } +} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Bios.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Tag.java similarity index 58% rename from plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Bios.java rename to plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Tag.java index ca68bfe475a..1a9493160b6 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Bios.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Tag.java @@ -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; } } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java index 9d18dcc2234..227845a37b0 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Vm.java @@ -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; // related resources private EmptyElement tags; // empty private NamedList diskAttachments; - private Nics nics; - private VmInitialization initialization; + private NamedList 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 getNics() { return nics; } - public void setNics(Nics nics) { + public void setNics(NamedList 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); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/VmInitialization.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/VmInitialization.java deleted file mode 100644 index a9e77b01a1c..00000000000 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/VmInitialization.java +++ /dev/null @@ -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; - } -} diff --git a/plugins/integrations/veeam-control-service/src/main/resources/META-INF/cloudstack/veeam-control-service/spring-veeam-control-service-context.xml b/plugins/integrations/veeam-control-service/src/main/resources/META-INF/cloudstack/veeam-control-service/spring-veeam-control-service-context.xml index f56a19d8471..cbe11724648 100644 --- a/plugins/integrations/veeam-control-service/src/main/resources/META-INF/cloudstack/veeam-control-service/spring-veeam-control-service-context.xml +++ b/plugins/integrations/veeam-control-service/src/main/resources/META-INF/cloudstack/veeam-control-service/spring-veeam-control-service-context.xml @@ -43,6 +43,7 @@ + diff --git a/plugins/integrations/veeam-control-service/src/main/resources/test.xml b/plugins/integrations/veeam-control-service/src/main/resources/test.xml new file mode 100644 index 00000000000..8d39bd42480 --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/resources/test.xml @@ -0,0 +1,618 @@ + + + + + + + List of networks + +
+ List of Virtual Disks + +
+ + test-vm-abhisar + + + 2026/01/07 13:37:09 + 2026/01/08 04:07:00 + false + guest_agent + false + 1 + Etc/GMT + 0 + 11 + 4.8 + 1 + AUTO_RESUME + 1024 + false + false + false + 0 + c067a148-e4d5-11f0-98ce-00163e6c35f4 + 0 + false + true + true + false + LOCK_SCREEN + 0 + + 2 + + + + 4096 + true + false + false + true + 0 + Default + 00000000-0000-0000-0000-000000000000 + Blank + true + 3 + 95e46398-e4d5-11f0-bb71-00163e6c35f4 + 2 + false + 00000000-0000-0000-0000-000000000000 + Blank + false + 2026/01/07 13:37:09 + 2026/01/07 13:38:03 + 0 +
+ Guest Operating System + other +
+
+ 1 CPU, 1024 Memory + + ENGINE 4.4.0.0 + + + 1 virtual cpu + Number of virtual CPU + 1 + 3 + 1 + 1 + 1 + 16 + 1 + + + 1024 MB of memory + Memory Size + 2 + 4 + MegaBytes + 1024 + + + test-vm-abhisar_Disk1 + 5cbc2ed5-de89-44a4-aa58-b7161f8afaf8 + 17 + ddf18375-4c69-4ec5-8371-6dabc94e4e60/5cbc2ed5-de89-44a4-aa58-b7161f8afaf8 + 00000000-0000-0000-0000-000000000000 + 00000000-0000-0000-0000-000000000000 + + 41609681-c92a-410a-bcc2-5b5e1305cdd1 + 91f4d826-e4d5-11f0-bd93-00163e6c35f4 + 2026/01/07 13:36:59 + 2026/01/07 13:53:36 + 2026/01/08 04:07:00 + disk + disk + {type=drive, bus=0, controller=0, target=0, unit=0} + 1 + true + false + ua-ddf18375-4c69-4ec5-8371-6dabc94e4e60 + + + Ethernet adapter on [No Network] + 9a6f804d-b305-41db-b1b4-bdfd82c4b446 + 10 + + 3 + + true + nic1 + nic1 + 56:6f:9f:c0:00:07 + 10000 + interface + bridge + {type=pci, slot=0x00, bus=0x01, domain=0x0000, function=0x0} + 0 + true + false + ua-9a6f804d-b305-41db-b1b4-bdfd82c4b446 + + + USB Controller + 3 + 23 + DISABLED + + + Graphical Controller + 0d4a490c-f9d7-45dd-8686-69d5bae218d6 + 20 + 1 + false + video + vga + {type=pci, slot=0x01, bus=0x00, domain=0x0000, function=0x0} + 0 + true + false + ua-0d4a490c-f9d7-45dd-8686-69d5bae218d6 + + 16384 + + + + Graphical Framebuffer + f62554f1-05fe-472e-a34b-9e6b980ad59f + 26 + graphics + vnc + + 0 + true + false + + + + CDROM + 9c38cc6a-9def-46f3-bf1c-2b3f4aa6b764 + 15 + disk + cdrom + {type=drive, bus=0, controller=0, target=0, unit=2} + 0 + true + true + ua-9c38cc6a-9def-46f3-bf1c-2b3f4aa6b764 + + + + + + 0 + a737450e-20b5-427e-a18b-85ec20683e31 + channel + unix + {type=virtio-serial, bus=0, controller=0, port=1} + 0 + true + false + channel0 + + + 0 + 1d3ba276-9e8d-4a16-9cdf-dfd25180b7bc + channel + unix + {type=virtio-serial, bus=0, controller=0, port=2} + 0 + true + false + channel1 + + + 0 + 8f21ce42-9499-4ded-88d4-04dff2fdc3ff + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x0, multifunction=on} + 0 + true + false + pci.1 + + 1 + pcie-root-port + + + + 0 + d1b9d421-1a57-469d-97fe-0682ad4594c3 + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x1} + 0 + true + false + pci.2 + + 2 + pcie-root-port + + + + 0 + 768c4772-eb7a-4f0f-85a7-2b94e20fe78c + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x2} + 0 + true + false + pci.3 + + 3 + pcie-root-port + + + + 0 + d20bae3b-f5d7-4131-b00a-3cf66f390434 + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x3} + 0 + true + false + pci.4 + + 4 + pcie-root-port + + + + 0 + 5887f3ad-c575-488e-9138-fca9c7064ae5 + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x4} + 0 + true + false + pci.5 + + 5 + pcie-root-port + + + + 0 + f880f086-227e-4e25-b2fc-8a3d13d1f1bd + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x5} + 0 + true + false + pci.6 + + 6 + pcie-root-port + + + + 0 + d64f62a0-6176-482b-8d24-f82fb32b8f12 + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x6} + 0 + true + false + pci.7 + + 7 + pcie-root-port + + + + 0 + 1544f32e-1e94-4e10-b198-7c5e95ab280d + controller + pci + {type=pci, slot=0x02, bus=0x00, domain=0x0000, function=0x7} + 0 + true + false + pci.8 + + 8 + pcie-root-port + + + + 0 + 7dd5080f-8c04-4593-8c6a-1dc5cd6c3e3e + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x0, multifunction=on} + 0 + true + false + pci.9 + + 9 + pcie-root-port + + + + 0 + 4dab4257-2729-482c-b4e1-6a3c05161153 + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x1} + 0 + true + false + pci.10 + + 10 + pcie-root-port + + + + 0 + 99effa2f-2963-4abd-9eab-1cbe8e913ca4 + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x2} + 0 + true + false + pci.11 + + 11 + pcie-root-port + + + + 0 + 2a376983-897b-4396-be32-89f2a9ca7d22 + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x3} + 0 + true + false + pci.12 + + 12 + pcie-root-port + + + + 0 + 2e763d82-4475-4268-bc0a-07c915ec19c8 + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x4} + 0 + true + false + pci.13 + + 13 + pcie-root-port + + + + 0 + ef39155f-760e-4374-afb9-ff05cc8b9609 + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x5} + 0 + true + false + pci.14 + + 14 + pcie-root-port + + + + 0 + 74be06f0-84b6-472e-a054-486343f66084 + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x6} + 0 + true + false + pci.15 + + 15 + pcie-root-port + + + + 0 + c68db43a-fa3a-4689-941d-b477d2676d27 + controller + pci + {type=pci, slot=0x03, bus=0x00, domain=0x0000, function=0x7} + 0 + true + false + pci.16 + + 16 + pcie-root-port + + + + 0 + d11cbe26-ee82-4e15-b8eb-2aa7b285d00d + controller + pci + {type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x0, multifunction=on} + 0 + true + false + pci.17 + + 17 + pcie-root-port + + + + 0 + c2ef6c73-f633-41c1-8736-7e9c8d748ac2 + controller + pci + {type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x1} + 0 + true + false + pci.18 + + 18 + pcie-root-port + + + + 0 + 5944d260-08c3-4f12-aa22-1e9ac76ae6c0 + controller + pci + {type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x2} + 0 + true + false + pci.19 + + 19 + pcie-root-port + + + + 0 + 8c7ad6aa-ac22-4d98-86b7-45f3a13c98da + controller + pci + {type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x3} + 0 + true + false + pci.20 + + 20 + pcie-root-port + + + + 0 + dc1cfae5-682d-4bb5-a53e-d604852e62cd + controller + pci + {type=pci, slot=0x04, bus=0x00, domain=0x0000, function=0x4} + 0 + true + false + pci.21 + + 21 + pcie-root-port + + + + 0 + 6117753b-8ce6-4568-8e09-c8b686396334 + controller + sata + {type=pci, slot=0x1f, bus=0x00, domain=0x0000, function=0x2} + 0 + true + false + ide + + 0 + + + + 0 + 17976687-41f8-4f7c-97f5-a76a282c40e4 + controller + virtio-serial + {type=pci, slot=0x00, bus=0x03, domain=0x0000, function=0x0} + 0 + true + false + ua-17976687-41f8-4f7c-97f5-a76a282c40e4 + + + 0 + 97f6991c-e4d5-11f0-9b4a-00163e6c35f4 + rng + virtio + {type=pci, slot=0x00, bus=0x06, domain=0x0000, function=0x0} + 0 + true + false + ua-97f6991c-e4d5-11f0-9b4a-00163e6c35f4 + + urandom + + + + 0 + 0eb75625-9891-4b03-9541-c58c43c323b2 + controller + virtio-scsi + {type=pci, slot=0x00, bus=0x02, domain=0x0000, function=0x0} + 0 + true + false + ua-0eb75625-9891-4b03-9541-c58c43c323b2 + + + + + + 0 + 59536909-bac6-4202-b2ad-d84a22a41013 + balloon + memballoon + {type=pci, slot=0x00, bus=0x05, domain=0x0000, function=0x0} + 0 + true + true + ua-59536909-bac6-4202-b2ad-d84a22a41013 + + virtio + + + + 0 + e95647b0-4bb2-4ccb-b867-cbde06311038 + controller + usb + {type=pci, slot=0x00, bus=0x04, domain=0x0000, function=0x0} + 0 + true + false + ua-e95647b0-4bb2-4ccb-b867-cbde06311038 + + 0 + qemu-xhci + + +
+
+ + ACTIVE + Active VM + 2026/01/07 13:37:09 + +
+
+
\ No newline at end of file diff --git a/plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtilTest.java b/plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtilTest.java new file mode 100644 index 00000000000..c01e19515fe --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/test/java/org/apache/cloudstack/veeam/api/dto/OvfXmlUtilTest.java @@ -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 = "" + + "adm-v9adm-v9"+ + "
1 CPU, 512 MemoryENGINE 4.4.0.01 virtual cpuNumber of virtual CPU1311111" + + "512 MB of memoryMemory Size24MegaBytes512" + + "
"; + + @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()); + } +} diff --git a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java index ed44ded2280..e8390c8536b 100644 --- a/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java +++ b/server/src/main/java/org/apache/cloudstack/backup/IncrementalBackupServiceImpl.java @@ -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);