package org.ovirt.engine.core.bll.storage.domain;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.regex.Pattern;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.ovirt.engine.core.bll.VmHandler;
import org.ovirt.engine.core.bll.provider.ProviderProxyFactory;
import org.ovirt.engine.core.bll.provider.storage.OpenStackImageProviderProxy;
import org.ovirt.engine.core.common.AuditLogType;
import org.ovirt.engine.core.common.BackendService;
import org.ovirt.engine.core.common.businessentities.Provider;
import org.ovirt.engine.core.common.businessentities.StorageDomain;
import org.ovirt.engine.core.common.businessentities.StorageDomainStatus;
import org.ovirt.engine.core.common.businessentities.StorageDomainType;
import org.ovirt.engine.core.common.businessentities.StoragePoolIsoMap;
import org.ovirt.engine.core.common.businessentities.StoragePoolStatus;
import org.ovirt.engine.core.common.businessentities.VDSStatus;
import org.ovirt.engine.core.common.businessentities.storage.ImageFileType;
import org.ovirt.engine.core.common.businessentities.storage.RepoImage;
import org.ovirt.engine.core.common.businessentities.storage.StorageType;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.config.ConfigValues;
import org.ovirt.engine.core.common.errors.EngineError;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.common.interfaces.VDSBrokerFrontend;
import org.ovirt.engine.core.common.utils.Pair;
import org.ovirt.engine.core.common.vdscommands.GetFileStatsParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.TransactionScopeOption;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogDirector;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogable;
import org.ovirt.engine.core.dal.dbbroker.auditloghandling.AuditLogableImpl;
import org.ovirt.engine.core.dao.RepoFileMetaDataDao;
import org.ovirt.engine.core.dao.StorageDomainDao;
import org.ovirt.engine.core.dao.StoragePoolIsoMapDao;
import org.ovirt.engine.core.dao.provider.ProviderDao;
import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil;
import org.ovirt.engine.core.utils.timer.OnTimerMethodAnnotation;
import org.ovirt.engine.core.utils.timer.SchedulerUtilQuartzImpl;
import org.ovirt.engine.core.utils.transaction.TransactionMethod;
import org.ovirt.engine.core.utils.transaction.TransactionSupport;
import org.ovirt.engine.core.vdsbroker.vdsbroker.VdsProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The class manages the Iso domain cache mechanism, <BR/>
* which reflects upon support for Iso tool validation, activation of Iso domain, and fetching the Iso list by query.<BR/>
* The cache is being refreshed with quartz scheduler which run by configuration value AutoRepoDomainRefreshTime. The
* cache procedure using VDSM to fetch the Iso files from all the Data Centers and update the DB cache table with the
* appropriate file data.<BR/>
*/
@SuppressWarnings("synthetic-access")
@Singleton
public class IsoDomainListSynchronizer implements BackendService {
private static final Logger log = LoggerFactory.getLogger(IsoDomainListSynchronizer.class);
private static final int MIN_TO_MILLISECONDS = 60 * 1000;
private static final String ISO_VDSM_FILE_PATTERN = "*.iso";
private static final Pattern ISO_FILE_PATTERN_REGEX = Pattern.compile("^.*\\.iso$", Pattern.CASE_INSENSITIVE);
private static final String FLOPPY_VDSM_FILE_PATTERN = "*.vfd";
private static final Pattern FLOPPY_FILE_PATTERN_REGEX = Pattern.compile("^.*\\.vfd$", Pattern.CASE_INSENSITIVE);
private static final String ALL_FILES_PATTERN = "*";
@Inject
private AuditLogDirector auditLogDirector;
@Inject
private VDSBrokerFrontend resourceManager;
@Inject
private RepoFileMetaDataDao repoFileMetaDataDao;
@Inject
private ProviderDao providerDao;
@Inject
private StorageDomainDao storageDomainDao;
@Inject
private StoragePoolIsoMapDao storagePoolIsoMapDao;
@Inject
private SchedulerUtilQuartzImpl schedulerUtil;
@Inject
private VmHandler vmHandler;
private List<RepoImage> problematicRepoFileList = new ArrayList<>();
private final ConcurrentMap<Object, Lock> syncDomainForFileTypeMap = new ConcurrentHashMap<>();
private int isoDomainRefreshRate;
public static final String TOOL_CLUSTER_LEVEL = "clusterLevel";
public static final String TOOL_VERSION = "toolVersion";
public static final String REGEX_TOOL_PATTERN =
String.format("%1$s(?<%2$s>[0-9]{1,}.[0-9])_{1}(?<%3$s>[0-9]{1,}).[i|I][s|S][o|O]$",
getGuestToolsSetupIsoPrefix(),
TOOL_CLUSTER_LEVEL,
TOOL_VERSION);
// Not kept as static member to enable reloading the config value
public static String getGuestToolsSetupIsoPrefix() {
return Config.getValue(ConfigValues.GuestToolsSetupIsoPrefix);
}
@PostConstruct
private void init() {
log.info("Start initializing {}", getClass().getSimpleName());
isoDomainRefreshRate = Config.<Integer> getValue(ConfigValues.AutoRepoDomainRefreshTime) * MIN_TO_MILLISECONDS;
schedulerUtil.scheduleAFixedDelayJob(this,
"fetchIsoDomains",
new Class[] {},
new Object[] {},
300000,
isoDomainRefreshRate,
TimeUnit.MILLISECONDS);
log.info("Finished initializing {}", getClass().getSimpleName());
}
/**
* Check and update if needed each Iso domain in each Data Center in the system.
*/
@OnTimerMethodAnnotation("fetchIsoDomains")
public synchronized void fetchIsoDomains() {
// Gets all the active Iso storage domains.
List<RepoImage> repofileList = repoFileMetaDataDao.getAllRepoFilesForAllStoragePools(StorageDomainType.ISO,
StoragePoolStatus.Up,
StorageDomainStatus.Active,
VDSStatus.Up);
resetProblematicList();
// Iterate for each storage domain.
List<Callable<Void>> tasks = new ArrayList<>();
for (final RepoImage repoImage : repofileList) {
// If the list should be refreshed and the refresh from the VDSM was succeeded, fetch the file list again
// from the DB.
if (shouldRefreshIsoDomain(repoImage.getLastRefreshed())) {
tasks.add(() -> {
updateCachedIsoFileListFromVdsm(repoImage);
return null;
});
} else {
log.debug("Automatic refresh process for '{}' file type in storage domain id '{}' was not performed"
+ " since refresh time out did not passed yet.",
repoImage.getFileType(),
repoImage.getRepoDomainId());
}
}
ThreadPoolUtil.invokeAll(tasks);
// After refresh for all Iso domains finished, handle the log.
handleErrorLog(new ArrayList<>(problematicRepoFileList));
}
/**
* Returns a RepoFilesMetaData list with Iso file names for storage domain Id and with file type extension.<BR>
* If user choose to refresh the cache, and a problem occurs, then throws EngineException.
*
* @param storageDomainId
* - The storage domain Id, which we fetch the Iso list from.
* @param imageType
* - The imageType we want to fetch the files from the cache.
* @param forceRefresh
* - Indicates if the domain should be refreshed from VDSM.
* @return List of RepoFilesMetaData files.
* @throws EngineException
* - if a problem occurs when refreshing the image repo cache.
*/
public List<RepoImage> getUserRequestForStorageDomainRepoFileList(Guid storageDomainId,
ImageFileType imageType,
boolean forceRefresh) {
// Query for storageDoaminId is looking for Active ISO domain
if (!isStorageDomainIdValid(storageDomainId)) {
throw new EngineException(EngineError.GetIsoListError);
}
if (forceRefresh && !refreshRepos(storageDomainId, imageType)) {
throw new EngineException(EngineError.IMAGES_NOT_SUPPORTED_ERROR);
}
// In any case, whether refreshed or not, get Iso list from the cache.
return getCachedIsoListByDomainId(storageDomainId, imageType);
}
private boolean refreshRepos(Guid storageDomainId, ImageFileType imageType) {
boolean refreshResult;
List<RepoImage> tempProblematicRepoFileList = new ArrayList<>();
StorageDomain storageDomain = storageDomainDao.get(storageDomainId);
if (storageDomain.getStorageDomainType() == StorageDomainType.ISO) {
refreshResult = refreshIsoDomain(storageDomainId, tempProblematicRepoFileList, imageType);
} else if (storageDomain.getStorageDomainType() == StorageDomainType.Image &&
storageDomain.getStorageType() == StorageType.GLANCE) {
refreshResult = refreshImageDomain(storageDomain, imageType);
} else {
log.error("Unable to refresh the storage domain '{}', Storage Domain Type '{}' not supported",
storageDomainId, storageDomain.getStorageDomainType());
return false;
}
handleErrorLog(tempProblematicRepoFileList);
// If refresh succeeded update the audit log
if (refreshResult) {
addToAuditLogSuccessMessage(storageDomain, imageType.name());
}
return refreshResult;
}
private boolean refreshImageDomain(final StorageDomain storageDomain, final ImageFileType imageType) {
Provider provider = providerDao.get(new Guid(storageDomain.getStorage()));
final OpenStackImageProviderProxy client = ProviderProxyFactory.getInstance().create(provider);
Lock syncObject = getSyncObject(storageDomain.getId(), imageType);
try {
syncObject.lock();
return TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew,
() -> {
repoFileMetaDataDao.removeRepoDomainFileList(storageDomain.getId(), imageType);
Integer totalListSize = Config.<Integer> getValue(ConfigValues.GlanceImageTotalListSize);
List<RepoImage> repoImages = client.getAllImagesAsRepoImages(
Config.<Integer> getValue(ConfigValues.GlanceImageListSize), totalListSize);
if (repoImages.size() >= totalListSize) {
AuditLogable logable = new AuditLogableImpl();
logable.addCustomValue("imageDomain", storageDomain.getName())
.addCustomValue("imageListSize", String.valueOf(repoImages.size()));
logable.setStorageDomainId(storageDomain.getId());
logable.setStorageDomainName(storageDomain.getName());
auditLogDirector.log(logable, AuditLogType.REFRESH_REPOSITORY_IMAGE_LIST_INCOMPLETE);
}
for (RepoImage repoImage : repoImages) {
repoImage.setRepoDomainId(storageDomain.getId());
repoFileMetaDataDao.addRepoFileMap(repoImage);
}
return true;
});
} finally {
syncObject.unlock();
}
}
/**
* The procedure Try to refresh the repository files of the storage domain id, with storage pool Id. If succeeded
* will return True, otherwise return false and update the list, of the problematic repository files with the
* storage pool and storage domain id, that could not complete the cache update transaction.
*
* @param storageDomainId
* - The Repository domain Id, we want to refresh.
* @param storagePoolId
* - The Storage pool Id, we use to fetch the Iso files from..
* @param imageType
* - The imageType we want to fetch the files from the cache.
* @return Boolean value indicating if the refresh succeeded or not.
*/
private boolean refreshIsoDomainFileForStoragePool(Guid storageDomainId,
Guid storagePoolId,
ImageFileType imageType) {
// Setting the indication to the indication whether the storage pool is valid.
boolean updateFromVDSMSucceeded = updateFileList(storageDomainId, storagePoolId, imageType);
// Log if the refresh succeeded or add the storage domain to the problematic list.
if (updateFromVDSMSucceeded) {
log.debug("Refresh succeeded for file type '{}' at storage domain id '{}' in storage pool id '{}'.",
imageType.name(),
storageDomainId,
storagePoolId);
}
return updateFromVDSMSucceeded;
}
private boolean updateFileList(Guid storageDomainId, Guid storagePoolId, ImageFileType imageType) {
switch (imageType) {
case All:
return updateAllFileListFromVDSM(storagePoolId, storageDomainId);
case ISO:
return updateIsoListFromVDSM(storagePoolId, storageDomainId);
case Floppy:
return updateFloppyListFromVDSM(storagePoolId, storageDomainId);
case Unknown:
return updateUnknownFileListFromVDSM(storagePoolId, storageDomainId);
default:
log.warn("Refreshing Iso domain using unsupported imageType: {}", imageType);
return false;
}
}
/**
* The procedure Try to refresh the repository files of the storage domain id, By iterate over the storage pools of
* this domain, and try to choose a valid storage pool, to fetch the repository files from the VDSM, and refresh the
* cached table. <BR/>
* If succeeded, will return True. Otherwise return false with updated list of problematic repository files with the
* storage pool, storage domain, and file type, that could not complete the cache update transaction.
*
* @param storageDomainId
* - The Repository domain Id, we want to refresh.
* @param problematicRepoFileList
* - List of business entities, each one indicating the problematic entity.
* @param imageType
* - The imageType we want to fetch the files from the cache.
* @return Boolean value indicating if the refresh succeeded or not.
*/
private boolean refreshIsoDomain(Guid storageDomainId,
List<RepoImage> problematicRepoFileList,
ImageFileType imageType) {
List<StoragePoolIsoMap> isoMapList = fetchAllStoragePoolsForIsoDomain(storageDomainId, imageType);
for (StoragePoolIsoMap storagePoolIsoMap : isoMapList) {
Guid storagePoolId = storagePoolIsoMap.getStoragePoolId();
StorageDomainStatus status = storagePoolIsoMap.getStatus();
if (StorageDomainStatus.Active != status) {
handleInactiveStorageDomain(storageDomainId, imageType, status);
} else {
// Try to refresh the domain of the storage pool id because its status is active.
boolean refreshOk = refreshIsoDomainFileForStoragePool(storageDomainId, storagePoolId, imageType);
if (!refreshOk) {
log.debug("Failed refreshing Storage domain id '{}', for '{}' file type in storage pool id '{}'.",
storageDomainId,
imageType,
storagePoolId);
// Add the repository file to the list of problematic Iso domains.
RepoImage repoImage = createMockRepositoryFileMetaData(storageDomainId, imageType, storagePoolId);
problematicRepoFileList.add(repoImage);
return false;
}
}
}
return true;
}
// Fetch all the Storage pools for this Iso domain Id.
private List<StoragePoolIsoMap> fetchAllStoragePoolsForIsoDomain(Guid storageDomainId, ImageFileType imageType) {
List<StoragePoolIsoMap> isoMapList = storagePoolIsoMapDao.getAllForStorage(storageDomainId);
log.debug("Fetched {} storage pools for '{}' file type, in Iso domain '{}'.",
isoMapList.size(),
imageType,
storageDomainId);
return isoMapList;
}
// set a mock repository file meta data with storage domain id and storage pool id.
private static RepoImage createMockRepositoryFileMetaData(Guid storageDomainId,
ImageFileType imageType,
Guid storagePoolId) {
RepoImage repoImage = new RepoImage();
repoImage.setStoragePoolId(storagePoolId);
repoImage.setRepoDomainId(storageDomainId);
repoImage.setFileType(imageType);
return repoImage;
}
private void handleInactiveStorageDomain(Guid storageDomainId,
ImageFileType imageType,
StorageDomainStatus status) {
log.debug("Storage domain id '{}', is not active, and therefore could not be refreshed for '{}'"
+ " file type (Iso domain status is '{}').",
storageDomainId,
imageType,
status);
}
/**
* Refresh the Iso domain when activating the domain,
* with executing a new Thread to prevent long lock status for the domain.
*
* @param isoStorageDomainId
* - The storage domain Id we want to get the file list from.
* @param storagePoolId
* - The storage pool Id we get an Iso active domain, we want to get the file list from (used mainly for log issues).
*/
public void refresheIsoDomainWhenActivateDomain(final Guid isoStorageDomainId,
final Guid storagePoolId) {
if (storagePoolId != null && (isoStorageDomainId != null)) {
ThreadPoolUtil.execute(() -> refreshActivatedStorageDomainFromVdsm(storagePoolId, isoStorageDomainId));
}
}
/**
* Returns the cached Iso file meta data list, for storage domain.
*
* @param isoStorageDomainId
* - The storage domain Id we want to get the file list from.
* @return List of Iso files fetched from DB, if parameter is invalid returns an empty list.
*/
public List<RepoImage> getCachedIsoListByDomainId(Guid isoStorageDomainId,
ImageFileType imageType) {
List<RepoImage> fileListMD = new ArrayList<>();
if (isoStorageDomainId != null) {
fileListMD =
repoFileMetaDataDao.getRepoListForStorageDomain(isoStorageDomainId, imageType);
}
return fileListMD;
}
/**
* Handling the list of problematic repository files, to maintain multi thread caching.
* @see #resetProblematicList()
*/
private void addRepoFileToProblematicList(List<RepoImage> repoImageList) {
problematicRepoFileList.addAll(repoImageList);
}
/**
* Reset the list of problematic repository files, before starting the refresh procedure.
* uses for multy thread caching.
* @see #addRepoFileToProblematicList(List)
*/
private void resetProblematicList() {
problematicRepoFileList.clear();
}
/**
* Print information on the problematic storage domain. Mainly transfer the business entity to list, for handling
* the error uniformly.
* Create a mock RepoImage object in a list, to use the functionality of the handleErrorLog with list.
*
* @param storagePoolId
* - The storage pool Id.
* @param storageDomainId
* - The storage domain Id.
* @param imageType
* - The file type extension (ISO or Floppy).
* @see #handleErrorLog(List)
*/
private void handleErrorLog(Guid storagePoolId, Guid storageDomainId, ImageFileType imageType) {
List<RepoImage> tempProblematicRepoFileList = new ArrayList<>();
RepoImage repoImage = createMockRepositoryFileMetaData(
storageDomainId,
imageType,
storagePoolId);
// Add the repository file to the list, and use handleError.
tempProblematicRepoFileList.add(repoImage);
handleErrorLog(tempProblematicRepoFileList);
}
/**
* Print information on the problematic storage domains and print an audit log.<BR/>
* If the problematicFileListForHandleError list retrieved empty or null,<BR/>
* then don't do nothing and return false flag.
*
* @param problematicFileListForHandleError
* - List of repository file meta data, each one indicating a problematic repository domain.
* @return true, if has problematic storage domains, false otherwise (List is empty).
*/
private boolean handleErrorLog(List<RepoImage> problematicFileListForHandleError) {
boolean hasProblematic = false;
if (problematicFileListForHandleError != null && !problematicFileListForHandleError.isEmpty()) {
StringBuilder problematicStorages = new StringBuilder();
StringBuilder problematicIsoDomainsForAuditLog = new StringBuilder();
Set<String> storageDomainNames = new HashSet<>();
for (RepoImage repoMap : problematicFileListForHandleError) {
problematicStorages.append(buildDetailedProblematicMapMsg(repoMap));
storageDomainNames.add(buildDetailedAuditLogMessage(repoMap));
}
// Build Audit log message with problematic domains.
for (String domainName : storageDomainNames) {
problematicIsoDomainsForAuditLog.append(" ").append(domainName);
}
hasProblematic = true;
log.error("The following storage domains had a problem retrieving data from VDSM: {}",
problematicStorages);
addToAuditLogErrorMessage(problematicIsoDomainsForAuditLog.toString());
}
return hasProblematic;
}
/**
* Returns a string builder contains problematic repoImage details.
*
* @param repoImage
* - The problematic storage domain.
*/
private static StringBuilder buildDetailedProblematicMapMsg(RepoImage repoImage) {
StringBuilder problematicStorageMsg = new StringBuilder();
if (repoImage != null) {
problematicStorageMsg.append(" (");
if (repoImage.getStoragePoolId() != null) {
problematicStorageMsg.append(" Storage Pool Id: ").append(repoImage.getStoragePoolId());
}
if (repoImage.getRepoDomainId() != null) {
problematicStorageMsg.append(" Storage domain Id: ").append(repoImage.getRepoDomainId());
}
problematicStorageMsg.append(" File type: ").append(repoImage.getFileType()).append(") ");
} else {
problematicStorageMsg.append("(A repository file meta data business entity, has null value) ");
}
return problematicStorageMsg;
}
/**
* Returns String contains problematic iso domain name for audit log message.
* @param repoImage
* - The problematic storage domain.
*/
private String buildDetailedAuditLogMessage(RepoImage repoImage) {
String storageDomainName = "Repository not found";
if (repoImage != null && repoImage.getRepoDomainId() != null) {
StorageDomain storageDomain =
storageDomainDao.get(repoImage.getRepoDomainId());
if (storageDomain != null) {
storageDomainName =
String.format("%s (%s file type)",
storageDomain.getStorageName(),
repoImage.getFileType().name());
}
} else {
log.error("Repository file meta data not found for logging");
}
return storageDomainName;
}
/**
* Updates the DB cache table with files fetched from VDSM.
* The method is dedicated for multiple threads refresh.
* If refresh from VDSM has encounter problems, we update the problematic domain list.
*/
private void updateCachedIsoFileListFromVdsm(RepoImage repoImage) {
boolean isRefreshed = false;
try {
List<RepoImage> problematicRepoFileList = new ArrayList<>();
isRefreshed =
refreshIsoDomain(repoImage.getRepoDomainId(),
problematicRepoFileList,
repoImage.getFileType());
addRepoFileToProblematicList(problematicRepoFileList);
} finally {
log.info("Finished automatic refresh process for '{}' file type with {}, for storage domain id '{}'.",
repoImage.getFileType(),
isRefreshed ? "success"
: "failure",
repoImage.getRepoDomainId());
}
}
private boolean refreshIsoFileListMetaData(final Guid repoStorageDomainId,
final Map<String, Map<String, Object>> fileStats,
final ImageFileType imageType) {
Lock syncObject = getSyncObject(repoStorageDomainId, imageType);
try {
syncObject.lock();
return (Boolean) TransactionSupport.executeInScope(TransactionScopeOption.RequiresNew,
new TransactionMethod<Object>() {
@Override
public Object runInTransaction() {
long currentTime = System.currentTimeMillis();
repoFileMetaDataDao.removeRepoDomainFileList(repoStorageDomainId, imageType);
for (Map.Entry<String, Map<String, Object>> entry : fileStats.entrySet()) {
repoFileMetaDataDao.addRepoFileMap(newRepoImage(currentTime, entry));
}
return true;
}
public RepoImage newRepoImage(long currentTime, Map.Entry<String, Map<String, Object>> entry) {
RepoImage repo_md = new RepoImage();
repo_md.setLastRefreshed(currentTime);
repo_md.setSize(retrieveIsoFileSize(entry));
repo_md.setRepoDomainId(repoStorageDomainId);
repo_md.setDateCreated(null);
repo_md.setRepoImageId(entry.getKey());
repo_md.setRepoImageName(null);
repo_md.setFileType(imageType);
return repo_md;
}
});
} catch (Exception e) {
log.warn("Updating repository content to DB failed for repoStorageDomainId={}, imageType={}: {}",
repoStorageDomainId,
imageType,
e.getMessage());
log.debug("Exception", e);
return false;
} finally {
syncObject.unlock();
}
}
private static Long retrieveIsoFileSize(Map.Entry<String, Map<String, Object>> fileStats) {
try {
Object fileSize = fileStats.getValue().get(VdsProperties.size);
if (fileSize == null) {
return null;
}
return Long.valueOf((String) fileStats.getValue().get(VdsProperties.size));
} catch (RuntimeException e) {
// Illegal number or null are treated as not available,
// handling exception in UI will be much more complicated.
log.error("File's '{}' size is illegal number: {}", fileStats.getKey(), e.getMessage());
log.debug("Exception", e);
return null;
}
}
/**
* Try to update the cached table from the VDSM, if succeeded fetch the file list again from the DB. if not ,handle
* the log message.
*
* @param storagePoolId
* - The storage pool id we want to get the file list from.
* @param storageDomainId
* - The storage domain id we want to get the file list from.
*/
private synchronized void refreshActivatedStorageDomainFromVdsm(Guid storagePoolId, Guid storageDomainId) {
if (!updateIsoListFromVDSM(storagePoolId, storageDomainId)) {
// Add an audit log that refresh was failed for Iso files.
handleErrorLog(storagePoolId, storageDomainId, ImageFileType.ISO);
}
if (!updateFloppyListFromVDSM(storagePoolId, storageDomainId)) {
// Add an audit log that refresh was failed for Floppy files.
handleErrorLog(storagePoolId, storageDomainId, ImageFileType.Floppy);
}
}
/**
* Check if last refreshed time has exceeded the time limit configured in isoDomainRefreshRate.
*
* @param lastRefreshed
* - Time when repository file was last refreshed.
* @return True if time exceeded, and should refresh the domain, false otherwise.
*/
private boolean shouldRefreshIsoDomain(long lastRefreshed) {
return (System.currentTimeMillis() - lastRefreshed) > isoDomainRefreshRate;
}
private boolean updateAllFileListFromVDSM(Guid repoStoragePoolId, Guid repoStorageDomainId) {
VDSReturnValue fileStatsVDSReturnValue = getFileStats(repoStoragePoolId,
repoStorageDomainId,
ALL_FILES_PATTERN);
Map<String, Map<String, Object>> fileStats = fileStatsFromVDSReturnValue(fileStatsVDSReturnValue);
updateIsoListFromVDSM(repoStoragePoolId,
repoStorageDomainId,
removeFileStatsForComplyingFileNames(fileStats, ISO_FILE_PATTERN_REGEX));
updateFloppyListFromVDSM(repoStoragePoolId,
repoStorageDomainId,
removeFileStatsForComplyingFileNames(fileStats, FLOPPY_FILE_PATTERN_REGEX));
//all remaining fileStats are uncategorized, of ImageFileType.Unknown type
return refreshVdsmFileList(repoStoragePoolId,
repoStorageDomainId,
ImageFileType.Unknown,
fileStats,
null);
}
private Map<String, Map<String, Object>> removeFileStatsForComplyingFileNames(Map<String, Map<String, Object>> fileStats,
Pattern filePatternRegex) {
Map<String, Map<String, Object>> result = new HashMap<>();
for (Iterator<Map.Entry<String, Map<String, Object>>> it = fileStats.entrySet().iterator(); it.hasNext(); ) {
Map.Entry<String, Map<String, Object>> entry = it.next();
String fileName = entry.getKey();
if (filePatternRegex.matcher(fileName).matches()) {
result.put(fileName, entry.getValue());
it.remove();
}
}
return result;
}
private boolean updateUnknownFileListFromVDSM(Guid repoStoragePoolId, Guid repoStorageDomainId) {
VDSReturnValue fileStatsVDSReturnValue = getFileStats(repoStoragePoolId,
repoStorageDomainId,
ALL_FILES_PATTERN);
Map<String, Map<String, Object>> fileStats = fileStatsFromVDSReturnValue(fileStatsVDSReturnValue);
removeFileStatsForComplyingFileNames(fileStats, ISO_FILE_PATTERN_REGEX);
removeFileStatsForComplyingFileNames(fileStats, FLOPPY_FILE_PATTERN_REGEX);
// all remaining fileStats are uncategorized, of ImageFileType.Unknown type
return refreshVdsmFileList(repoStoragePoolId,
repoStorageDomainId,
ImageFileType.Unknown,
fileStats,
null);
}
/**
* Gets the Iso file list from VDSM, and if the fetch is valid refresh the Iso list in the DB.
*
* @param repoStoragePoolId - The repository storage pool id, we want to update the file list.
* @param repoStorageDomainId - The repository storage domain id, for activate storage domain id.
*
* @return True, if the fetch from VDSM has succeeded. False otherwise.
*/
private boolean updateIsoListFromVDSM(Guid repoStoragePoolId, Guid repoStorageDomainId) {
VDSReturnValue fileStats = getFileStats(repoStoragePoolId,
repoStorageDomainId,
ISO_VDSM_FILE_PATTERN);
return updateIsoListFromVDSM(repoStoragePoolId, repoStorageDomainId, fileStatsFromVDSReturnValue(fileStats));
}
private boolean updateIsoListFromVDSM(Guid repoStoragePoolId,
Guid repoStorageDomainId, Map<String, Map<String, Object>> fileStats) {
return refreshVdsmFileList(repoStoragePoolId,
repoStorageDomainId,
ImageFileType.ISO, fileStats, vmHandler::refreshVmsToolsVersion);
}
private boolean refreshVdsmFileList(Guid repoStoragePoolId,
Guid repoStorageDomainId,
ImageFileType imageFileType,
Map<String, Map<String, Object>> fileStats,
FileListRefreshed fileListRefreshed) {
if (repoStorageDomainId == null) {
return false;
}
boolean vdsmRefreshOk = fileStats != null;
log.debug("The refresh process from VDSM, for {}, {}.",
imageFileType,
succeededOrFailed(vdsmRefreshOk));
if (!vdsmRefreshOk) {
return false;
}
boolean refreshSucceeded = refreshIsoFileListMetaData(repoStorageDomainId,
fileStats,
imageFileType);
if (refreshSucceeded && fileListRefreshed != null) {
fileListRefreshed.onFileListRefreshed(repoStoragePoolId, fileStats.keySet());
}
return refreshSucceeded;
}
private Map<String, Map<String, Object>> fileStatsFromVDSReturnValue(VDSReturnValue fileStats) {
if (fileStats == null || !fileStats.getSucceeded()) {
return null;
}
@SuppressWarnings("unchecked")
Map<String, Map<String, Object>> result = (Map<String, Map<String, Object>>) fileStats.getReturnValue();
return result;
}
public interface FileListRefreshed {
void onFileListRefreshed(Guid poolId, Set<String> isoList);
}
/**
* Gets the Iso floppy file list from VDSM, and if the fetch is valid refresh the Iso floppy list in the DB.
*
* @param repoStoragePoolId
* - The repository storage pool id, we want to update the file list.
* @param repoStorageDomainId
* - The repository storage domain id, for activate storage domain id.
* @return True, if the fetch from VDSM has succeeded. False otherwise.
*/
private boolean updateFloppyListFromVDSM(Guid repoStoragePoolId, Guid repoStorageDomainId) {
VDSReturnValue fileStats = getFileStats(repoStoragePoolId,
repoStorageDomainId,
FLOPPY_VDSM_FILE_PATTERN);
return updateFloppyListFromVDSM(repoStoragePoolId, repoStorageDomainId, fileStatsFromVDSReturnValue(fileStats));
}
private boolean updateFloppyListFromVDSM(Guid repoStoragePoolId,
Guid repoStorageDomainId, Map<String, Map<String, Object>> fileStats) {
return refreshVdsmFileList(repoStoragePoolId,
repoStorageDomainId,
ImageFileType.Floppy, fileStats, null
);
}
private String succeededOrFailed(boolean status) {
return status ? " succeeded" : "failed";
}
private VDSReturnValue getFileStats(Guid repoStoragePoolId,
Guid repoStorageDomainId,
String filePattern) {
try {
return resourceManager.runVdsCommand(VDSCommandType.GetFileStats,
new GetFileStatsParameters(repoStoragePoolId,
repoStorageDomainId, filePattern, false));
} catch (Exception e) {
log.warn("The refresh process for pattern {} failed: {}", filePattern, e.getMessage());
log.debug("Exception", e);
return null;
}
}
/**
* Maintain a <code>ConcurrentMap</code> which contains <code>Lock</code> object.<BR/>
* The key Object is a <code>Pair</code> object, which will represent the domain and the file type.<BR/>
* If no synchronized object found, the <code>Lock</code> will be add to the <code>ConcurrentMap</code>.
*
* @param domainId
* - The domain Id that supposed to be refreshed.
* @param imageType
* - The file type supposed to be refreshed.
* @return - The Lock object, which represent the domain and the file type, to lock.
*/
private Lock getSyncObject(Guid domainId, ImageFileType imageType) {
Pair<Guid, ImageFileType> domainPerFileType = new Pair<>(domainId, imageType);
syncDomainForFileTypeMap.putIfAbsent(domainPerFileType, new ReentrantLock());
return syncDomainForFileTypeMap.get(domainPerFileType);
}
/**
* Add audit log message when fetch encounter problems.
*
* @param problematicRepoFilesList
* - List of Iso domain names, which encounter problem fetching from VDSM.
*/
private void addToAuditLogErrorMessage(String problematicRepoFilesList) {
AuditLogable logable = new AuditLogableImpl();
// Get translated error by error code ,if no translation found (should not happened) ,
// will set the error code instead.
logable.addCustomValue("imageDomains", problematicRepoFilesList);
auditLogDirector.log(logable, AuditLogType.REFRESH_REPOSITORY_IMAGE_LIST_FAILED);
}
/**
* Add audit log message when fetch encounter problems.
*/
private void addToAuditLogSuccessMessage(StorageDomain isoDomain, String imageType) {
AuditLogable logable = new AuditLogableImpl();
logable.addCustomValue("imageDomains", String.format("%s (%s file type)", isoDomain.getName(), imageType));
logable.setStorageDomainId(isoDomain.getId());
logable.setStorageDomainName(isoDomain.getName());
auditLogDirector.log(logable, AuditLogType.REFRESH_REPOSITORY_IMAGE_LIST_SUCCEEDED);
}
private boolean isStorageDomainIdValid(Guid storageDomainId) {
if (storageDomainId == null) {
log.error("Storage domain ID received from command query is null.");
return false;
}
return true;
}
/**
* Checks if there is an active ISO domain in the storage pool. If so returns the Iso Guid, otherwise returns null.
* @param storagePoolId
* The storage pool id.
* @return Iso Guid of active Iso, and null if not.
*/
public Guid findActiveISODomain(Guid storagePoolId) {
List<StorageDomain> domains = storageDomainDao.getAllForStoragePool(
storagePoolId);
for (StorageDomain domain : domains) {
if (domain.getStorageDomainType() == StorageDomainType.ISO &&
domain.getStatus() == StorageDomainStatus.Active) {
return domain.getId();
}
}
return null;
}
}