Hostconfig for VPP 42/50742/12
authorTomas Cechvala <tcechval@cisco.com>
Mon, 13 Feb 2017 17:19:30 +0000 (18:19 +0100)
committerTomas Cechvala <tcechval@cisco.com>
Fri, 3 Mar 2017 10:02:02 +0000 (11:02 +0100)
L2 configuration is currently supported for VPP devices.
Vhostuser vif-type is used for VMs (endpoints).
Configurable fields:
 - socket directory
 - socket prefix
 - vhostuser-mode (client, server)

 ODL connects to VPP nodes via NETCONF. When a node is
 recognized as VPP node based on its capabilitites
 host configration is written to datastore.

Change-Id: I78b6ad2eeb3d1b11b09317f4c9be0e6878e71df8
Signed-off-by: Tomas Cechvala <tcechval@cisco.com>
12 files changed:
features/production/pom.xml
features/production/src/main/features/features.xml
neutron-hostconfig/pom.xml
neutron-hostconfig/utils/src/main/java/org/opendaylight/neutron/hostconfig/utils/NeutronHostconfigUtils.java
neutron-hostconfig/vpp/pom.xml [new file with mode: 0644]
neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/HostconfigUtil.java [new file with mode: 0644]
neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/NeutronHostconfigVppListener.java [new file with mode: 0644]
neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/SocketInfo.java [new file with mode: 0644]
neutron-hostconfig/vpp/src/main/resources/config/org.opendaylight.neutron.hostconfig.vpp [new file with mode: 0644]
neutron-hostconfig/vpp/src/main/resources/org/opendaylight/blueprint/neutron-hostconfig-vpp.xml [new file with mode: 0644]
neutron-hostconfig/vpp/src/test/java/org/opendaylight/neutron/hostconfig/vpp/HostconfigsDataBrokerTest.java [new file with mode: 0644]
neutron-hostconfig/vpp/src/test/java/org/opendaylight/neutron/hostconfig/vpp/NeutronHostconfigVppListenerTest.java [new file with mode: 0644]

index 77cac88262232c012c7c755a874146d6ed8ae118..5f15b31c774b7820b365c760d2dfa1e81e01636f 100644 (file)
@@ -18,6 +18,7 @@
       <mdsal.model.version>0.10.0-SNAPSHOT</mdsal.model.version>
       <restconf.version>1.5.0-SNAPSHOT</restconf.version>
       <ovsdb.version>1.4.0-SNAPSHOT</ovsdb.version>
+      <netconf.version>1.2.0-SNAPSHOT</netconf.version>
   </properties>
   <dependencyManagement>
     <dependencies>
@@ -38,8 +39,8 @@
       </dependency>
       <dependency>
         <groupId>org.opendaylight.netconf</groupId>
-        <artifactId>restconf-artifacts</artifactId>
-        <version>${restconf.version}</version>
+        <artifactId>netconf-artifacts</artifactId>
+        <version>${netconf.version}</version>
         <type>pom</type>
         <scope>import</scope>
       </dependency>
       <type>xml</type>
       <classifier>features</classifier>
     </dependency>
