diff --git a/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/GetVdsCertificateSubjectsByVmIdsQuery.java b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/GetVdsCertificateSubjectsByVmIdsQuery.java new file mode 100644 index 00000000000..16a317cd12c --- /dev/null +++ b/backend/manager/modules/bll/src/main/java/org/ovirt/engine/core/bll/GetVdsCertificateSubjectsByVmIdsQuery.java @@ -0,0 +1,71 @@ +package org.ovirt.engine.core.bll; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import javax.inject.Inject; + +import org.ovirt.engine.core.bll.context.EngineContext; +import org.ovirt.engine.core.common.businessentities.VdsStatic; +import org.ovirt.engine.core.common.businessentities.VmDynamic; +import org.ovirt.engine.core.common.queries.IdsQueryParameters; +import org.ovirt.engine.core.common.queries.QueryReturnValue; +import org.ovirt.engine.core.compat.Guid; +import org.ovirt.engine.core.dao.VdsStaticDao; +import org.ovirt.engine.core.dao.VmDynamicDao; +import org.ovirt.engine.core.utils.CertificateSubjectHelper; + + +public class GetVdsCertificateSubjectsByVmIdsQuery

extends QueriesCommandBase

{ + @Inject + private VmDynamicDao vmDynamicDao; + + @Inject + private VdsStaticDao vdsStaticDao; + + public GetVdsCertificateSubjectsByVmIdsQuery(P parameters, EngineContext engineContext) { + super(parameters, engineContext); + } + + @Override + protected void executeQueryCommand() { + // Initially we set the command as failed: + QueryReturnValue queryReturnValue = getQueryReturnValue(); + queryReturnValue.setSucceeded(false); + + // Check if the virtual machines are running on hosts, and if so then retrieve the hosts and copy the subject + // of the certificate to the value returned by the query: + List vms = vmDynamicDao.getByIds(getParameters().getIds()); + List vdsIds = vms.stream() + .map(VmDynamic::getRunOnVds) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toList()); + if (!vdsIds.isEmpty()) { + List vdss = vdsStaticDao.getByIds(vdsIds); + // Collect certificate subjects for all hosts running the VMs + Map certificateSubjects = vdss.stream() + .collect(Collectors.toMap( + VdsStatic::getId, + vds -> CertificateSubjectHelper.getCertificateSubject(vds.getHostName()))); + // Populate the certificate subjects for corresponding VMs + Map certificateForVms = new HashMap<>(); + for (VmDynamic vm : vms) { + Guid vdsId = vm.getRunOnVds(); + if (vdsId != null) { + String subject = certificateSubjects.get(vdsId); + if (subject != null) { + certificateForVms.put(vm.getId(), subject); + } + } + } + if (!certificateForVms.isEmpty()) { + queryReturnValue.setSucceeded(true); + queryReturnValue.setReturnValue(certificateForVms); + } + } + } +} diff --git a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/queries/QueryType.java b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/queries/QueryType.java index 2c429da1386..3cd353c2017 100644 --- a/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/queries/QueryType.java +++ b/backend/manager/modules/common/src/main/java/org/ovirt/engine/core/common/queries/QueryType.java @@ -168,6 +168,7 @@ public enum QueryType implements Serializable { // Cluster GetVdsCertificateSubjectByVmId(QueryAuthType.User), + GetVdsCertificateSubjectsByVmIds(QueryAuthType.User), GetAllClusters(QueryAuthType.User), GetClusterById(QueryAuthType.User), GetClusterByName(QueryAuthType.User), diff --git a/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDao.java b/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDao.java index 6cc445f09f0..05af987602f 100644 --- a/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDao.java +++ b/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDao.java @@ -40,6 +40,14 @@ public interface VmDynamicDao extends GenericDao, StatusAwareDa @Override VmDynamic get(Guid id); + /** + * Get all VmDynamic with the given ids + * @param vmIds + * the list of VM ids + * @return list of corresponding dynamics + */ + List getByIds(List vmIds); + /** * Updates the specified dynamic vm. * diff --git a/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDaoImpl.java b/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDaoImpl.java index 3776c9ab93d..52ba673deb7 100644 --- a/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDaoImpl.java +++ b/backend/manager/modules/dal/src/main/java/org/ovirt/engine/core/dao/VmDynamicDaoImpl.java @@ -163,6 +163,14 @@ public List getAllRunningForUserAndActionGroup(Guid userID, ActionGro getCustomMapSqlParameterSource().addValue("user_id", userID).addValue("action_group_id", actionGroup.getId())); } + @Override + public List getByIds(List vmIds) { + return getCallsHandler().executeReadList("GetVmDynamicByVmGuids", + createEntityRowMapper(), + getCustomMapSqlParameterSource() + .addValue("vm_guids", createArrayOfUUIDs(vmIds))); + } + @Override protected MapSqlParameterSource createIdParameterMapper(Guid id) { return getCustomMapSqlParameterSource().addValue("vm_guid", id); diff --git a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmsResource.java b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmsResource.java index f2b51f25746..82037bdda60 100644 --- a/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmsResource.java +++ b/backend/manager/modules/restapi/jaxrs/src/main/java/org/ovirt/engine/api/restapi/resource/BackendVmsResource.java @@ -26,6 +26,7 @@ import org.ovirt.engine.api.common.util.DetailHelper; import org.ovirt.engine.api.model.ActionableResource; import org.ovirt.engine.api.model.AutoPinningPolicy; +import org.ovirt.engine.api.model.Certificate; import org.ovirt.engine.api.model.Configuration; import org.ovirt.engine.api.model.ConfigurationType; import org.ovirt.engine.api.model.Disk; @@ -731,6 +732,9 @@ protected Vms mapCollection(List> vmsGraphicsDevices = DisplayHelper.getGraphicsDevicesForMultipleEntities(this, vmIds); + // optimization of DB access: retrieve Certificates for all VMs at once + Map vmsCertificate = + DisplayHelper.getDisplayCertificatesForMultipleEntities(this, vmIds); for (org.ovirt.engine.core.common.businessentities.VM entity : entities) { Vm vm = map(entity); @@ -742,7 +746,7 @@ protected Vms mapCollection(List getDisplayCertificatesForMultipleEntities(BackendResource res, List vmIds) { + QueryReturnValue result = + res.runQuery(QueryType.GetVdsCertificateSubjectsByVmIds, + new IdsQueryParameters(vmIds)); + + if (result != null && result.getSucceeded() && result.getReturnValue() != null) { + Map certificateForVms = result.getReturnValue(); + + String certificateContent = null; + final QueryReturnValue caCertificateReturnValue = + res.runQuery(QueryType.GetCACertificate, new QueryParametersBase()); + if (caCertificateReturnValue.getSucceeded()) { + certificateContent = caCertificateReturnValue.getReturnValue(); + } + String organizationName = CertificateSubjectHelper.getOrganizationName(); + + Map certificates = new HashMap<>(); + for (Map.Entry e : certificateForVms.entrySet()) { + Certificate cert = new Certificate(); + cert.setSubject(e.getValue()); + cert.setOrganization(organizationName); + cert.setContent(certificateContent); + + certificates.put(e.getKey(), cert); + } + return certificates; + } + return Collections.emptyMap(); + } + private static Display extractDisplayFromResource(BaseResource res) { if (res instanceof Vm) { return ((Vm) res).getDisplay(); diff --git a/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmsResourceTest.java b/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmsResourceTest.java index f5199a99dfa..061c79a75f1 100644 --- a/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmsResourceTest.java +++ b/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/resource/BackendVmsResourceTest.java @@ -26,6 +26,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; +import org.ovirt.engine.api.model.Certificate; import org.ovirt.engine.api.model.Configuration; import org.ovirt.engine.api.model.CreationStatus; import org.ovirt.engine.api.model.Disk; @@ -88,13 +89,14 @@ public class BackendVmsResourceTest private static final String DEFAULT_TEMPLATE_ID = Guid.Empty.toString(); public static final String CERTIFICATE = "O=Redhat,CN=X.Y.Z.Q"; private static final String CA_CERT = "dummy-cert"; + private static final String ORG = "ORG"; public BackendVmsResourceTest() { super(new BackendVmsResource(), SearchType.VM, "VMs : "); } public static Stream> mockConfiguration() { - return Stream.of(MockConfigDescriptor.of(ConfigValues.OrganizationName, "ORG"), + return Stream.of(MockConfigDescriptor.of(ConfigValues.OrganizationName, ORG), MockConfigDescriptor.of(ConfigValues.PropagateDiskErrors, false) ); } @@ -1161,11 +1163,11 @@ public void testList() throws Exception { UriInfo uriInfo = setUpUriExpectations(null); setUpGetGraphicsMultipleExpectations(3); - setUpQueryExpectations(""); - setUpGetCertificateExpectations(1, 0); + setUpGetDisplayCertificatesMultipleExpectations(); setUpGetCaRootExpectations(); + setUpQueryExpectations(""); collection.setUriInfo(uriInfo); - verifyCollection(getCollection()); + verifyCollection(getCollection(), false, true); } @Test @@ -1198,7 +1200,7 @@ private void testListAllConsoleAware(boolean allContent) throws Exception { setUpQueryExpectations(""); collection.setUriInfo(uriInfo); - verifyCollection(getCollection()); + verifyCollection(getCollection(), false, allContent); } @Test @@ -1209,7 +1211,7 @@ public void testListAllContentHeader() throws Exception { when(httpHeaders.getRequestHeader(BackendResource.ALL_CONTENT_HEADER)).thenReturn(populates); setUpAllContentExpectations(); collection.setUriInfo(uriInfo); - verifyCollection(getCollection()); + verifyCollection(getCollection(), false, true); } @Test @@ -1221,7 +1223,7 @@ public void testListAllContentQueryParameter() throws Exception { when(uriInfo.getQueryParameters()).thenReturn(queries); setUpAllContentExpectations(); collection.setUriInfo(uriInfo); - verifyCollection(getCollection(), true); + verifyCollection(getCollection(), true, true); } private void setUpAllContentExpectations() throws Exception { @@ -1501,16 +1503,23 @@ protected List getCollection() { @Override protected void verifyCollection(List collection) throws Exception { - verifyCollection(collection, false); + verifyCollection(collection, false, false); } - private void verifyCollection(List collection, boolean isPopulated) throws Exception { + private void verifyCollection(List collection, boolean isPopulated, boolean hasCertificates) throws Exception { super.verifyCollection(collection); boolean populated = isPopulated || checkPopulatedHeader(); for (Vm vm : collection) { assertTrue(populated ? vm.isSetConsole() : !vm.isSetConsole()); + assertEquals(hasCertificates, vm.getDisplay().isSetCertificate()); + if (hasCertificates) { + Certificate cert = vm.getDisplay().getCertificate(); + assertEquals(CERTIFICATE, cert.getSubject()); + assertEquals(CA_CERT, cert.getContent()); + assertEquals(ORG, cert.getOrganization()); + } } } @@ -1705,6 +1714,19 @@ protected void setUpGetGraphicsMultipleExpectations(int times) { vmDevices); } + protected void setUpGetDisplayCertificatesMultipleExpectations() { + Map certificates = new HashMap<>(); + for (Guid guid : GUIDS) { + certificates.put(guid, CERTIFICATE); + } + + setUpGetEntityExpectations(QueryType.GetVdsCertificateSubjectsByVmIds, + QueryParametersBase.class, + new String[]{}, + new Object[]{}, + certificates); + } + protected void setUpGetGraphicsExpectations(int times) { for (int i = 0; i < times; i++) { setUpGetEntityExpectations(QueryType.GetGraphicsDevices, diff --git a/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/util/DisplayHelperTest.java b/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/util/DisplayHelperTest.java new file mode 100644 index 00000000000..5d3c5d74a5c --- /dev/null +++ b/backend/manager/modules/restapi/jaxrs/src/test/java/org/ovirt/engine/api/restapi/util/DisplayHelperTest.java @@ -0,0 +1,93 @@ +package org.ovirt.engine.api.restapi.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.ovirt.engine.api.model.Certificate; +import org.ovirt.engine.api.model.Vm; +import org.ovirt.engine.api.restapi.resource.BackendResource; +import org.ovirt.engine.core.common.config.ConfigValues; +import org.ovirt.engine.core.common.queries.QueryReturnValue; +import org.ovirt.engine.core.common.queries.QueryType; +import org.ovirt.engine.core.compat.Guid; +import org.ovirt.engine.core.utils.MockConfigDescriptor; +import org.ovirt.engine.core.utils.MockConfigExtension; + +@MockitoSettings(strictness = Strictness.LENIENT) +@ExtendWith(MockConfigExtension.class) +public class DisplayHelperTest { + private static final String CERTIFICATE = "O=Redhat,CN=X.Y.Z.Q"; + private static final String CA_CERT = "dummy-cert"; + private static final String ORG = "ORG"; + private static final List GUIDS = Arrays.asList( + new Guid("11111111-1111-1111-1111-111111111111"), + new Guid("22222222-2222-2222-2222-222222222222")); + + public static Stream> mockConfiguration() { + return Stream.of(MockConfigDescriptor.of(ConfigValues.OrganizationName, ORG)); + } + + @Test + public void testAddDisplayCertificate() { + Vm vm = new Vm(); + Certificate certificate = new Certificate(); + DisplayHelper.addDisplayCertificate(vm, certificate); + + assertSame(certificate, vm.getDisplay().getCertificate()); + } + + @Test + public void testGetDisplayCertificatesForMultipleEntitiesNoResult() { + BackendResource res = mock(BackendResource.class); + QueryReturnValue result = new QueryReturnValue(); + result.setSucceeded(false); + when(res.runQuery(eq(QueryType.GetVdsCertificateSubjectsByVmIds), any())).thenReturn(result); + + Map certificates = DisplayHelper.getDisplayCertificatesForMultipleEntities(res, GUIDS); + assertTrue(certificates.isEmpty()); + } + + @Test + public void testGetDisplayCertificatesForMultipleEntities() { + BackendResource res = mock(BackendResource.class); + + QueryReturnValue result = new QueryReturnValue(); + result.setSucceeded(true); + Map subjects = GUIDS.stream().collect(Collectors.toMap(Function.identity(), id -> CERTIFICATE)); + result.setReturnValue(subjects); + when(res.runQuery(eq(QueryType.GetVdsCertificateSubjectsByVmIds), any())).thenReturn(result); + + result = new QueryReturnValue(); + result.setSucceeded(true); + result.setReturnValue(CA_CERT); + when(res.runQuery(eq(QueryType.GetCACertificate), any())).thenReturn(result); + + + Map certificates = DisplayHelper.getDisplayCertificatesForMultipleEntities(res, GUIDS); + assertEquals(GUIDS.size(), certificates.size()); + for (Guid guid : GUIDS) { + Certificate cert = certificates.get(guid); + assertNotNull(cert); + assertEquals(CERTIFICATE, cert.getSubject()); + assertEquals(CA_CERT, cert.getContent()); + assertEquals(ORG, cert.getOrganization()); + } + } +} diff --git a/packaging/dbscripts/vms_sp.sql b/packaging/dbscripts/vms_sp.sql index 5a1c875dd9a..e7471f38876 100644 --- a/packaging/dbscripts/vms_sp.sql +++ b/packaging/dbscripts/vms_sp.sql @@ -638,6 +638,19 @@ BEGIN END;$FUNCTION$ LANGUAGE plpgsql; +CREATE OR REPLACE FUNCTION GetVmDynamicByVmGuids (v_vm_guids UUID[]) +RETURNS SETOF vm_dynamic STABLE AS $FUNCTION$ +BEGIN + RETURN QUERY + + SELECT vm_dynamic.* + FROM vm_dynamic + WHERE vm_guid = ANY(v_vm_guids); + + RETURN; +END;$FUNCTION$ +LANGUAGE plpgsql; + DROP TYPE IF EXISTS GetAllHashesFromVmDynamic_rs CASCADE; CREATE TYPE GetAllHashesFromVmDynamic_rs AS (vm_guid UUID, hash VARCHAR); CREATE OR REPLACE FUNCTION GetAllHashesFromVmDynamic ()