package org.ovirt.engine.core.bll.gluster; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.concurrent.Callable; import javax.inject.Singleton; import org.apache.commons.lang.StringUtils; import org.ovirt.engine.core.common.AuditLogType; import org.ovirt.engine.core.common.businessentities.Cluster; import org.ovirt.engine.core.common.businessentities.VDS; import org.ovirt.engine.core.common.businessentities.gluster.StorageDevice; import org.ovirt.engine.core.common.config.ConfigValues; import org.ovirt.engine.core.common.utils.Pair; import org.ovirt.engine.core.common.vdscommands.VDSCommandType; import org.ovirt.engine.core.common.vdscommands.VDSReturnValue; import org.ovirt.engine.core.common.vdscommands.VdsIdVDSCommandParametersBase; import org.ovirt.engine.core.compat.Guid; import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil; import org.ovirt.engine.core.utils.timer.OnTimerMethodAnnotation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton public class StorageDeviceSyncJob extends GlusterJob { private static final Logger log = LoggerFactory.getLogger(StorageDeviceSyncJob.class); @Override public Collection<GlusterJobSchedulingDetails> getSchedulingDetails() { return Collections.singleton(new GlusterJobSchedulingDetails( "gluster_storage_device_pool_event", getRefreshRate(ConfigValues.GlusterRefreshRateStorageDevices))); } public void init() { log.info("Gluster Storage Device monitoring has been initialized"); } @OnTimerMethodAnnotation("gluster_storage_device_pool_event") public void refreshStorageDevices() { // get all clusters List<Cluster> clusters = clusterDao.getAll(); // for every cluster that supports disk provisioning for (Cluster cluster : clusters) { if (supportsGlusterDiskProvisioning(cluster)) { refreshStorageDevicesFromServers(glusterUtil.getAllUpServers(cluster.getId())); } } } private void refreshStorageDevicesFromServers(List<VDS> upServers) { List<Callable<Pair<VDS, List<StorageDevice>>>> storageDevicesListCalls = new ArrayList<>(); for (final VDS server : upServers) { storageDevicesListCalls.add(() -> { List<StorageDevice> storageDevices = getStorageDevicesFromServer(server); return new Pair<>(server, storageDevices); }); } if (!storageDevicesListCalls.isEmpty()) { List<Pair<VDS, List<StorageDevice>>> storageDevices = ThreadPoolUtil.invokeAll(storageDevicesListCalls); for (Pair<VDS, List<StorageDevice>> pair : storageDevices) { if (pair.getSecond() != null) { updateStorageDevices(pair.getFirst(), pair.getSecond()); } } } } private List<StorageDevice> getStorageDevicesFromServer(VDS server) { try { VDSReturnValue returnValue = runVdsCommand(VDSCommandType.GetStorageDeviceList, new VdsIdVDSCommandParametersBase(server.getId())); if (returnValue.getSucceeded()) { return (List<StorageDevice>) returnValue.getReturnValue(); } else { log.error("VDS error retriving storage device {}", returnValue.getVdsError().getMessage()); log.debug("VDS Error", returnValue.getVdsError()); return null; } } catch (Exception e) { log.error("Exception retriving storage device from vds {}", e.getMessage()); log.debug("Exception", e); return null; } } public void updateStorageDevices(VDS vds, List<StorageDevice> storageDevicesFromVdsm) { Set<String> deviceUuidsFromVdsm = new HashSet<>(); Set<String> deviceNamesFromVdsm = new HashSet<>(); List<StorageDevice> storageDevicesInDb = storageDeviceDao.getStorageDevicesInHost(vds.getId()); Map<String, StorageDevice> nameToDeviceMap = new HashMap<>(); Map<String, StorageDevice> deviceUuidToDeviceMap = new HashMap<>(); // Make deviceUuid to Device map and deviceName to device map so that we can find the // newly added and updated devices without looping over the same list again and again. for (StorageDevice storageDevice : storageDevicesInDb) { nameToDeviceMap.put(storageDevice.getName(), storageDevice); if (storageDevice.getDevUuid() != null && !storageDevice.getDevUuid().isEmpty()) { deviceUuidToDeviceMap.put(storageDevice.getDevUuid(), storageDevice); } } List<StorageDevice> storageDevicesToUpdate = new ArrayList<>(); List<StorageDevice> storageDevicesToDelete = new ArrayList<>(); for (StorageDevice storageDevice : storageDevicesFromVdsm) { // Create deviceName and deviceUuid set to use it while finding the deleted services. deviceNamesFromVdsm.add(storageDevice.getName()); if (storageDevice.getDevUuid() != null) { deviceUuidsFromVdsm.add(storageDevice.getDevUuid()); } // If DevUuid is already exits in the DB then its an existing devices // Assume device from vdsm doesn't have devUUID, but device name already exists in the DB // Following two cases possible: // 1. If device in DB doesn't have a devUUID // update the device if there is a change from vdsm. // 2. If device in DB has devUUID // Though name matches, its two different devices. So treat this device as new one. // Device in DB will be updated/removed by some other iteration in the loop StorageDevice storageDevByDevUuid = deviceUuidToDeviceMap.get(storageDevice.getDevUuid()); StorageDevice storageDevByName = nameToDeviceMap.get(storageDevice.getName()); if (storageDevByDevUuid != null) { storageDevice.setId(storageDevByDevUuid.getId()); if (!Objects.equals(storageDevByDevUuid, storageDevice)) { storageDevicesToUpdate.add(storageDevice); } } else if (storageDevByName != null && StringUtils.isBlank(storageDevByName.getDevUuid())) { storageDevice.setId(storageDevByName.getId()); if (!Objects.equals(storageDevByName, storageDevice)) { storageDevicesToUpdate.add(storageDevice); } } else { storageDevice.setId(Guid.newGuid()); storageDevice.setVdsId(vds.getId()); log.debug("detected new storage device '{}' for host '{}'", storageDevice.getName(), vds.getName()); storageDeviceDao.save(storageDevice); logStorageDeviceMessage(AuditLogType.NEW_STORAGE_DEVICE_DETECTED, vds, storageDevice); } } for (StorageDevice storageDevice : storageDevicesInDb) { if ((storageDevice.getDevUuid() != null && !deviceUuidsFromVdsm.contains(storageDevice.getDevUuid())) || (storageDevice.getDevUuid() == null && !deviceNamesFromVdsm.contains(storageDevice.getName()))) { log.debug("storage device '{}' detected removed for the host '{}'", storageDevice.getName(), vds.getName()); logStorageDeviceMessage(AuditLogType.STORAGE_DEVICE_REMOVED_FROM_THE_HOST, vds, storageDevice); storageDevicesToDelete.add(storageDevice); } } if (!storageDevicesToUpdate.isEmpty()) { storageDeviceDao.updateAllInBatch(storageDevicesToUpdate); } if (!storageDevicesToDelete.isEmpty()) { storageDeviceDao.removeAllInBatch(storageDevicesToDelete); } } private void logStorageDeviceMessage(AuditLogType logType, VDS vds, final StorageDevice device) { logUtil.logAuditMessage(vds.getClusterId(), null, vds, logType, Collections.singletonMap("storageDevice", device.getName())); } private boolean supportsGlusterDiskProvisioning(Cluster cluster) { return cluster.supportsGlusterService(); } }