From f52b114c8db92090ec6f4a7192d9c60c8007e2e9 Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Thu, 22 Jan 2026 16:43:05 +0530 Subject: [PATCH] changes Signed-off-by: Abhishek Kumar --- .../veeam/api/ClustersRouteHandler.java | 123 ++++++++ .../cloudstack/veeam/api/VmsRouteHandler.java | 17 +- .../ClusterVOToClusterConverter.java | 170 +++++++++++ .../DataCenterVOToDataCenterConverter.java | 2 +- .../StoreVOToStorageDomainConverter.java | 4 +- .../converter/UserVmJoinVOToVmConverter.java | 42 ++- .../VolumeJoinVOToDiskConverter.java | 11 +- .../cloudstack/veeam/api/dto/Cluster.java | 280 ++++++++++++++++++ .../cloudstack/veeam/api/dto/Clusters.java | 40 +++ .../spring-veeam-control-service-context.xml | 1 + 10 files changed, 669 insertions(+), 21 deletions(-) create mode 100644 plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ClustersRouteHandler.java create mode 100644 plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/ClusterVOToClusterConverter.java create mode 100644 plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cluster.java create mode 100644 plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Clusters.java diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ClustersRouteHandler.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ClustersRouteHandler.java new file mode 100644 index 00000000000..bb14b614479 --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/ClustersRouteHandler.java @@ -0,0 +1,123 @@ +// 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.api.converter.ClusterVOToClusterConverter; +import org.apache.cloudstack.veeam.api.dto.Cluster; +import org.apache.cloudstack.veeam.api.dto.Clusters; +import org.apache.cloudstack.veeam.utils.Negotiation; +import org.apache.cloudstack.veeam.utils.PathUtil; + +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.utils.Pair; +import com.cloud.utils.component.ManagerBase; + +public class ClustersRouteHandler extends ManagerBase implements RouteHandler { + public static final String BASE_ROUTE = "/api/clusters"; + + @Inject + ClusterDao clusterDao; + + @Inject + DataCenterDao dataCenterDao; + + @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; + } + + Pair idAndSubPath = PathUtil.extractIdAndSubPath(sanitizedPath, BASE_ROUTE); + if (idAndSubPath != null) { + // /api/disks/{id} + if (idAndSubPath.first() != null) { + if (idAndSubPath.second() == null) { + handleGetById(idAndSubPath.first(), resp, outFormat, io); + return; + } + } + } + + resp.sendError(HttpServletResponse.SC_NOT_FOUND, "Not found"); + } + + public void handleGet(final HttpServletRequest req, final HttpServletResponse resp, + Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException { + final List result = ClusterVOToClusterConverter.toClusterList(listClusters(), this::getZoneById); + final Clusters response = new Clusters(result); + + io.getWriter().write(resp, 200, response, outFormat); + } + + protected List listClusters() { + return clusterDao.listAll(); + } + + public void handleGetById(final String id, final HttpServletResponse resp, final Negotiation.OutFormat outFormat, + final VeeamControlServlet io) throws IOException { + final ClusterVO vo = clusterDao.findByUuid(id); + if (vo == null) { + io.notFound(resp, "DataCenter not found: " + id, outFormat); + return; + } + Cluster response = ClusterVOToClusterConverter.toCluster(vo, this::getZoneById); + + io.getWriter().write(resp, 200, response, outFormat); + } + + private DataCenterVO getZoneById(Long zoneId) { + if (zoneId == null) { + return null; + } + return dataCenterDao.findById(zoneId); + } +} 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 62e7c67dfa7..dc92f58715f 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 @@ -41,8 +41,10 @@ import org.apache.cloudstack.veeam.api.response.VmEntityResponse; import org.apache.cloudstack.veeam.utils.Negotiation; import org.apache.cloudstack.veeam.utils.PathUtil; +import com.cloud.api.query.dao.HostJoinDao; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.dao.VolumeJoinDao; +import com.cloud.api.query.vo.HostJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.utils.Pair; import com.cloud.utils.component.ManagerBase; @@ -56,6 +58,9 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { @Inject UserVmJoinDao userVmJoinDao; + @Inject + HostJoinDao hostJoinDao; + @Inject VolumeJoinDao volumeJoinDao; @@ -143,7 +148,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { return; } - final List result = UserVmJoinVOToVmConverter.toVmList(listUserVms()); + final List result = UserVmJoinVOToVmConverter.toVmList(listUserVms(), this::getHostById); final VmCollectionResponse response = new VmCollectionResponse(result); io.getWriter().write(resp, 200, response, outFormat); @@ -178,7 +183,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { io.notFound(resp, "VM not found: " + id, outFormat); return; } - VmEntityResponse response = new VmEntityResponse(UserVmJoinVOToVmConverter.toVm(userVmJoinVO)); + VmEntityResponse response = new VmEntityResponse(UserVmJoinVOToVmConverter.toVm(userVmJoinVO, this::getHostById)); io.getWriter().write(resp, 200, response, outFormat); } @@ -196,4 +201,12 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler { io.getWriter().write(resp, 200, response, outFormat); } + + private HostJoinVO getHostById(Long hostId) { + if (hostId == null) { + return null; + } + return hostJoinDao.findById(hostId); + } + } \ No newline at end of file diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/ClusterVOToClusterConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/ClusterVOToClusterConverter.java new file mode 100644 index 00000000000..54176d4004a --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/ClusterVOToClusterConverter.java @@ -0,0 +1,170 @@ +// 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.Collections; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.apache.cloudstack.veeam.VeeamControlService; +import org.apache.cloudstack.veeam.api.ClustersRouteHandler; +import org.apache.cloudstack.veeam.api.DataCentersRouteHandler; +import org.apache.cloudstack.veeam.api.dto.Actions; +import org.apache.cloudstack.veeam.api.dto.Cluster; +import org.apache.cloudstack.veeam.api.dto.Link; +import org.apache.cloudstack.veeam.api.dto.Ref; + +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; + +public class ClusterVOToClusterConverter { + public static Cluster toCluster(final ClusterVO vo, final Function dataCenterResolver) { + final Cluster c = new Cluster(); + final String basePath = VeeamControlService.ContextPath.value(); + + // NOTE: oVirt uses UUIDs. If your ClusterVO id is numeric, generate a stable UUID: + // - Prefer: store a UUID in details table and reuse it + // - Fallback: name-based UUID from "cluster:" + final String clusterId = vo.getUuid(); + c.id = clusterId; + c.href = basePath + ClustersRouteHandler.BASE_ROUTE + "/" + clusterId; + + c.name = vo.getName(); + c.description = vo.getName(); + c.comment = ""; + + // --- sensible defaults (match your sample) + c.ballooningEnabled = "true"; + c.biosType = "q35_ovmf"; // or "q35_secure_boot" if you want to align with VM BIOS you saw + c.fipsMode = "disabled"; + c.firewallType = "firewalld"; + c.glusterService = "false"; + c.haReservation = "false"; + c.switchType = "legacy"; + c.threadsAsCores = "false"; + c.trustedService = "false"; + c.tunnelMigration = "false"; + c.upgradeInProgress = "false"; + c.upgradePercentComplete = "0"; + c.virtService = "true"; + c.vncEncryption = "false"; + c.logMaxMemoryUsedThreshold = "95"; + c.logMaxMemoryUsedThresholdType = "percentage"; + + // --- cpu (best-effort defaults) + final Cluster.ClusterCpu cpu = new Cluster.ClusterCpu(); + cpu.architecture = "x86_64"; + cpu.type = "x86_64"; // replace if you can detect host cpu model + c.cpu = cpu; + + // --- version (ovirt engine version; keep fixed unless you want to expose something else) + final Cluster.Version ver = new Cluster.Version(); + ver.major = "4"; + ver.minor = "8"; + c.version = ver; + + // --- ksm / memory policy (defaults) + c.ksm = new Cluster.Ksm(); + c.ksm.enabled = "true"; + c.ksm.mergeAcrossNodes = "true"; + + c.memoryPolicy = new Cluster.MemoryPolicy(); + c.memoryPolicy.overCommit = new Cluster.OverCommit(); + c.memoryPolicy.overCommit.percent = "100"; + c.memoryPolicy.transparentHugepages = new Cluster.TransparentHugepages(); + c.memoryPolicy.transparentHugepages.enabled = "true"; + + // --- migration defaults + c.migration = new Cluster.Migration(); + c.migration.autoConverge = "inherit"; + c.migration.bandwidth = new Cluster.Bandwidth(); + c.migration.bandwidth.assignmentMethod = "auto"; + c.migration.compressed = "inherit"; + c.migration.encrypted = "inherit"; + c.migration.parallelMigrationsPolicy = "disabled"; + // policy ref (dummy but valid shape) + c.migration.policy = Ref.of(basePath + "/migrationpolicies/" + stableUuid("migrationpolicy:default"), + stableUuid("migrationpolicy:default") + ); + + // --- rng sources + c.requiredRngSources = new Cluster.RequiredRngSources(); + c.requiredRngSources.requiredRngSource = Collections.singletonList("urandom"); + + // --- error handling + c.errorHandling = new Cluster.ErrorHandling(); + c.errorHandling.onError = "migrate"; + + // --- fencing policy defaults + c.fencingPolicy = new Cluster.FencingPolicy(); + c.fencingPolicy.enabled = "true"; + c.fencingPolicy.skipIfConnectivityBroken = new Cluster.SkipIfConnectivityBroken(); + c.fencingPolicy.skipIfConnectivityBroken.enabled = "false"; + c.fencingPolicy.skipIfConnectivityBroken.threshold = "50"; + c.fencingPolicy.skipIfGlusterBricksUp = "false"; + c.fencingPolicy.skipIfGlusterQuorumNotMet = "false"; + c.fencingPolicy.skipIfSdActive = new Cluster.SkipIfSdActive(); + c.fencingPolicy.skipIfSdActive.enabled = "false"; + + // --- scheduling policy props (optional; dummy ok) + c.customSchedulingPolicyProperties = new Cluster.CustomSchedulingPolicyProperties(); + final Cluster.Property p1 = new Cluster.Property(); p1.name = "HighUtilization"; p1.value = "80"; + final Cluster.Property p2 = new Cluster.Property(); p2.name = "CpuOverCommitDurationMinutes"; p2.value = "2"; + c.customSchedulingPolicyProperties.property = List.of(p1, p2); + + // --- data_center ref mapping (CloudStack cluster -> pod -> zone) + if (dataCenterResolver != null) { + final DataCenterVO zone = dataCenterResolver.apply(vo.getDataCenterId()); + if (zone != null) { + c.dataCenter = Ref.of(basePath + DataCentersRouteHandler.BASE_ROUTE + "/" + zone.getUuid(), zone.getUuid()); + } + } + + // --- mac pool & scheduling policy refs (dummy but consistent) + c.macPool = Ref.of(basePath + "/macpools/" + stableUuid("macpool:default"), + stableUuid("macpool:default")); + c.schedulingPolicy = Ref.of(basePath + "/schedulingpolicies/" + stableUuid("schedpolicy:default"), + stableUuid("schedpolicy:default")); + + // --- actions.links (can be omitted; but Veeam sometimes expects actions to exist) + final Actions actions = new Actions(); + actions.link = Collections.emptyList(); + c.actions = actions; + + // --- related links (optional) + c.link = List.of( + new Link("networks", c.href + "/networks") + ); + + return c; + } + + public static List toClusterList(final List voList, + final Function dataCenterResolver) { + return voList.stream() + .map(vo -> toCluster(vo, dataCenterResolver)) + .collect(Collectors.toList()); + } + + private static String stableUuid(final String key) { + // deterministic UUID, so the same ClusterVO maps to same "ovirt id" every time + return UUID.nameUUIDFromBytes(key.getBytes()).toString(); + } +} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/DataCenterVOToDataCenterConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/DataCenterVOToDataCenterConverter.java index 395bb233ea5..c39b91a9684 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/DataCenterVOToDataCenterConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/DataCenterVOToDataCenterConverter.java @@ -36,7 +36,7 @@ public class DataCenterVOToDataCenterConverter { public static DataCenter toDataCenter(final DataCenterVO zone) { final String id = zone.getUuid(); final String basePath = VeeamControlService.ContextPath.value(); - final String href = basePath + DataCentersRouteHandler.BASE_ROUTE + "/datacenters/" + id; + final String href = basePath + DataCentersRouteHandler.BASE_ROUTE + DataCentersRouteHandler.BASE_ROUTE + "/" + id; final DataCenter dc = new DataCenter(); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/StoreVOToStorageDomainConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/StoreVOToStorageDomainConverter.java index 071ebc92c14..f974826ce40 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/StoreVOToStorageDomainConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/StoreVOToStorageDomainConverter.java @@ -77,7 +77,7 @@ public class StoreVOToStorageDomainConverter { // dc attachment String dcId = pool.getZoneUuid(); DataCenter dc = new DataCenter(); - dc.href = href(basePath, DataCentersRouteHandler.BASE_ROUTE + dcId); + dc.href = href(basePath, DataCentersRouteHandler.BASE_ROUTE + "/" + dcId); dc.id = dcId; sd.dataCenters = new DataCenters(List.of(dc)); @@ -132,7 +132,7 @@ public class StoreVOToStorageDomainConverter { // Optionally include dc attachment (your first object had it; second didn’t) String dcId = store.getZoneUuid(); DataCenter dc = new DataCenter(); - dc.href = href(basePath, DataCentersRouteHandler.BASE_ROUTE + dcId); + dc.href = href(basePath, DataCentersRouteHandler.BASE_ROUTE + "/" + dcId); dc.id = dcId; sd.dataCenters = new DataCenters(List.of(dc)); 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 4a8030149a8..8fb2578a028 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 @@ -20,6 +20,7 @@ package org.apache.cloudstack.veeam.api.converter; import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; import org.apache.cloudstack.veeam.VeeamControlService; @@ -34,6 +35,7 @@ import org.apache.cloudstack.veeam.api.dto.Topology; import org.apache.cloudstack.veeam.api.dto.Vm; import org.apache.commons.lang3.StringUtils; +import com.cloud.api.query.vo.HostJoinVO; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.vm.VirtualMachine; @@ -47,7 +49,7 @@ public final class UserVmJoinVOToVmConverter { * * @param src UserVmJoinVO */ - public static Vm toVm(final UserVmJoinVO src) { + public static Vm toVm(final UserVmJoinVO src, final Function hostResolver) { if (src == null) { return null; } @@ -71,14 +73,32 @@ public final class UserVmJoinVOToVmConverter { ); dst.template = template; dst.originalTemplate = template; - dst.host = buildRef( - basePath + ApiService.BASE_ROUTE, - "hosts", - src.getHostUuid()); - dst.cluster = buildRef( - basePath + ApiService.BASE_ROUTE, - "clusters", - src.getHostUuid()); + if (StringUtils.isNotBlank(src.getHostUuid())) { + dst.host = buildRef( + basePath + ApiService.BASE_ROUTE, + "hosts", + src.getHostUuid()); + + } + if (hostResolver != null) { + HostJoinVO hostVo = hostResolver.apply(src.getHostId() == null ? src.getLastHostId() : src.getHostId()); + if (hostVo != null) { + dst.host = buildRef( + basePath + ApiService.BASE_ROUTE, + "hosts", + hostVo.getUuid()); + dst.cluster = buildRef( + basePath + ApiService.BASE_ROUTE, + "clusters", + hostVo.getClusterUuid()); + } + } + Long hostId = src.getHostId() != null ? src.getHostId() : src.getLastHostId(); + if (hostId != null) { + // I want to get Host data from hostJoinDao but this is a static method without dao access. + + } + dst.memory = src.getRamSize() * 1024L * 1024L; dst.cpu = new Cpu(src.getArch(), new Topology(src.getCpu(), src.getCpu(), 1)); @@ -102,9 +122,9 @@ public final class UserVmJoinVOToVmConverter { return dst; } - public static List toVmList(final List srcList) { + public static List toVmList(final List srcList, final Function hostResolver) { return srcList.stream() - .map(UserVmJoinVOToVmConverter::toVm) + .map(v -> toVm(v, hostResolver)) .collect(Collectors.toList()); } diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java index 55a25706a91..3b2305f5218 100644 --- a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/converter/VolumeJoinVOToDiskConverter.java @@ -23,6 +23,7 @@ import java.util.stream.Collectors; import org.apache.cloudstack.veeam.VeeamControlService; import org.apache.cloudstack.veeam.api.ApiService; +import org.apache.cloudstack.veeam.api.DisksRouteHandler; import org.apache.cloudstack.veeam.api.dto.Actions; import org.apache.cloudstack.veeam.api.dto.Disk; import org.apache.cloudstack.veeam.api.dto.DiskAttachment; @@ -38,10 +39,10 @@ import com.cloud.storage.VolumeStats; public class VolumeJoinVOToDiskConverter { public static Disk toDisk(final VolumeJoinVO vol) { final Disk disk = new Disk(); - final String apiBase = VeeamControlService.ContextPath.value() + ApiService.BASE_ROUTE; + final String basePath = VeeamControlService.ContextPath.value() + ApiService.BASE_ROUTE; final String diskId = vol.getUuid(); - final String diskHref = apiBase + "/disks/" + diskId; + final String diskHref = basePath + DisksRouteHandler.BASE_ROUTE + "/" + diskId; disk.id = diskId; disk.href = diskHref; @@ -49,7 +50,7 @@ public class VolumeJoinVOToDiskConverter { // Names disk.name = vol.getName(); disk.alias = vol.getName(); - disk.description = ""; + disk.description = vol.getName(); // Sizes (bytes) final long size = vol.getSize(); @@ -96,7 +97,7 @@ public class VolumeJoinVOToDiskConverter { // Disk profile (optional) disk.diskProfile = Ref.of( - apiBase + "/diskprofiles/" + vol.getDiskOfferingId(), + basePath + "/diskprofiles/" + vol.getDiskOfferingId(), String.valueOf(vol.getDiskOfferingId()) ); @@ -105,7 +106,7 @@ public class VolumeJoinVOToDiskConverter { Disk.StorageDomains sds = new Disk.StorageDomains(); sds.storageDomain = List.of( Ref.of( - apiBase + "/storagedomains/" + vol.getPoolUuid(), + basePath + "/storagedomains/" + vol.getPoolUuid(), vol.getPoolUuid() ) ); diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cluster.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cluster.java new file mode 100644 index 00000000000..cdd4a18e2cc --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Cluster.java @@ -0,0 +1,280 @@ +// 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.JacksonXmlProperty; +import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement; + +@JsonInclude(JsonInclude.Include.NON_NULL) +@JacksonXmlRootElement(localName = "cluster") +public final class Cluster { + + // --- common identity + public String href; + public String id; + public String name; + public String description; + public String comment; + + // --- oVirt-ish knobs (strings in oVirt JSON) + @JsonProperty("ballooning_enabled") + @JacksonXmlProperty(localName = "ballooning_enabled") + public String ballooningEnabled; // "true"/"false" + + @JsonProperty("bios_type") + @JacksonXmlProperty(localName = "bios_type") + public String biosType; // e.g. "q35_ovmf" + + public ClusterCpu cpu; + + @JsonProperty("custom_scheduling_policy_properties") + @JacksonXmlProperty(localName = "custom_scheduling_policy_properties") + public CustomSchedulingPolicyProperties customSchedulingPolicyProperties; + + @JsonProperty("error_handling") + @JacksonXmlProperty(localName = "error_handling") + public ErrorHandling errorHandling; + + @JsonProperty("fencing_policy") + @JacksonXmlProperty(localName = "fencing_policy") + public FencingPolicy fencingPolicy; + + @JsonProperty("fips_mode") + @JacksonXmlProperty(localName = "fips_mode") + public String fipsMode; // "disabled" + + @JsonProperty("firewall_type") + @JacksonXmlProperty(localName = "firewall_type") + public String firewallType; // "firewalld" + + @JsonProperty("gluster_service") + @JacksonXmlProperty(localName = "gluster_service") + public String glusterService; + + @JsonProperty("ha_reservation") + @JacksonXmlProperty(localName = "ha_reservation") + public String haReservation; + + public Ksm ksm; + + @JsonProperty("log_max_memory_used_threshold") + @JacksonXmlProperty(localName = "log_max_memory_used_threshold") + public String logMaxMemoryUsedThreshold; + + @JsonProperty("log_max_memory_used_threshold_type") + @JacksonXmlProperty(localName = "log_max_memory_used_threshold_type") + public String logMaxMemoryUsedThresholdType; + + @JsonProperty("memory_policy") + @JacksonXmlProperty(localName = "memory_policy") + public MemoryPolicy memoryPolicy; + + public Migration migration; + + @JsonProperty("required_rng_sources") + @JacksonXmlProperty(localName = "required_rng_sources") + public RequiredRngSources requiredRngSources; + + @JsonProperty("switch_type") + @JacksonXmlProperty(localName = "switch_type") + public String switchType; + + @JsonProperty("threads_as_cores") + @JacksonXmlProperty(localName = "threads_as_cores") + public String threadsAsCores; + + @JsonProperty("trusted_service") + @JacksonXmlProperty(localName = "trusted_service") + public String trustedService; + + @JsonProperty("tunnel_migration") + @JacksonXmlProperty(localName = "tunnel_migration") + public String tunnelMigration; + + @JsonProperty("upgrade_in_progress") + @JacksonXmlProperty(localName = "upgrade_in_progress") + public String upgradeInProgress; + + @JsonProperty("upgrade_percent_complete") + @JacksonXmlProperty(localName = "upgrade_percent_complete") + public String upgradePercentComplete; + + public Version version; + + @JsonProperty("virt_service") + @JacksonXmlProperty(localName = "virt_service") + public String virtService; + + @JsonProperty("vnc_encryption") + @JacksonXmlProperty(localName = "vnc_encryption") + public String vncEncryption; + + // --- references + @JsonProperty("data_center") + @JacksonXmlProperty(localName = "data_center") + public Ref dataCenter; + + @JsonProperty("mac_pool") + @JacksonXmlProperty(localName = "mac_pool") + public Ref macPool; + + @JsonProperty("scheduling_policy") + @JacksonXmlProperty(localName = "scheduling_policy") + public Ref schedulingPolicy; + + // --- actions + links + public Actions actions; + + @JacksonXmlElementWrapper(useWrapping = false) + public List link; + + public Cluster() {} + + // ===== nested DTOs ===== + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class ClusterCpu { + public String architecture; + public String type; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class CustomSchedulingPolicyProperties { + @JacksonXmlElementWrapper(useWrapping = false) + @JsonProperty("property") + public List property; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Property { + public String name; + public String value; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class ErrorHandling { + @JsonProperty("on_error") + @JacksonXmlProperty(localName = "on_error") + public String onError; // "migrate" + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class FencingPolicy { + public String enabled; + + @JsonProperty("skip_if_connectivity_broken") + @JacksonXmlProperty(localName = "skip_if_connectivity_broken") + public SkipIfConnectivityBroken skipIfConnectivityBroken; + + @JsonProperty("skip_if_gluster_bricks_up") + @JacksonXmlProperty(localName = "skip_if_gluster_bricks_up") + public String skipIfGlusterBricksUp; + + @JsonProperty("skip_if_gluster_quorum_not_met") + @JacksonXmlProperty(localName = "skip_if_gluster_quorum_not_met") + public String skipIfGlusterQuorumNotMet; + + @JsonProperty("skip_if_sd_active") + @JacksonXmlProperty(localName = "skip_if_sd_active") + public SkipIfSdActive skipIfSdActive; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class SkipIfConnectivityBroken { + public String enabled; + public String threshold; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class SkipIfSdActive { + public String enabled; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Ksm { + public String enabled; + + @JsonProperty("merge_across_nodes") + @JacksonXmlProperty(localName = "merge_across_nodes") + public String mergeAcrossNodes; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class MemoryPolicy { + @JsonProperty("over_commit") + @JacksonXmlProperty(localName = "over_commit") + public OverCommit overCommit; + + @JsonProperty("transparent_hugepages") + @JacksonXmlProperty(localName = "transparent_hugepages") + public TransparentHugepages transparentHugepages; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class OverCommit { + public String percent; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class TransparentHugepages { + public String enabled; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Migration { + @JsonProperty("auto_converge") + @JacksonXmlProperty(localName = "auto_converge") + public String autoConverge; + + public Bandwidth bandwidth; + + public String compressed; + public String encrypted; + + @JsonProperty("parallel_migrations_policy") + @JacksonXmlProperty(localName = "parallel_migrations_policy") + public String parallelMigrationsPolicy; + + public Ref policy; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Bandwidth { + @JsonProperty("assignment_method") + @JacksonXmlProperty(localName = "assignment_method") + public String assignmentMethod; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class RequiredRngSources { + @JsonProperty("required_rng_source") + @JacksonXmlElementWrapper(useWrapping = false) + public List requiredRngSource; + } + + @JsonInclude(JsonInclude.Include.NON_NULL) + public static final class Version { + public String major; + public String minor; + } +} diff --git a/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Clusters.java b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Clusters.java new file mode 100644 index 00000000000..67eca4c989c --- /dev/null +++ b/plugins/integrations/veeam-control-service/src/main/java/org/apache/cloudstack/veeam/api/dto/Clusters.java @@ -0,0 +1,40 @@ +// 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 = "clusters") +public final class Clusters { + + @JsonProperty("cluster") + @JacksonXmlElementWrapper(useWrapping = false) + public List cluster; + + public Clusters() {} + + public Clusters(final List cluster) { + this.cluster = cluster; + } +} 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 0c553d8e553..1ed843cbb46 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 @@ -34,6 +34,7 @@ +