/*
* Copyright (c) 2016 Red Hat, 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.controller.md.sal.trace.dom.impl;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker;
import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
import org.opendaylight.controller.md.sal.common.api.data.TransactionChainListener;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
import org.opendaylight.controller.md.sal.dom.api.DOMDataBrokerExtension;
import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeListener;
import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeChangeService;
import org.opendaylight.controller.md.sal.dom.api.DOMDataTreeIdentifier;
import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
import org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker;
import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.Config;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("checkstyle:JavadocStyle")
//...because otherwise it whines about the elements in the @code block even though it's completely valid Javadoc
/**
* TracingBroker logs "write" operations and listener registrations to the md-sal. It logs the instance identifier path,
* the objects themselves, as well as the stack trace of the call invoking the registration or write operation.
* It works by operating as a "bump on the stack" between the application and actual DataBroker, intercepting write
* and registration calls and writing to the log.
*
Wiring:
* TracingBroker is designed to be easy to use. In fact, for bundles using Blueprint to inject their DataBroker
* TracingBroker can be used without modifying your code at all in two simple steps:
*
* -
* Simply add the dependency "mdsaltrace-features" to
* your karaf pom:
*
* {@code
*
* org.opendaylight.controller
* mdsal-trace-features
* features
* xml
* runtime
* 0.1.5-SNAPSHOT
*
* }
*
*
* -
* Then just load the odl-mdsal-trace feature before your feature and you're done.
*
*
* This works because the mdsaltrace-impl bundle registers its service implementing DOMDataBroker with a higher
* rank than sal-binding-broker. As such, any OSGi service lookup for DataBroker will receive the TracingBroker.
*
* Avoiding log bloat:
* TracingBroker can be configured to only print registrations or write ops pertaining to certain subtrees of the
* md-sal. This can be done in the code via the methods of this class or via a config file. TracingBroker uses a more
* convenient but non-standard representation of the instance identifiers. Each instance identifier segment's
* class.getSimpleName() is used separated by a '/'.
*
* Known issues
*
* -
* Filtering by paths. For some registrations the codec that converts back from the DOM to binding paths is
* busted. As such, an aproximated path is used in the output. For now it is recommended not to use
* watchRegistrations and allow all registrations to be logged.
*
*
*
*/
public class TracingBroker implements TracingDOMDataBroker {
static final Logger LOG = LoggerFactory.getLogger(TracingBroker.class);
private static final int STACK_TRACE_FIRST_RELEVANT_FRAME = 2;
private final BindingNormalizedNodeSerializer codec;
private final DOMDataBroker delegate;
private List registrationWatches = new ArrayList<>();
private List writeWatches = new ArrayList<>();
private class Watch {
final String iidString;
final LogicalDatastoreType store;
Watch(String iidString, LogicalDatastoreType storeOrNull) {
this.store = storeOrNull;
this.iidString = iidString;
}
private String toIidCompString(YangInstanceIdentifier iid) {
StringBuilder builder = new StringBuilder();
toPathString(iid, builder);
builder.append('/');
return builder.toString();
}
private boolean isParent(String parent, String child) {
return child.startsWith(parent);
}
public boolean subtreesOverlap(YangInstanceIdentifier iid, LogicalDatastoreType store,
AsyncDataBroker.DataChangeScope scope) {
if (this.store != null && !this.store.equals(store)) {
return false;
}
String otherIidString = toIidCompString(iid);
switch (scope) {
case BASE:
return isParent(iidString, otherIidString);
case ONE: //for now just treat like SUBTREE, even though it's not
case SUBTREE:
return isParent(iidString, otherIidString) || isParent(otherIidString, iidString);
default:
return false;
}
}
public boolean eventIsOfInterest(YangInstanceIdentifier iid, LogicalDatastoreType store) {
if (this.store != null && !this.store.equals(store)) {
return false;
}
return isParent(iidString, toPathString(iid));
}
}
public TracingBroker(DOMDataBroker delegate, Config config, BindingNormalizedNodeSerializer codec) {
this.delegate = Objects.requireNonNull(delegate);
this.codec = Objects.requireNonNull(codec);
configure(config);
}
private void configure(Config config) {
registrationWatches.clear();
List paths = config.getRegistrationWatches();
if (paths != null) {
for (String path : paths) {
watchRegistrations(path, null);
}
}
writeWatches.clear();
paths = config.getWriteWatches();
if (paths != null) {
for (String path : paths) {
watchWrites(path, null);
}
}
}
/**
* Log registrations to this subtree of the md-sal.
* @param iidString the iid path of the root of the subtree
* @param store Which LogicalDataStore? or null for both
*/
public void watchRegistrations(String iidString, LogicalDatastoreType store) {
registrationWatches.add(new Watch(iidString, store));
}
/**
* Log writes to this subtree of the md-sal.
* @param iidString the iid path of the root of the subtree
* @param store Which LogicalDataStore? or null for both
*/
public void watchWrites(String iidString, LogicalDatastoreType store) {
Watch watch = new Watch(iidString, store);
writeWatches.add(watch);
}
private boolean isRegistrationWatched(YangInstanceIdentifier iid,
LogicalDatastoreType store, DataChangeScope scope) {
if (registrationWatches.isEmpty()) {
return true;
}
for (Watch regInterest : registrationWatches) {
if (regInterest.subtreesOverlap(iid, store, scope)) {
return true;
}
}
return false;
}
boolean isWriteWatched(YangInstanceIdentifier iid, LogicalDatastoreType store) {
if (writeWatches.isEmpty()) {
return true;
}
for (Watch watch : writeWatches) {
if (watch.eventIsOfInterest(iid, store)) {
return true;
}
}
return false;
}
static void toPathString(InstanceIdentifier extends DataObject> iid, StringBuilder builder) {
for (InstanceIdentifier.PathArgument pathArg : iid.getPathArguments()) {
builder.append('/').append(pathArg.getType().getSimpleName());
}
}
String toPathString(YangInstanceIdentifier yiid) {
StringBuilder sb = new StringBuilder();
toPathString(yiid, sb);
return sb.toString();
}
void toPathString(YangInstanceIdentifier yiid, StringBuilder sb) {
InstanceIdentifier iid = codec.fromYangInstanceIdentifier(yiid);
if (null == iid) {
reconstructIidPathString(yiid, sb);
} else {
toPathString(iid, sb);
}
}
private void reconstructIidPathString(YangInstanceIdentifier yiid, StringBuilder sb) {
sb.append("RECONSTRUCTED: ");
for (YangInstanceIdentifier.PathArgument pathArg : yiid.getPathArguments()) {
if (pathArg instanceof YangInstanceIdentifier.AugmentationIdentifier) {
sb.append('/').append("AUGMENTATION");
continue;
}
sb.append('/').append(pathArg.getNodeType().getLocalName());
}
sb.append(" ->->-> [[[ ").append(yiid.toString()).append(" ]]]");
}
String getStackSummary() {
StackTraceElement[] stack = Thread.currentThread().getStackTrace();
StringBuilder sb = new StringBuilder();
for (int i = STACK_TRACE_FIRST_RELEVANT_FRAME; i < stack.length; i++) {
StackTraceElement frame = stack[i];
sb.append("\n\t(TracingBroker)\t").append(frame.getClassName()).append('.').append(frame.getMethodName());
}
return sb.toString();
}
@Override
public DOMDataReadWriteTransaction newReadWriteTransaction() {
return new TracingReadWriteTransaction(delegate.newReadWriteTransaction(), this);
}
@Override
public DOMDataWriteTransaction newWriteOnlyTransaction() {
return new TracingWriteTransaction(delegate.newWriteOnlyTransaction(), this);
}
@Override
public ListenerRegistration registerDataChangeListener(
LogicalDatastoreType store, YangInstanceIdentifier yiid,
DOMDataChangeListener listener, DataChangeScope scope) {
if (isRegistrationWatched(yiid, store, scope)) {
LOG.warn("Registration (registerDataChangeListener) for {} from {}",
toPathString(yiid), getStackSummary());
}
return delegate.registerDataChangeListener(store, yiid, listener, scope);
}
@Override
public DOMTransactionChain createTransactionChain(TransactionChainListener transactionChainListener) {
return delegate.createTransactionChain(transactionChainListener);
}
@Override
public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
return delegate.newReadOnlyTransaction();
}
@Nonnull
@Override
public Map, DOMDataBrokerExtension> getSupportedExtensions() {
Map, DOMDataBrokerExtension> res = delegate.getSupportedExtensions();
DOMDataTreeChangeService treeChangeSvc = (DOMDataTreeChangeService) res.get(DOMDataTreeChangeService.class);
if (treeChangeSvc == null) {
return res;
}
res = new HashMap<>(res);
res.put(DOMDataTreeChangeService.class, new DOMDataTreeChangeService() {
@Nonnull
@Override
public ListenerRegistration registerDataTreeChangeListener(
@Nonnull DOMDataTreeIdentifier domDataTreeIdentifier, @Nonnull L listener) {
if (isRegistrationWatched(domDataTreeIdentifier.getRootIdentifier(),
domDataTreeIdentifier.getDatastoreType(), DataChangeScope.SUBTREE)) {
LOG.warn("Registration (registerDataTreeChangeListener) for {} from {}",
toPathString(domDataTreeIdentifier.getRootIdentifier()), getStackSummary());
}
return treeChangeSvc.registerDataTreeChangeListener(domDataTreeIdentifier, listener);
}
});
return res;
}
}