Merge branch 'mdsal-trace' from controller
authorAnil Belur <abelur@linuxfoundation.org>
Wed, 29 Aug 2018 12:33:37 +0000 (18:03 +0530)
committerAnil Belur <abelur@linuxfoundation.org>
Wed, 29 Aug 2018 12:33:37 +0000 (18:03 +0530)
Moved code from controller.git:opendaylight/md-sal/mdsal-trace
to mdsal.git:opendaylight/md-sal/mdsal-trace

Issue-Id: RELENG-1170
Signed-off-by: Anil Belur <abelur@linuxfoundation.org>
Change-Id: I653644cd465e069331ca1cffdfa3502515a08771

30 files changed:
opendaylight/md-sal/mdsal-trace/api/pom.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/api/src/main/java/org/opendaylight/controller/md/sal/trace/api/TracingDOMDataBroker.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/api/src/main/resources/initial/mdsaltrace_config.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/api/src/main/yang/mdsaltrace.yang [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/binding-impl/pom.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/binding-impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/cli/pom.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/cli/src/main/java/org/opendaylight/controller/md/sal/trace/cli/PrintOpenTransactionsCommand.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/deploy-site.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/pom.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/AbstractCloseTracked.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTracked.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistry.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistryReportEntry.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedTrait.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/package-info.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/AbstractTracingWriteTransaction.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingBroker.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingReadOnlyTransaction.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingReadWriteTransaction.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingTransactionChain.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingWriteTransaction.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/tests/CloseTrackedRegistryTest.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/tests/TracingBrokerTest.java [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/features/features-mdsal-trace/pom.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/features/odl-mdsal-trace/pom.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/features/odl-mdsal-trace/src/main/feature/feature.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/features/pom.xml [new file with mode: 0644]
opendaylight/md-sal/mdsal-trace/pom.xml [new file with mode: 0644]

diff --git a/opendaylight/md-sal/mdsal-trace/api/pom.xml b/opendaylight/md-sal/mdsal-trace/api/pom.xml
new file mode 100644 (file)
index 0000000..0820ac1
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>mdsal-parent</artifactId>
+    <version>1.9.0-SNAPSHOT</version>
+    <relativePath>../../parent</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>mdsal-trace-api</artifactId>
+  <version>1.9.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-core-api</artifactId>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>build-helper-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>attach-artifacts</id>
+            <goals>
+              <goal>attach-artifact</goal>
+            </goals>
+            <phase>package</phase>
+            <configuration>
+              <artifacts>
+                <artifact>
+                  <file>${project.build.directory}/classes/initial/mdsaltrace_config.xml</file>
+                  <type>xml</type>
+                  <classifier>config</classifier>
+                </artifact>
+              </artifacts>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/api/src/main/java/org/opendaylight/controller/md/sal/trace/api/TracingDOMDataBroker.java b/opendaylight/md-sal/mdsal-trace/api/src/main/java/org/opendaylight/controller/md/sal/trace/api/TracingDOMDataBroker.java
new file mode 100644 (file)
index 0000000..88e2261
--- /dev/null
@@ -0,0 +1,26 @@
+/*
+ * 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.api;
+
+import java.io.PrintStream;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+
+/**
+ * Interface so that the tracing broker service can be more explicitly imported.
+ */
+public interface TracingDOMDataBroker extends DOMDataBroker {
+
+    /**
+     * Prints a human-readable "report" of all opened but not closed transactions,
+     * including transactions chains and transactions opened by them, onto the printStream.
+     * @param minOpenTransactions minimum open number of transactions (leaks with fewer are not printed)
+     * @return true if there were any open transactions, false if none
+     */
+    boolean printOpenTransactions(PrintStream printStream, int minOpenTransactions);
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/api/src/main/resources/initial/mdsaltrace_config.xml b/opendaylight/md-sal/mdsal-trace/api/src/main/resources/initial/mdsaltrace_config.xml
new file mode 100644 (file)
index 0000000..27c8bdd
--- /dev/null
@@ -0,0 +1,21 @@
+<config xmlns="urn:opendaylight:params:xml:ns:yang:mdsaltrace">
+    <!--  Both registration-watches as well as write-watches will
+          log EVERYTHING by default, if we do not constrain any paths;
+          therefore we set a fake one to get nothing out-of-the-box;
+          please remove this first fake one when you configure this
+          to watch what you are really interested in instead: -->
+    <registration-watches>/this/will/never/exist</registration-watches>
+    <!-- <registration-watches>/neutron-router-dpns/router-dpn-list</registration-watches> -->
+    <!-- <registration-watches>/tunnels_state/state-tunnel-list</registration-watches> -->
+
+    <write-watches>/this/will/never/exist</write-watches>
+    <!-- <write-watches> /NetworkTopology/Topology</write-watches> -->
+
+    <!-- Enable or disable transaction context debug. This will preserve the call site trace for
+         transactions, so that the original caller of un-close'd() transaction can be identified.
+         NB: This is a different property from the equally named one in etc/org.opendaylight.controller.cluster.datastore.cfg;
+         that one does something somewhat similar, but serves to include the stack trace on failed transaction submit,
+         whereas this one is specific to odl-mdsal-trace's trace:transaction leak troubleshooting command.
+         [This documentation has been copy/pasted from mdsaltrace.yang, and should be kept in line.] -->
+    <transaction-debug-context-enabled>true</transaction-debug-context-enabled>
+</config>
diff --git a/opendaylight/md-sal/mdsal-trace/api/src/main/yang/mdsaltrace.yang b/opendaylight/md-sal/mdsal-trace/api/src/main/yang/mdsaltrace.yang
new file mode 100644 (file)
index 0000000..60e0d91
--- /dev/null
@@ -0,0 +1,38 @@
+module mdsaltrace {
+    yang-version 1;
+    namespace "urn:opendaylight:params:xml:ns:yang:mdsaltrace";
+    prefix "mdsaltrace";
+
+    organization
+        "Red Hat, Inc.";
+
+    description
+        "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";
+
+    revision "2016-09-08" {
+        description "Initial revision of mdsaltrace model";
+    }
+
+    container config {
+        leaf-list registration-watches {
+            type string;
+        }
+        leaf-list write-watches {
+            type string;
+        }
+        leaf transaction-debug-context-enabled {
+            default false;
+            type boolean;
+            description "Enable or disable transaction context debug. This will preserve the call site trace for
+                         transactions, so that the original caller of un-close'd() transaction can be identified.
+                         NB: This is a different property from the equally named one in etc/org.opendaylight.controller.cluster.datastore.cfg;
+                         that one does something somewhat similar, but serves to include the stack trace on failed transaction submit,
+                         whereas this one is specific to odl-mdsal-trace's trace:transaction leak troubleshooting command.";
+            // This ^^^ description is also copy/pasted in mdsaltrace_config.xml, and should be kept in line.]
+        }
+    }
+}
diff --git a/opendaylight/md-sal/mdsal-trace/binding-impl/pom.xml b/opendaylight/md-sal/mdsal-trace/binding-impl/pom.xml
new file mode 100644 (file)
index 0000000..1cfd7ea
--- /dev/null
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>mdsal-parent</artifactId>
+    <version>1.9.0-SNAPSHOT</version>
+    <relativePath>../../parent</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>mdsal-trace-binding-impl</artifactId>
+  <version>1.9.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>mdsal-trace-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>mdsal-trace-dom-impl</artifactId>
+    </dependency>
+
+    <!-- Testing Dependencies -->
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>maven-bundle-plugin</artifactId>
+            <extensions>true</extensions>
+            <configuration>
+                <instructions>
+                    <Import-Package>
+                      org.opendaylight.controller.md.sal.trace.api,
+                      org.opendaylight.controller.md.sal.dom.api,
+                      org.opendaylight.controller.md.sal.binding.api,
+                      org.opendaylight.controller.md.sal.binding.spi,
+                      *
+                    </Import-Package>
+                </instructions>
+            </configuration>
+        </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/binding-impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml b/opendaylight/md-sal/mdsal-trace/binding-impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml
new file mode 100644 (file)
index 0000000..6f36213
--- /dev/null
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+  xmlns:ext="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
+  xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+  odl:use-default-for-reference-types="true">
+
+  <reference id="tracingDefaultDOMBroker"
+      interface="org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker"
+      odl:type="default" />
+
+  <reference id="adapterFactory" interface="org.opendaylight.controller.md.sal.binding.spi.AdapterFactory"/>
+
+  <bean id="tracingBindingDataBroker" factory-ref="adapterFactory" factory-method="createDataBroker">
+    <argument ref="tracingDefaultDOMBroker"/>
+  </bean>
+
+  <service id="tracingBindingDataBrokerSvc" ref="tracingBindingDataBroker"
+            interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"
+            odl:type="default" ranking="10"/>
+
+  <reference id="tracingPingPongDOMDataBroker"
+      interface="org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker"
+      odl:type="pingpong"/>
+
+  <bean id="bindingTracingPingPongDataBroker" factory-ref="adapterFactory" factory-method="createDataBroker">
+    <argument ref="tracingPingPongDOMDataBroker"/>
+  </bean>
+
+  <service ref="bindingTracingPingPongDataBroker" interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"
+                                                                                    odl:type="pingpong" ranking="10"/>
+</blueprint>
diff --git a/opendaylight/md-sal/mdsal-trace/cli/pom.xml b/opendaylight/md-sal/mdsal-trace/cli/pom.xml
new file mode 100644 (file)
index 0000000..e93e09a
--- /dev/null
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2017 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>mdsal-parent</artifactId>
+    <version>1.9.0-SNAPSHOT</version>
+    <relativePath>../../parent</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>mdsal-trace-cli</artifactId>
+  <version>1.9.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>mdsal-trace-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.karaf.shell</groupId>
+      <artifactId>org.apache.karaf.shell.core</artifactId>
+      <exclusions>
+        <exclusion>
+          <groupId>*</groupId>
+          <artifactId>*</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Karaf-Commands>*</Karaf-Commands>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/cli/src/main/java/org/opendaylight/controller/md/sal/trace/cli/PrintOpenTransactionsCommand.java b/opendaylight/md-sal/mdsal-trace/cli/src/main/java/org/opendaylight/controller/md/sal/trace/cli/PrintOpenTransactionsCommand.java
new file mode 100644 (file)
index 0000000..3ebd506
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017 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.cli;
+
+import java.util.List;
+import org.apache.karaf.shell.api.action.Action;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker;
+
+/**
+ * Karaf CLI command to dump all open transactions.
+ *
+ * @author Michael Vorburger.ch
+ */
+@Service
+@Command(scope = "trace", name = "transactions",
+    description = "Show all (still) open transactions; including stack trace of creator, "
+    + "if transaction-debug-context-enabled is true in mdsaltrace_config.xml")
+public class PrintOpenTransactionsCommand implements Action {
+
+    @Argument(index = 0, name = "minOpenTransactions", required = false, multiValued = false,
+            description = "Minimum open number of transactions (leaks with fewer are suppressed)")
+    Integer minOpenTransactions = 1;
+
+    @Reference
+    private List<TracingDOMDataBroker> tracingDOMDataBrokers;
+
+    // NB: Do NOT have a non-default constructor for injection of @Reference
+    // Karaf needs a default constructor to create the command - and it works as is.
+
+    @Override
+    @SuppressWarnings("checkstyle:RegexpSingleLineJava")
+    public Object execute() {
+        boolean hasFound = false;
+        for (TracingDOMDataBroker tracingDOMDataBroker : tracingDOMDataBrokers) {
+            hasFound |= tracingDOMDataBroker.printOpenTransactions(System.out, minOpenTransactions);
+        }
+        if (hasFound) {
+            System.out.println(
+                    "Actually did find real leaks with more than " + minOpenTransactions + " open transactions");
+        } else {
+            System.out.println(
+                    "Did not find any real leaks with more than " + minOpenTransactions + " open transactions");
+        }
+        return hasFound;
+    }
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/deploy-site.xml b/opendaylight/md-sal/mdsal-trace/deploy-site.xml
new file mode 100644 (file)
index 0000000..6a72564
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=2 tabstop=2: -->
+<!--
+    Copyright (c) 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.opendaylight.mdsal</groupId>
+  <artifactId>deploy-site</artifactId>
+  <version>0.1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <properties>
+    <stream>latest</stream>
+    <nexus.site.url>dav:https://nexus.opendaylight.org/content/sites/site/${project.groupId}/${stream}/</nexus.site.url>
+  </properties>
+
+  <build>
+    <extensions>
+      <extension>
+        <groupId>org.apache.maven.wagon</groupId>
+         <artifactId>wagon-webdav-jackrabbit</artifactId>
+         <version>2.9</version>
+      </extension>
+    </extensions>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>3.4</version>
+        <configuration>
+          <inputDirectory>${project.build.directory}/staged-site</inputDirectory>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <distributionManagement>
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}</url>
+    </site>
+  </distributionManagement>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/pom.xml b/opendaylight/md-sal/mdsal-trace/dom-impl/pom.xml
new file mode 100644 (file)
index 0000000..613b15f
--- /dev/null
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>mdsal-parent</artifactId>
+    <version>1.9.0-SNAPSHOT</version>
+    <relativePath>../../parent</relativePath>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>mdsal-trace-dom-impl</artifactId>
+  <version>1.9.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+
+  <dependencies>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>mdsal-trace-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>sal-binding-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>sal-core-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>sal-broker-impl</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.mdsal</groupId>
+      <artifactId>mdsal-binding-dom-codec</artifactId>
+    </dependency>
+
+    <!-- Testing Dependencies -->
+    <dependency>
+      <groupId>org.mockito</groupId>
+      <artifactId>mockito-core</artifactId>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+    <build>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.felix</groupId>
+            <artifactId>maven-bundle-plugin</artifactId>
+            <extensions>true</extensions>
+            <configuration>
+                <instructions>
+                    <Import-Package>
+                      org.opendaylight.controller.md.sal.dom.broker.impl,
+                      *
+                    </Import-Package>
+                    <Export-Package>org.opendaylight.controller.md.sal.trace.dom.impl</Export-Package>
+                </instructions>
+            </configuration>
+        </plugin>
+    </plugins>
+    </build>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/AbstractCloseTracked.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/AbstractCloseTracked.java
new file mode 100644 (file)
index 0000000..785e2ef
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2017 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.closetracker.impl;
+
+import javax.annotation.Nullable;
+
+/**
+ * Convenience abstract base class for {@link CloseTracked} implementors.
+ *
+ * @author Michael Vorburger.ch
+ */
+public abstract class AbstractCloseTracked<T extends AbstractCloseTracked<T>> implements CloseTracked<T> {
+
+    private final CloseTrackedTrait<T> closeTracker;
+
+    protected AbstractCloseTracked(CloseTrackedRegistry<T> transactionChainRegistry) {
+        this.closeTracker = new CloseTrackedTrait<>(transactionChainRegistry, this);
+    }
+
+    protected void removeFromTrackedRegistry() {
+        closeTracker.removeFromTrackedRegistry();
+    }
+
+    @Override
+    public @Nullable StackTraceElement[] getAllocationContextStackTrace() {
+        return closeTracker.getAllocationContextStackTrace();
+    }
+
+    @Override
+    public final CloseTracked<T> getRealCloseTracked() {
+        return this;
+    }
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTracked.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTracked.java
new file mode 100644 (file)
index 0000000..ef42c40
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (c) 2017 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.closetracker.impl;
+
+import javax.annotation.Nullable;
+
+/**
+ * Object which can track where something has been created, and if it has been correctly "closed".
+ *
+ * <p>Includes preserving the context of the call stack which created an object, and the instant it was created.
+ *
+ * @author Michael Vorburger.ch
+ */
+public interface CloseTracked<T extends CloseTracked<T>> {
+
+    /**
+     * This returns the allocation context as {@link StackTraceElement}s. NB that
+     * this is a relatively <b>EXPENSIVE</b> operation! You should only ever call
+     * this when you really need to, e.g. when you actually produce output for
+     * humans, but not too early.
+     */
+    // TODO When we're on Java 9, then instead return a StackWalker.StackFrame[] here?
+    @Nullable StackTraceElement[] getAllocationContextStackTrace();
+
+    CloseTracked<T> getRealCloseTracked();
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistry.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistry.java
new file mode 100644 (file)
index 0000000..7f99824
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2017 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.closetracker.impl;
+
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
+import javax.annotation.concurrent.ThreadSafe;
+
+/**
+ * Registry of {@link CloseTracked} instances.
+ *
+ * @author Michael Vorburger.ch
+ */
+@ThreadSafe
+public class CloseTrackedRegistry<T extends CloseTracked<T>> {
+
+    private final Object anchor;
+    private final String createDescription;
+
+    private final Set<CloseTracked<T>> tracked =
+        new ConcurrentSkipListSet<>(Comparator.comparingInt(System::identityHashCode));
+
+    private final boolean isDebugContextEnabled;
+
+    /**
+     * Constructor.
+     *
+     * @param anchor
+     *            object where this registry is stored in, used for human output in
+     *            logging and other output
+     * @param createDescription
+     *            description of creator of instances of this registry, typically
+     *            e.g. name of method in the anchor class
+     * @param isDebugContextEnabled
+     *            whether or not the call stack should be preserved; this is (of
+     *            course) an expensive operation, and should only be used during
+     *            troubleshooting
+     */
+    public CloseTrackedRegistry(Object anchor, String createDescription, boolean isDebugContextEnabled) {
+        this.anchor = anchor;
+        this.createDescription = createDescription;
+        this.isDebugContextEnabled = isDebugContextEnabled;
+    }
+
+    public boolean isDebugContextEnabled() {
+        return isDebugContextEnabled;
+    }
+
+    public Object getAnchor() {
+        return anchor;
+    }
+
+    public String getCreateDescription() {
+        return createDescription;
+    }
+
+    // package protected, not public; only CloseTrackedTrait invokes this
+    void add(CloseTracked<T> closeTracked) {
+        tracked.add(closeTracked);
+    }
+
+    // package protected, not public; only CloseTrackedTrait invokes this
+    void remove(CloseTracked<T> closeTracked) {
+        tracked.remove(closeTracked);
+    }
+
+    /**
+     * Creates and returns a "report" of (currently) tracked but not (yet) closed
+     * instances.
+     *
+     * @return Set of CloseTrackedRegistryReportEntry, of which each the stack trace
+     *         element identifies a unique allocation context (or an empty List if
+     *         debugContextEnabled is false), and value is the number of open
+     *         instances created at that place in the code.
+     */
+    // For some reason, FB sees 'map' as useless but it clearly isn't.
+    @SuppressFBWarnings("UC_USELESS_OBJECT")
+    public Set<CloseTrackedRegistryReportEntry<T>> getAllUnique() {
+        Map<List<StackTraceElement>, Long> map = new HashMap<>();
+        Set<CloseTracked<T>> copyOfTracked = new HashSet<>(tracked);
+        for (CloseTracked<T> closeTracked : copyOfTracked) {
+            final StackTraceElement[] stackTraceArray = closeTracked.getAllocationContextStackTrace();
+            List<StackTraceElement> stackTraceElements =
+                    stackTraceArray != null ? Arrays.asList(stackTraceArray) : Collections.emptyList();
+            map.merge(stackTraceElements, 1L, (oldValue, value) -> oldValue + 1);
+        }
+
+        Set<CloseTrackedRegistryReportEntry<T>> report = new HashSet<>();
+        map.forEach((stackTraceElements, number) -> copyOfTracked.stream().filter(closeTracked -> {
+            StackTraceElement[] closeTrackedStackTraceArray = closeTracked.getAllocationContextStackTrace();
+            List<StackTraceElement> closeTrackedStackTraceElements =
+                closeTrackedStackTraceArray != null ? asList(closeTrackedStackTraceArray) : emptyList();
+            return closeTrackedStackTraceElements.equals(stackTraceElements);
+        }).findAny().ifPresent(exampleCloseTracked -> report.add(
+            new CloseTrackedRegistryReportEntry<>(exampleCloseTracked, number, stackTraceElements))));
+        return report;
+    }
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistryReportEntry.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedRegistryReportEntry.java
new file mode 100644 (file)
index 0000000..2dd161a
--- /dev/null
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2017 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.closetracker.impl;
+
+import static java.util.Objects.requireNonNull;
+
+import java.util.List;
+
+/**
+ * Element of a "report" created by a {@link CloseTrackedRegistry}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class CloseTrackedRegistryReportEntry<T extends CloseTracked<T>> {
+
+    private final CloseTracked<T> exampleCloseTracked;
+    private final long numberAddedNotRemoved;
+    private final List<StackTraceElement> stackTraceElements;
+
+    public CloseTrackedRegistryReportEntry(CloseTracked<T> exampleCloseTracked, long numberAddedNotRemoved,
+            List<StackTraceElement> stackTraceElements) {
+        this.exampleCloseTracked = requireNonNull(exampleCloseTracked, "closeTracked");
+        this.numberAddedNotRemoved = requireNonNull(numberAddedNotRemoved, "numberAddedNotRemoved");
+        this.stackTraceElements = requireNonNull(stackTraceElements, "stackTraceElements");
+    }
+
+    public long getNumberAddedNotRemoved() {
+        return numberAddedNotRemoved;
+    }
+
+    public CloseTracked<T> getExampleCloseTracked() {
+        return exampleCloseTracked;
+    }
+
+    public List<StackTraceElement> getStackTraceElements() {
+        return stackTraceElements;
+    }
+
+    @Override
+    public String toString() {
+        return "CloseTrackedRegistryReportEntry [numberAddedNotRemoved=" + numberAddedNotRemoved + ", closeTracked="
+                + exampleCloseTracked + ", stackTraceElements.size=" + stackTraceElements.size() + "]";
+    }
+
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedTrait.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/CloseTrackedTrait.java
new file mode 100644 (file)
index 0000000..30e81c4
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2017 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.closetracker.impl;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.util.Objects;
+import javax.annotation.Nullable;
+
+/**
+ * Implementation of {@link CloseTracked} which can be used as a field in
+ * another class which implements {@link CloseTracked} and delegates its methods
+ * to this.
+ *
+ * <p>This is useful if that class already has another parent class.
+ * If it does not, then it's typically more convenient to just extend AbstractCloseTracked.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class CloseTrackedTrait<T extends CloseTracked<T>> implements CloseTracked<T> {
+
+    // NB: It's important that we keep a Throwable here, and not directly the StackTraceElement[] !
+    // This is because creating a new Throwable() is a lot less expensive in terms of runtime overhead
+    // than actually calling its getStackTrace(), which we can delay until we really need to.
+    // see also e.g. https://stackoverflow.com/a/26122232/421602
+    private final @Nullable Throwable allocationContext;
+    private final CloseTrackedRegistry<T> closeTrackedRegistry;
+    private final CloseTracked<T> realCloseTracked;
+
+    public CloseTrackedTrait(CloseTrackedRegistry<T> transactionChainRegistry, CloseTracked<T> realCloseTracked) {
+        if (transactionChainRegistry.isDebugContextEnabled()) {
+            // NB: We're NOT doing the (expensive) getStackTrace() here just yet (only below)
+            // TODO When we're on Java 9, then instead use the new java.lang.StackWalker API..
+            this.allocationContext = new Throwable();
+        } else {
+            this.allocationContext = null;
+        }
+        this.realCloseTracked = Objects.requireNonNull(realCloseTracked, "realCloseTracked");
+        this.closeTrackedRegistry = Objects.requireNonNull(transactionChainRegistry, "transactionChainRegistry");
+        this.closeTrackedRegistry.add(this);
+    }
+
+    @Override
+    @Nullable
+    @SuppressFBWarnings("PZLA_PREFER_ZERO_LENGTH_ARRAYS")
+    public StackTraceElement[] getAllocationContextStackTrace() {
+        return allocationContext != null ? allocationContext.getStackTrace() : null;
+    }
+
+    public void removeFromTrackedRegistry() {
+        closeTrackedRegistry.remove(this);
+    }
+
+    @Override
+    public CloseTracked<T> getRealCloseTracked() {
+        return realCloseTracked;
+    }
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/package-info.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/closetracker/impl/package-info.java
new file mode 100644 (file)
index 0000000..60d9674
--- /dev/null
@@ -0,0 +1,14 @@
+/*
+ * Copyright (c) 2017 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
+ */
+
+/**
+ * Utilities to track (non) "closing" of objects.
+ */
+// This generic infra may be moved somewhere else, later
+@org.eclipse.jdt.annotation.NonNullByDefault
+package org.opendaylight.controller.md.sal.trace.closetracker.impl;
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/AbstractTracingWriteTransaction.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/AbstractTracingWriteTransaction.java
new file mode 100644 (file)
index 0000000..553cbfb
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * 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 com.google.common.collect.ImmutableSet;
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+abstract class AbstractTracingWriteTransaction implements DOMDataWriteTransaction {
+
+    private final DOMDataWriteTransaction delegate;
+    private final TracingBroker tracingBroker;
+    private final List<String> logs = new ArrayList<>();
+
+    AbstractTracingWriteTransaction(DOMDataWriteTransaction delegate, TracingBroker tracingBroker) {
+        this.delegate = Objects.requireNonNull(delegate);
+        this.tracingBroker = Objects.requireNonNull(tracingBroker);
+        recordOp(null, null, "instantiate", null);
+    }
+
+    private void recordOp(LogicalDatastoreType store, YangInstanceIdentifier yiid, String method,
+            NormalizedNode<?, ?> node) {
+        if (!tracingBroker.isWriteWatched(yiid, store)) {
+            return;
+        }
+
+        final Object value = node != null ? node.getValue() : null;
+
+        if (value != null && value instanceof ImmutableSet && ((Set<?>)value).isEmpty()) {
+            if (TracingBroker.LOG.isDebugEnabled()) {
+                TracingBroker.LOG.debug("Empty data set write to {}", tracingBroker.toPathString(yiid));
+            }
+        } else {
+            StringBuilder sb = new StringBuilder();
+            sb.append("Method \"").append(method).append('"');
+            if (store != null) {
+                sb.append(" to ").append(store);
+            }
+            if (yiid != null) {
+                sb.append(" at ").append(tracingBroker.toPathString(yiid));
+            }
+            sb.append('.');
+            if (yiid != null) {
+                // If we don’t have an id, we don’t expect data either
+                sb.append(" Data: ");
+                if (node != null) {
+                    sb.append(node.getValue());
+                } else {
+                    sb.append("null");
+                }
+            }
+            sb.append(" Stack:").append(tracingBroker.getStackSummary());
+            synchronized (this) {
+                logs.add(sb.toString());
+            }
+        }
+    }
+
+    private synchronized void logOps() {
+        synchronized (this) {
+            if (TracingBroker.LOG.isWarnEnabled()) {
+                TracingBroker.LOG.warn("Transaction {} contains the following operations:", getIdentifier());
+                logs.forEach(TracingBroker.LOG::warn);
+            }
+            logs.clear();
+        }
+    }
+
+    @Override
+    public void put(LogicalDatastoreType store, YangInstanceIdentifier yiid, NormalizedNode<?, ?> node) {
+        recordOp(store, yiid, "put", node);
+        delegate.put(store, yiid, node);
+    }
+
+    @Override
+    public void merge(LogicalDatastoreType store, YangInstanceIdentifier yiid, NormalizedNode<?, ?> node) {
+        recordOp(store, yiid, "merge", node);
+        delegate.merge(store, yiid, node);
+    }
+
+    @Override
+    public boolean cancel() {
+        synchronized (this) {
+            logs.clear();
+        }
+        return delegate.cancel();
+    }
+
+    @Override
+    public void delete(LogicalDatastoreType store, YangInstanceIdentifier yiid) {
+        recordOp(store, yiid, "delete", null);
+        delegate.delete(store, yiid);
+    }
+
+    @Override
+    public FluentFuture<? extends CommitInfo> commit() {
+        recordOp(null, null, "commit", null);
+        logOps();
+        return delegate.commit();
+    }
+
+    @Override
+    @Nonnull
+    public Object getIdentifier() {
+        return delegate.getIdentifier();
+    }
+
+    // https://jira.opendaylight.org/browse/CONTROLLER-1792
+
+    @Override
+    public final boolean equals(Object object) {
+        return object == this || delegate.equals(object);
+    }
+
+    @Override
+    public final int hashCode() {
+        return delegate.hashCode();
+    }
+
+    @Override
+    public final String toString() {
+        return getClass().getName() + "; delegate=" + delegate;
+    }
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingBroker.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingBroker.java
new file mode 100644 (file)
index 0000000..7103a00
--- /dev/null
@@ -0,0 +1,444 @@
+/*
+ * 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 static java.util.Objects.requireNonNull;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Nonnull;
+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.ClusteredDOMDataTreeChangeListener;
+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.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.controller.md.sal.trace.closetracker.impl.CloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistryReportEntry;
+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.
+ *
+ * <p>In addition, it (optionally) can also keep track of the stack trace of all new transaction allocations
+ * (including TransactionChains, and transactions created in turn from them), in order to detect and report leaks
+ * from transactions which were not closed.
+ *
+ * <h1>Wiring:</h1>
+ * 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:
+ * <ol>
+ * <li>
+ * Simply add the dependency "mdsaltrace-features" to
+ * your Karaf pom:
+ * <pre>
+ * {@code
+ *  <dependency>
+ *    <groupId>org.opendaylight.controller</groupId>
+ *    <artifactId>features-mdsal-trace</artifactId>
+ *    <version>1.7.0-SNAPSHOT</version>
+ *    <classifier>features</classifier>
+ *    <type>xml</type>
+ *    <scope>runtime</scope>
+ *  </dependency>
+ * }
+ * </pre>
+ * </li>
+ * <li>
+ * Then just "feature:install odl-mdsal-trace" before you install your "real" feature(s) and you're done.
+ * Beware that with Karaf 4 due to <a href="https://bugs.opendaylight.org/show_bug.cgi?id=9068">Bug 9068</a>
+ * you'll probably have to use feature:install's --no-auto-refresh flag when installing your "real" feature.
+ * </li>
+ * </ol>
+ * 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.
+ * <p> </p>
+ * <h1>Avoiding log bloat:</h1>
+ * 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 '/'.
+ * <p> </p>
+ * <h1>Known issues</h1>
+ * <ul>
+ *     <li>
+ *        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.
+ *     </li>
+ * </ul>
+ *
+ */
+public class TracingBroker implements TracingDOMDataBroker {
+    @SuppressFBWarnings("SLF4J_LOGGER_SHOULD_BE_PRIVATE")
+    static final Logger LOG = LoggerFactory.getLogger(TracingBroker.class);
+
+    private static final int STACK_TRACE_FIRST_RELEVANT_FRAME = 2;
+
+    private final String type; // "default" VS "pingpong"
+    private final BindingNormalizedNodeSerializer codec;
+    private final DOMDataBroker delegate;
+    private final List<Watch> registrationWatches = new ArrayList<>();
+    private final List<Watch> writeWatches = new ArrayList<>();
+
+    private final boolean isDebugging;
+    private final CloseTrackedRegistry<TracingTransactionChain> transactionChainsRegistry;
+    private final CloseTrackedRegistry<TracingReadOnlyTransaction> readOnlyTransactionsRegistry;
+    private final CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry;
+    private final CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry;
+
+    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) {
+            int parentOffset = 0;
+            if (parent.length() > 0 && parent.charAt(0) == '<') {
+                parentOffset = parent.indexOf('>') + 1;
+            }
+
+            int childOffset = 0;
+            if (child.length() > 0 && child.charAt(0) == '<') {
+                childOffset = child.indexOf('>') + 1;
+            }
+
+            return child.startsWith(parent.substring(parentOffset), childOffset);
+        }
+
+        @SuppressWarnings({ "checkstyle:hiddenField", "hiding" })
+        public boolean subtreesOverlap(YangInstanceIdentifier iid, LogicalDatastoreType store) {
+            if (this.store != null && !this.store.equals(store)) {
+                return false;
+            }
+
+            String otherIidString = toIidCompString(iid);
+            return isParent(iidString, otherIidString) || isParent(otherIidString, iidString);
+        }
+
+        @SuppressWarnings({ "checkstyle:hiddenField", "hiding" })
+        public boolean eventIsOfInterest(YangInstanceIdentifier iid, LogicalDatastoreType store) {
+            if (this.store != null && !this.store.equals(store)) {
+                return false;
+            }
+
+            return isParent(iidString, toPathString(iid));
+        }
+    }
+
+    public TracingBroker(String type, DOMDataBroker delegate, Config config, BindingNormalizedNodeSerializer codec) {
+        this.type = requireNonNull(type, "type");
+        this.delegate = requireNonNull(delegate, "delegate");
+        this.codec = requireNonNull(codec, "codec");
+        configure(config);
+
+        if (config.isTransactionDebugContextEnabled() != null) {
+            this.isDebugging = config.isTransactionDebugContextEnabled();
+        } else {
+            this.isDebugging = false;
+        }
+        final String db = "DataBroker";
+        this.transactionChainsRegistry     = new CloseTrackedRegistry<>(db, "createTransactionChain()", isDebugging);
+        this.readOnlyTransactionsRegistry  = new CloseTrackedRegistry<>(db, "newReadOnlyTransaction()", isDebugging);
+        this.writeTransactionsRegistry     = new CloseTrackedRegistry<>(db, "newWriteOnlyTransaction()", isDebugging);
+        this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(db, "newReadWriteTransaction()", isDebugging);
+    }
+
+    private void configure(Config config) {
+        registrationWatches.clear();
+        List<String> 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) {
+        LOG.info("Watching registrations to {} in {}", iidString, 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) {
+        LOG.info("Watching writes to {} in {}", iidString, store);
+        Watch watch = new Watch(iidString, store);
+        writeWatches.add(watch);
+    }
+
+    private boolean isRegistrationWatched(YangInstanceIdentifier iid, LogicalDatastoreType store) {
+        if (registrationWatches.isEmpty()) {
+            return true;
+        }
+
+        for (Watch regInterest : registrationWatches) {
+            if (regInterest.subtreesOverlap(iid, store)) {
+                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();
+    }
+
+
+    private 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 FROM: \"").append(yiid.toString()).append("\">");
+        for (YangInstanceIdentifier.PathArgument pathArg : yiid.getPathArguments()) {
+            if (pathArg instanceof YangInstanceIdentifier.AugmentationIdentifier) {
+                sb.append('/').append("AUGMENTATION");
+                continue;
+            }
+            sb.append('/').append(pathArg.getNodeType().getLocalName());
+        }
+    }
+
+    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, readWriteTransactionsRegistry);
+    }
+
+    @Override
+    public DOMDataWriteTransaction newWriteOnlyTransaction() {
+        return new TracingWriteTransaction(delegate.newWriteOnlyTransaction(), this, writeTransactionsRegistry);
+    }
+
+    @Override
+    public DOMTransactionChain createTransactionChain(TransactionChainListener transactionChainListener) {
+        return new TracingTransactionChain(
+                delegate.createTransactionChain(transactionChainListener), this, transactionChainsRegistry);
+    }
+
+    @Override
+    public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+        return new TracingReadOnlyTransaction(delegate.newReadOnlyTransaction(), readOnlyTransactionsRegistry);
+    }
+
+    @Nonnull
+    @Override
+    public Map<Class<? extends DOMDataBrokerExtension>, DOMDataBrokerExtension> getSupportedExtensions() {
+        Map<Class<? extends DOMDataBrokerExtension>, 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 <L extends DOMDataTreeChangeListener> ListenerRegistration<L> registerDataTreeChangeListener(
+                    @Nonnull DOMDataTreeIdentifier domDataTreeIdentifier, @Nonnull L listener) {
+                if (isRegistrationWatched(domDataTreeIdentifier.getRootIdentifier(),
+                        domDataTreeIdentifier.getDatastoreType())) {
+                    LOG.warn("{} registration (registerDataTreeChangeListener) for {} from {}.",
+                            listener instanceof ClusteredDOMDataTreeChangeListener ? "Clustered" : "Non-clustered",
+                            toPathString(domDataTreeIdentifier.getRootIdentifier()), getStackSummary());
+                }
+                return treeChangeSvc.registerDataTreeChangeListener(domDataTreeIdentifier, listener);
+            }
+        });
+
+        return res;
+    }
+
+    @Override
+    public boolean printOpenTransactions(PrintStream ps, int minOpenTXs) {
+        if (transactionChainsRegistry.getAllUnique().isEmpty()
+            && readOnlyTransactionsRegistry.getAllUnique().isEmpty()
+            && writeTransactionsRegistry.getAllUnique().isEmpty()
+            && readWriteTransactionsRegistry.getAllUnique().isEmpty()) {
+
+            ps.println(type + ": No open transactions, great!");
+            return false;
+        }
+
+        ps.println(type + ": " + getClass().getSimpleName()
+                 + " found some not yet (or never..) closed transaction[chain]s!");
+        ps.println("[NB: If no stack traces are shown below, then "
+                 + "enable transaction-debug-context-enabled in mdsaltrace_config.xml]");
+        ps.println();
+        // Flag to track if we really found any real leaks with more (or equal) to minOpenTXs
+        boolean hasFound = print(readOnlyTransactionsRegistry, ps, "  ", minOpenTXs);
+        hasFound |= print(writeTransactionsRegistry, ps, "  ", minOpenTXs);
+        hasFound |= print(readWriteTransactionsRegistry, ps, "  ", minOpenTXs);
+
+        // Now print details for each non-closed TransactionChain
+        // incl. in turn each ones own read/Write[Only]TransactionsRegistry
+        Set<CloseTrackedRegistryReportEntry<TracingTransactionChain>>
+            entries = transactionChainsRegistry.getAllUnique();
+        if (!entries.isEmpty()) {
+            ps.println("  " + transactionChainsRegistry.getAnchor() + " : "
+                    + transactionChainsRegistry.getCreateDescription());
+        }
+        for (CloseTrackedRegistryReportEntry<TracingTransactionChain> entry : entries) {
+            ps.println("    " + entry.getNumberAddedNotRemoved() + "x TransactionChains opened but not closed here:");
+            printStackTraceElements(ps, "      ", entry.getStackTraceElements());
+            @SuppressWarnings("resource")
+            TracingTransactionChain txChain = (TracingTransactionChain) entry
+                .getExampleCloseTracked().getRealCloseTracked();
+            hasFound |= print(txChain.getReadOnlyTransactionsRegistry(), ps, "        ", minOpenTXs);
+            hasFound |= print(txChain.getWriteTransactionsRegistry(), ps, "        ", minOpenTXs);
+            hasFound |= print(txChain.getReadWriteTransactionsRegistry(), ps, "        ", minOpenTXs);
+        }
+        ps.println();
+
+        return hasFound;
+    }
+
+    private <T extends CloseTracked<T>> boolean print(
+            CloseTrackedRegistry<T> registry, PrintStream ps, String indent, int minOpenTransactions) {
+        Set<CloseTrackedRegistryReportEntry<T>> unsorted = registry.getAllUnique();
+        if (unsorted.size() < minOpenTransactions) {
+            return false;
+        }
+
+        List<CloseTrackedRegistryReportEntry<T>> entries = new ArrayList<>(unsorted);
+        entries.sort((o1, o2) -> Long.compare(o2.getNumberAddedNotRemoved(), o1.getNumberAddedNotRemoved()));
+
+        if (!entries.isEmpty()) {
+            ps.println(indent + registry.getAnchor() + " : " + registry.getCreateDescription());
+        }
+        entries.forEach(entry -> {
+            ps.println(indent + "  " + entry.getNumberAddedNotRemoved()
+                + "x transactions opened here, which are not closed:");
+            printStackTraceElements(ps, indent + "    ", entry.getStackTraceElements());
+        });
+        if (!entries.isEmpty()) {
+            ps.println();
+        }
+        return true;
+    }
+
+    private void printStackTraceElements(PrintStream ps, String indent, List<StackTraceElement> stackTraceElements) {
+        boolean ellipsis = false;
+        for (final StackTraceElement stackTraceElement : stackTraceElements) {
+            if (isStackTraceElementInteresting(stackTraceElement)) {
+                ps.println(indent + stackTraceElement);
+                ellipsis = false;
+            } else if (!ellipsis) {
+                ps.println(indent + "(...)");
+                ellipsis = true;
+            }
+        }
+    }
+
+    private boolean isStackTraceElementInteresting(StackTraceElement element) {
+        final String className = element.getClassName();
+        return !className.startsWith(getClass().getPackage().getName())
+            && !className.startsWith(CloseTracked.class.getPackage().getName())
+            && !className.startsWith("Proxy")
+            && !className.startsWith("akka")
+            && !className.startsWith("scala")
+            && !className.startsWith("sun.reflect")
+            && !className.startsWith("java.lang.reflect")
+            && !className.startsWith("org.apache.aries.blueprint")
+            && !className.startsWith("org.osgi.util.tracker");
+    }
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingReadOnlyTransaction.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingReadOnlyTransaction.java
new file mode 100644 (file)
index 0000000..56e8d90
--- /dev/null
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2017 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 com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.AbstractCloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+class TracingReadOnlyTransaction
+        extends AbstractCloseTracked<TracingReadOnlyTransaction>
+        implements DOMDataReadOnlyTransaction {
+
+    private final DOMDataReadOnlyTransaction delegate;
+
+    TracingReadOnlyTransaction(DOMDataReadOnlyTransaction delegate,
+            CloseTrackedRegistry<TracingReadOnlyTransaction> readOnlyTransactionsRegistry) {
+        super(readOnlyTransactionsRegistry);
+        this.delegate = delegate;
+    }
+
+    @Override
+    public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(LogicalDatastoreType store,
+            YangInstanceIdentifier path) {
+        return delegate.read(store, path);
+    }
+
+    @Override
+    public CheckedFuture<Boolean, ReadFailedException> exists(LogicalDatastoreType store, YangInstanceIdentifier path) {
+        return delegate.exists(store, path);
+    }
+
+    @Override
+    public Object getIdentifier() {
+        return delegate.getIdentifier();
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+        super.removeFromTrackedRegistry();
+    }
+
+
+    // https://jira.opendaylight.org/browse/CONTROLLER-1792
+
+    @Override
+    public final boolean equals(Object object) {
+        return object == this || delegate.equals(object);
+    }
+
+    @Override
+    public final int hashCode() {
+        return delegate.hashCode();
+    }
+
+    @Override
+    public final String toString() {
+        return getClass().getName() + "; delegate=" + delegate;
+    }
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingReadWriteTransaction.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingReadWriteTransaction.java
new file mode 100644 (file)
index 0000000..82c1a3b
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * 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 com.google.common.base.Optional;
+import com.google.common.util.concurrent.CheckedFuture;
+import com.google.common.util.concurrent.FluentFuture;
+import java.util.Objects;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedTrait;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+
+class TracingReadWriteTransaction
+    extends AbstractTracingWriteTransaction
+        implements DOMDataReadWriteTransaction, CloseTracked<TracingReadWriteTransaction> {
+
+    private final CloseTrackedTrait<TracingReadWriteTransaction> closeTracker;
+    private final DOMDataReadWriteTransaction delegate;
+
+    TracingReadWriteTransaction(DOMDataReadWriteTransaction delegate, TracingBroker tracingBroker,
+            CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry) {
+        super(delegate, tracingBroker);
+        this.closeTracker = new CloseTrackedTrait<>(readWriteTransactionsRegistry, this);
+        this.delegate = Objects.requireNonNull(delegate);
+    }
+
+    @Override
+    public CheckedFuture<Optional<NormalizedNode<?, ?>>, ReadFailedException> read(
+                                                            LogicalDatastoreType store, YangInstanceIdentifier yiid) {
+        return delegate.read(store, yiid);
+    }
+
+    @Override
+    public CheckedFuture<Boolean, ReadFailedException> exists(LogicalDatastoreType store, YangInstanceIdentifier yiid) {
+        return delegate.exists(store, yiid);
+    }
+
+    @Override
+    public FluentFuture<? extends CommitInfo> commit() {
+        closeTracker.removeFromTrackedRegistry();
+        return super.commit();
+    }
+
+    @Override
+    public boolean cancel() {
+        closeTracker.removeFromTrackedRegistry();
+        return super.cancel();
+    }
+
+    @Override
+    public StackTraceElement[] getAllocationContextStackTrace() {
+        return closeTracker.getAllocationContextStackTrace();
+    }
+
+    @Override
+    public CloseTracked<TracingReadWriteTransaction> getRealCloseTracked() {
+        return this;
+    }
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingTransactionChain.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingTransactionChain.java
new file mode 100644 (file)
index 0000000..2e5070f
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2017 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.Objects;
+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.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.AbstractCloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+
+class TracingTransactionChain extends AbstractCloseTracked<TracingTransactionChain> implements DOMTransactionChain {
+
+    private final DOMTransactionChain delegate;
+    private final TracingBroker tracingBroker;
+    private final CloseTrackedRegistry<TracingReadOnlyTransaction> readOnlyTransactionsRegistry;
+    private final CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry;
+    private final CloseTrackedRegistry<TracingReadWriteTransaction> readWriteTransactionsRegistry;
+
+    TracingTransactionChain(DOMTransactionChain delegate, TracingBroker tracingBroker,
+            CloseTrackedRegistry<TracingTransactionChain> transactionChainsRegistry) {
+        super(transactionChainsRegistry);
+        this.delegate = Objects.requireNonNull(delegate);
+        this.tracingBroker = Objects.requireNonNull(tracingBroker);
+
+        final boolean isDebug = transactionChainsRegistry.isDebugContextEnabled();
+        String anchor = "TransactionChain@" + Integer.toHexString(hashCode());
+        this.readOnlyTransactionsRegistry  = new CloseTrackedRegistry<>(anchor, "newReadOnlyTransaction()", isDebug);
+        this.writeTransactionsRegistry     = new CloseTrackedRegistry<>(anchor, "newWriteOnlyTransaction()", isDebug);
+        this.readWriteTransactionsRegistry = new CloseTrackedRegistry<>(anchor, "newReadWriteTransaction()", isDebug);
+    }
+
+    @Override
+    @SuppressWarnings("resource")
+    public DOMDataReadOnlyTransaction newReadOnlyTransaction() {
+        final DOMDataReadOnlyTransaction tx = delegate.newReadOnlyTransaction();
+        return new TracingReadOnlyTransaction(tx, readOnlyTransactionsRegistry);
+    }
+
+    @Override
+    public DOMDataReadWriteTransaction newReadWriteTransaction() {
+        return new TracingReadWriteTransaction(delegate.newReadWriteTransaction(), tracingBroker,
+                readWriteTransactionsRegistry);
+    }
+
+    @Override
+    public DOMDataWriteTransaction newWriteOnlyTransaction() {
+        final DOMDataWriteTransaction tx = delegate.newWriteOnlyTransaction();
+        return new TracingWriteTransaction(tx, tracingBroker, writeTransactionsRegistry);
+    }
+
+    @Override
+    public void close() {
+        delegate.close();
+        super.removeFromTrackedRegistry();
+    }
+
+    public CloseTrackedRegistry<TracingReadOnlyTransaction> getReadOnlyTransactionsRegistry() {
+        return readOnlyTransactionsRegistry;
+    }
+
+    public CloseTrackedRegistry<TracingReadWriteTransaction> getReadWriteTransactionsRegistry() {
+        return readWriteTransactionsRegistry;
+    }
+
+    public CloseTrackedRegistry<TracingWriteTransaction> getWriteTransactionsRegistry() {
+        return writeTransactionsRegistry;
+    }
+
+
+    // https://jira.opendaylight.org/browse/CONTROLLER-1792
+
+    @Override
+    public final boolean equals(Object object) {
+        return object == this || delegate.equals(object);
+    }
+
+    @Override
+    public final int hashCode() {
+        return delegate.hashCode();
+    }
+
+    @Override
+    public final String toString() {
+        return getClass().getName() + "; delegate=" + delegate;
+    }
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingWriteTransaction.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/java/org/opendaylight/controller/md/sal/trace/dom/impl/TracingWriteTransaction.java
new file mode 100644 (file)
index 0000000..5fe313f
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2017 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 com.google.common.util.concurrent.FluentFuture;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedTrait;
+import org.opendaylight.mdsal.common.api.CommitInfo;
+
+class TracingWriteTransaction extends AbstractTracingWriteTransaction
+        implements CloseTracked<TracingWriteTransaction> {
+
+    private final CloseTrackedTrait<TracingWriteTransaction> closeTracker;
+
+    TracingWriteTransaction(DOMDataWriteTransaction delegate, TracingBroker tracingBroker,
+            CloseTrackedRegistry<TracingWriteTransaction> writeTransactionsRegistry) {
+        super(delegate, tracingBroker);
+        this.closeTracker = new CloseTrackedTrait<>(writeTransactionsRegistry, this);
+    }
+
+    @Override
+    public FluentFuture<? extends CommitInfo> commit() {
+        closeTracker.removeFromTrackedRegistry();
+        return super.commit();
+    }
+
+    @Override
+    public boolean cancel() {
+        closeTracker.removeFromTrackedRegistry();
+        return super.cancel();
+    }
+
+    @Override
+    public StackTraceElement[] getAllocationContextStackTrace() {
+        return closeTracker.getAllocationContextStackTrace();
+    }
+
+    @Override
+    public CloseTracked<TracingWriteTransaction> getRealCloseTracked() {
+        return this;
+    }
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml b/opendaylight/md-sal/mdsal-trace/dom-impl/src/main/resources/org/opendaylight/blueprint/impl-blueprint.xml
new file mode 100644 (file)
index 0000000..95f4e1f
--- /dev/null
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- vi: set et smarttab sw=4 tabstop=4: -->
+<!--
+Copyright © 2016 Red Hat 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
+-->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+  xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+  odl:use-default-for-reference-types="true">
+
+  <odl:clustered-app-config id="mdsalConfig"
+                            binding-class="org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.Config">
+  </odl:clustered-app-config>
+
+  <reference id="codec"
+        interface="org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer"
+        odl:type="default" />
+
+  <!-- Tracing Binding DataBroker -->
+
+  <reference id="realDefaultDOMBroker"
+      interface="org.opendaylight.controller.md.sal.dom.api.DOMDataBroker"
+      odl:type="default" />
+
+  <bean id="tracingDefaultDOMBroker" class="org.opendaylight.controller.md.sal.trace.dom.impl.TracingBroker">
+      <argument value="default" />
+      <argument ref="realDefaultDOMBroker" />
+      <argument ref="mdsalConfig" />
+      <argument ref="codec" />
+  </bean>
+
+  <service id="tracingDefaultDOMBrokerSvc" ref="tracingDefaultDOMBroker" ranking="10" odl:type="default">
+    <interfaces>
+      <value>org.opendaylight.controller.md.sal.dom.api.DOMDataBroker</value>
+      <value>org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker</value>
+    </interfaces>
+  </service>
+
+  <!-- Tracing Binding PingPong DataBroker -->
+
+  <reference id="realPingPongDOMDataBroker"
+      interface="org.opendaylight.controller.md.sal.dom.api.DOMDataBroker"
+      odl:type="pingpong"/>
+
+  <bean id="tracingPingPongDOMBroker" class="org.opendaylight.controller.md.sal.trace.dom.impl.TracingBroker">
+      <argument value="pingpong" />
+      <argument ref="realPingPongDOMDataBroker" />
+      <argument ref="mdsalConfig" />
+      <argument ref="codec" />
+  </bean>
+
+  <service id="tracingPingPongDOMBrokerSvc" ref="tracingPingPongDOMBroker" ranking="10" odl:type="pingpong">
+    <interfaces>
+      <value>org.opendaylight.controller.md.sal.dom.api.DOMDataBroker</value>
+      <value>org.opendaylight.controller.md.sal.trace.api.TracingDOMDataBroker</value>
+    </interfaces>
+  </service>
+</blueprint>
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/tests/CloseTrackedRegistryTest.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/tests/CloseTrackedRegistryTest.java
new file mode 100644 (file)
index 0000000..c91080c
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2017 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.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import java.util.Set;
+import java.util.function.Predicate;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.AbstractCloseTracked;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistry;
+import org.opendaylight.controller.md.sal.trace.closetracker.impl.CloseTrackedRegistryReportEntry;
+
+public class CloseTrackedRegistryTest {
+
+    private static class SomethingClosable extends AbstractCloseTracked<SomethingClosable> implements AutoCloseable {
+        SomethingClosable(CloseTrackedRegistry<SomethingClosable> transactionChainRegistry) {
+            super(transactionChainRegistry);
+        }
+
+        @Override
+        public void close() {
+            removeFromTrackedRegistry();
+        }
+    }
+
+    @Test
+    public void testDuplicateAllocationContexts() {
+        final CloseTrackedRegistry<SomethingClosable> registry =
+                new CloseTrackedRegistry<>(this, "testDuplicateAllocationContexts", true);
+
+        for (int i = 0; i < 100; i++) {
+            SomethingClosable isClosedManyTimes = new SomethingClosable(registry);
+            isClosedManyTimes.close();
+            someOtherMethodWhichDoesNotClose(registry);
+        }
+        @SuppressWarnings({ "resource", "unused" })
+        SomethingClosable forgotToCloseOnce = new SomethingClosable(registry);
+
+        Set<CloseTrackedRegistryReportEntry<SomethingClosable>> uniqueNonClosed = registry.getAllUnique();
+        assertThat(uniqueNonClosed).hasSize(2);
+        assertThatIterableContains(uniqueNonClosed, entry ->
+            entry.getNumberAddedNotRemoved() == 100 || entry.getNumberAddedNotRemoved() == 1);
+        uniqueNonClosed.forEach(entry -> {
+            if (entry.getNumberAddedNotRemoved() == 100) {
+                assertThatIterableContains(entry.getStackTraceElements(),
+                    element -> element.getMethodName().equals("someOtherMethodWhichDoesNotClose"));
+            } else if (entry.getNumberAddedNotRemoved() == 1) {
+                assertThatIterableContains(entry.getStackTraceElements(),
+                    element -> element.getMethodName().equals("testDuplicateAllocationContexts"));
+            } else {
+                fail("Unexpected number of added, not removed: " + entry.getNumberAddedNotRemoved());
+            }
+        });
+    }
+
+    // Something like this really should be in Google Truth...
+    private <T> void assertThatIterableContains(Iterable<T> iterable, Predicate<T> predicate) {
+        for (T element : iterable) {
+            if (predicate.test(element)) {
+                return;
+            }
+        }
+        fail("Iterable did not contain any element matching predicate");
+    }
+
+    @SuppressWarnings({ "resource", "unused" })
+    private void someOtherMethodWhichDoesNotClose(CloseTrackedRegistry<SomethingClosable> registry) {
+        new SomethingClosable(registry);
+    }
+
+    @Test
+    @SuppressWarnings({ "unused", "resource" })
+    public void testDebugContextDisabled() {
+        final CloseTrackedRegistry<SomethingClosable> debugContextDisabledRegistry =
+                new CloseTrackedRegistry<>(this, "testDebugContextDisabled", false);
+
+        SomethingClosable forgotToCloseOnce = new SomethingClosable(debugContextDisabledRegistry);
+
+        Set<CloseTrackedRegistryReportEntry<SomethingClosable>>
+            closeRegistryReport = debugContextDisabledRegistry.getAllUnique();
+        assertThat(closeRegistryReport).hasSize(1);
+
+        CloseTrackedRegistryReportEntry<SomethingClosable>
+            closeRegistryReportEntry1 = closeRegistryReport.iterator().next();
+        assertThat(closeRegistryReportEntry1.getNumberAddedNotRemoved()).isEqualTo(1);
+        assertThat(closeRegistryReportEntry1.getStackTraceElements()).isEmpty();
+    }
+}
diff --git a/opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/tests/TracingBrokerTest.java b/opendaylight/md-sal/mdsal-trace/dom-impl/src/test/java/org/opendaylight/controller/md/sal/trace/tests/TracingBrokerTest.java
new file mode 100644 (file)
index 0000000..4e24c7d
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2017 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.tests;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMTransactionChain;
+import org.opendaylight.controller.md.sal.trace.dom.impl.TracingBroker;
+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.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.mdsaltrace.rev160908.ConfigBuilder;
+
+/**
+ * Test of {@link TracingBroker}.
+ *
+ * @author Michael Vorburger.ch
+ */
+public class TracingBrokerTest {
+
+    @Test
+    @SuppressWarnings({ "resource", "unused" }) // Finding resource leaks is the point of this test
+    public void testPrintOpenTransactions() {
+        DOMDataBroker domDataBroker = mock(DOMDataBroker.class, RETURNS_DEEP_STUBS);
+        Config config = new ConfigBuilder().setTransactionDebugContextEnabled(true).build();
+        BindingNormalizedNodeSerializer codec = mock(BindingNormalizedNodeSerializer.class);
+        TracingBroker tracingBroker = new TracingBroker("mock", domDataBroker, config, codec);
+
+        for (int i = 0; i < 3; i++) {
+            DOMDataReadWriteTransaction tx = tracingBroker.newReadWriteTransaction();
+        }
+        DOMDataReadWriteTransaction anotherTx = tracingBroker.newReadWriteTransaction();
+
+        DOMTransactionChain txChain = tracingBroker.createTransactionChain(null);
+        DOMDataReadWriteTransaction txFromChain = txChain.newReadWriteTransaction();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        PrintStream ps = new PrintStream(baos);
+        boolean printReturnValue = tracingBroker.printOpenTransactions(ps, 1);
+        String output = new String(baos.toByteArray(), UTF_8);
+
+        assertThat(printReturnValue).isTrue();
+        // Assert expectations about stack trace
+        assertThat(output).contains("testPrintOpenTransactions(TracingBrokerTest.java");
+        assertThat(output).doesNotContain(TracingBroker.class.getName());
+
+        String previousLine = "";
+        for (String line : output.split("\n")) {
+            if (line.contains("(...")) {
+                assertThat(previousLine.contains("(...)")).isFalse();
+            }
+            previousLine = line;
+        }
+
+        // assert that the sorting works - the x3 is shown before the x1
+        assertThat(output).contains("  DataBroker : newReadWriteTransaction()\n    3x");
+
+        // We don't do any verify/times on the mocks,
+        // because the main point of the test is just to verify that
+        // printOpenTransactions runs through without any exceptions
+        // (e.g. it used to have a ClassCastException).
+    }
+
+}
diff --git a/opendaylight/md-sal/mdsal-trace/features/features-mdsal-trace/pom.xml b/opendaylight/md-sal/mdsal-trace/features/features-mdsal-trace/pom.xml
new file mode 100644 (file)
index 0000000..6ebaea7
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.opendaylight.odlparent</groupId>
+        <artifactId>feature-repo-parent</artifactId>
+        <version>3.1.3</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>features-mdsal-trace</artifactId>
+    <version>1.9.0-SNAPSHOT</version>
+    <name>OpenDaylight :: TracingBroker</name>
+    <packaging>feature</packaging>
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>odl-mdsal-trace</artifactId>
+            <version>${project.version}</version>
+            <type>xml</type>
+            <classifier>features</classifier>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/features/odl-mdsal-trace/pom.xml b/opendaylight/md-sal/mdsal-trace/features/odl-mdsal-trace/pom.xml
new file mode 100644 (file)
index 0000000..f5551cf
--- /dev/null
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.opendaylight.odlparent</groupId>
+        <artifactId>single-feature-parent</artifactId>
+        <version>3.1.3</version>
+        <relativePath/>
+    </parent>
+
+    <groupId>org.opendaylight.controller</groupId>
+    <artifactId>odl-mdsal-trace</artifactId>
+    <version>1.9.0-SNAPSHOT</version>
+    <name>OpenDaylight :: TracingBroker</name>
+    <packaging>feature</packaging>
+
+    <dependencyManagement>
+        <dependencies>
+            <dependency>
+                <groupId>org.opendaylight.controller</groupId>
+                <artifactId>mdsal-artifacts</artifactId>
+                <version>${project.version}</version>
+                <type>pom</type>
+                <scope>import</scope>
+            </dependency>
+        </dependencies>
+
+    </dependencyManagement>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>odl-mdsal-broker</artifactId>
+            <version>${project.version}</version>
+            <type>xml</type>
+            <classifier>features</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>odl-mdsal-broker-local</artifactId>
+            <version>${project.version}</version>
+            <type>xml</type>
+            <classifier>features</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>mdsal-trace-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>mdsal-trace-dom-impl</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>mdsal-trace-binding-impl</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.opendaylight.controller</groupId>
+            <artifactId>mdsal-trace-cli</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+    </dependencies>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/features/odl-mdsal-trace/src/main/feature/feature.xml b/opendaylight/md-sal/mdsal-trace/features/odl-mdsal-trace/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..8e6070b
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="odl-mdsal-trace-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.2.0">
+    <feature name="odl-mdsal-trace" version="${project.version}">
+        <configfile finalname="etc/opendaylight/datastore/initial/config/mdsaltrace_config.xml">mvn:org.opendaylight.controller/mdsal-trace-api/${project.version}/xml/config</configfile>
+    </feature>
+</features>
diff --git a/opendaylight/md-sal/mdsal-trace/features/pom.xml b/opendaylight/md-sal/mdsal-trace/features/pom.xml
new file mode 100644 (file)
index 0000000..5486b5f
--- /dev/null
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2016 Red Hat 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 INTERNAL
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.odlparent</groupId>
+    <artifactId>odlparent-lite</artifactId>
+    <version>3.1.3</version>
+    <relativePath/>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>mdsal-trace-feature-aggregator</artifactId>
+  <version>1.9.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <modules>
+    <module>features-mdsal-trace</module>
+    <module>odl-mdsal-trace</module>
+  </modules>
+</project>
diff --git a/opendaylight/md-sal/mdsal-trace/pom.xml b/opendaylight/md-sal/mdsal-trace/pom.xml
new file mode 100644 (file)
index 0000000..c57cfa1
--- /dev/null
@@ -0,0 +1,86 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright © 2016 Red Hat 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 INTERNAL
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.opendaylight.odlparent</groupId>
+    <artifactId>odlparent-lite</artifactId>
+    <version>3.1.3</version>
+    <relativePath/>
+  </parent>
+
+  <groupId>org.opendaylight.controller</groupId>
+  <artifactId>mdsal-trace-aggregator</artifactId>
+  <version>1.9.0-SNAPSHOT</version>
+  <name>mdsaltrace</name>
+  <packaging>pom</packaging>
+
+  <modules>
+    <module>api</module>
+    <module>dom-impl</module>
+    <module>binding-impl</module>
+    <module>cli</module>
+    <module>features</module>
+  </modules>
+
+  <!-- DO NOT install or deploy the repo root pom as it's only needed to initiate a build -->
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-deploy-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-install-plugin</artifactId>
+        <configuration>
+          <skip>true</skip>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <profiles>
+    <profile>
+      <!--
+          This profile is to ensure we only build javadocs reports
+          when we plan to deploy Maven site for our project.
+      -->
+      <id>maven-site</id>
+      <activation>
+        <file>
+          <exists>${user.dir}/deploy-site.xml</exists>
+        </file>
+      </activation>
+
+      <build>
+        <plugins>
+          <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-javadoc-plugin</artifactId>
+            <inherited>false</inherited>
+            <executions>
+              <execution>
+                <id>aggregate</id>
+                <goals>
+                  <goal>aggregate</goal>
+                </goals>
+                <phase>package</phase>
+            </execution>
+            </executions>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
+  </profiles>
+
+</project>