-    <dependency>
-      <groupId>org.opendaylight.netconf</groupId>
-      <artifactId>features-restconf</artifactId>
-      <version>${restconf.version}</version>
-      <type>xml</type>
-      <classifier>features</classifier>
-    </dependency>
     <dependency>
       <groupId>org.opendaylight.aaa</groupId>
       <artifactId>features-aaa-shiro</artifactId>
       <artifactId>utils.mdsal-utils</artifactId>
       <version>${ovsdb.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.opendaylight.netconf</groupId>
+      <artifactId>features-netconf-connector</artifactId>
+      <classifier>features</classifier>
+      <type>xml</type>
+      <scope>runtime</scope>
+    </dependency>
     <dependency>
       <groupId>${project.groupId}</groupId>
       <artifactId>neutron-hostconfig-utils</artifactId>
       <artifactId>neutron-hostconfig-ovs</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>${project.groupId}</groupId>
+      <artifactId>neutron-hostconfig-vpp</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
   <scm>
     <connection>scm:git:ssh://git.opendaylight.org:29418/neutron.git</connection>
index b5a171e3c66ca01da202c8dffb1aee53d89a8328..6a07b733095a986077b608b1a64b9c9e21e87f86 100644 (file)
@@ -9,6 +9,7 @@
    <repository>mvn:org.opendaylight.controller/features-mdsal/{{VERSION}}/xml/features</repository>
    <repository>mvn:org.opendaylight.ovsdb/southbound-features/{{VERSION}}/xml/features</repository>
    <repository>mvn:org.opendaylight.netconf/features-restconf/{{VERSION}}/xml/features</repository>
+   <repository>mvn:org.opendaylight.netconf/features-netconf-connector/{{VERSION}}/xml/features</repository>
    <feature name='odl-neutron-service' version='${project.version}' description="OpenDaylight :: Neutron :: API">
     <feature version='${project.version}'>odl-neutron-spi</feature>
     <feature version='${project.version}'>odl-neutron-northbound-api</feature>
     <bundle>mvn:org.opendaylight.neutron/neutron-hostconfig-ovs/{{VERSION}}</bundle>
     <bundle>mvn:org.osgi/org.osgi.core/{{VERSION}}</bundle>
   </feature>
+  <feature name='odl-neutron-hostconfig-vpp' version='${project.version}' description="OpenDaylight :: Neutron :: HostconfigVpp">
+    <feature version='${controller.mdsal.version}'>odl-mdsal-broker</feature>
+    <feature version="${netconf.version}">odl-netconf-clustered-topology</feature>
+    <bundle>mvn:org.opendaylight.neutron/model/{{VERSION}}</bundle>
+    <bundle>mvn:org.opendaylight.neutron/neutron-hostconfig-utils/{{VERSION}}</bundle>
+    <bundle>mvn:org.opendaylight.neutron/neutron-hostconfig-vpp/{{VERSION}}</bundle>
+    <bundle>mvn:org.osgi/org.osgi.core/{{VERSION}}</bundle>
+  </feature>
 </features>
index c1f5009d93dc9c904aa6c86ce5f74ddca07c89b6..8c381720a8eafd46a3926b19a487a38e845bd8f0 100644 (file)
@@ -17,6 +17,7 @@
   <modules>
     <module>utils</module>
     <module>ovs</module>
+    <module>vpp</module>
   </modules>
   <!-- DO NOT install or deploy the repo root pom as it's only needed to initiate a build -->
   <build>
index abbf86817210559108ffa5c42a4dda922a249dc3..fcab6e31ce3d06f26d7302e62a171e7e2827f5b6 100644 (file)
@@ -8,7 +8,11 @@
 
 package org.opendaylight.neutron.hostconfig.utils;
 
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.concurrent.ExecutionException;
+
 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;
@@ -47,11 +51,13 @@ public class NeutronHostconfigUtils {
                     hostConfigId = createInstanceIdentifier(hostConfig);
                     writeTx.put(LogicalDatastoreType.OPERATIONAL, hostConfigId, hostConfig, true);
                     writeTx.submit().get();
+                    LOG.trace("Hostconfig updated for node {}", hostConfig.getHostId());
                     break;
                 case DELETE:
                     final WriteTransaction delTx = dataBroker.newWriteOnlyTransaction();
                     hostConfigId = createInstanceIdentifier(hostConfig);
                     delTx.delete(LogicalDatastoreType.OPERATIONAL, hostConfigId);
+                    LOG.trace("Hostconfig deleted for node {}", hostConfig.getHostId());
                     delTx.submit().get();
                     break;
                 default:
@@ -74,4 +80,16 @@ public class NeutronHostconfigUtils {
         return InstanceIdentifier.create(Neutron.class).child(Hostconfigs.class)
                 .child(Hostconfig.class, hostconfig.getKey());
     }
+
+    /**
+     * Used for parsing model revisions.
+     */
+    public static Date parseDate(final String strDate) {
+        final SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
+        try {
+            return formatter.parse(strDate);
+        } catch (final ParseException e) {
+            throw new IllegalArgumentException("Date " + strDate + " not valid.", e);
+        }
+    }
 }
diff --git a/neutron-hostconfig/vpp/pom.xml b/neutron-hostconfig/vpp/pom.xml
new file mode 100644 (file)
index 0000000..ed9b6e2
--- /dev/null
@@ -0,0 +1,141 @@
+<?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.controller</groupId>
+    <artifactId>config-parent</artifactId>
+    <version>0.6.0-SNAPSHOT</version>
+    <relativePath/>
+  </parent>
+
+  <groupId>org.opendaylight.neutron</groupId>
+  <artifactId>neutron-hostconfig-vpp</artifactId>
+  <version>0.8.0-SNAPSHOT</version>
+  <packaging>bundle</packaging>
+  <properties>
+    <netconf.version>1.2.0-SNAPSHOT</netconf.version>
+    <checkstyle.location>${project.parent.basedir}/src/main/resources</checkstyle.location>
+  </properties>
+  <dependencyManagement>
+    <dependencies>
+      <dependency>
+        <groupId>org.opendaylight.mdsal.model</groupId>
+        <artifactId>mdsal-model-artifacts</artifactId>
+        <version>0.10.0-SNAPSHOT</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+      <dependency>
+        <groupId>org.opendaylight.netconf</groupId>
+        <artifactId>netconf-artifacts</artifactId>
+        <version>${netconf.version}</version>
+        <type>pom</type>
+        <scope>import</scope>
+      </dependency>
+    </dependencies>
+  </dependencyManagement>
+  <dependencies>
+    <dependency>
+      <groupId>org.opendaylight.neutron</groupId>
+      <artifactId>neutron-spi</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.neutron</groupId>
+      <artifactId>model</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.neutron</groupId>
+      <artifactId>neutron-hostconfig-utils</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.osgi</groupId>
+      <artifactId>org.osgi.core</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>config-api</artifactId>
+      <version>0.6.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.netconf</groupId>
+      <artifactId>sal-netconf-connector</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.opendaylight.controller</groupId>
+      <artifactId>sal-binding-broker-impl</artifactId>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <inherited>true</inherited>
+        <configuration>
+          <compilerArgs>
+            <arg>-Xlint:unchecked</arg>
+            <arg>-Xlint:deprecation</arg>
+          </compilerArgs>
+        </configuration>
+      </plugin>
+      <plugin>
+        <artifactId>maven-checkstyle-plugin</artifactId>
+        <configuration>
+          <excludes>org/opendaylight/yang/gen/**,**/yang/**</excludes>
+          <consoleOutput>true</consoleOutput>
+          <propertyExpansion>checkstyle.violationSeverity=error</propertyExpansion>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>check</goal>
+            </goals>
+            <phase>process-sources</phase>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-Name>${project.groupId}.${project.artifactId}</Bundle-Name>
+            <Import-Package>*</Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+  <scm>
+    <connection>scm:git:ssh://git.opendaylight.org:29418/neutron.git</connection>
+    <developerConnection>scm:git:ssh://git.opendaylight.org:29418/neutron.git</developerConnection>
+    <tag>HEAD</tag>
+    <url>https://wiki.opendaylight.org/view/NeutronNorthBound:Main</url>
+  </scm>
+  <distributionManagement>
+    <!-- Site deployment -->
+    <site>
+      <id>opendaylight-site</id>
+      <url>${nexus.site.url}/${project.artifactId}/</url>
+    </site>
+  </distributionManagement>
+
+  <!--
+      Maven Site Configuration
+
+      The following configuration is necessary for maven-site-plugin to
+      correctly identify the correct deployment path for OpenDaylight Maven
+      sites.
+  -->
+  <url>${odl.site.url}/${project.groupId}/${stream}/${project.artifactId}/</url>
+</project>
diff --git a/neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/HostconfigUtil.java b/neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/HostconfigUtil.java
new file mode 100644 (file)
index 0000000..72e0a93
--- /dev/null
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.neutron.hostconfig.vpp;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NodeId;
+
+public class HostconfigUtil {
+
+    static final String L2_HOST_TYPE = "ODL L2";
+    private static final String VHOST_USER = "vhostuser";
+    private static final String VNIC_TYPE = "normal";
+    private static final String HAS_DATAPATH_TYPE_NETDEV = "False";
+    private static final String SUPPORT_VHOST_USER = "True";
+    private static final List<String> SUPPORTED_NET_TYPES = Lists.newArrayList("local", "vlan", "vxlan", "gre");
+
+    public static Map<String, String> createHostconfigsDataFor(NodeId nodeId, SocketInfo socketInfo) {
+        Preconditions.checkNotNull(nodeId);
+        Preconditions.checkNotNull(socketInfo);
+        JsonObject odlL2 = new JsonObject();
+        odlL2.add("allowed_network_types", buildSupportedNetworkTypes());
+        odlL2.add("supported_vnic_types", buildSupportedVnicTypes(socketInfo));
+        Map<String, String> hostConfigs = new HashMap<>();
+        hostConfigs.put(L2_HOST_TYPE, odlL2.toString());
+        return hostConfigs;
+    }
+
+    private static JsonArray buildSupportedNetworkTypes() {
+        JsonArray networkTypes = new JsonArray();
+        SUPPORTED_NET_TYPES.forEach(networkTypes::add);
+        return networkTypes;
+    }
+
+    private static JsonArray buildSupportedVnicTypes(SocketInfo socketInfo) {
+        JsonObject supportedVnicType = new JsonObject();
+        supportedVnicType.addProperty("vnic_type", VNIC_TYPE);
+        supportedVnicType.addProperty("vif_type", VHOST_USER);
+        supportedVnicType.add("vif_details", buildVifDetails(socketInfo));
+        JsonArray supportedVnicTypes = new JsonArray();
+        supportedVnicTypes.add(supportedVnicType);
+        return supportedVnicTypes;
+    }
+
+    private static JsonObject buildVifDetails(SocketInfo socketInfo) {
+        JsonObject vifDetails = new JsonObject();
+        vifDetails.addProperty("has_datapath_type_netdev", HAS_DATAPATH_TYPE_NETDEV);
+        vifDetails.addProperty("support_vhost_user", SUPPORT_VHOST_USER);
+        vifDetails.addProperty("port_prefix", socketInfo.getSocketPrefix());
+        vifDetails.addProperty("vhostuser_socket_dir", socketInfo.getSocketPath());
+        vifDetails.addProperty("vhostuser_mode", socketInfo.getVhostuserMode());
+        vifDetails.addProperty("vhostuser_socket", socketInfo.getVhostUserSocket());
+        return vifDetails;
+    }
+}
diff --git a/neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/NeutronHostconfigVppListener.java b/neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/NeutronHostconfigVppListener.java
new file mode 100644 (file)
index 0000000..42e34a9
--- /dev/null
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2017 Intel Corporation. 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.neutron.hostconfig.vpp;
+
+import com.google.common.base.Preconditions;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataBroker;
+import org.opendaylight.controller.md.sal.binding.api.DataObjectModification;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.neutron.hostconfig.utils.NeutronHostconfigUtils;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
+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.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.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class NeutronHostconfigVppListener implements ClusteredDataTreeChangeListener<Node> {
+
+    private static final Logger LOG = LoggerFactory.getLogger(NeutronHostconfigVppListener.class);
+    private final DataBroker dataBroker;
+    private final NeutronHostconfigUtils neutronHostconfig;
+    private ListenerRegistration<DataTreeChangeListener<Node>> listenerRegistration;
+    private final ExecutorService executorService = Executors.newFixedThreadPool(1);
+
+    private static final TopologyId TOPOLOGY_NETCONF = new TopologyId("topology-netconf");
+    private static final QName V3PO_CAPABILITY = QName.create(URI.create("urn:opendaylight:params:xml:ns:yang:v3po"),
+            NeutronHostconfigUtils.parseDate("2016-12-14"), "v3po");
+    private static final QName INTERFACES_CAPABILITY =
+            QName.create(URI.create("urn:ietf:params:xml:ns:yang:ietf-interfaces"),
+                    NeutronHostconfigUtils.parseDate("2014-05-08"), "ietf-interfaces");
+    private static final List<QName> REQUIRED_CAPABILITIES = new ArrayList<>();
+    private SocketInfo socketInfo;
+
+    public NeutronHostconfigVppListener(final DataBroker dataBroker, String spath, String sname, String vhostMode) {
+        LOG.info("Initializing Neutron-Hostconfig-Vpp-Listener");
+        this.dataBroker = Preconditions.checkNotNull(dataBroker);
+        vhostMode = Preconditions.checkNotNull(vhostMode).toLowerCase();
+        Preconditions.checkArgument(vhostMode.equals("server") || vhostMode.equals("client"),
+                "Supported values for vhostuser-mode are client and server.");
+        this.socketInfo =
+                new SocketInfo(Preconditions.checkNotNull(spath), Preconditions.checkNotNull(sname), vhostMode);
+        this.neutronHostconfig = new NeutronHostconfigUtils(dataBroker);
+        REQUIRED_CAPABILITIES.add(V3PO_CAPABILITY);
+        REQUIRED_CAPABILITIES.add(INTERFACES_CAPABILITY);
+    }
+
+    @Override
+    public void onDataTreeChanged(@Nonnull Collection<DataTreeModification<Node>> changes) {
+        LOG.info("onDataTreeChanged: Received Data Tree Changed ...", changes);
+        executorService.submit(() -> {
+            for (DataTreeModification<Node> change : Preconditions.checkNotNull(changes, "Changes may not be null!")) {
+                final InstanceIdentifier<Node> key = change.getRootPath().getRootIdentifier();
+                final DataObjectModification<Node> mod = change.getRootNode();
+                LOG.info("onDataTreeChanged: Received Data Tree Changed Update of Type={} for Key={}",
+                        mod.getModificationType(), key);
+                switch (mod.getModificationType()) {
+                    case SUBTREE_MODIFIED:
+                        if (validateVppNode(mod.getDataAfter())) {
+                            updateHostConfig(mod.getDataAfter(), NeutronHostconfigUtils.Action.UPDATE);
+                        } else {
+                            updateHostConfig(mod.getDataBefore(), NeutronHostconfigUtils.Action.DELETE);
+                        }
+                        break;
+                    case DELETE:
+                        updateHostConfig(mod.getDataBefore(), NeutronHostconfigUtils.Action.DELETE);
+                        break;
+                    case WRITE:
+                        if (validateVppNode(mod.getDataAfter())) {
+                            updateHostConfig(mod.getDataAfter(), NeutronHostconfigUtils.Action.ADD);
+                        }
+                        break;
+                    default:
+                }
+            }
+        });
+    }
+
+    public void init() {
+        LOG.info("Initializing {}", getClass().getSimpleName());
+        DataTreeIdentifier<Node> dataTreeIdentifier = new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
+                InstanceIdentifier.builder(NetworkTopology.class)
+                    .child(Topology.class, new TopologyKey(TOPOLOGY_NETCONF))
+                    .child(Node.class)
+                    .build());
+        listenerRegistration =
+                dataBroker.registerDataTreeChangeListener(dataTreeIdentifier, NeutronHostconfigVppListener.this);
+        LOG.info("Registered listener to netconf nodes {}.", dataTreeIdentifier.getRootIdentifier());
+    }
+
+    private void updateHostConfig(Node node, NeutronHostconfigUtils.Action action) {
+        for (Map.Entry<String, String> entry : HostconfigUtil.createHostconfigsDataFor(node.getNodeId(), socketInfo)
+            .entrySet()) {
+            LOG.info("Updating hostconfig for node {}. Action: {}.", node.getKey(), action);
+            neutronHostconfig.updateMdsal(neutronHostconfig.buildHostConfigInfo(node.getNodeId().getValue(),
+                    entry.getKey(), entry.getValue()), action);
+        }
+    }
+
+    private boolean validateVppNode(Node node) {
+        LOG.info("Registering new node {}", node.getNodeId().getValue());
+        NetconfNode netconfNode = node.getAugmentation(NetconfNode.class);
+        if (netconfNode == null) {
+            LOG.warn("Node {} is not a netconf device", node.getNodeId().getValue());
+            return false;
+        }
+        NetconfNodeConnectionStatus.ConnectionStatus connectionStatus = netconfNode.getConnectionStatus();
+        switch (connectionStatus) {
+            case Connecting:
+                LOG.info("Connecting device {} ...", node.getNodeId().getValue());
+                break;
+            case Connected:
+                if (netconfNode.getAvailableCapabilities() == null
+                        || netconfNode.getAvailableCapabilities().getAvailableCapability() == null
+                        || netconfNode.getAvailableCapabilities().getAvailableCapability().isEmpty()) {
+                    LOG.warn("Node {} does not contain any capabilities", node.getNodeId().getValue());
+                    break;
+                }
+                if (!capabilityCheck(netconfNode.getAvailableCapabilities().getAvailableCapability())) {
+                    LOG.warn("Node {} does not contain all capabilities required by vpp-renderer",
+                            node.getNodeId().getValue());
+                    break;
+                }
+                LOG.info("VPP node connected {}", node.getNodeId().getValue());
+                return true;
+            case UnableToConnect:
+                LOG.warn("Unable to connect to node {}.", node.getNodeId().getValue());
+                break;
+            default:
+        }
+        return false;
+    }
+
+    private boolean capabilityCheck(final List<AvailableCapability> capabilities) {
+        final List<String> availableCapabilities =
+                capabilities.stream().map(AvailableCapability::getCapability).collect(Collectors.toList());
+        return REQUIRED_CAPABILITIES.stream().map(QName::toString).allMatch(availableCapabilities::contains);
+    }
+
+    public void close() throws Exception {
+        if (listenerRegistration != null) {
+            listenerRegistration.close();
+            LOG.info("HostConfig listener Closed");
+        }
+    }
+}
diff --git a/neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/SocketInfo.java b/neutron-hostconfig/vpp/src/main/java/org/opendaylight/neutron/hostconfig/vpp/SocketInfo.java
new file mode 100644 (file)
index 0000000..5c07fef
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.neutron.hostconfig.vpp;
+
+import com.google.common.base.Preconditions;
+
+public class SocketInfo {
+
+    private final String socketPath;
+    private final String socketPrefix;
+    private final String vhostuserMode;
+    private static final String PORT_ID = "$PORT_ID";
+
+    public SocketInfo(String socketPath, String socketPrefix, String vhostuserMode) {
+        this.socketPath = Preconditions.checkNotNull(socketPath);
+        this.socketPrefix = Preconditions.checkNotNull(socketPrefix);
+        this.vhostuserMode = Preconditions.checkNotNull(vhostuserMode);
+    }
+
+    public String getSocketPath() {
+        return socketPath;
+    }
+
+    public String getSocketPrefix() {
+        return socketPrefix;
+    }
+
+    public String getVhostuserMode() {
+        return vhostuserMode;
+    }
+
+    public String getVhostUserSocket() {
+        return new StringBuilder().append(socketPath).append(socketPrefix).append(PORT_ID).toString();
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((socketPath == null) ? 0 : socketPath.hashCode());
+        result = prime * result + ((socketPrefix == null) ? 0 : socketPrefix.hashCode());
+        result = prime * result + ((vhostuserMode == null) ? 0 : vhostuserMode.hashCode());
+        return result;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        SocketInfo other = (SocketInfo) obj;
+        if (socketPath == null) {
+            if (other.socketPath != null) {
+                return false;
+            }
+        } else if (!socketPath.equals(other.socketPath)) {
+            return false;
+        }
+        if (socketPrefix == null) {
+            if (other.socketPrefix != null) {
+                return false;
+            }
+        } else if (!socketPrefix.equals(other.socketPrefix)) {
+            return false;
+        }
+        if (vhostuserMode == null) {
+            if (other.vhostuserMode != null) {
+                return false;
+            }
+        } else if (!vhostuserMode.equals(other.vhostuserMode)) {
+            return false;
+        }
+        return true;
+    }
+}
diff --git a/neutron-hostconfig/vpp/src/main/resources/config/org.opendaylight.neutron.hostconfig.vpp b/neutron-hostconfig/vpp/src/main/resources/config/org.opendaylight.neutron.hostconfig.vpp
new file mode 100644 (file)
index 0000000..58ce0c2
--- /dev/null
@@ -0,0 +1,19 @@
+# INITIAL VPP HOSTCONFIG CONFIGURATION
+#
+# Uncomment lines on the bottom of this file to change
+# default values of neutron-hostconfig-vpp bundle config.
+# The file should be placed to karaf_root_dir/etc
+# vhostuser-mode can be set to client or server
+#   server - VPP owns the connection creates a socket
+#            file on hosting machine
+#   client - VPP accepts the connection and looks for
+#            a socket file on hosting machine
+#
+# socket-dir - directory where socket files should be
+#              created or searched
+# socket-prefix - name prefix of created socket files,
+#                 e.g. socket_<port-uuid>
+#
+#vhostuser-mode=server
+#socket-dir=/tmp/
+#socket-prefix = socket_
diff --git a/neutron-hostconfig/vpp/src/main/resources/org/opendaylight/blueprint/neutron-hostconfig-vpp.xml b/neutron-hostconfig/vpp/src/main/resources/org/opendaylight/blueprint/neutron-hostconfig-vpp.xml
new file mode 100644 (file)
index 0000000..e2205fe
--- /dev/null
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
+    xmlns:odl="http://opendaylight.org/xmlns/blueprint/v1.0.0"
+    xmlns:cm="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
+    odl:use-default-for-reference-types="true">
+
+  <reference id="dataBroker"
+      interface="org.opendaylight.controller.md.sal.binding.api.DataBroker"
+      odl:type="default" />
+
+  <cm:property-placeholder persistent-id="org.opendaylight.neutron.hostconfig.vpp" update-strategy="none">
+    <cm:default-properties>
+      <cm:property name="vhostuser-mode" value="server"/>
+      <cm:property name="socket-dir" value="/tmp/"/>
+      <cm:property name="socket-prefix" value="socket_"/>
+    </cm:default-properties>
+  </cm:property-placeholder>
+
+  <bean id="neutronHostconfigVpp"
+     class="org.opendaylight.neutron.hostconfig.vpp.NeutronHostconfigVppListener"
+     init-method="init"
+     destroy-method="close">
+    <argument ref="dataBroker"/>
+    <argument value="${socket-dir}"/>
+    <argument value="${socket-prefix}"/>
+    <argument value="${vhostuser-mode}"/>
+  </bean>
+
+</blueprint>
\ No newline at end of file
diff --git a/neutron-hostconfig/vpp/src/test/java/org/opendaylight/neutron/hostconfig/vpp/HostconfigsDataBrokerTest.java b/neutron-hostconfig/vpp/src/test/java/org/opendaylight/neutron/hostconfig/vpp/HostconfigsDataBrokerTest.java
new file mode 100644 (file)
index 0000000..20040f7
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.neutron.hostconfig.vpp;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+
+import org.opendaylight.controller.md.sal.binding.test.AbstractDataChangeListenerTest;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
+import org.opendaylight.yangtools.yang.binding.YangModuleInfo;
+import org.opendaylight.yangtools.yang.binding.util.BindingReflections;
+
+public class HostconfigsDataBrokerTest extends AbstractDataChangeListenerTest {
+
+    @Override
+    protected Iterable<YangModuleInfo> getModuleInfos() throws Exception {
+        Builder<YangModuleInfo> moduleInfoSet = ImmutableSet.<YangModuleInfo>builder();
+        for (Class<?> moduleClass : ImmutableList.<Class<?>>of(
+            NetworkTopology.class, Neutron.class, NetconfNode.class)) {
+            YangModuleInfo moduleInfo = BindingReflections.getModuleInfo(moduleClass);
+            Preconditions.checkNotNull(moduleInfo, "Module Info for %s is not available.", moduleClass);
+            collectYangModuleInfo(moduleInfo, moduleInfoSet);
+        }
+        return moduleInfoSet.build();
+    }
+
+    private static void collectYangModuleInfo(final YangModuleInfo moduleInfo,
+            final Builder<YangModuleInfo> moduleInfoSet) {
+        moduleInfoSet.add(moduleInfo);
+        for (YangModuleInfo dependency : moduleInfo.getImportedModules()) {
+            collectYangModuleInfo(dependency, moduleInfoSet);
+        }
+    }
+}
diff --git a/neutron-hostconfig/vpp/src/test/java/org/opendaylight/neutron/hostconfig/vpp/NeutronHostconfigVppListenerTest.java b/neutron-hostconfig/vpp/src/test/java/org/opendaylight/neutron/hostconfig/vpp/NeutronHostconfigVppListenerTest.java
new file mode 100644 (file)
index 0000000..883d91e
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2017 Cisco Systems, Inc. and others. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.neutron.hostconfig.vpp;
+
+import com.google.common.util.concurrent.SettableFuture;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+import javax.annotation.Nonnull;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.controller.md.sal.binding.api.ClusteredDataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataObjectModification.ModificationType;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeChangeListener;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeIdentifier;
+import org.opendaylight.controller.md.sal.binding.api.DataTreeModification;
+import org.opendaylight.controller.md.sal.binding.api.WriteTransaction;
+import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNode;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeConnectionStatus.ConnectionStatus;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.AvailableCapabilitiesBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapability;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.connection.status.available.capabilities.AvailableCapabilityBuilder;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.hostconfig.rev150712.hostconfig.attributes.Hostconfigs;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.hostconfig.rev150712.hostconfig.attributes.hostconfigs.Hostconfig;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.hostconfig.rev150712.hostconfig.attributes.hostconfigs.HostconfigKey;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.neutron.rev150712.Neutron;
+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.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
+
+public class NeutronHostconfigVppListenerTest extends HostconfigsDataBrokerTest
+        implements ClusteredDataTreeChangeListener<Hostconfig> {
+
+    private static final String V3PO = "(urn:opendaylight:params:xml:ns:yang:v3po?revision=2016-12-14)v3po";
+    private static final String INTERFACES =
+            "(urn:ietf:params:xml:ns:yang:ietf-interfaces?revision=2014-05-08)ietf-interfaces";
+    private static final NodeId NODE_ID = new NodeId("node1");
+    private static final String SOCKET_PATH = "/tmp";
+    private static final String SOCKET_PREFIX = "socket_";
+    private static final String VHOSTUSER_MODE = "server";
+    private SettableFuture<Integer> sf;
+    private ListenerRegistration<DataTreeChangeListener<Hostconfig>> listenerRegistration;
+    private NeutronHostconfigVppListener neutronHostconfigVppListener;
+
+    @Before
+    public void init() throws InterruptedException, ExecutionException {
+        DataTreeIdentifier<Hostconfig> dataTreeIdentifier = new DataTreeIdentifier<>(LogicalDatastoreType.OPERATIONAL,
+                hostConfigIid(NODE_ID).firstIdentifierOf(Hostconfigs.class).builder().child(Hostconfig.class).build());
+        listenerRegistration = getDataBroker().registerDataTreeChangeListener(dataTreeIdentifier,
+                NeutronHostconfigVppListenerTest.this);
+        neutronHostconfigVppListener =
+                new NeutronHostconfigVppListener(getDataBroker(), SOCKET_PATH, SOCKET_PREFIX, VHOSTUSER_MODE);
+        neutronHostconfigVppListener.init();
+        sf = SettableFuture.create();
+    }
+
+    @Test
+    public void testPutCreateParentsSuccess() throws Exception {
+        InstanceIdentifier<Node> iid = InstanceIdentifier.builder(NetworkTopology.class)
+            .child(Topology.class, new TopologyKey(new TopologyId("topology-netconf")))
+            .child(Node.class, new NodeKey(new NodeId(NODE_ID)))
+            .build();
+        Node node1 = createNetconfNode(NODE_ID, V3PO, INTERFACES);
+        WriteTransaction writeTx = getDataBroker().newWriteOnlyTransaction();
+        writeTx.put(LogicalDatastoreType.OPERATIONAL, iid, node1, true);
+        writeTx.submit().get();
+        Assert.assertEquals(sf.get(), Integer.valueOf(1));
+        sf = SettableFuture.create();
+        writeTx = getDataBroker().newWriteOnlyTransaction();
+        writeTx.delete(LogicalDatastoreType.OPERATIONAL, iid);
+        writeTx.submit().get();
+        Assert.assertEquals(sf.get(), Integer.valueOf(2));
+    }
+
+    private InstanceIdentifier<Hostconfig> hostConfigIid(@Nonnull NodeId nodeId) {
+        return InstanceIdentifier.builder(Neutron.class)
+            .child(Hostconfigs.class)
+            .child(Hostconfig.class, new HostconfigKey(nodeId.getValue(), HostconfigUtil.L2_HOST_TYPE))
+            .build();
+    }
+
+    private Node createNetconfNode(NodeId nodeId, String... capabilities) {
+        List<AvailableCapability> caps = Arrays.asList(capabilities)
+            .stream()
+            .map(name -> new AvailableCapabilityBuilder().setCapability(name).build())
+            .collect(Collectors.toList());
+        NetconfNode netconfNode = new NetconfNodeBuilder().setConnectionStatus(ConnectionStatus.Connected)
+            .setAvailableCapabilities(new AvailableCapabilitiesBuilder().setAvailableCapability(caps).build())
+            .build();
+        return new NodeBuilder().setNodeId(nodeId).addAugmentation(NetconfNode.class, netconfNode).build();
+    }
+
+    @Override
+    public void onDataTreeChanged(Collection<DataTreeModification<Hostconfig>> hostConfigDtm) {
+        for (DataTreeModification<Hostconfig> dtm : hostConfigDtm) {
+            ModificationType mod = dtm.getRootNode().getModificationType();
+            switch (mod) {
+                case WRITE: {
+                    sf.set(1);
+                    break;
+                }
+                case DELETE: {
+                    sf.set(2);
+                    break;
+                }
+                default:
+            }
+        }
+    }
+
+    @After
+    public void close() throws Exception {
+        if (listenerRegistration != null) {
+            listenerRegistration = null;
+        }
+        if (neutronHostconfigVppListener != null) {
+            neutronHostconfigVppListener.close();
+        }
+    }
+}