Improve error reporting for tell-based reads 10/78310/5
authorTom Pantelis <tompantelis@gmail.com>
Fri, 30 Nov 2018 03:00:34 +0000 (22:00 -0500)
committerRobert Varga <nite@hq.sk>
Wed, 16 Jan 2019 14:14:20 +0000 (14:14 +0000)
Added contextual info similar to ask-based, including the
yang path of the requested read and the backend shard name.
Also wrapped RequestTimeoutException with
DataStoreUnavailableException.

Change-Id: I5487e5531034cc1abbda27a4953897da7212eba8
Signed-off-by: Tom Pantelis <tompantelis@gmail.com>
15 files changed:
opendaylight/md-sal/cds-access-client/src/main/java/org/opendaylight/controller/cluster/access/client/AbstractClientConnection.java
opendaylight/md-sal/cds-access-client/src/main/java/org/opendaylight/controller/cluster/access/client/BackendInfo.java
opendaylight/md-sal/cds-access-client/src/main/java/org/opendaylight/controller/cluster/access/client/BackendInfoResolver.java
opendaylight/md-sal/cds-access-client/src/main/java/org/opendaylight/controller/cluster/access/client/ClientActorBehavior.java
opendaylight/md-sal/cds-access-client/src/main/java/org/opendaylight/controller/cluster/access/client/ConnectingClientConnection.java
opendaylight/md-sal/cds-access-client/src/test/java/org/opendaylight/controller/cluster/access/client/AccessClientUtil.java
opendaylight/md-sal/cds-access-client/src/test/java/org/opendaylight/controller/cluster/access/client/ConnectedClientConnectionTest.java
opendaylight/md-sal/cds-access-client/src/test/java/org/opendaylight/controller/cluster/access/client/ConnectingClientConnectionTest.java
opendaylight/md-sal/cds-access-client/src/test/java/org/opendaylight/controller/cluster/access/client/ReconnectingClientConnectionTest.java
opendaylight/md-sal/cds-access-client/src/test/java/org/opendaylight/controller/cluster/access/client/TransmittingTransmitQueueTest.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/ModuleShardBackendResolver.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/RemoteProxyTransaction.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/ShardBackendInfo.java
opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/SimpleShardBackendResolver.java
opendaylight/md-sal/sal-distributed-datastore/src/test/java/org/opendaylight/controller/cluster/databroker/actors/dds/ModuleShardBackendResolverTest.java

