MountPoint for data-tree based DataBroker.
authorMarek Gradzki <[email protected]>
Fri, 4 Mar 2016 11:32:10 +0000 (12:32 +0100)
committerMarek Gradzki <[email protected]>
Mon, 21 Mar 2016 16:39:17 +0000 (17:39 +0100)
Change-Id: I6a15e79747484790607d82d4024971763b4bae54
Signed-off-by: Marek Gradzki <[email protected]>
v3po/impl/pom.xml
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/V3poProvider.java
v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataBrokerInitializationProvider.java [new file with mode: 0644]
v3po/impl/src/main/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/v3po/impl/rev141210/V3poModule.java
v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppDataBrokerInitializationProviderTest.java [new file with mode: 0644]
v3po/impl/src/test/java/org/opendaylight/yang/gen/v1/urn/opendaylight/params/xml/ns/yang/v3po/impl/rev141210/V3poModuleTest.java

index 9315c15..ff27d12 100644 (file)
       <version>1.0.0-SNAPSHOT</version>
     </dependency>
 
+    <dependency>
+      <groupId>org.opendaylight.mdsal.model</groupId>
+      <artifactId>ietf-topology</artifactId>
+    </dependency>
+
     <!-- Testing Dependencies -->
     <dependency>
       <groupId>junit</groupId>
index b761000..54acce3 100644 (file)
 
 package io.fd.honeycomb.v3po.impl;
 
+import com.google.common.base.Preconditions;
 import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.CheckedFuture;
 import com.google.common.util.concurrent.Futures;
+import io.fd.honeycomb.v3po.impl.data.VppDataBrokerInitializationProvider;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Timer;
 import java.util.TimerTask;
+import javax.annotation.Nonnull;
 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
 import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
@@ -32,6 +35,7 @@ import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFaile
 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.ProviderContext;
 import org.opendaylight.controller.sal.binding.api.BindingAwareBroker.RpcRegistration;
 import org.opendaylight.controller.sal.binding.api.BindingAwareProvider;
+import org.opendaylight.controller.sal.core.api.Broker;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.EthernetCsmacd;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana._if.type.rev140508.SoftwareLoopback;
 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.InterfaceType;
