/* * Copyright (c) 2008-2011 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.plugins.metering; import java.io.IOException; import java.net.URI; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.URIUtil; import com.emc.storageos.db.client.constraint.ContainmentConstraint; import com.emc.storageos.db.client.model.BlockObject; import com.emc.storageos.db.client.model.BlockSnapshot; import com.emc.storageos.db.client.model.DataObject; import com.emc.storageos.db.client.model.FileShare; import com.emc.storageos.db.client.model.Stat; import com.emc.storageos.db.client.model.Volume; import com.emc.storageos.plugins.AccessProfile; import com.emc.storageos.plugins.common.Constants; import com.google.common.base.Functions; import com.google.common.collect.Lists; import com.google.common.collect.Sets; /** * * This class is useful in zeroing the records which are inactive and for the * resources which are deleted outside the Bourne system. * */ public abstract class ZeroRecordGenerator { private Logger _logger = LoggerFactory.getLogger(ZeroRecordGenerator.class); /** * Say, Cache has 100 Volumes stored in it. The current Metering Collection * results in 90 Volumes being retrieved from Providers. i.e. 10 Volumes * might get deleted, and needs to be Zeroed. Logic below identifies the 10 * Missing Volumes and pushes to a Map,later Zero Stat Records would get * generated for those 10 Volumes before pushing to Cassandra. * * @param keyMap * * @param Volumes */ public void identifyRecordstobeZeroed( Map<String, Object> keyMap, List<Stat> metricsObjList, final Class clazz) { try { @SuppressWarnings("unchecked") Set<String> resourceIds = (Set<String>) keyMap.get(Constants._nativeGUIDs); DbClient dbClient = (DbClient) keyMap.get(Constants.dbClient); AccessProfile profile = (AccessProfile) keyMap.get(Constants.ACCESSPROFILE); URI storageSystemURI = profile.getSystemId(); List<URI> volumeURIsInDB = extractVolumesOrFileSharesFromDB(storageSystemURI, dbClient, clazz); Set<String> zeroedRecords = Sets.newHashSet(); Set<String> volumeURIsInDBSet = new HashSet<String>(Lists.transform( volumeURIsInDB, Functions.toStringFunction())); // used Sets in Guava libraries, which has the ability to get us // the difference without altering the Cache. Sets.difference(volumeURIsInDBSet, resourceIds).copyInto(zeroedRecords); if (!zeroedRecords.isEmpty()) { _logger.info("Records Zeroed : {}", zeroedRecords.size()); // used in caching Volume Records for (String record : zeroedRecords) { Stat zeroStatRecord = injectattr(keyMap, record, clazz); if (null != zeroStatRecord) { generateZeroRecord(zeroStatRecord, keyMap); metricsObjList.add(zeroStatRecord); } else { _logger.debug( "Records need to get Zeroed doesn't have VolumeUUID : {}", record); } } } } catch (Exception ex) { // No need to throw Exception just because Zeroing Records failed, // continue with persisting other records _logger.error("Error in Zeroing records :", ex); } } /** * extract List of Volume or FileSahre URIs associated to StorageSystem * * @param storageSystemURI * @param dbClient * @return * @throws IOException */ @SuppressWarnings("deprecation") public List<URI> extractVolumesOrFileSharesFromDB( URI storageSystemURI, DbClient dbClient, Class clazz) throws IOException { if (clazz.equals(Volume.class)) { return dbClient.queryByConstraint(ContainmentConstraint.Factory .getStorageDeviceVolumeConstraint(storageSystemURI)); } else if (clazz.equals(FileShare.class)) { return dbClient.queryByConstraint(ContainmentConstraint.Factory .getStorageDeviceFileshareConstraint(storageSystemURI)); } return Collections.emptyList(); } /** * * Inject UUID, vpool, & project into Stats before pushing to Cassandra. * To-Do: Inspect on using any external Cache Mechanism like EhCache * Memcache, if needed. * * Made this method as abstract sothat each plugin will implement their logic * set the resourceId. * * @param Map * <String, Object> * @param Stat * @param NativeGUID * */ public <T extends DataObject> Stat injectattr( Map<String, Object> keyMap, String nativeGuid, Class<T> clazz) { Stat statObj = null; URI volURI = null; URI projectURI = null; URI tenantURI = null; URI vPoolURI = null; T volObj = null; boolean snapProcessed = false; try { DbClient client = (DbClient) keyMap.get(Constants.dbClient); /** * Number of records to be Zeroed, generally will not be a huge * number compared to the Volumes getting processed. Hence, the * number of calls would be minimum. (Or) the other approach would * be to Cache NativeGuids. * * the Argument nativeGuid would be the Resource ID URI in case of * Zero Records. * */ if (null != clazz) { volURI = new URI(nativeGuid); volObj = client.queryObject(clazz, volURI); if (volObj instanceof Volume) { Volume volume = (Volume) volObj; nativeGuid = volume.getNativeGuid(); projectURI = volume.getProject().getURI(); tenantURI = volume.getTenant().getURI(); vPoolURI = volume.getVirtualPool(); } else { FileShare fileShare = (FileShare) volObj; nativeGuid = fileShare.getNativeGuid(); projectURI = fileShare.getProject().getURI(); tenantURI = fileShare.getTenant().getURI(); vPoolURI = fileShare.getVirtualPool(); } } else { List<URI> volumeURIs = injectResourceURI(client, nativeGuid); if (null == volumeURIs || volumeURIs.isEmpty()) { _logger.debug("Querying Cassandra using nativeGUID:" + nativeGuid + "yields : 0 ResourceID"); return statObj; } volURI = volumeURIs.get(0); } long allocatedCapacity = 0L; // if snap,process the parent volume if (!URIUtil.isType(volURI, Volume.class) && !URIUtil.isType(volURI, FileShare.class)) { _logger.debug("Skipping Statistics for Snapshots :" + volURI); BlockObject bo = BlockObject.fetch(client, volURI); if (bo instanceof BlockSnapshot) { Volume parent = client.queryObject(Volume.class, ((BlockSnapshot) (bo)).getParent().getURI()); _logger.info("Processing snapshot's parent Volume {}", parent.getNativeGuid()); volURI = parent.getId(); nativeGuid = parent.getNativeGuid(); allocatedCapacity = ((BlockSnapshot) bo).getAllocatedCapacity(); snapProcessed = true; } } // No need to verify whether Volume is inactive or not, as for // zeroing records // even inactive Volumes need to get zeroed. // for verification purpose _logger.debug("Querying Cassandra using nativeGUID:" + nativeGuid + "yields Resource ID :" + volURI); if (keyMap.containsKey(nativeGuid)) { statObj = (Stat) keyMap.get(nativeGuid); } else { // create a Metrics Object statObj = getStatObject(volURI, client); if (null == statObj) { return statObj; } keyMap.put(nativeGuid, statObj); statObj.setResourceId(volURI); } statObj.setNativeGuid(nativeGuid); // set Project, Tenant and vPool info for Zero records, // as we already have Volume/File object queried from DB above to // get nativeGuid from URI. // calling Block/FileInsertion.injectColumnsDetails() results in // additional DB call to get Volume/File object. if (clazz != null) { statObj.setProject(projectURI); statObj.setTenant(tenantURI); statObj.setVirtualPool(vPoolURI); } if (snapProcessed) { _logger.info("Adding SnapShot details"); // I can't add this as default value due to existing layers // which consume metering data. if (null == statObj.getSnapshotCount()) { statObj.setSnapshotCount(0); statObj.setSnapshotCapacity(0); } statObj.setSnapshotCount(statObj.getSnapshotCount() + 1); statObj.setSnapshotCapacity(statObj.getSnapshotCapacity() + allocatedCapacity); } // Add Volume URIs to local Collection, which will be compared // against Volumes in DB to determine Zero Records. @SuppressWarnings("unchecked") Set<String> volumeURIList = (Set<String>) keyMap.get(Constants._nativeGUIDs); volumeURIList.add(volURI.toString()); } catch (Exception e) { // Even if one volume fails, no need to throw exception instead // continue processing other volumes if (null != nativeGuid) { _logger.error( "Cassandra Database Error while querying VolumeUUId, VirtualPool & Project URIs : {}-->", nativeGuid, e); } } // if processing snap, parent volume would have already added to the list, hence return null to skip return snapProcessed ? null : statObj; } /** * Return the Stat object based on the certain conditions. * Ex. If there are no project details in Volume/FS, don't create Stat object. * * @param resourceURI * @return */ protected abstract Stat getStatObject(URI resourceURI, DbClient dbClient); /** * inject ResourceId of the given nativeGuid. * * @param client * @param nativeGuid * @return */ protected abstract List<URI> injectResourceURI(DbClient client, String nativeGuid); /** * Generate Zero Record for Volumes * * @param nativeGuid * @param keyMap */ public abstract void generateZeroRecord( Stat zeroStatRecord, Map<String, Object> keyMap); }