index 7375fe0..c32f7b2 100644 (file)
@@ -78,6 +78,7 @@ public abstract class AbstractClientConnection<T extends BackendInfo> {
     @GuardedBy("lock")
     private final TransmitQueue queue;
     private final Long cookie;
+    private final String backendName;
 
     @GuardedBy("lock")
     private boolean haveTimer;
@@ -90,9 +91,11 @@ public abstract class AbstractClientConnection<T extends BackendInfo> {
     private volatile RequestException poisoned;
 
     // Private constructor to avoid code duplication.
-    private AbstractClientConnection(final AbstractClientConnection<T> oldConn, final TransmitQueue newQueue) {
+    private AbstractClientConnection(final AbstractClientConnection<T> oldConn, final TransmitQueue newQueue,
+            final String backendName) {
         this.context = Preconditions.checkNotNull(oldConn.context);
         this.cookie = Preconditions.checkNotNull(oldConn.cookie);
+        this.backendName = Preconditions.checkNotNull(backendName);
         this.queue = Preconditions.checkNotNull(newQueue);
         // Will be updated in finishReplay if needed.
         this.lastReceivedTicks = oldConn.lastReceivedTicks;
@@ -100,9 +103,11 @@ public abstract class AbstractClientConnection<T extends BackendInfo> {
 
     // This constructor is only to be called by ConnectingClientConnection constructor.
     // Do not allow subclassing outside of this package
-    AbstractClientConnection(final ClientActorContext context, final Long cookie, final int queueDepth) {
+    AbstractClientConnection(final ClientActorContext context, final Long cookie, final String backendName,
+            final int queueDepth) {
         this.context = Preconditions.checkNotNull(context);
         this.cookie = Preconditions.checkNotNull(cookie);
+        this.backendName = Preconditions.checkNotNull(backendName);
         this.queue = new TransmitQueue.Halted(queueDepth);
         this.lastReceivedTicks = currentTime();
     }
@@ -110,14 +115,15 @@ public abstract class AbstractClientConnection<T extends BackendInfo> {
     // This constructor is only to be called (indirectly) by ReconnectingClientConnection constructor.
     // Do not allow subclassing outside of this package
     AbstractClientConnection(final AbstractClientConnection<T> oldConn) {
-        this(oldConn, new TransmitQueue.Halted(oldConn.queue, oldConn.currentTime()));
+        this(oldConn, new TransmitQueue.Halted(oldConn.queue, oldConn.currentTime()), oldConn.backendName);
     }
 
     // This constructor is only to be called (indirectly) by ConnectedClientConnection constructor.
     // Do not allow subclassing outside of this package
-    AbstractClientConnection(final AbstractClientConnection<T> oldConn, final T newBackend, final int queueDepth) {
+    AbstractClientConnection(final AbstractClientConnection<T> oldConn, final T newBackend,
+            final int queueDepth) {
         this(oldConn, new TransmitQueue.Transmitting(oldConn.queue, queueDepth, newBackend, oldConn.currentTime(),
-                Preconditions.checkNotNull(oldConn.context).messageSlicer()));
+                Preconditions.checkNotNull(oldConn.context).messageSlicer()), newBackend.getName());
     }
 
     public final ClientActorContext context() {
@@ -422,7 +428,8 @@ public abstract class AbstractClientConnection<T extends BackendInfo> {
         context.executeInActor(current -> {
             final double time = beenOpen * 1.0 / 1_000_000_000;
             entry.complete(entry.getRequest().toRequestFailure(
-                new RequestTimeoutException(entry.getRequest() + " timed out after " + time + " seconds")));
+                new RequestTimeoutException(entry.getRequest() + " timed out after " + time
+                        + " seconds. The backend for " + backendName + " is not available.")));
             return current;
         });
     }
index cea174b..01aff95 100644 (file)
@@ -29,10 +29,13 @@ public class BackendInfo {
     private final ActorRef actor;
     private final int maxMessages;
     private final long sessionId;
+    private final String name;
 
-    protected BackendInfo(final ActorRef actor, final long sessionId, final ABIVersion version, final int maxMessages) {
+    protected BackendInfo(final ActorRef actor, final String name, final long sessionId, final ABIVersion version,
+            final int maxMessages) {
         this.version = Preconditions.checkNotNull(version);
         this.actor = Preconditions.checkNotNull(actor);
+        this.name = Preconditions.checkNotNull(name);
         Preconditions.checkArgument(maxMessages > 0, "Maximum messages has to be positive, not %s", maxMessages);
         this.maxMessages = maxMessages;
         this.sessionId = sessionId;
@@ -42,6 +45,10 @@ public class BackendInfo {
         return actor;
     }
 
+    public final String getName() {
+        return name;
+    }
+
     public final ABIVersion getVersion() {
         return version;
     }
index 3c6e093..fc633ad 100644 (file)
@@ -59,6 +59,9 @@ public abstract class BackendInfoResolver<T extends BackendInfo> implements Auto
     @Nonnull
     public abstract Registration notifyWhenBackendInfoIsStale(Consumer<Long> callback);
 
+    @Nonnull
+    public abstract String resolveCookieName(Long cookie);
+
     @Override
     public void close() {
     }
index ddf1dc1..6ede15b 100644 (file)
@@ -440,7 +440,8 @@ public abstract class ClientActorBehavior<T extends BackendInfo> extends
     }
 
     private ConnectingClientConnection<T> createConnection(final Long shard) {
-        final ConnectingClientConnection<T> conn = new ConnectingClientConnection<>(context(), shard);
+        final ConnectingClientConnection<T> conn = new ConnectingClientConnection<>(context(), shard,
+                resolver().resolveCookieName(shard));
         resolveConnection(shard, conn);
         return conn;
     }
index cae6981..1015990 100644 (file)
@@ -21,8 +21,8 @@ public final class ConnectingClientConnection<T extends BackendInfo> extends Abs
     private static final int TARGET_QUEUE_DEPTH = 4000;
 
     // Initial state, never instantiated externally
-    ConnectingClientConnection(final ClientActorContext context, final Long cookie) {
-        super(context, cookie, TARGET_QUEUE_DEPTH);
+    ConnectingClientConnection(final ClientActorContext context, final Long cookie, final String backendName) {
+        super(context, cookie, backendName, TARGET_QUEUE_DEPTH);
     }
 
     @Override
index 92d310d..7521ed6 100644 (file)
@@ -47,7 +47,8 @@ public final class AccessClientUtil {
 
     public static <T extends BackendInfo> ConnectedClientConnection<T> createConnectedConnection(
             final ClientActorContext context, final Long cookie, final T backend) {
-        return new ConnectedClientConnection<>(new ConnectingClientConnection<>(context, cookie), backend);
+        return new ConnectedClientConnection<>(new ConnectingClientConnection<>(context, cookie, backend.getName()),
+                backend);
     }
 
     public static void completeRequest(final AbstractClientConnection<? extends BackendInfo> connection,
index 226c173..530ac5a 100644 (file)
@@ -7,8 +7,8 @@
  */
 package org.opendaylight.controller.cluster.access.client;
 
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.same;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.same;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -44,8 +44,9 @@ public class ConnectedClientConnectionTest
 
     @Override
     protected ConnectedClientConnection<BackendInfo> createConnection() {
-        final BackendInfo backend = new BackendInfo(backendProbe.ref(), 0L, ABIVersion.BORON, 10);
-        final ConnectingClientConnection<BackendInfo> connectingConn = new ConnectingClientConnection<>(context, 0L);
+        final BackendInfo backend = new BackendInfo(backendProbe.ref(), "test", 0L, ABIVersion.BORON, 10);
+        final ConnectingClientConnection<BackendInfo> connectingConn = new ConnectingClientConnection<>(context, 0L,
+                backend.getName());
         return  new ConnectedClientConnection<>(connectingConn, backend);
     }
 
index 911f349..a3c4b5c 100644 (file)
@@ -13,7 +13,7 @@ import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
@@ -150,14 +150,14 @@ public class ConnectingClientConnectionTest {
         doReturn(mock(MessageSlicer.class)).when(mockContext).messageSlicer();
 
         mockActor = TestProbe.apply(actorSystem);
-        mockBackendInfo = new BackendInfo(mockActor.ref(), 0, ABIVersion.current(), 5);
+        mockBackendInfo = new BackendInfo(mockActor.ref(), "test", 0, ABIVersion.current(), 5);
         mockRequest = new MockRequest(mockIdentifier, mockReplyTo);
         mockRequest2 = new MockRequest(mockIdentifier, mockReplyTo);
         mockResponse = mockRequest.toRequestFailure(mockCause);
         mockResponseEnvelope = new FailureEnvelope(mockResponse, 0, 0, 0);
         mockCookie = ThreadLocalRandom.current().nextLong();
 
-        queue = new ConnectingClientConnection<>(mockContext, mockCookie);
+        queue = new ConnectingClientConnection<>(mockContext, mockCookie, mockBackendInfo.getName());
     }
 
     @After
@@ -361,7 +361,7 @@ public class ConnectingClientConnectionTest {
 
     private void setupBackend() {
         final ConnectingClientConnection<BackendInfo> connectingConn =
-                new ConnectingClientConnection<>(mockContext, mockCookie);
+                new ConnectingClientConnection<>(mockContext, mockCookie, "test");
         final ConnectedClientConnection<BackendInfo> connectedConn =
                 new ConnectedClientConnection<>(connectingConn, mockBackendInfo);
         queue.setForwarder(new SimpleReconnectForwarder(connectedConn));
index 564de90..0b7d0c2 100644 (file)
@@ -7,7 +7,7 @@
  */
 package org.opendaylight.controller.cluster.access.client;
 
-import static org.mockito.Matchers.any;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.after;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
@@ -42,8 +42,9 @@ public class ReconnectingClientConnectionTest
 
     @Override
     protected ReconnectingClientConnection<BackendInfo> createConnection() {
-        final BackendInfo backend = new BackendInfo(backendProbe.ref(), 0L, ABIVersion.BORON, 10);
-        final ConnectingClientConnection<BackendInfo> connectingConn = new ConnectingClientConnection<>(context, 0L);
+        final BackendInfo backend = new BackendInfo(backendProbe.ref(), "test", 0L, ABIVersion.BORON, 10);
+        final ConnectingClientConnection<BackendInfo> connectingConn = new ConnectingClientConnection<>(context, 0L,
+                backend.getName());
         final ConnectedClientConnection<BackendInfo> connectedConn =
                 new ConnectedClientConnection<>(connectingConn, backend);
         return new ReconnectingClientConnection<>(connectedConn, mock(RequestException.class));
@@ -69,4 +70,4 @@ public class ReconnectingClientConnectionTest
         connection.receiveResponse(envelope);
         verify(callback, after(1000).never()).accept(any());
     }
-}
\ No newline at end of file
+}
index ea39e42..d6e9f5c 100644 (file)
@@ -64,7 +64,7 @@ public class TransmittingTransmitQueueTest extends AbstractTransmitQueueTest<Tra
     @Override
     protected TransmitQueue.Transmitting createQueue() {
         doReturn(false).when(mockMessageSlicer).slice(any());
-        backendInfo = new BackendInfo(probe.ref(), 0L, ABIVersion.BORON, 3);
+        backendInfo = new BackendInfo(probe.ref(), "test", 0L, ABIVersion.BORON, 3);
         return new TransmitQueue.Transmitting(new TransmitQueue.Halted(0), 0, backendInfo, now(), mockMessageSlicer);
     }
 
index 004df59..f6452a1 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.controller.cluster.databroker.actors.dds;
 
 import static akka.pattern.Patterns.ask;
+import static com.google.common.base.Verify.verifyNotNull;
 
 import akka.dispatch.ExecutionContexts;
 import akka.dispatch.OnComplete;
@@ -159,7 +160,7 @@ final class ModuleShardBackendResolver extends AbstractShardBackendResolver {
             }
 
             LOG.debug("Invalidating backend information {}", staleInfo);
-            flushCache(staleInfo.getShardName());
+            flushCache(staleInfo.getName());
 
             LOG.trace("Invalidated cache {}", staleInfo);
             backends.remove(cookie, existing);
@@ -177,4 +178,9 @@ final class ModuleShardBackendResolver extends AbstractShardBackendResolver {
             }
         }, ExecutionContexts.global());
     }
+
+    @Override
+    public String resolveCookieName(Long cookie) {
+        return verifyNotNull(shards.inverse().get(cookie), "Unexpected null cookie: %s", cookie);
+    }
 }
index e12c724..284be4a 100644 (file)
@@ -7,13 +7,13 @@
  */
 package org.opendaylight.controller.cluster.databroker.actors.dds;
 
-import com.google.common.base.Function;
 import com.google.common.util.concurrent.FluentFuture;
 import com.google.common.util.concurrent.ListenableFuture;
 import com.google.common.util.concurrent.SettableFuture;
 import java.util.Optional;
 import java.util.function.Consumer;
 import javax.annotation.Nullable;
+import org.opendaylight.controller.cluster.access.client.RequestTimeoutException;
 import org.opendaylight.controller.cluster.access.commands.AbortLocalTransactionRequest;
 import org.opendaylight.controller.cluster.access.commands.AbstractLocalTransactionRequest;
 import org.opendaylight.controller.cluster.access.commands.AbstractReadTransactionRequest;
@@ -36,10 +36,12 @@ import org.opendaylight.controller.cluster.access.commands.TransactionPurgeReque
 import org.opendaylight.controller.cluster.access.commands.TransactionRequest;
 import org.opendaylight.controller.cluster.access.commands.TransactionSuccess;
 import org.opendaylight.controller.cluster.access.commands.TransactionWrite;
+import org.opendaylight.controller.cluster.access.concepts.RequestException;
 import org.opendaylight.controller.cluster.access.concepts.RequestFailure;
 import org.opendaylight.controller.cluster.access.concepts.Response;
 import org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier;
 import org.opendaylight.controller.cluster.datastore.util.AbstractDataTreeModificationCursor;
+import org.opendaylight.mdsal.common.api.DataStoreUnavailableException;
 import org.opendaylight.mdsal.common.api.ReadFailedException;
 import org.opendaylight.yangtools.util.concurrent.FluentFutures;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -66,8 +68,6 @@ import org.slf4j.LoggerFactory;
 final class RemoteProxyTransaction extends AbstractProxyTransaction {
     private static final Logger LOG = LoggerFactory.getLogger(RemoteProxyTransaction.class);
 
-    private static final Function<Exception, Exception> NOOP_EXCEPTION_MAPPER = ex -> ex;
-
     // FIXME: make this tuneable
     private static final int REQUEST_MAX_MODIFICATIONS = 1000;
 
@@ -131,14 +131,14 @@ final class RemoteProxyTransaction extends AbstractProxyTransaction {
     FluentFuture<Boolean> doExists(final YangInstanceIdentifier path) {
         final SettableFuture<Boolean> future = SettableFuture.create();
         return sendReadRequest(new ExistsTransactionRequest(getIdentifier(), nextSequence(), localActor(), path,
-            isSnapshotOnly()), t -> completeExists(future, t), future);
+            isSnapshotOnly()), t -> completeExists(path, future, t), future);
     }
 
     @Override
     FluentFuture<Optional<NormalizedNode<?, ?>>> doRead(final YangInstanceIdentifier path) {
         final SettableFuture<Optional<NormalizedNode<?, ?>>> future = SettableFuture.create();
         return sendReadRequest(new ReadTransactionRequest(getIdentifier(), nextSequence(), localActor(), path,
-            isSnapshotOnly()), t -> completeRead(future, t), future);
+            isSnapshotOnly()), t -> completeRead(path, future, t), future);
     }
 
     private void ensureInitializedBuilder() {
@@ -197,15 +197,16 @@ final class RemoteProxyTransaction extends AbstractProxyTransaction {
             // Happy path
             recordSuccessfulRequest(request);
         } else {
-            recordFailedResponse(response, NOOP_EXCEPTION_MAPPER);
+            recordFailedResponse(response);
         }
     }
 
-    private <X extends Exception> X recordFailedResponse(final Response<?, ?> response,
-            final Function<Exception, X> exMapper) {
+    private Exception recordFailedResponse(final Response<?, ?> response) {
         final Exception failure;
         if (response instanceof RequestFailure) {
-            failure = ((RequestFailure<?, ?>) response).getCause();
+            final RequestException cause = ((RequestFailure<?, ?>) response).getCause();
+            failure = cause instanceof RequestTimeoutException
+                    ? new DataStoreUnavailableException(cause.getMessage(), cause) : cause;
         } else {
             LOG.warn("Unhandled response {}", response);
             failure = new IllegalArgumentException("Unhandled response " + response.getClass());
@@ -215,33 +216,35 @@ final class RemoteProxyTransaction extends AbstractProxyTransaction {
             LOG.debug("Transaction {} failed", getIdentifier(), failure);
             operationFailure = failure;
         }
-        return exMapper.apply(failure);
+        return failure;
     }
 
-    private void failReadFuture(final SettableFuture<?> future, final Response<?, ?> response) {
-        future.setException(recordFailedResponse(response, ReadFailedException.MAPPER));
+    private void failReadFuture(final SettableFuture<?> future, final String message,
+            final Response<?, ?> response) {
+        future.setException(new ReadFailedException(message, recordFailedResponse(response)));
     }
 
-    private void completeExists(final SettableFuture<Boolean> future, final Response<?, ?> response) {
-        LOG.debug("Exists request completed with {}", response);
+    private void completeExists(final YangInstanceIdentifier path, final SettableFuture<Boolean> future,
+            final Response<?, ?> response) {
+        LOG.debug("Exists request for {} completed with {}", path, response);
 
         if (response instanceof ExistsTransactionSuccess) {
             future.set(((ExistsTransactionSuccess) response).getExists());
         } else {
-            failReadFuture(future, response);
+            failReadFuture(future, "Error executing exists request for path " + path, response);
         }
 
         recordFinishedRequest(response);
     }
 
-    private void completeRead(final SettableFuture<Optional<NormalizedNode<?, ?>>> future,
-            final Response<?, ?> response) {
-        LOG.debug("Read request completed with {}", response);
+    private void completeRead(final YangInstanceIdentifier path,
+            final SettableFuture<Optional<NormalizedNode<?, ?>>> future, final Response<?, ?> response) {
+        LOG.debug("Read request for {} completed with {}", path, response);
 
         if (response instanceof ReadTransactionSuccess) {
             future.set(((ReadTransactionSuccess) response).getData());
         } else {
-            failReadFuture(future, response);
+            failReadFuture(future, "Error reading data for path " + path, response);
         }
 
         recordFinishedRequest(response);
index d13099c..fda7fc6 100644 (file)
@@ -28,12 +28,10 @@ import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
 final class ShardBackendInfo extends BackendInfo {
     private final Optional<DataTree> dataTree;
     private final UnsignedLong cookie;
-    private final String shardName;
 
     ShardBackendInfo(final ActorRef actor, final long sessionId, final ABIVersion version, final String shardName,
         final UnsignedLong cookie, final Optional<DataTree> dataTree, final int maxMessages) {
-        super(actor, sessionId, version, maxMessages);
-        this.shardName = Preconditions.checkNotNull(shardName);
+        super(actor, shardName, sessionId, version, maxMessages);
         this.cookie = Preconditions.checkNotNull(cookie);
         this.dataTree = Preconditions.checkNotNull(dataTree);
     }
@@ -46,10 +44,6 @@ final class ShardBackendInfo extends BackendInfo {
         return dataTree;
     }
 
-    String getShardName() {
-        return shardName;
-    }
-
     LocalHistoryIdentifier brandHistory(final LocalHistoryIdentifier id) {
         Preconditions.checkArgument(id.getCookie() == 0, "History %s is already branded", id);
         return new LocalHistoryIdentifier(id.getClientId(), id.getHistoryId(), cookie.longValue());
@@ -57,7 +51,7 @@ final class ShardBackendInfo extends BackendInfo {
 
     @Override
     protected ToStringHelper addToStringAttributes(final ToStringHelper toStringHelper) {
-        return super.addToStringAttributes(toStringHelper).add("cookie", cookie).add("shard", shardName)
+        return super.addToStringAttributes(toStringHelper).add("cookie", cookie).add("shard", getName())
                 .add("dataTree", getDataTree().isPresent() ? "present" : "absent");
     }
 }
index 8c0db7e..5576ba7 100644 (file)
@@ -120,7 +120,7 @@ public class ModuleShardBackendResolverTest {
         final ShardBackendInfo shardBackendInfo = TestUtils.getWithTimeout(stage.toCompletableFuture());
         Assert.assertEquals(0L, shardBackendInfo.getCookie().longValue());
         Assert.assertEquals(dataTree, shardBackendInfo.getDataTree().get());
-        Assert.assertEquals(DefaultShardStrategy.DEFAULT_SHARD, shardBackendInfo.getShardName());
+        Assert.assertEquals(DefaultShardStrategy.DEFAULT_SHARD, shardBackendInfo.getName());
     }
 
     @Test

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.