@@ -55,12 +59,18 @@ import org.slf4j.LoggerFactory;
 public class V3poProvider implements BindingAwareProvider, AutoCloseable {
 
     private static final Logger LOG = LoggerFactory.getLogger(V3poProvider.class);
+    private final Broker domBroker;
     private RpcRegistration<V3poService> v3poService;
     private VppIetfInterfaceListener vppInterfaceListener;
     private VppBridgeDomainListener vppBridgeDomainListener;
     private vppApi api;
     private DataBroker db;
     VppPollOperDataImpl vppPollOperData;
+    private VppDataBrokerInitializationProvider vppDataBrokerInitializationProvider;
+
+    public V3poProvider(@Nonnull final Broker domBroker) {
+        this.domBroker = Preconditions.checkNotNull(domBroker, "domBroker should not be null");
+    }
 
     private void initializeVppConfig() {
 
@@ -176,6 +186,10 @@ public class V3poProvider implements BindingAwareProvider, AutoCloseable {
         v3poService = session.addRpcImplementation(V3poService.class,
                                                    vppPollOperData);
         startOperationalUpdateTimer();
+
+        // TODO make configurable
+        vppDataBrokerInitializationProvider = new VppDataBrokerInitializationProvider(db);
+        domBroker.registerProvider(vppDataBrokerInitializationProvider);
     }
 
     @Override
@@ -187,5 +201,8 @@ public class V3poProvider implements BindingAwareProvider, AutoCloseable {
         if (api != null) {
             api.close();
         }
+        if (vppDataBrokerInitializationProvider != null) {
+            vppDataBrokerInitializationProvider.close();
+        }
     }
 }
diff --git a/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataBrokerInitializationProvider.java b/v3po/impl/src/main/java/io/fd/honeycomb/v3po/impl/data/VppDataBrokerInitializationProvider.java
new file mode 100644 (file)
index 0000000..9a64356
--- /dev/null
@@ -0,0 +1,249 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.impl.data;
+
+import com.google.common.base.Optional;
+import com.google.common.base.Preconditions;
+import com.google.common.util.concurrent.AsyncFunction;
+import com.google.common.util.concurrent.Futures;
+import com.google.common.util.concurrent.ListenableFuture;
+import io.fd.honeycomb.v3po.impl.LoggingFuturesCallBack;
+import io.fd.honeycomb.v3po.impl.trans.DefaultVppWriter;
+import io.fd.honeycomb.v3po.impl.trans.VppInterfacesReader;
+import java.util.Collection;
+import java.util.Collections;
+import javassist.ClassPool;
+import javax.annotation.Nonnull;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.ReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataReadOnlyTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
+import org.opendaylight.controller.md.sal.dom.api.DOMMountPointService;
+import org.opendaylight.controller.sal.core.api.Broker;
+import org.opendaylight.controller.sal.core.api.Provider;
+import org.opendaylight.controller.sal.core.api.model.SchemaService;
+import org.opendaylight.mdsal.binding.dom.codec.api.BindingNormalizedNodeSerializer;
+import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.interfaces.rev140508.Interfaces;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.TopologyId;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.TopologyKey;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.NodeKey;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.DataObjectSerializerGenerator;
+import org.opendaylight.yangtools.binding.data.codec.gen.impl.StreamWriterGenerator;
+import org.opendaylight.yangtools.binding.data.codec.impl.BindingNormalizedNodeCodecRegistry;
+import org.opendaylight.yangtools.concepts.ObjectRegistration;
+import org.opendaylight.yangtools.sal.binding.generator.impl.GeneratedClassLoadingStrategy;
+import org.opendaylight.yangtools.sal.binding.generator.util.BindingRuntimeContext;
+import org.opendaylight.yangtools.sal.binding.generator.util.JavassistUtils;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.binding.KeyedInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.TreeType;
+import org.opendaylight.yangtools.yang.data.impl.schema.tree.InMemoryDataTreeFactory;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Creates VppDataBroker which uses DataTree instead of DataStore internally in order to obtain better control over the
+ * data processing in Honeycomb agent
+ */
+public final class VppDataBrokerInitializationProvider implements Provider, AutoCloseable {
+
+    private static final Logger LOG = LoggerFactory.getLogger(VppDataBrokerInitializationProvider.class);
+
+    private final TopologyId VPP_TOPOLOGY_ID = TopologyId.getDefaultInstance("vpp-topology");
+    private final NodeId VPP_TOPOLOGY_NODE_ID = NodeId.getDefaultInstance("vpp");
+    private final DataBroker bindingBroker;
+    private final InstanceIdentifier<Node> mountPointPath;
+    private ObjectRegistration<DOMMountPoint> mountPointRegistration;
+
+    public VppDataBrokerInitializationProvider(@Nonnull final DataBroker bindingBroker) {
+        this.bindingBroker = Preconditions.checkNotNull(bindingBroker, "bindingBroker should not be null");
+        this.mountPointPath = getMountPointPath();
+    }
+
+    // TODO make configurable
+    private InstanceIdentifier<Node> getMountPointPath() {
+        final InstanceIdentifier<NetworkTopology> networkTopology =
+                InstanceIdentifier.builder(NetworkTopology.class).build();
+        final KeyedInstanceIdentifier<Topology, TopologyKey> topology =
+                networkTopology.child(Topology.class, new TopologyKey(VPP_TOPOLOGY_ID));
+        return topology.child(Node.class, new NodeKey(VPP_TOPOLOGY_NODE_ID));
+    }
+
+    @Override
+    public void onSessionInitiated(final Broker.ProviderSession providerSession) {
+        LOG.info("Session initialized, providerSession={}", providerSession);
+        Preconditions.checkState(!isMountPointRegistered(), "Mount point is already registered");
+
+        final DOMMountPointService mountPointService = providerSession.getService(DOMMountPointService.class);
+        final SchemaService schemaService = providerSession.getService(SchemaService.class);
+
+        final SchemaContext globalContext = schemaService.getGlobalContext();
+        final BindingNormalizedNodeSerializer serializer = initSerializer(globalContext);
+        final YangInstanceIdentifier path = serializer.toYangInstanceIdentifier(mountPointPath);
+
+        final DOMMountPointService.DOMMountPointBuilder mountPointBuilder = mountPointService.createMountPoint(path);
+        mountPointBuilder.addInitialSchemaContext(globalContext);
+
+        final DOMDataBroker broker = initVppDataBroker(globalContext, serializer);
+        mountPointBuilder.addService(DOMDataBroker.class, broker);
+
+        mountPointRegistration = mountPointBuilder.register();
+        final DOMMountPoint mountPoint = mountPointRegistration.getInstance();
+        LOG.debug("Created mountPoint: identifier={}, schemaContext={}", mountPoint.getIdentifier(),
+                mountPoint.getSchemaContext());
+
+        createMountPointPlaceholder();
+
+        initialVppStateSynchronization(broker);
+    }
+
+    @Override
+    public Collection<ProviderFunctionality> getProviderFunctionality() {
+        return Collections.EMPTY_LIST;
+    }
+
+    private boolean isMountPointRegistered() {
+        final ReadOnlyTransaction readTx = bindingBroker.newReadOnlyTransaction();
+        try {
+            final Optional<Node> cfgPlaceholder =
+                    readTx.read(LogicalDatastoreType.CONFIGURATION, mountPointPath).checkedGet();
+            final Optional<Node> operPlaceholder =
+                    readTx.read(LogicalDatastoreType.OPERATIONAL, mountPointPath).checkedGet();
+            return cfgPlaceholder.isPresent() || operPlaceholder.isPresent();
+        } catch (ReadFailedException e) {
+            throw new IllegalStateException("Failed to read mountpoint placeholder data", e);
+        }
+    }
+
+    private BindingNormalizedNodeSerializer initSerializer(final SchemaContext globalContext) {
+        final JavassistUtils utils = JavassistUtils.forClassPool(ClassPool.getDefault());
+        // TODO this produces ClassNotFoundException
+        //final GeneratedClassLoadingStrategy loading = GeneratedClassLoadingStrategy.getTCCLClassLoadingStrategy();
+
+        // FIXME get global class loader instance
+        final GeneratedClassLoadingStrategy loadingStrategy =
+                new GeneratedClassLoadingStrategy() {
+                    @Override
+                    public Class<?> loadClass(final String fullyQualifiedName)
+                            throws ClassNotFoundException {
+                        return Class.forName(fullyQualifiedName);
+                    }
+                };
+        final DataObjectSerializerGenerator generator = StreamWriterGenerator.create(utils);
+
+        // TODO make configurable:
+        final BindingNormalizedNodeCodecRegistry serializer = new BindingNormalizedNodeCodecRegistry(generator);
+        final BindingRuntimeContext context = BindingRuntimeContext.create(loadingStrategy, globalContext);
+        serializer.onBindingRuntimeContextUpdated(context);
+        return serializer;
+    }
+
+    private DOMDataBroker initVppDataBroker(final SchemaContext globalContext,
+                                            final BindingNormalizedNodeSerializer serializer) {
+        final ReadableVppDataTree operationalData =
+                new VppOperationalDataTree(serializer, new VppInterfacesReader()); // TODO make configurable
+
+        final DataTree dataTree =
+                InMemoryDataTreeFactory.getInstance().create(TreeType.CONFIGURATION); // TODO make configurable
+        dataTree.setSchemaContext(globalContext);
+
+        final VppDataTree configDataProxy = new VppConfigDataTree(serializer, dataTree,
+                new DefaultVppWriter()); // TODO make configurable
+        return new VppDataBroker(operationalData, configDataProxy);
+    }
+
+    /**
+     * Writes placeholder data into MD-SAL's global datastore to indicate the presence of VPP mountpoint.
+     */
+    private void createMountPointPlaceholder() {
+        final NodeBuilder nodeBuilder = new NodeBuilder();
+        nodeBuilder.setKey(new NodeKey(VPP_TOPOLOGY_NODE_ID));
+        final Node node = nodeBuilder.build();
+
+        final WriteTransaction writeTx = bindingBroker.newWriteOnlyTransaction();
+        writeTx.merge(LogicalDatastoreType.CONFIGURATION, mountPointPath, node, true);
+        writeTx.merge(LogicalDatastoreType.OPERATIONAL, mountPointPath, node, true);
+
+        try {
+            writeTx.submit().checkedGet();
+        } catch (TransactionCommitFailedException e) {
+            throw new IllegalStateException("Failed to create mountpoint placeholder", e);
+        }
+    }
+
+    // TODO operational and config models are not 1-1
+    // decide what part of operational data should be written to config during initialization
+    private void initialVppStateSynchronization(final DOMDataBroker broker) {
+        // read from operational
+        final DOMDataReadOnlyTransaction readTx = broker.newReadOnlyTransaction();
+
+        final YangInstanceIdentifier interfacesID = YangInstanceIdentifier.of(Interfaces.QNAME);
+
+        final ListenableFuture<Void> writeFuture = Futures.transform(
+                readTx.read(LogicalDatastoreType.OPERATIONAL, interfacesID),
+                new AsyncFunction<Optional<NormalizedNode<?, ?>>, Void>() {
+                    @Override
+                    public ListenableFuture<Void> apply(final Optional<NormalizedNode<?, ?>> readResult)
+                            throws Exception {
+                        if (readResult.isPresent()) {
+                            final DOMDataWriteTransaction writeTx = broker.newWriteOnlyTransaction();
+                            writeTx.put(LogicalDatastoreType.CONFIGURATION, interfacesID, readResult.get());
+                            return writeTx.submit();
+                        } else {
+                            return Futures
+                                    .immediateFailedFuture(
+                                            new IllegalStateException("Failed to read data from VPP."));
+                        }
+                    }
+                });
+
+        Futures.addCallback(writeFuture,
+                new LoggingFuturesCallBack<Void>("Initializing VPP config DataTree failed", LOG));
+    }
+
+    @Override
+    public void close() throws Exception {
+        if (mountPointRegistration != null) {
+            mountPointRegistration.close();
+        }
+        // remove MD-SAL placeholder data for VPP mount point:
+        final WriteTransaction rwTx = bindingBroker.newWriteOnlyTransaction();
+        // does not fail if data is not present:
+        rwTx.delete(LogicalDatastoreType.CONFIGURATION, mountPointPath);
+        try {
+            rwTx.submit().checkedGet();
+        } catch (TransactionCommitFailedException e) {
+            throw new IllegalStateException("Failed to remove mountpoint's placeholder from MD-SAL's global datastore",
+                    e);
+        }
+    }
+}
index f51eb05..3905455 100644 (file)
@@ -31,14 +31,10 @@ import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.rev
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 public class V3poModule extends
         org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.v3po.impl.rev141210.AbstractV3poModule {
 
-    private static final Logger LOG = LoggerFactory.getLogger(V3poModule.class);
-
     public V3poModule(org.opendaylight.controller.config.api.ModuleIdentifier identifier,
                       org.opendaylight.controller.config.api.DependencyResolver dependencyResolver) {
         super(identifier, dependencyResolver);
@@ -53,14 +49,14 @@ public class V3poModule extends
 
     @Override
     public void customValidation() {
-        // add custom validation form module attributes here.AbstractModule
+        // add custom validation form module attributes here
     }
 
     @Override
     public java.lang.AutoCloseable createInstance() {
-        getDomBrokerDependency().registerProvider(new InitializationProvider());
-
-        V3poProvider provider = new V3poProvider();
+        final Broker domBroker = getDomBrokerDependency();
+        domBroker.registerProvider(new InitializationProvider());
+        final V3poProvider provider = new V3poProvider(domBroker);
         getBrokerDependency().registerProvider(provider);
         return provider;
     }
@@ -109,4 +105,5 @@ public class V3poModule extends
         }
     }
 
+
 }
