/*
* Copyright (c) 2008-2011 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.volumecontroller.impl.monitoring;
import java.io.IOException;
import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.emc.storageos.cimadapter.connections.ConnectionManagerException;
import com.emc.storageos.coordinator.client.service.DistributedQueueItemProcessedCallback;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.model.DiscoveredDataObject.CompatibilityStatus;
import com.emc.storageos.db.client.model.StorageProvider.ConnectionStatus;
import com.emc.storageos.db.client.model.StorageSystem;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.volumecontroller.impl.smis.CIMConnectionFactory;
import com.netflix.astyanax.connectionpool.ConnectionFactory;
/**
* Handles monitoring for vnxFile device indications
*/
public class VNXFileMonitoringImpl implements IMonitoringStorageSystem {
private final Logger _logger = LoggerFactory.getLogger(VNXFileMonitoringImpl.class);
private DbClient _dbClient;
private CIMConnectionFactory _connectionFactory;
/**
* Lock instance to handle static CACHE synchronization
*/
private final Object cacheLock = new Object();
/**
* Holds list of unique vnxfile URIs managed by this local bourne node and its callback instances.
* 1. Key -> vnxFile URI
* 2. Value -> callback instance of the monitoringJob lock
*/
private final Map<String, DistributedQueueItemProcessedCallback> VNXFILE_CACHE =
new ConcurrentHashMap<String, DistributedQueueItemProcessedCallback>();
/**
* Holds list of unique vnxFile URI failed subscription. So that scheduler can try subscription in the next cycle.
*/
private final Set<String> FAILED_VNXFILE_SUBSCRIPTION = Collections.synchronizedSet(new HashSet<String>());
/**
* Takes care monitoring for the given vnxfile immediately after acquiring monitoringJob token from zookeeper queue.
*/
@Override
public void startMonitoring(MonitoringJob monitoringJob,
DistributedQueueItemProcessedCallback callback) {
_logger.debug("Entering {}", Thread.currentThread().getStackTrace()[1].getMethodName());
synchronized (cacheLock) {
_logger.info("monitoringJob.getId() {}", monitoringJob.getId());
String storageSystemURI = monitoringJob.getId().toString();
addVNXFileStorageSystemIntoCache(storageSystemURI, callback);
/**
* Delete existing stale subscriptions and make new subscription
*/
boolean isSuccess = makeVNXFileSubscription(storageSystemURI);
if (!isSuccess) {
_logger.debug("VNX File:{} subscription is not successful.", storageSystemURI);
addVNXFailedSubscription(storageSystemURI);
} else {
_logger.info("Successfully subscribed with vnxfile's smi-s provider for indication");
}
}
_logger.debug("Exiting {}", Thread.currentThread().getStackTrace()[1].getMethodName());
}
/**
* Scheduled action for the vnxfile monitoring.
* 1. Removes Stale vnxFile from cache
* 2. Makes new subscription to the failed subscription happened on last schedule cycle.
*/
@Override
public void scheduledMonitoring() {
_logger.debug("Entering {}", Thread.currentThread().getStackTrace()[1].getMethodName());
synchronized (cacheLock) {
try {
stopMonitoringStaleSystem();
handleVNXFileSubscription();
} catch (IOException e) {
_logger.error(e.getMessage(), e);
} catch (ConnectionManagerException e) {
_logger.error(e.getMessage(), e);
}
}
_logger.debug("Exiting {}", Thread.currentThread().getStackTrace()[1].getMethodName());
}
/**
* 1. Find stale vnxFile devices.
* 2. UNsubscribe existing subscription to avoid indications for the stale vnx devices.
* 3. Clear stale devices from zoo keeper queue and local CACHE.
*/
@Override
public void stopMonitoringStaleSystem() {
_logger.debug("Entering {}", Thread.currentThread().getStackTrace()[1].getMethodName());
Iterator<Map.Entry<String, DistributedQueueItemProcessedCallback>> iter = VNXFILE_CACHE.entrySet().iterator();
StorageSystem storageDevice = null;
while (iter.hasNext()) {
Map.Entry<String, DistributedQueueItemProcessedCallback> entry = iter.next();
String storageDeviceURI = entry.getKey();
_logger.debug("storageDeviceURI :{}", storageDeviceURI);
try {
storageDevice = _dbClient.queryObject(StorageSystem.class, URI.create(storageDeviceURI));
} catch (DatabaseException e) {
_logger.error(e.getMessage(), e);
}
if (null == storageDevice || storageDevice.getInactive()) {
_logger.info("Stale vnxfiler {} has been removed from monitoring", storageDeviceURI);
_connectionFactory.unsubscribeSMIProviderConnection(storageDeviceURI);
try {
entry.getValue().itemProcessed();// Removes monitorinJob token from queue
} catch (Exception e) {
_logger.error("Exception occurred while removing monitoringJob token from ZooKeeper queue", e);
} finally {
iter.remove();// Removes from local CACHE
FAILED_VNXFILE_SUBSCRIPTION.remove(storageDeviceURI);
}
}
}
_logger.debug("Exiting {}", Thread.currentThread().getStackTrace()[1].getMethodName());
}
/**
* 1. Refresh all vnxfile connections.
* 2. Make new subscription for the failed one in last schedule cycle.
*
* @throws IOException
* @throws ConnectionManagerException
*/
private void handleVNXFileSubscription() throws IOException, ConnectionManagerException {
_logger.debug("Entering {}", Thread.currentThread().getStackTrace()[1].getMethodName());
_connectionFactory.refreshVnXFileConnections();
Iterator<String> failedVnxSubscriptionIter = FAILED_VNXFILE_SUBSCRIPTION.iterator();
while (failedVnxSubscriptionIter.hasNext()) {
String storageSystemURI = failedVnxSubscriptionIter.next();
if (makeVNXFileSubscription(storageSystemURI)) {
failedVnxSubscriptionIter.remove();
} else {
_logger.debug("Failed to make new subscription for the {}. " +
"This will be taking care in the next monitoring cycle", storageSystemURI);
}
}
_logger.debug("Exiting {}", Thread.currentThread().getStackTrace()[1].getMethodName());
}
/**
* 1. Delete existing stale subscription.
* 2. Make new subscription for monitoring indication
*
* @param storageSystemURI
* @return status. true if it success.
*/
public boolean makeVNXFileSubscription(String storageSystemURI) {
_logger.debug("Entering {}", Thread.currentThread().getStackTrace()[1].getMethodName());
boolean isSuccess = false;
// Check for Storage System compatibility & SMIS provider connection status
if (isCompatibleDevice(storageSystemURI) &&
isSMISProviderConnected(storageSystemURI)) {
/**
* Delete stale subscriptions
*/
_logger.debug("Storage System(vnx file) delete stale subscription status: {}"
, _connectionFactory.deleteVnxFileStaleSubscriptions(storageSystemURI));
isSuccess = _connectionFactory.subscribeVnxFileForIndication(storageSystemURI);
_logger.debug("Exiting {}", Thread.currentThread().getStackTrace()[1].getMethodName());
}
return isSuccess;
}
/**
* Returns true for the COMPATABLIE/UNKNOWN status
* Returns false for the other
*
* @param storageSystemURI
* @return {@link Boolean}
*/
private Boolean isCompatibleDevice(String storageSystemURI) {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, URI.create(storageSystemURI));
if (CompatibilityStatus.COMPATIBLE.name().equalsIgnoreCase(storageSystem.getCompatibilityStatus())
|| CompatibilityStatus.UNKNOWN.name().equalsIgnoreCase(storageSystem.getCompatibilityStatus())) {
return true;
} else {
_logger.info("Subscription will not initiated for the incompatible storage device :{}", storageSystemURI);
return false;
}
}
/**
* Returns true if the storage provider is in CONNECTED state
* Returns false otherwise
*
* @param storageSystemURI
* @return
*/
private Boolean isSMISProviderConnected(String storageSystemURI) {
StorageSystem storageSystem = _dbClient.queryObject(StorageSystem.class, URI.create(storageSystemURI));
if (null != storageSystem.getSmisConnectionStatus() &&
ConnectionStatus.CONNECTED.toString().equalsIgnoreCase(
storageSystem.getSmisConnectionStatus())) {
return true;
} else {
_logger.info("Subscription will not be initiated for storage device {} as the storage provider is in NOT_CONNECTED state",
storageSystemURI);
return false;
}
}
/**
* Adds vnxFile's URI and callback instance into CACHE
*
* @param storageSystemURI
* @param callBack
*/
private void addVNXFileStorageSystemIntoCache(String storageSystemURI, DistributedQueueItemProcessedCallback callBack) {
if (StringUtils.isNotEmpty(storageSystemURI)) {
VNXFILE_CACHE.put(storageSystemURI, callBack);
}
}
/**
* Adds vnxFile's URI into failed CACHE. SO that subscription will happen through scheduled monitoring
*
* @param storageSystemURI
*/
private void addVNXFailedSubscription(String storageSystemURI) {
if (StringUtils.isNotEmpty(storageSystemURI)) {
FAILED_VNXFILE_SUBSCRIPTION.add(storageSystemURI);
}
}
/**
* Setter method for {@link ConnectionFactory} instance
*
* @param _connectionFactory {@link ConnectionFactory}
*/
public void setConnectionFactory(CIMConnectionFactory connectionFactory) {
this._connectionFactory = connectionFactory;
}
/**
* Setter method for DbClient instance
*
* @param _dbClient {@link DbClient}
*/
public void setDbClient(DbClient dbClient) {
this._dbClient = dbClient;
}
@Override
public void clearCache() {
_logger.debug("Entering {}", Thread.currentThread().getStackTrace()[1].getMethodName());
synchronized (cacheLock) {
VNXFILE_CACHE.clear();
FAILED_VNXFILE_SUBSCRIPTION.clear();
}
_logger.debug("Exiting {}", Thread.currentThread().getStackTrace()[1].getMethodName());
}
}