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>
<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>
</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>
<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>
<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>
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;
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:
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);
+ }
+ }
}
--- /dev/null
+<?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>
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+/*
+ * 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");
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}
--- /dev/null
+# 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_
--- /dev/null
+<?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
--- /dev/null
+/*
+ * 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}