cleanup; return tags with specific key only

Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
This commit is contained in:
Abhishek Kumar 2026-04-09 18:25:14 +05:30
parent d804b7597b
commit 2f673568aa
33 changed files with 229 additions and 220 deletions

View File

@ -63,6 +63,8 @@ public interface ResourceTagDao extends GenericDao<ResourceTagVO, Long> {
List<? extends ResourceTag> listByResourceUuid(String resourceUuid);
List<ResourceTagVO> listByResourceTypeAndOwners(ResourceObjectType resourceType, List<Long> accountIds,
List<Long> domainIds, Filter filter);
List<ResourceTagVO> listByResourceTypeKeyAndOwners(ResourceObjectType resourceType, String key,
List<Long> accountIds, List<Long> domainIds, Filter filter);
ResourceTagVO findByResourceTypeKeyAndValue(ResourceObjectType resourceType, String key, String value);
}

View File

@ -124,10 +124,12 @@ public class ResourceTagsDaoImpl extends GenericDaoBase<ResourceTagVO, Long> imp
}
@Override
public List<ResourceTagVO> listByResourceTypeAndOwners(ResourceObjectType resourceType, List<Long> accountIds,
List<Long> domainIds, Filter filter) {
public List<ResourceTagVO> listByResourceTypeKeyAndOwners(ResourceObjectType resourceType, String key,
List<Long> accountIds, List<Long> domainIds,
Filter filter) {
SearchBuilder<ResourceTagVO> sb = createSearchBuilder();
sb.and("resourceType", sb.entity().getResourceType(), Op.EQ);
sb.and("key", sb.entity().getKey(), Op.EQ);
boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds);
boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds);
if (accountIdsNotEmpty || domainIdsNotEmpty) {
@ -136,8 +138,9 @@ public class ResourceTagsDaoImpl extends GenericDaoBase<ResourceTagVO, Long> imp
sb.cp();
}
sb.done();
final SearchCriteria<ResourceTagVO> sc = sb.create();;
final SearchCriteria<ResourceTagVO> sc = sb.create();
sc.setParameters("resourceType", resourceType);
sc.setParameters("key", key);
if (accountIdsNotEmpty) {
sc.setParameters("account", accountIds.toArray());
}
@ -146,4 +149,19 @@ public class ResourceTagsDaoImpl extends GenericDaoBase<ResourceTagVO, Long> imp
}
return listBy(sc, filter);
}
@Override
public ResourceTagVO findByResourceTypeKeyAndValue(ResourceObjectType resourceType, String key,
String value) {
SearchBuilder<ResourceTagVO> sb = createSearchBuilder();
sb.and("resourceType", sb.entity().getResourceType(), Op.EQ);
sb.and("key", sb.entity().getKey(), Op.EQ);
sb.and("value", sb.entity().getValue(), Op.EQ);
sb.done();
final SearchCriteria<ResourceTagVO> sc = sb.create();
sc.setParameters("resourceType", resourceType);
sc.setParameters("key", key);
sc.setParameters("value", value);
return findOneBy(sc);
}
}

View File

@ -56,6 +56,7 @@ SELECT
`vm_instance`.`display_vm` AS `display_vm`,
`vm_instance`.`delete_protection` AS `delete_protection`,
`guest_os`.`uuid` AS `guest_os_uuid`,
`guest_os`.`display_name` AS `guest_os_display_name`,
`vm_instance`.`pod_id` AS `pod_id`,
`host_pod_ref`.`uuid` AS `pod_uuid`,
`vm_instance`.`private_ip_address` AS `private_ip_address`,

View File