diff --git a/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppDataBrokerInitializationProviderTest.java b/v3po/impl/src/test/java/io/fd/honeycomb/v3po/impl/data/VppDataBrokerInitializationProviderTest.java
new file mode 100644 (file)
index 0000000..782a7b6
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2016 Cisco and/or its affiliates.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package io.fd.honeycomb.v3po.impl.data;
+
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.google.common.util.concurrent.CheckedFuture;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class VppDataBrokerInitializationProviderTest {
+
+    @Mock
+    private DataBroker bindingBroker;
+    @Mock
+    private WriteTransaction writeTx;
+
+    private VppDataBrokerInitializationProvider provider;
+
+    @Before
+    public void setUp() throws Exception {
+        initMocks(this);
+        doReturn(writeTx).when(bindingBroker).newWriteOnlyTransaction();
+        provider = new VppDataBrokerInitializationProvider(bindingBroker);
+    }
+
+    @Test
+    public void testGetProviderFunctionality() {
+        assertNotNull(provider.getProviderFunctionality());
+    }
+
+    @Test
+    public void testClose() throws Exception {
+        doReturn(mock(CheckedFuture.class)).when(writeTx).submit();
+        provider.close();
+        verify(writeTx).delete(eq(LogicalDatastoreType.CONFIGURATION), any(InstanceIdentifier.class));
+        verify(writeTx).submit();
+    }
+
+    @Test(expected = IllegalStateException.class)
+    public void testCloseFailed() throws Exception {
+        doReturn(writeTx).when(bindingBroker).newWriteOnlyTransaction();
+        doThrow(TransactionCommitFailedException.class).when(writeTx).submit();
+        provider.close();
+        verify(writeTx).delete(eq(LogicalDatastoreType.CONFIGURATION), any(InstanceIdentifier.class));
+    }
+}
\ No newline at end of file