/* * Copyright (c) 2016 EMC Corporation * All Rights Reserved */ package com.emc.storageos.volumecontroller.impl.metering.plugins.vplex; import java.net.URI; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.joda.time.DateTime; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.emc.storageos.customconfigcontroller.CustomConfigConstants; import com.emc.storageos.customconfigcontroller.CustomConfigResolver; import com.emc.storageos.customconfigcontroller.CustomConfigTypeProvider; import com.emc.storageos.customconfigcontroller.DataSource; import com.emc.storageos.customconfigcontroller.exceptions.CustomConfigControllerException; import com.emc.storageos.customconfigcontroller.impl.CustomConfigHandler; import com.emc.storageos.db.client.DbAggregatorItf; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.TimeSeriesMetadata; import com.emc.storageos.db.client.TimeSeriesQueryResult; import com.emc.storageos.db.client.constraint.Constraint; import com.emc.storageos.db.client.constraint.QueryResultList; import com.emc.storageos.db.client.model.DataObject; import com.emc.storageos.db.client.model.DiscoveredDataObject; import com.emc.storageos.db.client.model.NamedURI; import com.emc.storageos.db.client.model.Operation; import com.emc.storageos.db.client.model.Stat; import com.emc.storageos.db.client.model.StorageHADomain; import com.emc.storageos.db.client.model.StoragePort; import com.emc.storageos.db.client.model.StorageSystem; import com.emc.storageos.db.client.model.StringMap; import com.emc.storageos.db.client.model.TimeSeries; import com.emc.storageos.db.client.model.TimeSeriesSerializer; import com.emc.storageos.db.exceptions.DatabaseException; import com.emc.storageos.model.ResourceOperationTypeEnum; import com.emc.storageos.plugins.AccessProfile; import com.emc.storageos.plugins.common.Constants; import com.emc.storageos.services.util.EnvConfig; import com.emc.storageos.services.util.StorageDriverManager; import com.emc.storageos.svcs.errorhandling.model.ServiceCoded; import com.emc.storageos.volumecontroller.impl.plugins.metering.smis.processor.PortMetricsProcessor; import com.emc.storageos.volumecontroller.impl.plugins.metering.vplex.ListVPlexPerpetualCSVFileNames; import com.emc.storageos.volumecontroller.impl.plugins.metering.vplex.ReadAndParseVPlexPerpetualCSVFile; import com.emc.storageos.volumecontroller.impl.plugins.metering.vplex.VPlexPerpetualCSVFileCollector; import com.emc.storageos.volumecontroller.impl.plugins.metering.vplex.VPlexPerpetualCSVFileData; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.iwave.ext.linux.LinuxSystemCLI; /** * Tester class for VPlex metering related classes and functions * This test requires a sanity.properties file to run, so therefore is not part of the master unit test suite. */ @Ignore public class VPlexMeteringTest { public static final String SANITY = "sanity"; public static final String VPLEX_HOST = "vplex.host"; public static final String VPLEX_USERNAME = "vplex.username"; public static final String VPLEX_PASSWORD = "vplex.password"; public static final String VPLEX_DIRECTORS = "vplex.directors"; public static final String VPLEX_PORTS_PER_DIRECTOR = "vplex.portsPerDirector"; private final static String HOST = EnvConfig.get(SANITY, VPLEX_HOST); private final static String USERNAME = EnvConfig.get(SANITY, VPLEX_USERNAME); private final static String PASSWORD = EnvConfig.get(SANITY, VPLEX_PASSWORD); private final static String DIRECTORS = EnvConfig.get(SANITY, VPLEX_DIRECTORS); private final static int PORT_PER_DIRECTOR = Integer.valueOf(EnvConfig.get(SANITY, VPLEX_PORTS_PER_DIRECTOR)); private static boolean alreadyPrinted = false; private static boolean outputToLog = Boolean.valueOf(EnvConfig.get(SANITY, "output_to_log")); private static Logger log = LoggerFactory.getLogger(VPlexMeteringTest.class); @Test public void testListingPerpetualDataFilenames() { LinuxSystemCLI cli = new LinuxSystemCLI(HOST, USERNAME, PASSWORD); ListVPlexPerpetualCSVFileNames listDataFileNamesCmd = new ListVPlexPerpetualCSVFileNames(); cli.executeCommand(listDataFileNamesCmd); List<String> filenames = listDataFileNamesCmd.getResults(); Assert.assertFalse("Expected to find file names", filenames.isEmpty()); out("Following files were found {}", filenames); Matcher matcher = Pattern.compile(".*?/([\\w\\-_]+)_PERPETUAL_vplex_sys_perf_mon.log").matcher(filenames.get(0)); Assert.assertTrue("Expected filename matcher to match", matcher.matches()); out("The director name for file '{}' is '{}'", filenames.get(0), matcher.group(1)); } @Test public void testReadingDataFiles() { LinuxSystemCLI cli = new LinuxSystemCLI(HOST, USERNAME, PASSWORD); ListVPlexPerpetualCSVFileNames listDataFileNamesCmd = new ListVPlexPerpetualCSVFileNames(); cli.executeCommand(listDataFileNamesCmd); List<String> filenames = listDataFileNamesCmd.getResults(); Assert.assertFalse("Expected to find file names", filenames.isEmpty()); for (String filename : filenames) { ReadAndParseVPlexPerpetualCSVFile readDataFile = new ReadAndParseVPlexPerpetualCSVFile(filename); cli.executeCommand(readDataFile); VPlexPerpetualCSVFileData fileData = readDataFile.getResults(); Assert.assertNotNull("Expect file data to be non-null", fileData); out("For file {} these are the headers:\n{}", filename, Joiner.on('\n').join(fileData.getHeaders())); List<Map<String, String>> dataLines = fileData.getDataLines(); Assert.assertTrue("Expect file data to have data values", !dataLines.isEmpty()); out("For file {} there are {} data lines", filename, dataLines.size()); fileData.close(); } } @Test public void testReadAndParseMetrics() { LinuxSystemCLI cli = new LinuxSystemCLI(HOST, USERNAME, PASSWORD); ListVPlexPerpetualCSVFileNames listDataFileNamesCmd = new ListVPlexPerpetualCSVFileNames(); cli.executeCommand(listDataFileNamesCmd); List<String> fileNames = listDataFileNamesCmd.getResults(); for (String fileName : fileNames) { ReadAndParseVPlexPerpetualCSVFile readDataFile = new ReadAndParseVPlexPerpetualCSVFile(fileName); cli.executeCommand(readDataFile); VPlexPerpetualCSVFileData fileData = readDataFile.getResults(); // Read each data line from the file int processedLines = 0; int headerCount = fileData.getHeaders().size(); for (Map<String, String> dataLine : fileData.getDataLines()) { // Extract the metrics and their values, translate them into ViPR statistics Set<String> keys = dataLine.keySet(); Assert.assertTrue("Expected number of data keys to match headers", headerCount == keys.size()); for (String metricName : keys) { String value = dataLine.get(metricName); // If the value is not "no data" for the value, then parse it... if (!VPlexPerpetualCSVFileData.NO_DATA.equals(value)) { readAndParseMetrics(metricName, value); } } processedLines++; alreadyPrinted = true; } Assert.assertTrue("Data lines processed does not match total expected", fileData.getTotalLines() == (processedLines + 1)); // Clean up fileData resources fileData.close(); } } private void readAndParseMetrics(String metric, String value) { if (alreadyPrinted) { return; } Pattern pattern = Pattern.compile("([\\w+\\-\\.]+)\\s+([\\w+\\-]*+)\\s*\\(([\\w+/%]+)\\)"); Matcher matcher = pattern.matcher(metric); if (matcher.matches()) { String name = matcher.group(1); String qualifier = matcher.group(2); String units = matcher.group(3); if (Strings.isNullOrEmpty(qualifier)) { qualifier = "N/A"; } out("metric = {}, name = {} qualifier = {} units = {} --> {}", metric, name, qualifier, units, value); } } @Test public void testVPlexPerpetualCSVFileCollector() throws InstantiationException, IllegalAccessException { MockDbClient mockDbClient = new MockDbClient(); mockStorageDriverManager(); StorageSystem storageSystem = mockStorageSystem("vplex-1", "000123ABC000XYZ"); mockDbClient.MOCK_DB.put(storageSystem.getId(), storageSystem); int directorIndex = 1; String directors[] = DIRECTORS.split(","); for (String directorName : directors) { int adapterIndex = (directorIndex % 2); String aOrB = directorName.substring(directorName.length() - 1); String adapterSerial = String.format("CF23K00000%d%d", directorIndex, adapterIndex); StorageHADomain director = mockVPlexAdapter(storageSystem, directorName, adapterSerial); mockDbClient.MOCK_DB.put(director.getId(), director); for (int cpuIndex = 0; cpuIndex < 2; cpuIndex++) { for (int portIndex = 0; portIndex < PORT_PER_DIRECTOR; portIndex++) { String portName = String.format("%s%d-FC%02d", aOrB, cpuIndex, portIndex); String wwn = String.format("5C:CC:DD:EE:FF:00:%02d:%02d", directorIndex, portIndex); StoragePort port = mockStoragePort(director, portName, wwn, adapterSerial); mockDbClient.MOCK_DB.put(port.getId(), port); } } directorIndex++; } AccessProfile accessProfile = new AccessProfile(); accessProfile.setIpAddress(HOST); accessProfile.setUserName(USERNAME); accessProfile.setPassword(PASSWORD); accessProfile.setSystemId(storageSystem.getId()); Map<String, Object> context = new HashMap<>(); context.put(Constants.dbClient, mockDbClient); context.put(Constants._Stats, new LinkedList<Stat>()); context.put(Constants._TimeCollected, System.currentTimeMillis()); VPlexPerpetualCSVFileCollector collector = new VPlexPerpetualCSVFileCollector(); PortMetricsProcessor portMetricsProcessor = mockPortMetricsProcessor(mockDbClient); collector.setPortMetricsProcessor(portMetricsProcessor); collector.collect(accessProfile, context); } private PortMetricsProcessor mockPortMetricsProcessor(MockDbClient mockDbClient) { CustomConfigHandler customConfigHandler = new MockCustomConfigHandler(); customConfigHandler.setDbClient(mockDbClient); PortMetricsProcessor portMetricsProcessor = new PortMetricsProcessor(); portMetricsProcessor.setDbClient(mockDbClient); portMetricsProcessor.setCustomConfigHandler(customConfigHandler); return portMetricsProcessor; } private StorageHADomain mockVPlexAdapter(StorageSystem storageSystem, String name, String serialNumber) throws InstantiationException, IllegalAccessException { StorageHADomain director = mockObject(StorageHADomain.class, name); director.setAdapterName(name); director.setName(name); director.setSerialNumber(serialNumber); director.setProtocol(StoragePort.TransportType.FC.name()); director.setNumberofPorts("8"); director.setNativeGuid(String.format("%s:+ADAPTER+%s", storageSystem.getNativeGuid(), serialNumber)); director.setStorageDeviceURI(storageSystem.getId()); return director; } private StorageSystem mockStorageSystem(String name, String serialNumber) throws InstantiationException, IllegalAccessException { StorageSystem storageSystem = mockObject(StorageSystem.class, name); storageSystem.setSerialNumber(serialNumber); storageSystem.setNativeGuid(String.format("VPLEX+%s", serialNumber)); storageSystem.setSystemType(DiscoveredDataObject.Type.vplex.name()); return storageSystem; } private StoragePort mockStoragePort(StorageHADomain director, String name, String wwn, String serialNumber) throws InstantiationException, IllegalAccessException { StoragePort port = mockObject(StoragePort.class, name); port.setCompatibilityStatus(DiscoveredDataObject.CompatibilityStatus.COMPATIBLE.name()); port.setDiscoveryStatus(DiscoveredDataObject.DiscoveryStatus.VISIBLE.name()); port.setLabel(String.format("%s+PORT+%s", director.getNativeGuid(), wwn)); port.setNativeGuid(String.format("%s+PORT+%s", director.getNativeGuid(), wwn)); port.setPortGroup(director.getAdapterName()); port.setPortName(name); port.setPortNetworkId(wwn); port.setPortSpeed(8L); port.setPortType(StoragePort.PortType.frontend.name()); port.setStorageDevice(director.getStorageDeviceURI()); port.setStorageHADomain(director.getId()); port.setTransportType(StoragePort.TransportType.FC.name()); return port; } private void mockStorageDriverManager() { StorageDriverManager storageDriverManager = new StorageDriverManager(); storageDriverManager.setApplicationContext(new ClassPathXmlApplicationContext("driver-conf.xml")); } private <T extends DataObject> T mockObject(Class<T> clazz, String name) throws IllegalAccessException, InstantiationException { URI uri = URI.create(String.format("%s:%s", clazz.getSimpleName(), name)); DataObject object = clazz.newInstance(); object.setId(uri); object.setLabel(name); return (T) object; } private void out(String format, Object... args) { if (outputToLog) { log.info(format, args); } else { String modFormat = format.replaceAll("\\{\\}", "%s"); System.out.println(String.format(modFormat, args)); } } /** * Used for mocking up DataObjects used by the collector: StorageSystem, StorageHADomain, and StoragePort. */ static private class MockDbClient implements DbClient { private Map<URI, DataObject> MOCK_DB = new HashMap<>(); @Override public DataObject queryObject(URI id) { return null; } @Override public <T extends DataObject> T queryObject(Class<T> clazz, URI id) { return (T) MOCK_DB.get(id); } @Override public <T extends DataObject> T queryObject(Class<T> clazz, NamedURI id) { return null; } @Override public <T extends DataObject> List<T> queryObject(Class<T> clazz, Collection<URI> ids) { return null; } @Override public <T extends DataObject> List<T> queryObject(Class<T> clazz, Collection<URI> ids, boolean activeOnly) { return null; } @Override public <T extends DataObject> List<T> queryObject(Class<T> clazz, URI... id) { return null; } @Override public <T extends DataObject> Iterator<T> queryIterativeObjects(Class<T> clazz, Collection<URI> id) { List<T> list = new ArrayList<>(); for (URI uri : id) { T object = (T) MOCK_DB.get(uri); if (object != null) { list.add(object); } } return list.iterator(); } @Override public <T extends DataObject> Iterator<T> queryIterativeObjects(Class<T> clazz, Collection<URI> ids, boolean activeOnly) { List<T> list = new ArrayList<>(); Iterator<URI> iterator = ids.iterator(); while (iterator.hasNext()) { URI uri = iterator.next(); T object = (T) MOCK_DB.get(uri); if (object != null) { list.add(object); } } return list.iterator(); } @Override public <T extends DataObject> List<T> queryObjectField(Class<T> clazz, String fieldName, Collection<URI> ids) { return null; } @Override public <T extends DataObject> Iterator<T> queryIterativeObjectField(Class<T> clazz, String fieldName, Collection<URI> ids) { return null; } @Override public <T extends DataObject> void aggregateObjectField(Class<T> clazz, Iterator<URI> ids, DbAggregatorItf aggregator) { } @Override public <T extends DataObject> List<URI> queryByType(Class<T> clazz, boolean activeOnly) { return null; } @Override public <T extends DataObject> List<URI> queryByType(Class<T> clazz, boolean activeOnly, URI startId, int count) { return null; } @Override public <T extends DataObject> void queryInactiveObjects(Class<T> clazz, long timeBefore, QueryResultList<URI> result) { } @Override public List<URI> queryByConstraint(Constraint constraint) { return null; } @Override public <T> void queryByConstraint(Constraint constraint, QueryResultList<T> result) { List<T> list = new ArrayList<>(); Class<? extends DataObject> type = constraint.getDataObjectType(); List<Object> objects = constraint.toConstraintDescriptor().getArguments(); if (type.equals(StorageHADomain.class)) { // Looking for StorageHADomains that are associated with a particular StorageSystem URI for (DataObject dataObject : MOCK_DB.values()) { if (dataObject instanceof StorageHADomain) { T domain = (T) dataObject; URI uri = (URI) objects.iterator().next(); if (((StorageHADomain) domain).getStorageDeviceURI().equals(uri)) { list.add(result.createQueryHit(dataObject.getId())); } } } } if (type.equals(StoragePort.class)) { // Looking for StoragePorts associated with StorageHADomain for (DataObject dataObject : MOCK_DB.values()) { if (dataObject instanceof StoragePort) { T port = (T) dataObject; URI uri = (URI) objects.iterator().next(); if (((StoragePort) port).getStorageHADomain().equals(uri)) { list.add(result.createQueryHit(dataObject.getId())); } } } } result.setResult(list.iterator()); } @Override public <T> void queryByConstraint(Constraint constraint, QueryResultList<T> result, URI startId, int maxCount) { } @Override public Integer countObjects(Class<? extends DataObject> type, String columnField, URI uri) { return null; } @Override public <T extends DataObject> void createObject(T object) { } @Override public <T extends DataObject> void createObject(Collection<T> objects) { } @Override public <T extends DataObject> void createObject(T... object) { } @Override public <T extends DataObject> void persistObject(T object) { } @Override public <T extends DataObject> void persistObject(Collection<T> objects) { } @Override public <T extends DataObject> void persistObject(T... object) { } @Override public <T extends DataObject> void updateAndReindexObject(T object) { } @Override public <T extends DataObject> void updateAndReindexObject(Collection<T> objects) { } @Override public <T extends DataObject> void updateAndReindexObject(T... object) { } @Override public <T extends DataObject> void updateObject(T object) { } @Override public <T extends DataObject> void updateObject(Collection<T> objects) { } @Override public <T extends DataObject> void updateObject(T... object) { } @Override public Operation ready(Class<? extends DataObject> clazz, URI id, String opId) { return null; } @Override public Operation ready(Class<? extends DataObject> clazz, URI id, String opId, String message) { return null; } @Override public Operation pending(Class<? extends DataObject> clazz, URI id, String opId, String message) { return null; } @Override public Operation pending(Class<? extends DataObject> clazz, URI id, String opId, String message, boolean resetStartTime) { return null; } @Override public Operation error(Class<? extends DataObject> clazz, URI id, String opId, ServiceCoded serviceCoded) { return null; } @Override public void setStatus(Class<? extends DataObject> clazz, URI id, String opId, String status) { } @Override public void setStatus(Class<? extends DataObject> clazz, URI id, String opId, String status, String message) { } @Override public Operation createTaskOpStatus(Class<? extends DataObject> clazz, URI id, String opId, Operation newOperation) { return null; } @Override public Operation createTaskOpStatus(Class<? extends DataObject> clazz, URI id, String opId, ResourceOperationTypeEnum type) { return null; } @Override public Operation createTaskOpStatus(Class<? extends DataObject> clazz, URI id, String opId, ResourceOperationTypeEnum type, String associatedResources) { return null; } @Override public Operation updateTaskOpStatus(Class<? extends DataObject> clazz, URI id, String opId, Operation updateOperation) { return null; } @Override public void markForDeletion(DataObject object) { } @Override public void markForDeletion(Collection<? extends DataObject> objects) { } @Override public <T extends DataObject> void markForDeletion(T... object) { } @Override public void removeObject(DataObject... object) { } @Override public <T extends TimeSeriesSerializer.DataPoint> String insertTimeSeries(Class<? extends TimeSeries> tsType, T... data) { return null; } @Override public <T extends TimeSeriesSerializer.DataPoint> String insertTimeSeries(Class<? extends TimeSeries> tsType, DateTime time, T data) { return null; } @Override public <T extends TimeSeriesSerializer.DataPoint> void queryTimeSeries(Class<? extends TimeSeries> tsType, DateTime timeBucket, TimeSeriesQueryResult<T> callback, ExecutorService workerThreads) { } @Override public <T extends TimeSeriesSerializer.DataPoint> void queryTimeSeries(Class<? extends TimeSeries> tsType, DateTime timeBucket, TimeSeriesMetadata.TimeBucket bucket, TimeSeriesQueryResult<T> callback, ExecutorService workerThreads) { } @Override public TimeSeriesMetadata queryTimeSeriesMetadata(Class<? extends TimeSeries> tsType) { return null; } @Override public void start() { } @Override public void stop() { } @Override public <T extends DataObject> Collection<T> queryObjectFields(Class<T> clazz, Collection<String> fieldNames, Collection<URI> ids) { return null; } @Override public <T extends DataObject> Iterator<T> queryIterativeObjectFields(Class<T> clazz, Collection<String> fieldNames, Collection<URI> ids) { return null; } @Override public String getSchemaVersion() { return null; } @Override public String getLocalShortVdcId() { return null; } @Override public URI getVdcUrn(String shortVdcId) { return null; } @Override public void invalidateVdcUrnCache() { } @Override public boolean checkGeoCompatible(String expectVersion) { return false; } @Override public boolean hasUsefulData() { return false; } @Override public Operation suspended_no_error(Class<? extends DataObject> clazz, URI id, String opId, String message) throws DatabaseException { // TODO Auto-generated method stub return null; } @Override public Operation suspended_no_error(Class<? extends DataObject> clazz, URI id, String opId) throws DatabaseException { // TODO Auto-generated method stub return null; } @Override public Operation suspended_error(Class<? extends DataObject> clazz, URI id, String opId, ServiceCoded serviceCoded) throws DatabaseException { // TODO Auto-generated method stub return null; } } static private class MockCustomConfigHandler extends CustomConfigHandler { private Map<String, String> MOCK_CONFIG_DB = new HashMap<>(); public MockCustomConfigHandler() { MOCK_CONFIG_DB.put(CustomConfigConstants.PORT_ALLOCATION_DAYS_TO_AVERAGE_UTILIZATION, "1"); MOCK_CONFIG_DB.put(CustomConfigConstants.PORT_ALLOCATION_EMA_FACTOR, "0.6"); } @Override public void setConfigResolvers(Map<String, CustomConfigResolver> configResolvers) { } @Override public void setConfigTypeProvider(CustomConfigTypeProvider configTypeProvider) { } @Override public String getCustomConfigValue(String configName, StringMap scope) throws CustomConfigControllerException { return ""; } @Override public String getComputedCustomConfigValue(String name, StringMap scope, DataSource dataSource) throws CustomConfigControllerException { return ""; } @Override public String getComputedCustomConfigValue(String name, String scope, DataSource sources) throws CustomConfigControllerException { return MOCK_CONFIG_DB.get(name); } @Override public String resolve(String name, String scope, DataSource dataSource) throws CustomConfigControllerException { return ""; } @Override public void validate(String name, StringMap scope, String value, boolean isCheckDuplicate) { } @Override public String getCustomConfigPreviewValue(String name, String value, StringMap scope, Map<String, String> variables) { return ""; } } }