/* * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.tsdr.osc; import java.io.File; import java.io.PrintStream; import java.math.BigDecimal; import java.math.BigInteger; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import org.opendaylight.controller.md.sal.binding.api.DataBroker; import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction; import org.opendaylight.controller.md.sal.binding.api.WriteTransaction; import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType; import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException; import org.opendaylight.controller.sal.binding.api.RpcProviderRegistry; import org.opendaylight.tsdr.osc.handlers.FlowCapableNodeConnectorQueueStatisticsDataHandler; import org.opendaylight.tsdr.osc.handlers.FlowStatisticsDataHandler; import org.opendaylight.tsdr.osc.handlers.NodeConnectorStatisticsChangeHandler; import org.opendaylight.tsdr.osc.handlers.NodeGroupStatisticsChangeHandler; import org.opendaylight.tsdr.osc.handlers.NodeMeterStatisticsChangeHandler; import org.opendaylight.tsdr.osc.handlers.NodeTableStatisticsChangeHandler; import org.opendaylight.yang.gen.v1.opendaylight.tsdr.rev150219.DataCategory; import org.opendaylight.yang.gen.v1.opendaylight.tsdr.rev150219.tsdrrecord.RecordKeys; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.Counter64; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNode; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.FlowCapableNodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.meters.Meter; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.Table; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.inventory.rev130819.tables.table.Flow; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.statistics.rev130819.FlowStatisticsData; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.table.statistics.rev131215.FlowTableStatisticsData; import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.port.rev130925.queues.Queue; import org.opendaylight.yang.gen.v1.urn.opendaylight.group.statistics.rev131111.NodeGroupStatistics; import org.opendaylight.yang.gen.v1.urn.opendaylight.group.types.rev131018.groups.Group; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.Nodes; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.node.NodeConnector; import org.opendaylight.yang.gen.v1.urn.opendaylight.inventory.rev130819.nodes.Node; import org.opendaylight.yang.gen.v1.urn.opendaylight.meter.statistics.rev131111.NodeMeterStatistics; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.InsertTSDRMetricRecordInput; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.InsertTSDRMetricRecordInputBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.TsdrCollectorSpiService; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.inserttsdrmetricrecord.input.TSDRMetricRecord; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.collector.spi.rev150915.inserttsdrmetricrecord.input.TSDRMetricRecordBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.openflow.statistics.collector.rev150820.SetPollingIntervalInput; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.openflow.statistics.collector.rev150820.TSDROSCConfig; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.openflow.statistics.collector.rev150820.TSDROSCConfigBuilder; import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.controller.config.tsdr.openflow.statistics.collector.rev150820.TsdrOpenflowStatisticsCollectorService; import org.opendaylight.yang.gen.v1.urn.opendaylight.port.statistics.rev131214.FlowCapableNodeConnectorStatisticsData; import org.opendaylight.yang.gen.v1.urn.opendaylight.queue.statistics.rev131216.FlowCapableNodeConnectorQueueStatisticsData; import org.opendaylight.yangtools.yang.binding.DataObject; import org.opendaylight.yangtools.yang.binding.InstanceIdentifier; import org.opendaylight.yangtools.yang.common.RpcResult; import org.opendaylight.yangtools.yang.common.RpcResultBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Optional; import com.google.common.util.concurrent.CheckedFuture; /** * @author Sharon Aicler(saichler@gmail.com) **/ /* * The TSDRDOMCollector is the place to collect metric data that exist in the * Inventory model and its augmentations It registers on specific locations in * the data broker and every 30 seconds persists the data to the TSDR data * storage */ public class TSDRDOMCollector implements TsdrOpenflowStatisticsCollectorService { // A reference to the data broker private DataBroker dataBroker = null; // A map representing the instance identifier of the metric collection to // the place in the cached builder collection array private Map<InstanceIdentifier<?>, ContainerIndex> id2Index = new ConcurrentHashMap<InstanceIdentifier<?>, ContainerIndex>(); // An array of BuilderContainer, a builder container is a collection of // metric builders that serves as a cache // so we won't need to instantiate and set all the static meta data of the // metric when ever we want to store // It is an array to avoid iteration problems and synchronization issues // when working with List/Set // As we don't really care about synchronization when reading the array, it // will be much faster than using // some object that we need to synchronize. private TSDRMetricRecordBuilderContainer[] containers = new TSDRMetricRecordBuilderContainer[0]; // Is the collector running, an indication to stop the thresds if it is // closed private boolean running = true; // The reference to the the RPC registry to store the data private RpcProviderRegistry rpcRegistry = null; // Logger reference private static final Logger logger = LoggerFactory .getLogger(TSDRDOMCollector.class); // for debugging, specify if the logs should go to external file or the // karaf log private static boolean logToExternalFile = false; // collectors private Map<Class<? extends DataObject>, TSDRBaseDataHandler> handlers = new ConcurrentHashMap<>(); private Map<InstanceIdentifier<Node>, Set<InstanceIdentifier<?>>> nodeID2SubIDs = new ConcurrentHashMap<>(); private TSDROSCConfig config = null; protected Object pollerSyncObject = new Object(); private TsdrCollectorSpiService collectorSPIService = null; private static final String COLLECTOR_CODE_NAME = TSDRDOMCollector.class.getSimpleName(); public TSDRDOMCollector(DataBroker _dataBroker, RpcProviderRegistry _rpcRegistry) { log("TSDR DOM Collector Started", INFO); this.dataBroker = _dataBroker; this.rpcRegistry = _rpcRegistry; // initialize handlers handlers.put(FlowCapableNodeConnectorQueueStatisticsData.class, new FlowCapableNodeConnectorQueueStatisticsDataHandler(this)); handlers.put(FlowStatisticsData.class, new FlowStatisticsDataHandler( this)); handlers.put(FlowCapableNodeConnectorStatisticsData.class, new NodeConnectorStatisticsChangeHandler(this)); handlers.put(NodeGroupStatistics.class, new NodeGroupStatisticsChangeHandler(this)); handlers.put(FlowTableStatisticsData.class, new NodeTableStatisticsChangeHandler(this)); handlers.put(NodeMeterStatistics.class, new NodeMeterStatisticsChangeHandler(this)); TSDROSCConfigBuilder b = new TSDROSCConfigBuilder(); b.setPollingInterval(15000l); this.config = b.build(); saveConfigData(); new TSDRInventoryNodesPoller(this); new StoringThread(); } public void loadConfigData() { // try to load the configuration data from the configuration data store ReadOnlyTransaction rot = null; try { InstanceIdentifier<TSDROSCConfig> cid = InstanceIdentifier .create(TSDROSCConfig.class); rot = this.dataBroker.newReadOnlyTransaction(); CheckedFuture<Optional<TSDROSCConfig>, ReadFailedException> read = rot .read(LogicalDatastoreType.CONFIGURATION, cid); if (read != null && read.get() != null) { if (read.get().isPresent()) { this.config = read.get().get(); } } } catch (Exception err) { log("Failed to read TSDR Data Collection configuration from data store, using defaults.", WARNING); } finally { if (rot != null) { rot.close(); } } } public void saveConfigData() { try { InstanceIdentifier<TSDROSCConfig> cid = InstanceIdentifier .create(TSDROSCConfig.class); WriteTransaction wrt = this.dataBroker.newWriteOnlyTransaction(); wrt.put(LogicalDatastoreType.CONFIGURATION, cid, this.config); wrt.submit(); } catch (Exception err) { log("Failed to write TSDR Data Collection configuration to data store.", WARNING); } } public TSDROSCConfig getConfigData() { return this.config; } public void shutdown() { this.running = false; synchronized(TSDRDOMCollector.this.pollerSyncObject){ TSDRDOMCollector.this.pollerSyncObject.notifyAll(); } synchronized(TSDRDOMCollector.this){ TSDRDOMCollector.this.notifyAll(); } } // Adds a new builder to the builder container, the first metric for the // InstanceIdenfier will create // the builder container. public void addBuilderToContainer(InstanceIdentifier<Node> nodeID, InstanceIdentifier<?> id, TSDRMetricRecordBuilder builder) { TSDRMetricRecordBuilderContainer container = null; // We want to synchronize here because when adding a new builder we want // to make sure there // is only one builder container per metric path as we might get on the // same InstanceIdentifier two notification in a very short time // and we don't want to instantiate two containers for the same metric // path. synchronized (id2Index) { ContainerIndex index = id2Index.get(id); if (index != null) { container = containers[index.index]; } else { container = new TSDRMetricRecordBuilderContainer(); TSDRMetricRecordBuilderContainer temp[] = new TSDRMetricRecordBuilderContainer[containers.length + 1]; System.arraycopy(containers, 0, temp, 0, containers.length); id2Index.put(id, new ContainerIndex(containers.length)); Set<InstanceIdentifier<?>> nodeIDs = nodeID2SubIDs.get(nodeID); if (nodeIDs == null) { nodeIDs = new HashSet<InstanceIdentifier<?>>(); nodeID2SubIDs.put(nodeID, nodeIDs); } nodeIDs.add(id); temp[containers.length] = container; containers = temp; } } // once we have the container, synchronization of the builders array // inside the container // is under the container responsibility container.addBuilder(builder); } public void removeBuilderContailer(InstanceIdentifier<?> id) { synchronized (id2Index) { ContainerIndex index = id2Index.remove(id); if (index != null) { TSDRMetricRecordBuilderContainer temp[] = new TSDRMetricRecordBuilderContainer[containers.length - 1]; if (index.index == 0) { System.arraycopy(containers, 1, temp, 0, temp.length); } else if (index.index == containers.length - 1) { System.arraycopy(containers, 0, temp, 0, temp.length); } else { System.arraycopy(containers, 0, temp, 0, index.index); System.arraycopy(containers, index.index + 1, temp, index.index, containers.length - (index.index + 1)); } for (ContainerIndex ndx : id2Index.values()) { if (ndx.index > index.index) { ndx.index--; } } containers = temp; } } } // Retrieve a BuilderContainer according to the InstanceIdentifier public TSDRMetricRecordBuilderContainer getTSDRMetricRecordBuilderContainer( InstanceIdentifier<?> id) { ContainerIndex index = this.id2Index.get(id); if (index != null) { return containers[index.index]; } return null; } // Create a new TSDRMetricRecordBuilder and adds it to its builder container // according to the instanceIdentifier public void createTSDRMetricRecordBuilder(InstanceIdentifier<Node> nodeID, InstanceIdentifier<?> id, List<RecordKeys> recKeys, String metricName, BigDecimal value, DataCategory category) { TSDRMetricRecordBuilder builder = new TSDRMetricRecordBuilder(); builder.setRecordKeys(recKeys); builder.setNodeID(getNodeIDFrom(recKeys)); builder.setMetricName(metricName); builder.setTSDRDataCategory(category); builder.setMetricValue(value); builder.setTimeStamp(System.currentTimeMillis()); addBuilderToContainer(nodeID, id, builder); } // Finds the handler for this statistics and apply it public void handle(InstanceIdentifier<Node> nodeID, InstanceIdentifier<?> id, DataObject dataObject, Class<? extends DataObject> cls) { if (dataObject == null) return; TSDRBaseDataHandler c = handlers.get(cls); if (c == null) { log("Error, can't find collector for " + cls.getSimpleName(), ERROR); return; } c.handleData(nodeID, id, dataObject); } // Extract the statistics from a node and updates the builders with the // updated data public void collectStatistics(Node node) { try { if (node != null) { InstanceIdentifier<Node> nodeID = InstanceIdentifier.create( Nodes.class).child(Node.class, node.getKey()); FlowCapableNode fcnode = node .getAugmentation(FlowCapableNode.class); if (fcnode != null) { List<Meter> meters = fcnode.getMeter(); if (meters != null) { for (Meter meter : meters) { NodeMeterStatistics nodeMeterStatistics = meter .getAugmentation(NodeMeterStatistics.class); if (nodeMeterStatistics != null) { InstanceIdentifier<NodeMeterStatistics> mIID = InstanceIdentifier .create(Nodes.class) .child(Node.class, node.getKey()) .augmentation(FlowCapableNode.class) .child(Meter.class, meter.getKey()) .augmentation(NodeMeterStatistics.class); handle(nodeID, mIID, nodeMeterStatistics, NodeMeterStatistics.class); } } } // Node Flow Statistics List<Table> tables = fcnode.getTable(); if (tables != null) { for (Table t : tables) { FlowTableStatisticsData data = t .getAugmentation(FlowTableStatisticsData.class); if (data != null) { InstanceIdentifier<FlowTableStatisticsData> tIID = InstanceIdentifier .create(Nodes.class) .child(Node.class, node.getKey()) .augmentation(FlowCapableNode.class) .child(Table.class, t.getKey()) .augmentation( FlowTableStatisticsData.class); handle(nodeID, tIID, data, FlowTableStatisticsData.class); } // Flow Statistics if (t.getFlow() != null) { for (Flow flow : t.getFlow()) { FlowStatisticsData flowStatisticsData = flow .getAugmentation(FlowStatisticsData.class); if (flowStatisticsData != null) { InstanceIdentifier<FlowStatisticsData> tIID = InstanceIdentifier .create(Nodes.class) .child(Node.class, node.getKey()) .augmentation( FlowCapableNode.class) .child(Table.class, t.getKey()) .child(Flow.class, flow.getKey()) .augmentation( FlowStatisticsData.class); handle(nodeID, tIID, flowStatisticsData, FlowStatisticsData.class); } } } } } // Node Group Statistics List<Group> groups = fcnode.getGroup(); if (groups != null) { for (Group g : groups) { NodeGroupStatistics ngs = g .getAugmentation(NodeGroupStatistics.class); InstanceIdentifier<NodeGroupStatistics> tIID = InstanceIdentifier .create(Nodes.class) .child(Node.class, node.getKey()) .augmentation(FlowCapableNode.class) .child(Group.class, g.getKey()) .augmentation(NodeGroupStatistics.class); handle(nodeID, tIID, ngs, NodeGroupStatistics.class); } } } // Node Connector Statistics List<NodeConnector> ports = node.getNodeConnector(); if (ports != null) { for (NodeConnector nc : ports) { FlowCapableNodeConnectorStatisticsData fnc = nc .getAugmentation(FlowCapableNodeConnectorStatisticsData.class); InstanceIdentifier<FlowCapableNodeConnectorStatisticsData> tIID = InstanceIdentifier .create(Nodes.class) .child(Node.class, node.getKey()) .child(NodeConnector.class, nc.getKey()) .augmentation( FlowCapableNodeConnectorStatisticsData.class); handle(nodeID, tIID, fnc, FlowCapableNodeConnectorStatisticsData.class); FlowCapableNodeConnector fcnc = nc .getAugmentation(FlowCapableNodeConnector.class); if (fcnc != null) { List<Queue> queues = fcnc.getQueue(); if (queues != null) { for (Queue q : queues) { InstanceIdentifier<FlowCapableNodeConnectorQueueStatisticsData> tIID2 = InstanceIdentifier .create(Nodes.class) .child(Node.class, node.getKey()) .child(NodeConnector.class, nc.getKey()) .augmentation( FlowCapableNodeConnector.class) .child(Queue.class, q.getKey()) .augmentation( FlowCapableNodeConnectorQueueStatisticsData.class); handle(nodeID, tIID2, q.getAugmentation(FlowCapableNodeConnectorQueueStatisticsData.class), FlowCapableNodeConnectorQueueStatisticsData.class); } } } } } } } catch (Exception err) { log("Failed to register on metric data due to the following exception:", ERROR); log(err); } } private String getNodeIDFrom(List<RecordKeys> recordKeys) { String result = null; for (RecordKeys key : recordKeys) { if (key.getKeyName().equalsIgnoreCase("Node")) { if (key.getKeyValue() != null) { return key.getKeyValue(); } } } return result; } // This class is the storing thread, every 30 seconds it will wake up and // iterate over the builder container array and create // metric data list out of the container builders, wrap it up as input for // the RPC and invoke the storage RPC method. private class StoringThread extends Thread { public StoringThread() { this.setName("TSDR Storing Thread"); this.setDaemon(true); this.start(); log("Storing Thread Started", INFO); } public void run() { while (running) { synchronized (TSDRDOMCollector.this) { try { /* * We wait for 2x the polling interval just for the case * where the polling thread is dead and there will be no * thread to wake this thread up if we do "wait()", e.g. * to avoid "stuck" thread. Disregarding the case where * storing will take more than the polling interval, we * have bigger issues in that case...:o) */ TSDRDOMCollector.this.wait(getConfigData() .getPollingInterval() * 2); } catch (InterruptedException err) { log("Storing Thread Interrupted.", ERROR); } } try { for (int i = 0; i < containers.length; i++) { try { InsertTSDRMetricRecordInputBuilder input = new InsertTSDRMetricRecordInputBuilder(); List<TSDRMetricRecord> list = new LinkedList<>(); TSDRMetricRecordBuilderContainer bc = containers[i]; for (TSDRMetricRecordBuilder builder : bc.getBuilders()) { list.add(builder.build()); } input.setTSDRMetricRecord(list); input.setCollectorCodeName("OpenFlowStatistics"); store(input.build()); // store.storeTSDRMetricRecord(input.build()); } catch (Exception err) { log("Fail to store data due to the following exception:", ERROR); log(err); } } } catch (Exception err) { log("Fail to iterate over builder containers due to the following error:", ERROR); log(err); } } } } // Invoke the storage rpc method private void store(InsertTSDRMetricRecordInput input) { if(this.collectorSPIService==null){ this.collectorSPIService = this.rpcRegistry .getRpcService(TsdrCollectorSpiService.class); } this.collectorSPIService.insertTSDRMetricRecord(input); log("Data Storage called", DEBUG); } public TsdrCollectorSpiService getTSDRService(){ if(this.collectorSPIService==null){ this.collectorSPIService = this.rpcRegistry .getRpcService(TsdrCollectorSpiService.class); } return this.collectorSPIService; } // For debugging, enable the ability to output to a different file to avoid // looking for TSDR logs in the main log. public static PrintStream out = null; public static final int INFO = 1; public static final int DEBUG = 2; public static final int ERROR = 3; public static final int WARNING = 4; public static synchronized void log(Exception e) { if (logToExternalFile) { try { if (out == null) { File f = new File("/tmp/tsdr.log"); out = new PrintStream(f); } e.printStackTrace(out); out.flush(); } catch (Exception err) { err.printStackTrace(); } } else { logger.error(e.getMessage(), e); } } public static synchronized void log(String str, int type) { if (logToExternalFile) { try { if (out == null) { File f = new File("/tmp/tsdr.log"); out = new PrintStream(f); } out.println(str); out.flush(); } catch (Exception err) { err.printStackTrace(); } } else { switch (type) { case INFO: logger.info(str); break; case DEBUG: logger.debug(str); break; case ERROR: logger.error(str); break; case WARNING: logger.warn(str); break; default: logger.debug(str); } } } public void removeAllNodeBuilders(InstanceIdentifier<Node> nodeID) { synchronized (id2Index) { Set<InstanceIdentifier<?>> subIDs = nodeID2SubIDs.get(nodeID); if (subIDs == null) return; for (InstanceIdentifier<?> subID : subIDs) { removeBuilderContailer(subID); } subIDs.clear(); log("Removed all data for node-" + nodeID, INFO); } } public DataBroker getDataBroker() { return this.dataBroker; } public boolean isRunning() { return this.running; } private class ContainerIndex { public ContainerIndex(Integer _index) { this.index = _index; } private Integer index = -1; } @Override public Future<RpcResult<Void>> setPollingInterval( SetPollingIntervalInput input) { TSDROSCConfigBuilder builder = new TSDROSCConfigBuilder(); builder.setPollingInterval(input.getInterval()); this.config = builder.build(); saveConfigData(); RpcResultBuilder<Void> rpc = RpcResultBuilder.success(); return rpc.buildFuture(); } }