/* * Copyright (c) 2012 EMC Corporation * All Rights Reserved */ package com.emc.storageos.cimadapter.connections.cim; // java imports import java.net.URL; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import javax.cim.CIMDataType; import javax.cim.CIMInstance; import javax.cim.CIMObjectPath; import javax.cim.CIMProperty; import javax.wbem.CloseableIterator; import javax.wbem.WBEMException; import javax.wbem.client.WBEMClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.cimadapter.connections.ConnectionManagerException; /** * CIM subscription manager that serves a CIM connection. It handles the * creation and destruction of indication filter, destination and subscription * objects on the CIMOM. */ public class CimSubscriptionManager { // The connection associated with the subscription manager. private CimConnection _connection; // An identifier that is used in the filter and handler names of subscriptions // that provides a means for subscriptions to identified. private String _subscriptionsIdentifier = null; // The CIM object path for the indication handler. private CIMObjectPath _handlerPath = null; // The CIM object paths for the subscribed filters. private Set<CIMObjectPath> _filterPaths = new HashSet<CIMObjectPath>(); // The CIM object paths for the managed subscriptions. private Set<CIMObjectPath> _subscriptionPaths = new HashSet<CIMObjectPath>(); // Logger reference. private static final Logger s_logger = LoggerFactory.getLogger(CimSubscriptionManager.class); /** * Constructs a CIM subscription manager for the given CIM connection. * * @param connection The CIM connection. * @param subscriptionsIdentifier The identifier that is used in the filter * and handler names of created subscriptions. */ public CimSubscriptionManager(CimConnection connection, String subscriptionsIdentifier) { _connection = connection; _subscriptionsIdentifier = subscriptionsIdentifier; } /** * Creates subscription objects using the CIM connection's indication filter * map and listener configuration. * * @throws WBEMException, Exception */ public void subscribe() throws WBEMException, Exception { s_logger.info("Subscribing to indications"); CimFilterMap filterMap = _connection.getIndicationFilterMap(); try { getHandler(); for (CimFilterInfo filterInfo : filterMap.getFilters().values()) { try { createSubscription(filterInfo); } catch (WBEMException e) { if (e.getID() != WBEMException.CIM_ERR_ALREADY_EXISTS) { throw e; } s_logger.debug("Subscription for filter {} already exists", filterInfo.getName()); } } } catch (WBEMException e) { if (e.getID() != WBEMException.CIM_ERR_ALREADY_EXISTS) { throw e; } s_logger.debug("Subscription handler already exists."); } } /** * Destroys subscription objects. */ public void unsubscribe() { s_logger.info("Unsubscribing to indications"); Set<CIMObjectPath> subscriptionsPaths = _subscriptionPaths; // Find and destroy all subscriptions that look like they were created // by this manager's connection. Doing this instead of just using the // manager's list cleans up stale objects on the CIMOM. try { subscriptionsPaths = enumerateSubscriptions(); } catch (Exception e) { s_logger.warn("Failure enumerating all subscriptions for {}", _connection.getConnectionName(), e); } Iterator<CIMObjectPath> pathsIter = subscriptionsPaths.iterator(); while (pathsIter.hasNext()) { deleteInstance(pathsIter.next()); } // Destroy filters. pathsIter = _filterPaths.iterator(); while (pathsIter.hasNext()) { deleteInstance(pathsIter.next()); } // Destroy the handler path. if (_handlerPath != null) { deleteInstance(_handlerPath); } } /** * Deletes stale instances on the CIM provider that are associated with * stale subscriptions. The connection manager can be configured to * optionally call this function when establishing a connection to the * provider for the purpose of cleaning up old subscriptions from previous * connections to the provider that were not properly cleaned up. * * @throws WBEMException Enumerating the subscription instances on the * provider. */ public void deleteStaleSubscriptions() throws WBEMException { CIMInstance subscription; CIMProperty<?> property; CIMObjectPath subscriptionHandlerPath; CIMObjectPath subscriptionFilterPath; String subscriptionFilterName; String subscriptionHandlerName; Set<CIMObjectPath> staleSubscriptionSet = new HashSet<CIMObjectPath>(); Map<String, CIMObjectPath> staleFilterMap = new HashMap<String, CIMObjectPath>(); Map<String, CIMObjectPath> staleHandlerMap = new HashMap<String, CIMObjectPath>(); // Get and loop over all subscriptions. WBEMClient cimClient = _connection.getCimClient(); CIMObjectPath subscriptionPath = CimObjectPathCreator.createInstance(CimConstants.CIM_SUBSCRIPTION_NAME, _connection.getInteropNamespace()); CloseableIterator<CIMInstance> subscriptionIter = null; try { subscriptionIter = cimClient.enumerateInstances(subscriptionPath, true, true, false, null); while (subscriptionIter.hasNext()) { subscription = subscriptionIter.next(); // Get the handler for the subscription. property = subscription.getProperty(CimConstants.SUBSCRIPTION_PROP_HANDLER); subscriptionHandlerPath = (CIMObjectPath) property.getValue(); // If the name of the handler contains the passed subscription // identifier then this is a stale subscription to be deleted. // Note that subscriptions themselves are not assigned names // when they are created. However, both the handler and filter // associated with the subscription are assigned names, and the // name contains the subscription identifier configured for // the connection manager. Therefore, we could check either the // handler or the filter. subscriptionHandlerName = subscriptionHandlerPath.getKey(CimConstants.NAME_KEY).getValue().toString(); if (subscriptionHandlerName.contains(_subscriptionsIdentifier)) { // Add the subscription to the stale subscription set. staleSubscriptionSet.add(subscription.getObjectPath()); // Add the handler to the stale handler map, if not already // added. Multiple subscriptions can reference the same // handler. if (!staleHandlerMap.keySet().contains(subscriptionHandlerName)) { staleHandlerMap.put(subscriptionHandlerName, subscriptionHandlerPath); } // Now get and add the filter for the subscription to the stale // filters map. Again, it is possible that multiple // subscriptions reference the same filter. property = subscription.getProperty(CimConstants.SUBSCRIPTION_PROP_FILTER); subscriptionFilterPath = (CIMObjectPath) property.getValue(); subscriptionFilterName = subscriptionFilterPath.getKey(CimConstants.NAME_KEY).getValue().toString(); if (!staleFilterMap.keySet().contains(subscriptionFilterName)) { staleFilterMap.put(subscriptionFilterName, subscriptionFilterPath); } } } // Delete the stale subscriptions first, which reference the filters and // handlers. Iterator<CIMObjectPath> pathsIter = staleSubscriptionSet.iterator(); while (pathsIter.hasNext()) { deleteInstance(pathsIter.next()); } // Next delete the stale filters. pathsIter = staleFilterMap.values().iterator(); while (pathsIter.hasNext()) { deleteInstance(pathsIter.next()); } // Finally, delete the stale handlers. pathsIter = staleHandlerMap.values().iterator(); while (pathsIter.hasNext()) { deleteInstance(pathsIter.next()); } } finally { if (subscriptionIter != null) { subscriptionIter.close(); } } } /** * Finds all of the CIM_Subscription objects that look like they were * created for this manager's connection. This is useful for rounding up and * removing stale subscriptions in the CIMOM. * * This works by looking for subscriptions that are attached to the handler * for this connection. The name of the handler is fixed by its destination * properties, so all subscriptions for a destination are going to be * attached to the same handler whose name is more or less predictable (if * created by this class). * * @return The set of subscriptions that look like they were created for * this manager's connection. * * @throws WBEMException, ConnectionManagerException */ private Set<CIMObjectPath> enumerateSubscriptions() throws WBEMException, ConnectionManagerException { CIMInstance subscription; CIMProperty<?> property; CIMObjectPath subscriptionHandlerPath; Object subscriptionHandlerName; Set<CIMObjectPath> subscriptionSet = new HashSet<CIMObjectPath>(); WBEMClient cimClient = _connection.getCimClient(); String interopNS = _connection.getInteropNamespace(); CIMObjectPath subscriptionPath = CimObjectPathCreator.createInstance(CimConstants.CIM_SUBSCRIPTION_NAME, interopNS); CloseableIterator<CIMInstance> subscriptionIter = null; try { subscriptionIter = cimClient.enumerateInstances(subscriptionPath, true, true, false, null); while (subscriptionIter.hasNext()) { subscription = subscriptionIter.next(); property = subscription.getProperty(CimConstants.SUBSCRIPTION_PROP_HANDLER); subscriptionHandlerPath = (CIMObjectPath) property.getValue(); subscriptionHandlerName = subscriptionHandlerPath.getKey(CimConstants.NAME_KEY).getValue(); if (subscriptionHandlerName.equals(getHandler().getKey(CimConstants.NAME_KEY).getValue())) { subscriptionPath = subscription.getObjectPath(); s_logger.debug("Found: {}", subscriptionPath); subscriptionSet.add(subscriptionPath); } } } finally { if (subscriptionIter != null) { subscriptionIter.close(); } } return subscriptionSet; } /** * Creates an indication subscription in the CIMOM for the given filter. * * @param filterInfo the filter information. * * @return the CIM object path of the subscription * * @throws WBEMException, ConnectionManagerException */ protected CIMObjectPath createSubscription(CimFilterInfo filterInfo) throws WBEMException, ConnectionManagerException { CIMObjectPath filterPath; if (filterInfo instanceof CimManagedFilterInfo) { filterPath = createFilter((CimManagedFilterInfo) filterInfo); } else { filterPath = getInstance(CimConstants.CIM_FILTER_NAME, filterInfo.getName()).getObjectPath(); } s_logger.trace("filterPath :{}", filterPath); CIMProperty<?> filterProp = new CIMProperty<CIMObjectPath>(CimConstants.SUBSCRIPTION_PROP_FILTER, new CIMDataType(CimConstants.CIM_FILTER_NAME), filterPath); CIMProperty<?> handlerProp = new CIMProperty<CIMObjectPath>(CimConstants.SUBSCRIPTION_PROP_HANDLER, new CIMDataType(CimConstants.CIM_HANDLER_NAME), getHandler()); s_logger.trace("filterProp :{}", filterProp); s_logger.trace("handlerProp :{}", handlerProp); CIMProperty<?>[] subscriptionProperties = new CIMProperty[] { filterProp, handlerProp }; CIMObjectPath subscriptionPath = createInstance(CimConstants.CIM_SUBSCRIPTION_NAME, subscriptionProperties); _subscriptionPaths.add(subscriptionPath); s_logger.trace("subscriptionPath :{}", subscriptionPath); return subscriptionPath; } /** * Creates an indication filter in the CIMOM for the given configuration. * * @param filterInfo The filter information. * * @return The CIM object path of the filter object. * * @throws WBEMException */ private CIMObjectPath createFilter(CimManagedFilterInfo filterInfo) throws WBEMException { StringBuilder filterNameBuilder = new StringBuilder(); filterNameBuilder.append(_subscriptionsIdentifier); filterNameBuilder.append(CimConstants.PATH_NAME_DELIMITER); filterNameBuilder.append(filterInfo.getName()); String filterName = filterNameBuilder.toString(); String implNS = _connection.getImplNamespace(); CIMProperty<?> nameProperty = new CIMProperty<String>(CimConstants.NAME_KEY, CIMDataType.STRING_T, filterName); CIMProperty<?> srcNamespaceProp = new CIMProperty<String>(CimConstants.FILTER_PROP_SRC_NAMESPACE, CIMDataType.STRING_T, implNS); CIMProperty<?> srcNamespacesProp = new CIMProperty<String[]>(CimConstants.FILTER_PROP_SRC_NAMESPACES, CIMDataType.STRING_ARRAY_T, new String[] { implNS }); CIMProperty<?> queryLangProp = new CIMProperty<String>(CimConstants.FILTER_PROP_QUERY_LANGUAGE, CIMDataType.STRING_T, filterInfo.getQueryLanguage()); CIMProperty<?> queryProp = new CIMProperty<String>(CimConstants.FILTER_PROP_QUERY, CIMDataType.STRING_T, filterInfo.getQuery()); CIMProperty<?>[] filterProperties = new CIMProperty[] { nameProperty, srcNamespaceProp, srcNamespacesProp, queryLangProp, queryProp }; CIMObjectPath filterPath = createInstance(CimConstants.CIM_FILTER_NAME, filterName, filterProperties); _filterPaths.add(filterPath); return filterPath; } /** * Creates an indication handler in the CIMOM from the indication listener's * configuration. * * The WBEM listener interface does not provide a mechanism for getting the * source IP address of a received indication. To match indications with * connections, the connection name is put in the handler's destination URL * as the path component. * * @return the CIM object path of the handler * @throws WBEMException, ConnectionManagerException */ private CIMObjectPath createHandler() throws WBEMException, ConnectionManagerException { CimListener listener = _connection.getIndicationListener(); URL listenerURL = listener.getURL(); if (listenerURL == null) { // Verify that the listener URL has been set. throw new ConnectionManagerException("Listener URL is not set, Subscription handler cannot be set."); } StringBuffer handlerNameBuff = new StringBuffer(); handlerNameBuff.append(_subscriptionsIdentifier); handlerNameBuff.append(CimConstants.PATH_NAME_DELIMITER); handlerNameBuff.append(listenerURL.getHost()); handlerNameBuff.append(CimConstants.PATH_NAME_DELIMITER); handlerNameBuff.append(listenerURL.getPort()); String handlerName = handlerNameBuff.toString(); CIMProperty<?> destinationProperty = new CIMProperty<String>(CimConstants.HANLDER_PROP_DESTINATION, CIMDataType.STRING_T, listenerURL.toString() + '/' + _connection.getConnectionName()); CIMProperty<?>[] handlerProperties = new CIMProperty[] { destinationProperty }; return createInstance(CimConstants.CIM_HANDLER_NAME, handlerName, handlerProperties); } /** * Gets the indication handler in the CIMOM. * * If the handler does not exist, this method creates it. * * @return the CIM object path of the handler. * * @throws WBEMException, ConnectionManagerException */ private CIMObjectPath getHandler() throws WBEMException, ConnectionManagerException { if (_handlerPath == null) { _handlerPath = createHandler(); } return _handlerPath; } /** * Creates an instance in the CIMOM. * * If the instance already exists, its object path is returned. * * @param className The CIM class name. * @param name The instance name. * @param Properties The CIM properties. * * @return The CIM object path of the instance. * * @throws WBEMException */ private CIMObjectPath createInstance(String className, String name, CIMProperty<?>[] properties) throws WBEMException { CIMProperty<?>[] array = new CIMProperty<?>[properties.length + 1]; System.arraycopy(properties, 0, array, 0, properties.length); array[properties.length] = new CIMProperty<String>(CimConstants.NAME_KEY, CIMDataType.STRING_T, name); properties = array; try { return createInstance(className, properties); } catch (WBEMException e) { if (e.getID() == WBEMException.CIM_ERR_ALREADY_EXISTS) { CIMInstance instance = getInstance(className, name); if (instance != null) { return instance.getObjectPath(); } } throw e; } } /** * Creates an instance in the CIMOM. * * The instance name is included as one of the properties and/or derived by * the CIMOM. * * @param className The CIM class name. * @param properties The CIM properties. * * @return The CIM object path of the instance. * * @throws WBEMException */ private CIMObjectPath createInstance(String className, CIMProperty<?>[] properties) throws WBEMException { s_logger.trace("className :{}", className); s_logger.trace("properties :{}", properties); WBEMClient cimClient = _connection.getCimClient(); String interopNS = _connection.getInteropNamespace(); CIMObjectPath path = CimObjectPathCreator.createInstance(className, interopNS); CIMInstance instance = new CIMInstance(path, properties); s_logger.trace("interopNS :{}", interopNS); s_logger.trace("path :{}", path); path = cimClient.createInstance(instance); s_logger.debug("Created: {}", path); return path; } /** * Gets the named instance from the CIMOM. * * @param className The CIM class name. * @param name The CIM Name property value. * * @return The CIM instance or null * * @throws javax.wbem.WBEMException */ private CIMInstance getInstance(String className, String name) throws WBEMException { CIMInstance instance = null; WBEMClient cimClient = _connection.getCimClient(); String interopNS = _connection.getInteropNamespace(); CIMObjectPath path = CimObjectPathCreator.createInstance(className, interopNS); CloseableIterator<CIMInstance> instanceIter = null; CIMProperty<?> property; try { instanceIter = cimClient.enumerateInstances(path, true, true, false, null); while (instanceIter.hasNext()) { instance = instanceIter.next(); property = instance.getProperty(CimConstants.NAME_KEY); if (property.getValue().toString().equals(name)) { s_logger.debug("Found: {}", instance.getObjectPath()); break; } instance = null; } } finally { if (instanceIter != null) { instanceIter.close(); } } return instance; } /** * Deletes the instance with the given object path from the CIMOM. * * @param path The CIM object path. */ protected void deleteInstance(CIMObjectPath path) { try { _connection.getCimClient().deleteInstance(path); s_logger.info("Deleted: {}", path); } catch (WBEMException e) { s_logger.error("Failed to delete {}", path, e); } } }