package org.ovirt.engine.core.bll;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.commons.lang.StringUtils;
import org.ovirt.engine.api.extensions.ExtMap;
import org.ovirt.engine.core.aaa.AuthenticationProfileRepository;
import org.ovirt.engine.core.aaa.DirectoryGroup;
import org.ovirt.engine.core.aaa.DirectoryUser;
import org.ovirt.engine.core.aaa.QueryData;
import org.ovirt.engine.core.aaa.SsoOAuthServiceUtils;
import org.ovirt.engine.core.bll.aaa.DirectoryUtils;
import org.ovirt.engine.core.bll.aaa.SessionDataContainer;
import org.ovirt.engine.core.bll.quota.QuotaManager;
import org.ovirt.engine.core.bll.storage.pool.DcSingleMacPoolFinder;
import org.ovirt.engine.core.common.businessentities.AuditLog;
import org.ovirt.engine.core.common.businessentities.Cluster;
import org.ovirt.engine.core.common.businessentities.EngineSession;
import org.ovirt.engine.core.common.businessentities.IVdcQueryable;
import org.ovirt.engine.core.common.businessentities.Provider;
import org.ovirt.engine.core.common.businessentities.Quota;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StoragePool;
import org.ovirt.engine.core.common.businessentities.UserSession;
import org.ovirt.engine.core.common.businessentities.VDS;
import org.ovirt.engine.core.common.businessentities.VM;
import org.ovirt.engine.core.common.businessentities.VmPool;
import org.ovirt.engine.core.common.businessentities.VmTemplate;
import org.ovirt.engine.core.common.businessentities.aaa.DbGroup;
import org.ovirt.engine.core.common.businessentities.aaa.DbUser;
import org.ovirt.engine.core.common.businessentities.gluster.GlusterVolumeEntity;
import org.ovirt.engine.core.common.businessentities.network.NetworkView;
import org.ovirt.engine.core.common.businessentities.storage.Disk;
import org.ovirt.engine.core.common.businessentities.storage.ImageTransfer;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.SearchEngineIllegalCharacterException;
import org.ovirt.engine.core.common.errors.SqlInjectionException;
import org.ovirt.engine.core.common.queries.SearchParameters;
import org.ovirt.engine.core.common.queries.VdcQueryParametersBase;
import org.ovirt.engine.core.common.queries.VdcQueryType;
import org.ovirt.engine.core.compat.DateTime;
import org.ovirt.engine.core.compat.TimeSpan;
import org.ovirt.engine.core.compat.Version;
import org.ovirt.engine.core.dao.AuditLogDao;
import org.ovirt.engine.core.dao.ClusterDao;
import org.ovirt.engine.core.dao.DbGroupDao;
import org.ovirt.engine.core.dao.DbUserDao;
import org.ovirt.engine.core.dao.DiskDao;
import org.ovirt.engine.core.dao.EngineSessionDao;
import org.ovirt.engine.core.dao.ImageTransferDao;
import org.ovirt.engine.core.dao.QuotaDao;
import org.ovirt.engine.core.dao.SearchDao;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.dao.StoragePoolDao;
import org.ovirt.engine.core.dao.VdsDao;
import org.ovirt.engine.core.dao.VmDao;
import org.ovirt.engine.core.dao.VmPoolDao;
import org.ovirt.engine.core.dao.VmTemplateDao;
import org.ovirt.engine.core.dao.gluster.GlusterVolumeDao;
import org.ovirt.engine.core.dao.network.NetworkViewDao;
import org.ovirt.engine.core.dao.provider.ProviderDao;
import org.ovirt.engine.core.searchbackend.ISyntaxChecker;
import org.ovirt.engine.core.searchbackend.SearchObjects;
import org.ovirt.engine.core.searchbackend.SyntaxCheckerFactory;
import org.ovirt.engine.core.searchbackend.SyntaxContainer;
import org.ovirt.engine.core.searchbackend.SyntaxError;
public class SearchQuery<P extends SearchParameters> extends QueriesCommandBase<P> {
private static final HashMap<String, QueryData> queriesCache = new HashMap<>();
public static final String LDAP = "LDAP";
@Inject
private QuotaManager quotaManager;
@Inject
private SessionDataContainer sessionDataContainer;
@Inject
protected CpuFlagsManagerHandler cpuFlagsManagerHandler;
@Inject
private VmHandler vmHandler;
@Inject
private VmDao vmDao;
@Inject
private VdsDao vdsDao;
@Inject
private DbUserDao dbUserDao;
@Inject
private DbGroupDao dbGroupDao;
@Inject
private VmTemplateDao vmTemplateDao;
@Inject
private AuditLogDao auditLogDao;
@Inject
private VmPoolDao vmPoolDao;
@Inject
private ClusterDao clusterDao;
@Inject
private StoragePoolDao storagePoolDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private QuotaDao quotaDao;
@Inject
private DiskDao diskDao;
@Inject
private GlusterVolumeDao glusterVolumeDao;
@Inject
private NetworkViewDao networkViewDao;
@Inject
private ProviderDao providerDao;
@Inject
private EngineSessionDao engineSessionDao;
@Inject
private ImageTransferDao imageTransferDao;
@Inject
private DirectoryUtils directoryUtils;
@Inject
private DcSingleMacPoolFinder dcSingleMacPoolFinder;
public SearchQuery(P parameters) {
super(parameters);
}
@Override
protected void executeQueryCommand() {
List<? extends IVdcQueryable> returnValue = new ArrayList<>();
switch (getParameters().getSearchTypeValue()) {
case VM:
returnValue = searchVmsFromDb();
break;
case DirectoryGroup:
returnValue = searchDirectoryGroups();
break;
case DirectoryUser:
returnValue = searchDirectoryUsers();
break;
case AuditLog:
returnValue = searchAuditLogEvents();
break;
case DBUser:
returnValue = searchDbUsers();
break;
case DBGroup:
returnValue = searchDbGroups();
break;
case VDS:
returnValue = searchVDSsByDb();
break;
case VmTemplate:
returnValue = searchVmTemplates();
break;
case VmPools:
returnValue = searchVmPools();
break;
case Cluster:
returnValue = searchClusters();
break;
case StoragePool:
returnValue = searchStoragePool();
break;
case StorageDomain:
returnValue = searchStorageDomain();
break;
case Quota:
returnValue = searchQuota();
break;
case Disk:
returnValue = searchDisk();
break;
case GlusterVolume:
returnValue = searchGlusterVolumes();
break;
case Network:
returnValue = searchNetworks();
break;
case Provider:
returnValue = searchProviders();
break;
case InstanceType:
returnValue = searchInstanceTypes();
break;
case ImageType:
returnValue = searchImageTypes();
break;
case Session:
returnValue = searchSessions();
break;
case ImageTransfer:
returnValue = searchImageTransfer();
break;
default:
log.error("Search object type not handled: {}", getParameters().getSearchTypeValue());
break;
}
getQueryReturnValue().setReturnValue(returnValue);
}
private List<VM> searchVmsFromDb() {
QueryData data = initQueryData(true);
if (data == null) {
return Collections.emptyList();
}
List<VM> vms = vmDao.getAllUsingQuery(data.getQuery());
for (VM vm : vms) {
vmHandler.updateVmGuestAgentVersion(vm);
vmHandler.updateVmLock(vm);
vmHandler.updateOperationProgress(vm);
vmHandler.updateVmStatistics(vm);
}
return vms;
}
private List<VDS> searchVDSsByDb() {
List<VDS> data = genericSearch(vdsDao, true);
for (VDS vds : data) {
vds.setCpuName(cpuFlagsManagerHandler.findMaxServerCpuByFlags(vds.getCpuFlags(),
vds.getClusterCompatibilityVersion()));
}
return data;
}
private List<DirectoryUser> searchDirectoryUsers() {
// Parse the query:
QueryData data = initQueryData(true);
if (data == null) {
return Collections.emptyList();
}
List<DirectoryUser> results = new ArrayList<>();
Map<String, Object> response = SsoOAuthServiceUtils.searchUsers(
sessionDataContainer.getSsoAccessToken(getParameters().getSessionId()),
getParamsMap(data));
if (response.containsKey("result")) {
Collection<ExtMap> users = (Collection<ExtMap>) response.get("result");
results = users.stream()
.map((ExtMap u) -> directoryUtils.mapPrincipalRecordToDirectoryUser(data.getAuthz(), u))
.collect(Collectors.toList());
}
return results;
}
private static Map<String, Object> getParamsMap(QueryData queryData) {
Map<String, Object> params = new HashMap<>();
params.put("authz", queryData.getAuthz());
params.put("namespace", queryData.getNamespace());
params.put("query", queryData.getQuery());
return params;
}
private List<DirectoryGroup> searchDirectoryGroups() {
// Parse the query:
QueryData data = initQueryData(true);
if (data == null) {
return Collections.emptyList();
}
List<DirectoryGroup> results = new ArrayList<>();
Map<String, Object> response = SsoOAuthServiceUtils.searchGroups(
sessionDataContainer.getSsoAccessToken(getParameters().getSessionId()),
getParamsMap(data));
if (response.containsKey("result")) {
Collection<ExtMap> groups = (Collection<ExtMap>) response.get("result");
results = groups.stream()
.map((ExtMap g) -> directoryUtils.mapGroupRecordToDirectoryGroup(data.getAuthz(), g))
.collect(Collectors.toList());
}
return results;
}
private List<DbUser> searchDbUsers() {
return genericSearch(dbUserDao, true);
}
private List<DbGroup> searchDbGroups() {
return genericSearch(dbGroupDao, true);
}
private List<VmTemplate> searchVmTemplates() {
return genericSearch(vmTemplateDao, true);
}
private List<VmTemplate> searchInstanceTypes() {
return genericSearch(vmTemplateDao, true);
}
private List<VmTemplate> searchImageTypes() {
return genericSearch(vmTemplateDao, true);
}
private <T extends IVdcQueryable> List<T> genericSearch(final SearchDao<T> dao,
final boolean useCache) {
final QueryData data = initQueryData(useCache);
if (data == null) {
return new ArrayList<>();
}
log.debug("Executing generic query: {}", data.getQuery());
return dao.getAllWithQuery(data.getQuery());
}
private List<AuditLog> searchAuditLogEvents() {
return genericSearch(auditLogDao, false);
}
private List<VmPool> searchVmPools() {
return genericSearch(vmPoolDao, true);
}
private List<Cluster> searchClusters() {
Optional<Version> retVal = Config.<HashSet<Version>> getValue(ConfigValues.SupportedClusterLevels).stream()
.max(Comparator.naturalOrder());
List<Cluster> clusters = genericSearch(clusterDao, true);
if (retVal.isPresent()) {
clusters.forEach(cluster -> cluster.setClusterCompatibilityLevelUpgradeNeeded(
retVal.get().compareTo(cluster.getCompatibilityVersion()) > 0)
);
}
return clusters;
}
private List<StoragePool> searchStoragePool() {
List<StoragePool> dataCenters = genericSearch(storagePoolDao, true);
dataCenters.forEach(this::setDcSingleMacPoolId);
setDcCompatibilityLevelUpgradeNeeded(dataCenters);
return dataCenters;
}
private void setDcSingleMacPoolId(StoragePool dataCenter) {
dataCenter.setMacPoolId(dcSingleMacPoolFinder.find(dataCenter.getId()));
}
private void setDcCompatibilityLevelUpgradeNeeded(List<StoragePool> dataCenters) {
Config.<Set<Version>> getValue(ConfigValues.SupportedClusterLevels)
.stream()
.max(Comparator.naturalOrder())
.ifPresent(maxSupportedClusterLevel ->
setDcCompatibilityLevelUpgradeNeeded(dataCenters, maxSupportedClusterLevel));
}
private void setDcCompatibilityLevelUpgradeNeeded(List<StoragePool> dataCenters, Version maxSupportedClusterLevel) {
dataCenters.forEach(
dataCenter -> dataCenter.setStoragePoolCompatibilityLevelUpgradeNeeded(
maxSupportedClusterLevel.compareTo(dataCenter.getCompatibilityVersion()) > 0));
}
private List<StorageDomain> searchStorageDomain() {
return genericSearch(storageDomainDao, true);
}
private List<Quota> searchQuota() {
List<Quota> quotaList = genericSearch(quotaDao, true);
quotaManager.updateUsage(quotaList);
return quotaList;
}
private List<Disk> searchDisk() {
return genericSearch(diskDao, true);
}
private List<GlusterVolumeEntity> searchGlusterVolumes() {
return genericSearch(glusterVolumeDao, true);
}
private List<NetworkView> searchNetworks() {
return genericSearch(networkViewDao, true);
}
private List<Provider<?>> searchProviders() {
return genericSearch(providerDao, true);
}
private List<UserSession> searchSessions() {
return genericSearch(engineSessionDao, false).stream().peek(this::injectSessionInfo)
.map(UserSession::new)
.collect(Collectors.toList());
}
private List<ImageTransfer> searchImageTransfer() {
return genericSearch(imageTransferDao, false);
}
private void injectSessionInfo(EngineSession engineSession) {
engineSession.setStartTime(getSessionDataContainer().getSessionStartTime(engineSession.getEngineSessionId()));
engineSession.setLastActiveTime(
getSessionDataContainer().getSessionLastActiveTime(engineSession.getEngineSessionId()));
}
private static final String[] AD_SEARCH_TYPES = {
SearchObjects.AD_USER_OBJ_NAME,
SearchObjects.AD_USER_PLU_OBJ_NAME,
SearchObjects.AD_GROUP_OBJ_NAME,
SearchObjects.AD_GROUP_PLU_OBJ_NAME
};
private static final Pattern adSearchPattern = Pattern.compile(
String.format(
"^((?<prefix>(%s))@)(?<content>.*)",
StringUtils.join(AD_SEARCH_TYPES, "|")));
private QueryData initQueryData(boolean useCache) {
final String ASTR = "*";
QueryData data = null;
boolean isExistsValue = false;
boolean IsFromYesterday = false;
boolean isSafe = false;
String searchKey = "";
try {
if (getParameters().getMaxCount() < 0) {
throw new RuntimeException(String.format("Illegal max count value for query : %s", getParameters().getMaxCount()));
}
String searchText = getParameters().getSearchPattern();
// do not cache expressions with '*' since it is translated to specific IDs that might be changed
useCache = useCache && !searchText.contains(ASTR);
if (useCache) {
// first lets check the cache of queries.
searchKey = String.format("%1$s,%2$s,%3$s", searchText, getParameters().getMaxCount(), getParameters().getCaseSensitive());
data = queriesCache.get(searchKey);
isExistsValue = data != null;
if (isExistsValue) {
TimeSpan span = DateTime.getNow().subtract(new Date(data.getDate()));
if (span.Days >= 1) {
IsFromYesterday = true;
}
}
}
// query not in cache or the cached entry is too old, process the
// search text.
if (!isExistsValue || IsFromYesterday) {
log.debug("ResourceManager::searchBusinessObjects(''{}'') - entered", searchText);
final char AT='@';
String queryAuthz = null;
String queryNamespace = null;
ISyntaxChecker curSyntaxChecker;
Matcher m = adSearchPattern.matcher(searchText);
// checks if this is a AD query, if it is, verify given profile and namespace and pass the query
if (m.matches()) {
final String COLON = ":";
String prefix = m.group("prefix");
searchText = m.group("content");
// get profile
List<String> profiles = backend.runInternalQuery(VdcQueryType.GetDomainList,
new VdcQueryParametersBase(getParameters().getSessionId())).getReturnValue();
for (String profile : profiles) {
if (searchText.startsWith(profile + COLON)) {
queryAuthz = profile;
searchText = searchText.replaceFirst(profile + COLON, StringUtils.EMPTY);
break;
}
}
if (queryAuthz == null) {
queryAuthz = getDefaultAuthz();
}
// get namespace
HashMap<String, List<String>> namespacesMap =
backend.runInternalQuery(VdcQueryType.GetAvailableNamespaces,
new VdcQueryParametersBase(getParameters().getSessionId())).getReturnValue();
List<String> namespaces = namespacesMap.get(queryAuthz);
for (String namespace : namespaces) {
if (searchText.startsWith(namespace + COLON)) {
queryNamespace = namespace;
searchText = searchText.replace(namespace + COLON, StringUtils.EMPTY);
break;
}
}
// Check if query is for all namespaces (REST) i.e.:
// ADUSER/ADGROUP<profile>::<query>
if (searchText.startsWith(COLON)) {
searchText = prefix + searchText;
}
else {
searchText = prefix + COLON + searchText;
}
curSyntaxChecker = SyntaxCheckerFactory.createADSyntaxChecker(LDAP);
} else {
curSyntaxChecker = SyntaxCheckerFactory.createBackendSyntaxChecker(LDAP);
}
SyntaxContainer searchObj = curSyntaxChecker.analyzeSyntaxState(searchText, true);
// set the case-sensitive flag
searchObj.setCaseSensitive(getParameters().getCaseSensitive());
// If a number > maxValue is given then maxValue will be used
searchObj.setMaxCount(Math.min(Integer.MAX_VALUE, getParameters().getMaxCount()));
// setting FromSearch value
searchObj.setSearchFrom(getParameters().getSearchFrom());
if (searchObj.getError() != SyntaxError.NO_ERROR) {
log.info("ResourceManager::searchBusinessObjects - erroneous search text - ''{}''",
searchText);
int startPos = searchObj.getErrorStartPos();
int endPos = searchObj.getErrorEndPos();
int length = endPos - startPos;
String error =
(length > 0 && ((startPos + 1 + length) < searchText.length())
&& (endPos + 1 < searchText.length()))
?
searchText.substring(0, startPos)
+ "$"
+ searchText.substring(startPos + 1, startPos + 1
+ length) + "$"
+ searchText.substring(endPos + 1)
:
searchObj.getError().toString();
getQueryReturnValue().setExceptionString(error);
return null;
}
if (!searchObj.getvalid()) {
log.warn("ResourceManager::searchBusinessObjects - Invalid search text - ''{}''", searchText);
return null;
}
// find if this is a trivial search expression (like 'Vms:' etc).
isSafe = SearchObjects.isSafeExpression(searchText);
// An expression is considered safe if matches a trivial search.
data =
new QueryData(curSyntaxChecker.generateQueryFromSyntaxContainer(searchObj, isSafe),
DateTime.getNow().getTime(),
queryAuthz, queryNamespace);
// when looking for tags , the query contains all parent children tag id's
// statically, therefore , in order to reflect changes in the parent tree
// we should not rely on the cached query in such case and have to build the
// query from scratch.
if (!containsStaticInValues(data.getQuery())) {
queriesCache.put(searchKey, data);
}
}
} catch (SearchEngineIllegalCharacterException e) {
log.error("Search expression can not end with ESCAPE character: {}", getParameters().getSearchPattern());
data = null;
} catch (SqlInjectionException e) {
log.error("Sql Injection in search: {}", getParameters().getSearchPattern());
data = null;
} catch (RuntimeException ex) {
log.warn("Illegal search: {}: {}", getParameters().getSearchPattern(), ex.getMessage());
log.debug("Exception", ex);
throw ex;
}
return data;
}
protected String getDefaultAuthz() {
return AuthenticationProfileRepository.getInstance().getProfiles().get(0).getName();
}
private static boolean containsStaticInValues(String query) {
final String MATCH_IN_TAG_ID_CLAUSE = "with_tags.tag_id in";
return query.toLowerCase().contains(MATCH_IN_TAG_ID_CLAUSE);
}
}