@ -19,7 +19,6 @@ package org.apache.cloudstack.veeam;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.regex.Pattern;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@ -30,7 +29,6 @@ import org.apache.logging.log4j.Logger;
import com.cloud.utils.component.Adapter;
public interface RouteHandler extends Adapter {
static final Pattern PAGE_PATTERN = Pattern.compile("\\bpage\\s+(\\d+)");
default int priority() { return 0; }
boolean canHandle(String method, String path) throws IOException;
void handle(HttpServletRequest req, HttpServletResponse resp, String path, Negotiation.OutFormat outFormat, VeeamControlServlet io)
@ -60,7 +58,6 @@ public interface RouteHandler extends Adapter {
if (!"application/json".equals(mime) && !"application/x-www-form-urlencoded".equals(mime)) {
return null;
}
String result = null;
try {
StringBuilder data = new StringBuilder();
String line;
@ -74,4 +71,9 @@ public interface RouteHandler extends Adapter {
return null;
}
}
static boolean isRequestAsync(HttpServletRequest req) {
String asyncStr = req.getParameter("async");
return Boolean.TRUE.toString().equals(asyncStr);
}
}

View File

@ -28,7 +28,7 @@ import javax.servlet.DispatcherType;
import javax.servlet.http.HttpServletRequest;
import org.apache.cloudstack.utils.server.ServerPropertiesUtil;
import org.apache.cloudstack.veeam.api.ApiService;
import org.apache.cloudstack.veeam.api.ApiRouteHandler;
import org.apache.cloudstack.veeam.filter.AllowedClientCidrsFilter;
import org.apache.cloudstack.veeam.filter.BearerOrBasicAuthFilter;
import org.apache.commons.lang3.StringUtils;
@ -125,12 +125,12 @@ public class VeeamControlServer {
// CIDR filter for all routes
AllowedClientCidrsFilter cidrFilter = new AllowedClientCidrsFilter(veeamControlService);
FilterHolder cidrHolder = new FilterHolder(cidrFilter);
ctx.addFilter(cidrHolder, ApiService.BASE_ROUTE + "/*", EnumSet.of(DispatcherType.REQUEST));
ctx.addFilter(cidrHolder, ApiRouteHandler.BASE_ROUTE + "/*", EnumSet.of(DispatcherType.REQUEST));
// Bearer or Basic Auth for all routes
BearerOrBasicAuthFilter authFilter = new BearerOrBasicAuthFilter(veeamControlService);
FilterHolder authHolder = new FilterHolder(authFilter);
ctx.addFilter(authHolder, ApiService.BASE_ROUTE + "/*", EnumSet.of(DispatcherType.REQUEST));
ctx.addFilter(authHolder, ApiRouteHandler.BASE_ROUTE + "/*", EnumSet.of(DispatcherType.REQUEST));
// Front controller servlet
ctx.addServlet(new ServletHolder(new VeeamControlServlet(routeHandlers)), "/*");

View File

@ -21,10 +21,13 @@ import java.util.List;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
import org.apache.cloudstack.utils.CloudStackVersion;
import com.cloud.utils.component.PluggableService;
public interface VeeamControlService extends PluggableService, Configurable {
String PLUGIN_NAME = "CloudStack Veeam Control Service";
ConfigKey<Boolean> Enabled = new ConfigKey<>("Advanced", Boolean.class, "integration.veeam.control.enabled",
"false", "Enable the Veeam Integration REST API server", false);
ConfigKey<String> BindAddress = new ConfigKey<>("Advanced", String.class, "integration.veeam.control.bind.address",
@ -57,4 +60,16 @@ public interface VeeamControlService extends PluggableService, Configurable {
List<String> getAllowedClientCidrs();
boolean validateCredentials(String username, String password);
static String getPackageVersion() {
return VeeamControlService.class.getPackage().getImplementationVersion();
}
static CloudStackVersion getCSVersion() {
try {
return CloudStackVersion.parse(getPackageVersion());
} catch (Exception e) {
return null;
}
}
}

View File

@ -101,19 +101,6 @@ public class VeeamControlServlet extends HttpServlet {
String name = headerNames.nextElement();
details.append(name).append("=").append(req.getHeader(name)).append("; ");
}
// String body = "";
// if (!"GET".equalsIgnoreCase(method)) {
// StringBuilder bodySb = new StringBuilder();
// java.io.BufferedReader reader = req.getReader();
// if (reader != null) {
// String line;
// while ((line = reader.readLine()) != null) {
// bodySb.append(line).append('\n');
// }
// }
// body = bodySb.toString().trim();
// }
// details.append(", Body: ").append(body);
LOGGER.debug(details.toString());
} catch (Exception e) {
LOGGER.debug("Failed to capture request details", e);
@ -135,8 +122,8 @@ public class VeeamControlServlet extends HttpServlet {
}
writer.write(resp, 200, Map.of(
"name", "CloudStack Veeam Control Service",
"pluginVersion", "0.1"), outFormat);
"name", VeeamControlService.PLUGIN_NAME,
"pluginVersion", this.getClass().getPackage().getImplementationVersion()), outFormat);
}
public void methodNotAllowed(final HttpServletResponse resp, final String allow, final Negotiation.OutFormat outFormat) throws IOException {

View File

@ -238,7 +238,8 @@ public class ServerAdapter extends ManagerBase {
Storage.StoragePoolType.NetworkFilesystem,
Storage.StoragePoolType.SharedMountPoint
);
public static final String WORKER_VM_GUEST_CPU_MODE = "host-passthrough";
private static final String VM_TA_KEY = "veeam_tag";
private static final String WORKER_VM_GUEST_CPU_MODE = "host-passthrough";
@Inject
RoleService roleService;
@ -413,22 +414,6 @@ public class ServerAdapter extends ManagerBase {
accountService.getActiveAccountById(userAccount.getAccountId()));
}
protected Pair<User, Account> getServiceAccount() {
String serviceAccountUuid = VeeamControlService.ServiceAccountId.value();
if (StringUtils.isEmpty(serviceAccountUuid)) {
throw new CloudRuntimeException("Service account is not configured, unable to proceed");
}
Account account = accountService.getActiveAccountByUuid(serviceAccountUuid);
if (account == null) {
throw new CloudRuntimeException("Service account with ID " + serviceAccountUuid + " not found, unable to proceed");
}
User user = accountService.getOneActiveUserForAccount(account);
if (user == null) {
throw new CloudRuntimeException("No active user found for service account with ID " + serviceAccountUuid);
}
return new Pair<>(user, account);
}
protected void waitForJobCompletion(long jobId) {
long timeoutNanos = TimeUnit.MINUTES.toNanos(5);
final long deadline = System.nanoTime() + timeoutNanos;
@ -858,6 +843,22 @@ public class ServerAdapter extends ManagerBase {
return vmInstanceDetailsDao.listDetailsKeyPairs(instanceId, true);
}
public Pair<User, Account> getServiceAccount() {
String serviceAccountUuid = VeeamControlService.ServiceAccountId.value();
if (StringUtils.isEmpty(serviceAccountUuid)) {
throw new CloudRuntimeException("Service account is not configured, unable to proceed");
}
Account account = accountService.getActiveAccountByUuid(serviceAccountUuid);
if (account == null) {
throw new CloudRuntimeException("Service account with ID " + serviceAccountUuid + " not found, unable to proceed");
}
User user = accountService.getOneActiveUserForAccount(account);
if (user == null) {
throw new CloudRuntimeException("No active user found for service account with ID " + serviceAccountUuid);
}
return new Pair<>(user, account);
}
@Override
public boolean start() {
getServiceAccount();
@ -1185,15 +1186,13 @@ public class ServerAdapter extends ManagerBase {
@ApiAccess(command = ListTagsCmd.class)
protected List<Tag> listTagsByInstanceId(final long instanceId) {
List<? extends ResourceTag> vmResourceTags = resourceTagDao.listBy(instanceId,
ResourceTag.ResourceObjectType.UserVm);
ResourceTag vmResourceTag = resourceTagDao.findByKey(instanceId,
ResourceTag.ResourceObjectType.UserVm, VM_TA_KEY);
List<ResourceTagVO> tags = new ArrayList<>();
for (ResourceTag t : vmResourceTags) {
if (t instanceof ResourceTagVO) {
tags.add((ResourceTagVO)t);
continue;
}
tags.add(resourceTagDao.findById(t.getId()));
if (vmResourceTag instanceof ResourceTagVO) {
tags.add((ResourceTagVO)vmResourceTag);
} else {
tags.add(resourceTagDao.findById(vmResourceTag.getId()));
}
return ResourceTagVOToTagConverter.toTags(tags);
}
@ -1760,8 +1759,8 @@ public class ServerAdapter extends ManagerBase {
List<Tag> tags = new ArrayList<>(getDummyTags().values());
Filter filter = new Filter(ResourceTagVO.class, "id", true, offset, limit);
Pair<List<Long>, List<Long>> ownerDetails = getResourceOwnerFiltersWithDomainIds();
List<ResourceTagVO> vmResourceTags = resourceTagDao.listByResourceTypeAndOwners(
ResourceTag.ResourceObjectType.UserVm, ownerDetails.first(), ownerDetails.second(), filter);
List<ResourceTagVO> vmResourceTags = resourceTagDao.listByResourceTypeKeyAndOwners(
ResourceTag.ResourceObjectType.UserVm, VM_TA_KEY, ownerDetails.first(), ownerDetails.second(), filter);
if (CollectionUtils.isNotEmpty(vmResourceTags)) {
tags.addAll(ResourceTagVOToTagConverter.toTags(vmResourceTags));
}
@ -1775,7 +1774,8 @@ public class ServerAdapter extends ManagerBase {
}
Tag tag = getDummyTags().get(uuid);
if (tag == null) {
ResourceTagVO resourceTagVO = resourceTagDao.findByUuid(uuid);
ResourceTagVO resourceTagVO = resourceTagDao.findByResourceTypeKeyAndValue(
ResourceTag.ResourceObjectType.UserVm, VM_TA_KEY, uuid);
accountService.checkAccess(CallContext.current().getCallingAccount(), null, false,
resourceTagVO);
if (resourceTagVO != null) {

View File

@ -21,21 +21,21 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
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.VeeamControlService;
import org.apache.cloudstack.veeam.VeeamControlServlet;
import org.apache.cloudstack.veeam.adapter.ServerAdapter;
import org.apache.cloudstack.veeam.api.dto.Api;
import org.apache.cloudstack.veeam.api.dto.ApiSummary;
import org.apache.cloudstack.veeam.api.dto.EmptyElement;
import org.apache.cloudstack.veeam.api.dto.Link;
import org.apache.cloudstack.veeam.api.dto.ProductInfo;
import org.apache.cloudstack.veeam.api.dto.Ref;
import org.apache.cloudstack.veeam.api.dto.SpecialObjects;
import org.apache.cloudstack.veeam.api.dto.SummaryCount;
import org.apache.cloudstack.veeam.api.dto.Version;
import org.apache.cloudstack.veeam.utils.Negotiation;
@ -43,9 +43,12 @@ import org.apache.cloudstack.veeam.utils.Negotiation;
import com.cloud.utils.UuidUtils;
import com.cloud.utils.component.ManagerBase;
public class ApiService extends ManagerBase implements RouteHandler {
public class ApiRouteHandler extends ManagerBase implements RouteHandler {
public static final String BASE_ROUTE = "/api";
@Inject
ServerAdapter serverAdapter;
@Override
public boolean canHandle(String method, String path) {
return getSanitizedPath(path).startsWith("/api");
@ -63,11 +66,11 @@ public class ApiService extends ManagerBase implements RouteHandler {
private void handleRootApiRequest(HttpServletRequest req, HttpServletResponse resp, Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
io.getWriter().write(resp, HttpServletResponse.SC_OK,
createDummyApi(VeeamControlService.ContextPath.value() + BASE_ROUTE),
createApiObject(VeeamControlService.ContextPath.value() + BASE_ROUTE),
outFormat);
}
private static Api createDummyApi(String basePath) {
protected Api createApiObject(String basePath) {
Api api = new Api();
/* ---------------- Links ---------------- */
@ -96,30 +99,11 @@ public class ApiService extends ManagerBase implements RouteHandler {
ProductInfo productInfo = new ProductInfo();
productInfo.setInstanceId(UuidUtils.nameUUIDFromBytes(
VeeamControlService.BindAddress.value().getBytes(StandardCharsets.UTF_8)).toString());
productInfo.name = "oVirt Engine";
productInfo.name = VeeamControlService.PLUGIN_NAME;
Version version = new Version();
version.setBuild("8");
version.setFullVersion("4.5.8-0.master.fake.el9");
version.setMajor("4");
version.setMinor("5");
version.setRevision("0");
productInfo.version = version;
productInfo.version = Version.fromPackageAndCSVersion(true);
api.setProductInfo(productInfo);
/* ---------------- Special objects ---------------- */
SpecialObjects specialObjects = new SpecialObjects();
specialObjects.setBlankTemplate(Ref.of(
basePath + "/templates/00000000-0000-0000-0000-000000000000",
"00000000-0000-0000-0000-000000000000"
));
specialObjects.setRootTag(Ref.of(
basePath + "/tags/00000000-0000-0000-0000-000000000000",
"00000000-0000-0000-0000-000000000000"
));
api.setSpecialObjects(specialObjects);
/* ---------------- Summary ---------------- */
ApiSummary summary = new ApiSummary();
summary.setHosts(new SummaryCount(1, 1));
@ -132,7 +116,7 @@ public class ApiService extends ManagerBase implements RouteHandler {
api.setTime(System.currentTimeMillis());
/* ---------------- Users ---------------- */
String userId = UUID.randomUUID().toString();
String userId = serverAdapter.getServiceAccount().first().getUuid();
api.setAuthenticatedUser(Ref.of(basePath + "/users/" + userId, userId));
api.setEffectiveUser(Ref.of(basePath + "/users/" + userId, userId));

View File

@ -222,11 +222,6 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
io.notFound(resp, null, outFormat);
}
protected static boolean isRequestAsync(HttpServletRequest req) {
String asyncStr = req.getParameter("async");
return Boolean.TRUE.toString().equals(asyncStr);
}
protected void handleGet(final HttpServletRequest req, final HttpServletResponse resp,
Negotiation.OutFormat outFormat, VeeamControlServlet io) throws IOException {
try {
@ -287,7 +282,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
protected void handleDeleteById(final String id, final HttpServletRequest req, final HttpServletResponse resp,
final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException {
boolean async = isRequestAsync(req);
boolean async = RouteHandler.isRequestAsync(req);
try {
VmAction vm = serverAdapter.deleteInstance(id, async);
io.getWriter().write(resp, HttpServletResponse.SC_OK, vm, outFormat);
@ -298,7 +293,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
protected void handleStartVmById(final String id, final HttpServletRequest req, final HttpServletResponse resp,
final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException {
boolean async = isRequestAsync(req);
boolean async = RouteHandler.isRequestAsync(req);
try {
VmAction vm = serverAdapter.startInstance(id, async);
io.getWriter().write(resp, HttpServletResponse.SC_ACCEPTED, vm, outFormat);
@ -309,7 +304,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
protected void handleStopVmById(final String id, final HttpServletRequest req, final HttpServletResponse resp,
final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException {
boolean async = isRequestAsync(req);
boolean async = RouteHandler.isRequestAsync(req);
try {
VmAction vm = serverAdapter.stopInstance(id, async);
io.getWriter().write(resp, HttpServletResponse.SC_ACCEPTED, vm, outFormat);
@ -320,7 +315,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
protected void handleShutdownVmById(final String id, final HttpServletRequest req, final HttpServletResponse resp,
final Negotiation.OutFormat outFormat, final VeeamControlServlet io) throws IOException {
boolean async = isRequestAsync(req);
boolean async = RouteHandler.isRequestAsync(req);
try {
VmAction vm = serverAdapter.shutdownInstance(id, async);
io.getWriter().write(resp, HttpServletResponse.SC_ACCEPTED, vm, outFormat);
@ -422,7 +417,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
protected void handleDeleteSnapshotById(final String id, final HttpServletRequest req,
final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io)
throws IOException {
boolean async = isRequestAsync(req);
boolean async = RouteHandler.isRequestAsync(req);
try {
ResourceAction action = serverAdapter.deleteSnapshot(id, async);
if (action != null) {
@ -438,7 +433,7 @@ public class VmsRouteHandler extends ManagerBase implements RouteHandler {
protected void handleRestoreSnapshotById(final String id, final HttpServletRequest req,
final HttpServletResponse resp, final Negotiation.OutFormat outFormat, final VeeamControlServlet io)
throws IOException {
boolean async = isRequestAsync(req);
boolean async = RouteHandler.isRequestAsync(req);
String data = RouteHandler.getRequestData(req, logger);
try {
ResourceAction response = serverAdapter.revertInstanceToSnapshot(id, async);

View File

@ -34,26 +34,6 @@ import com.cloud.api.query.vo.UserVmJoinVO;
public class AsyncJobJoinVOToJobConverter {
public static Job toJob(String uuid, String state, long startTime) {
Job job = new Job();
final String basePath = VeeamControlService.ContextPath.value();
// Fill in dummy data for now, as the AsyncJobJoinVO does not contain all the necessary information to populate a Job object.
job.setId(uuid);
job.setHref(basePath + JobsRouteHandler.BASE_ROUTE + "/" + uuid);
job.setAutoCleared(Boolean.TRUE.toString());
job.setExternal(Boolean.TRUE.toString());
job.setLastUpdated(System.currentTimeMillis());
job.setStartTime(startTime);
job.setStatus(state);
if ("complete".equalsIgnoreCase(state) || "finished".equalsIgnoreCase(state)) {
job.setEndTime(System.currentTimeMillis());
}
job.setOwner(Ref.of(basePath + "/api/users/" + uuid, uuid));
job.setDescription("Something");
job.setLink(Collections.emptyList());
return job;
}
public static Job toJob(AsyncJobJoinVO vo) {
Job job = new Job();
final String basePath = VeeamControlService.ContextPath.value();

View File

@ -23,9 +23,12 @@ import java.util.stream.Collectors;
import org.apache.cloudstack.backup.BackupVO;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.ApiRouteHandler;
import org.apache.cloudstack.veeam.api.VmsRouteHandler;
import org.apache.cloudstack.veeam.api.dto.Backup;
import org.apache.cloudstack.veeam.api.dto.Disk;
import org.apache.cloudstack.veeam.api.dto.Host;
import org.apache.cloudstack.veeam.api.dto.NamedList;
import org.apache.cloudstack.veeam.api.dto.Vm;
import com.cloud.api.query.vo.HostJoinVO;
@ -55,6 +58,16 @@ public class BackupVOToBackupConverter {
backup.setVm(Vm.of(basePath + VmsRouteHandler.BASE_ROUTE + "/" + vmVO.getUuid(), vmVO.getUuid()));
}
}
if (backupVO.getHostId() != null && hostResolver != null) {
final HostJoinVO hostVO = hostResolver.apply(backupVO.getHostId());
if (hostVO != null) {
backup.setHost(Host.of(basePath + ApiRouteHandler.BASE_ROUTE + "/" + hostVO.getUuid(), hostVO.getUuid()));
}
}
if (disksResolver != null) {
List<Disk> disks = disksResolver.apply(backupVO);
backup.setDisks(NamedList.of("disks", disks));
}
return backup;
}

View File

@ -30,6 +30,7 @@ import org.apache.cloudstack.veeam.api.dto.Cpu;
import org.apache.cloudstack.veeam.api.dto.Link;
import org.apache.cloudstack.veeam.api.dto.Ref;
import org.apache.cloudstack.veeam.api.dto.Version;
import org.apache.cloudstack.veeam.api.dto.Vm;
import com.cloud.api.query.vo.DataCenterJoinVO;
import com.cloud.dc.ClusterVO;
@ -39,19 +40,14 @@ public class ClusterVOToClusterConverter {
public static Cluster toCluster(final ClusterVO vo, final Function<Long, DataCenterJoinVO> 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:<id>"
final String clusterId = vo.getUuid();
c.setId(clusterId);
c.setHref(basePath + ClustersRouteHandler.BASE_ROUTE + "/" + clusterId);
c.setName(vo.getName());
// --- sensible defaults (match your sample)
c.setBallooningEnabled("true");
c.setBiosType("q35_ovmf"); // or "q35_secure_boot" if you want to align with VM BIOS you saw
c.setBiosType(Vm.Bios.getDefault().getType());
c.setFipsMode("disabled");
c.setFirewallType("firewalld");
c.setGlusterService("false");
@ -64,19 +60,14 @@ public class ClusterVOToClusterConverter {
c.setUpgradePercentComplete("0");
c.setVirtService("true");
c.setVncEncryption("false");
c.setLogMaxMemoryUsedThreshold("95");
c.setLogMaxMemoryUsedThresholdType("percentage");
// --- cpu (best-effort defaults)
final Cpu cpu = new Cpu();
cpu.setArchitecture("x86_64");
cpu.setType("x86_64"); // replace if you can detect host cpu model
cpu.setArchitecture(vo.getArch().getType());
cpu.setType(vo.getArch().getType()); // replace if you can detect host cpu model
c.setCpu(cpu);
// --- version (ovirt engine version; keep fixed unless you want to expose something else)
final Version ver = new Version();
ver.setMajor("4");
ver.setMinor("8");
final Version ver = Version.fromPackageAndCSVersion(false);
c.setVersion(ver);
// --- ksm / memory policy (defaults)

View File

@ -52,12 +52,10 @@ public class DataCenterJoinVOToDataCenterConverter {
dc.setQuotaMode("disabled");
dc.setStorageFormat("v5");
// ---- Versions (static but valid) ----
final Version v48 = new Version();
v48.setMajor("4");
v48.setMinor("8");
dc.setVersion(v48);
dc.setSupportedVersions(new SupportedVersions(List.of(v48)));
// ---- Versions ----
final Version ver = Version.fromPackageAndCSVersion(false);
dc.setVersion(ver);
dc.setSupportedVersions(new SupportedVersions(List.of(ver)));
// ---- mac_pool (static placeholder) ----
dc.setMacPool(Ref.of(basePath + "/macpools/default", "default"));

View File

@ -38,7 +38,6 @@ public class HostJoinVOToHostConverter {
/**
* Convert CloudStack HostJoinVO -> oVirt-like Host.
*
* @param vo HostJoinVO from listHosts (join query)
*/
public static Host toHost(final HostJoinVO vo) {
final Host h = new Host();
@ -49,8 +48,6 @@ public class HostJoinVOToHostConverter {
final String basePath = VeeamControlService.ContextPath.value();
h.setHref(basePath + HostsRouteHandler.BASE_ROUTE + "/" + hostUuid);
// --- name / address ---
// Prefer DNS name if set; otherwise fall back to IP
final String name = vo.getName() != null ? vo.getName() : ("host-" + hostUuid);
h.setName(name);
@ -75,9 +72,7 @@ public class HostJoinVOToHostConverter {
h.setMemory(String.valueOf(vo.getTotalMemory()));
h.setMaxSchedulingMemory(String.valueOf(vo.getTotalMemory() - vo.getMemUsedCapacity()));
// --- OS / versions (optional placeholders) ---
// If you want, you can set conservative defaults to match oVirt shape.
h.setType("rhel");
h.setType("ovirt_node");
h.setAutoNumaStatus("unknown");
h.setKdumpStatus("disabled");
h.setNumaSupported("false");
@ -85,8 +80,6 @@ public class HostJoinVOToHostConverter {
h.setUpdateAvailable("false");
// --- links/actions ---
// Start minimal (empty). Add actions only if Veeam tries to follow them.
h.setActions(null);
h.setLink(Collections.emptyList());
@ -98,13 +91,13 @@ public class HostJoinVOToHostConverter {
}
private static String mapStatus(final HostJoinVO vo) {
// CloudStack examples:
// state: Up/Down/Maintenance/Error/Disconnected
// status: Up/Down/Connecting/etc
if (vo.isInMaintenanceStates()) return "maintenance";
if (Status.Up.equals(vo.getStatus()) && ResourceState.Enabled.equals(vo.getResourceState())) return "up";
// Default
if (vo.isInMaintenanceStates()) {
return "maintenance";
}
if (Status.Up.equals(vo.getStatus()) &&
ResourceState.Enabled.equals(vo.getResourceState())) {
return "up";
}
return "down";
}
}

View File

@ -55,7 +55,6 @@ public class NetworkVOToNetworkConverter {
// Best-effort mapping for vdsm_name
dto.setVdsmName(dto.getName());
// zone -> oVirt datacenter ref
if (dcResolver != null) {
final DataCenterJoinVO dc = dcResolver.apply(vo.getDataCenterId());
if (dc != null) {

View File

@ -45,7 +45,6 @@ public class NetworkVOToVnicProfileConverter {
vnicProfile.setNetwork(Ref.of(basePath + NetworksRouteHandler.BASE_ROUTE + "/" + networkUuid, networkUuid));
vnicProfile.setDescription(vo.getDisplayText());
// zone -> oVirt datacenter ref
if (dcResolver != null) {
final DataCenterJoinVO dc = dcResolver.apply(vo.getDataCenterId());
if (dc != null) {

View File

@ -39,6 +39,8 @@ import com.cloud.network.dao.NetworkVO;
import com.cloud.vm.NicVO;
public class NicVOToNicConverter {
private static final String DEFAULT_INTERFACE_TYPE = "virtio";
private static final String DEFAULT_REPORTED_DEVICE_NAME = "eth0";
public static Nic toNic(final NicVO vo, final String vmUuid, final Function<Long, NetworkVO> networkResolver) {
final String basePath = VeeamControlService.ContextPath.value();
@ -56,7 +58,7 @@ public class NicVOToNicConverter {
nic.setVm(vm);
nic.setHref(vm.getHref() + "/nics/" + vo.getUuid());
}
nic.setInterfaceType("virtio");
nic.setInterfaceType(DEFAULT_INTERFACE_TYPE);
ReportedDevice device = getReportedDevice(vo, mac, nic.getVm());
nic.setReportedDevices(NamedList.of("reported_device", List.of(device)));
if (networkResolver != null) {
@ -73,7 +75,7 @@ public class NicVOToNicConverter {
ReportedDevice device = new ReportedDevice();
device.setType("network");
device.setId(vo.getUuid());
device.setName("eth0");
device.setName(DEFAULT_REPORTED_DEVICE_NAME);
device.setDescription(String.format("%s device", vo.getReserver()));
device.setMac(mac);
if (ObjectUtils.anyNotNull(vo.getIPv4Address(), vo.getIPv6Address())) {

View File

@ -48,10 +48,11 @@ public class ResourceTagVOToTagConverter {
public static Tag toTag(ResourceTagVO vo) {
String basePath = VeeamControlService.ContextPath.value();
Tag tag = new Tag();
tag.setId(vo.getUuid());
tag.setName(String.format("%s-%s", vo.getKey(), vo.getValue()).replaceAll("\\s+", ""));
String id = vo.getValue();
tag.setId(id);
tag.setName(vo.getValue());
tag.setDescription(String.format("Tag %s with value: %s", vo.getKey(), vo.getValue()));
tag.setHref(basePath + TagsRouteHandler.BASE_ROUTE + "/" + vo.getUuid());
tag.setHref(basePath + TagsRouteHandler.BASE_ROUTE + "/" + id);
if (ResourceTag.ResourceObjectType.UserVm.equals(vo.getResourceType())) {
tag.setVm(Ref.of(basePath + VmsRouteHandler.BASE_ROUTE + "/" + vo.getResourceUuid(),
vo.getResourceUuid()));

View File

@ -21,7 +21,7 @@ import java.util.List;
import java.util.stream.Collectors;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.ApiService;
import org.apache.cloudstack.veeam.api.ApiRouteHandler;
import org.apache.cloudstack.veeam.api.DataCentersRouteHandler;
import org.apache.cloudstack.veeam.api.dto.DataCenter;
import org.apache.cloudstack.veeam.api.dto.Link;
@ -42,7 +42,7 @@ public class StoreVOToStorageDomainConverter {
StorageDomain sd = new StorageDomain();
sd.setId(id);
final String href = href(basePath, ApiService.BASE_ROUTE + "/storagedomains/" + id);
final String href = href(basePath, ApiRouteHandler.BASE_ROUTE + "/storagedomains/" + id);
sd.setHref(href);
sd.setName(pool.getName());
@ -96,7 +96,7 @@ public class StoreVOToStorageDomainConverter {
StorageDomain sd = new StorageDomain();
sd.setId(id);
final String href = href(basePath, ApiService.BASE_ROUTE + "/storagedomains/" + id);
final String href = href(basePath, ApiRouteHandler.BASE_ROUTE + "/storagedomains/" + id);
sd.setHref(href);
sd.setName(store.getName());

View File

@ -26,7 +26,7 @@ import java.util.stream.Collectors;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.ApiService;
import org.apache.cloudstack.veeam.api.ApiRouteHandler;
import org.apache.cloudstack.veeam.api.VmsRouteHandler;
import org.apache.cloudstack.veeam.api.dto.BaseDto;
import org.apache.cloudstack.veeam.api.dto.Cpu;
@ -54,7 +54,6 @@ public final class UserVmJoinVOToVmConverter {
/**
* Convert CloudStack UserVmJoinVO -> oVirt-like Vm DTO.
*
* @param src UserVmJoinVO
*/
public static Vm toVm(final UserVmJoinVO src,
final Function<Long, HostJoinVO> hostResolver,
@ -84,7 +83,7 @@ public final class UserVmJoinVOToVmConverter {
dst.setStartTime(lastUpdated.getTime());
}
final Ref template = buildRef(
basePath + ApiService.BASE_ROUTE,
basePath + ApiRouteHandler.BASE_ROUTE,
"templates",
src.getTemplateUuid()
);
@ -92,20 +91,19 @@ public final class UserVmJoinVOToVmConverter {
dst.setOriginalTemplate(template);
if (StringUtils.isNotBlank(src.getHostUuid())) {
dst.setHost(buildRef(
basePath + ApiService.BASE_ROUTE,
basePath + ApiRouteHandler.BASE_ROUTE,
"hosts",
src.getHostUuid()));
}
if (hostResolver != null) {
HostJoinVO hostVo = hostResolver.apply(src.getHostId() == null ? src.getLastHostId() : src.getHostId());
if (hostVo != null) {
dst.setHost(buildRef(
basePath + ApiService.BASE_ROUTE,
basePath + ApiRouteHandler.BASE_ROUTE,
"hosts",
hostVo.getUuid()));
dst.setCluster(buildRef(
basePath + ApiService.BASE_ROUTE,
basePath + ApiRouteHandler.BASE_ROUTE,
"clusters",
hostVo.getClusterUuid()));
}
@ -123,9 +121,7 @@ public final class UserVmJoinVOToVmConverter {
cpu.setTopology(new Topology(src.getCpu(), 1, 1));
dst.setCpu(cpu);
Os os = new Os();
os.setType(src.getGuestOsId() % 2 == 0
? "windows"
: "linux");
os.setType(src.getGuestOsDisplayName());
Os.Boot boot = new Os.Boot();
boot.setDevices(NamedList.of("device", List.of("hd")));
os.setBoot(boot);
@ -167,7 +163,7 @@ public final class UserVmJoinVOToVmConverter {
dst.setTags(NamedList.of("tag", tags));
}
dst.setCpuProfile(Ref.of(
basePath + ApiService.BASE_ROUTE + "/cpuprofiles/" + src.getServiceOfferingUuid(),
basePath + ApiRouteHandler.BASE_ROUTE + "/cpuprofiles/" + src.getServiceOfferingUuid(),
src.getServiceOfferingUuid()));
if (allContent) {
dst.setInitialization(getOvfInitialization(dst, src));
@ -204,9 +200,10 @@ public final class UserVmJoinVOToVmConverter {
}
private static String mapStatus(final VirtualMachine.State state) {
// CloudStack-ish states -> oVirt-ish up/down
if (Arrays.asList(VirtualMachine.State.Running,
VirtualMachine.State.Migrating, VirtualMachine.State.Restoring).contains(state)) {
if (Arrays.asList(
VirtualMachine.State.Running,
VirtualMachine.State.Migrating,
VirtualMachine.State.Restoring).contains(state)) {
return "up";
}
return "down";

View File

@ -24,7 +24,7 @@ import java.util.stream.Collectors;
import org.apache.cloudstack.backup.Backup;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.cloudstack.veeam.api.ApiService;
import org.apache.cloudstack.veeam.api.ApiRouteHandler;
import org.apache.cloudstack.veeam.api.DisksRouteHandler;
import org.apache.cloudstack.veeam.api.VmsRouteHandler;
import org.apache.cloudstack.veeam.api.dto.Disk;
@ -43,7 +43,7 @@ public class VolumeJoinVOToDiskConverter {
public static Disk toDisk(final VolumeJoinVO vol, final Function<VolumeJoinVO, Long> physicalSizeResolver) {
final Disk disk = new Disk();
final String basePath = VeeamControlService.ContextPath.value();
final String apiBasePath = basePath + ApiService.BASE_ROUTE;
final String apiBasePath = basePath + ApiRouteHandler.BASE_ROUTE;
final String diskId = vol.getUuid();
final String diskHref = basePath + DisksRouteHandler.BASE_ROUTE + "/" + diskId;

View File

@ -23,9 +23,11 @@ public class Backup extends BaseDto {
private String description;
private Long creationDate;
private Vm vm;
private Host host;
private String phase;
private String fromCheckpointId;
private String toCheckpointId;
private NamedList<Disk> disks;
public String getName() {
return name;
@ -59,6 +61,14 @@ public class Backup extends BaseDto {
this.vm = vm;
}
public Host getHost() {
return host;
}
public void setHost(Host host) {
this.host = host;
}
public String getPhase() {
return phase;
}
@ -82,4 +92,12 @@ public class Backup extends BaseDto {
public void setToCheckpointId(String toCheckpointId) {
this.toCheckpointId = toCheckpointId;
}
public NamedList<Disk> getDisks() {
return disks;
}
public void setDisks(NamedList<Disk> disks) {
this.disks = disks;
}
}

View File

@ -46,4 +46,10 @@ public class BaseDto {
public static Link getActionLink(final String action, final String baseHref) {
return Link.of(action, baseHref + "/" + action);
}
protected static <T extends BaseDto> T withHrefAndId(T dto, String href, String id) {
dto.setHref(href);
dto.setId(id);
return dto;
}
}

View File

@ -308,4 +308,8 @@ public class Host extends BaseDto {
this.version = version;
}
}
public static Host of(String href, String id) {
return withHrefAndId(new Host(), href, id);
}
}

View File

@ -17,6 +17,10 @@
package org.apache.cloudstack.veeam.api.dto;
import org.apache.cloudstack.utils.CloudStackVersion;
import org.apache.cloudstack.veeam.VeeamControlService;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
@ -70,4 +74,21 @@ public final class Version {
public void setRevision(String revision) {
this.revision = revision;
}
public static Version fromPackageAndCSVersion(boolean complete) {
Version version = new Version();
String packageVersion = VeeamControlService.getPackageVersion();
if (StringUtils.isNotBlank(packageVersion) && complete) {
version.setFullVersion(packageVersion);
}
CloudStackVersion csVersion = VeeamControlService.getCSVersion();
if (csVersion == null) {
return version;
}
version.setMajor(String.valueOf(csVersion.getMajorRelease()));
version.setMinor(String.valueOf(csVersion.getMinorRelease()));
version.setBuild(String.valueOf(csVersion.getPatchRelease()));
version.setRevision(String.valueOf(csVersion.getSecurityRelease()));
return version;
}
}

View File

@ -513,9 +513,6 @@ public final class Vm extends BaseDto {
}
public static Vm of(String href, String id) {
Vm vm = new Vm();
vm.setHref(href);
vm.setId(id);
return vm;
return withHrefAndId(new Vm(), href, id);
}
}

View File

@ -1,39 +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.response;
import org.apache.cloudstack.veeam.api.dto.Fault;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
@JsonInclude(JsonInclude.Include.NON_NULL)
@JacksonXmlRootElement(localName = "fault")
public final class FaultResponse {
public Fault fault;
public FaultResponse() {}
public FaultResponse(final Fault fault) {
this.fault = fault;
}
public static FaultResponse of(final String reason, final String detail) {
return new FaultResponse(new Fault(reason, detail));
}
}

View File

@ -25,6 +25,7 @@ import org.apache.commons.lang3.StringUtils;
import com.cloud.utils.UuidUtils;
public class PathUtil {
private static final boolean CONSIDER_ONLY_UUID_AS_ID = false;
public static List<String> extractIdAndSubPath(final String path, final String baseRoute) {
@ -65,7 +66,7 @@ public class PathUtil {
}
// Validate first segment is a UUID
if (validParts.isEmpty() || !UuidUtils.isUuid(validParts.get(0))) {
if (validParts.isEmpty() || (CONSIDER_ONLY_UUID_AS_ID && !UuidUtils.isUuid(validParts.get(0)))) {
return null;
}

View File

@ -23,7 +23,6 @@ import java.nio.charset.StandardCharsets;
import javax.servlet.http.HttpServletResponse;
import org.apache.cloudstack.veeam.api.dto.Fault;
import org.apache.cloudstack.veeam.api.response.FaultResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -78,7 +77,7 @@ public final class ResponseWriter {
if (fmt == Negotiation.OutFormat.XML) {
write(resp, status, fault, fmt);
} else {
write(resp, status, new FaultResponse(fault), fmt);
write(resp, status, fault, fmt);
}
}
}

View File

@ -36,7 +36,7 @@
<bean id="veeamControlSsoService" class="org.apache.cloudstack.veeam.sso.SsoService"/>
<bean id="pkiResourceRouteHandler" class="org.apache.cloudstack.veeam.services.PkiResourceRouteHandler"/>
<bean id="veeamControlApiService" class="org.apache.cloudstack.veeam.api.ApiService" />
<bean id="apiRouteHandler" class="org.apache.cloudstack.veeam.api.ApiRouteHandler" />
<bean id="dataCentersRouteHandler" class="org.apache.cloudstack.veeam.api.DataCentersRouteHandler"/>
<bean id="clustersRouteHandler" class="org.apache.cloudstack.veeam.api.ClustersRouteHandler"/>
<bean id="hostsRouteHandler" class="org.apache.cloudstack.veeam.api.HostsRouteHandler"/>

View File

@ -1,3 +1,21 @@
<!--
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.
-->
<?xml version="1.0" encoding="UTF-8"?>
<ovf:Envelope
xmlns:ovf="http://schemas.dmtf.org/ovf/envelope/1/"

View File

@ -131,6 +131,9 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
@Column(name = "guest_os_uuid")
private String guestOsUuid;
@Column(name = "guest_os_display_name")
private String guestOsDisplayName;
@Column(name = "hypervisor_type")
@Convert(converter = HypervisorTypeConverter.class)
private HypervisorType hypervisorType;
@ -612,6 +615,10 @@ public class UserVmJoinVO extends BaseViewWithTagInformationVO implements Contro
return guestOsUuid;
}
public String getGuestOsDisplayName() {
return guestOsDisplayName;
}
public HypervisorType getHypervisorType() {
return hypervisorType;
}