Moving from a searching a string for capabilities to a Set.
ONOS-5947 ONOS-5948
Change-Id: Icac65263691e624dc74cfbc03c27e3974b935da2
diff --git a/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfSession.java b/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfSession.java
index 31481d2..4fffde6 100644
--- a/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfSession.java
+++ b/protocols/netconf/api/src/main/java/org/onosproject/netconf/NetconfSession.java
@@ -20,8 +20,12 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* NETCONF session object that allows NETCONF operations on top with the physical
@@ -315,17 +319,45 @@
String getSessionId();
/**
- * Gets the capabilities of the Netconf server associated to this session.
+ * Gets the capabilities of the remote Netconf device associated to this
+ * session.
+ *
+ * @return Network capabilities as strings in a Set.
+ *
+ * @since 1.10.0
+ * Note: default implementation provided with the interface
+ * will be removed when {@code getServerCapabilities()} reaches
+ * deprecation grace period.
+ */
+ default Set<String> getDeviceCapabilitiesSet() {
+ // default implementation should be removed in the future
+ Set<String> capabilities = new LinkedHashSet<>();
+ Matcher capabilityMatcher =
+ Pattern.compile("<capability>\\s*(.*?)\\s*</capability>")
+ .matcher(getServerCapabilities());
+ while (capabilityMatcher.find()) {
+ capabilities.add(capabilityMatcher.group(1));
+ }
+ return capabilities;
+ }
+
+ /**
+ * Gets the capabilities of the Netconf server (remote device) associated
+ * to this session.
*
* @return Network capabilities as a string.
+ * @deprecated 1.10.0 use {@link #getDeviceCapabilitiesSet()} instead
*/
+ @Deprecated
String getServerCapabilities();
/**
* Sets the ONOS side capabilities.
*
* @param capabilities list of capabilities the device has.
+ * @deprecated 1.10.0 use {@link #setOnosCapabilities(Iterable)} instead
*/
+ @Deprecated
void setDeviceCapabilities(List<String> capabilities);
/**
@@ -340,6 +372,17 @@
Logger log = LoggerFactory.getLogger(NetconfSession.class);
log.error("Not implemented/exposed by the underlying session implementation");
}
+ /**
+ * Sets the ONOS side capabilities.
+ *
+ * @param capabilities list of capabilities ONOS has.
+ *
+ * @since 1.10.0
+ */
+ default void setOnosCapabilities(Iterable<String> capabilities) {
+ // default implementation should be removed in the future
+ // no-op
+ }
/**
* Remove a listener from the underlying stream handler implementation.
diff --git a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfSessionImpl.java b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfSessionImpl.java
index 7da836c..457bcf2 100644
--- a/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfSessionImpl.java
+++ b/protocols/netconf/ctl/src/main/java/org/onosproject/netconf/ctl/NetconfSessionImpl.java
@@ -33,17 +33,20 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Pattern;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
/**
* Implementation of a NETCONF session to talk to a device.
@@ -85,16 +88,27 @@
private static final String SUBSCRIPTION_SUBTREE_FILTER_OPEN =
"<filter xmlns:base10=\"urn:ietf:params:xml:ns:netconf:base:1.0\" base10:type=\"subtree\">";
- private static Pattern msgIdPattern = Pattern.compile("(message-id=\"[0-9]+\")");
+ private static final String INTERLEAVE_CAPABILITY_STRING = "urn:ietf:params:netconf:capability:interleave:1.0";
+ private static final String CAPABILITY_REGEX = "<capability>\\s*(.*?)\\s*</capability>";
+ private static final Pattern CAPABILITY_REGEX_PATTERN = Pattern.compile(CAPABILITY_REGEX);
+
+ private static final String SESSION_ID_REGEX = "<session-id>\\s*(.*?)\\s*</session-id>";
+ private static final Pattern SESSION_ID_REGEX_PATTERN = Pattern.compile(SESSION_ID_REGEX);
+
+ private String sessionID;
private final AtomicInteger messageIdInteger = new AtomicInteger(0);
private Connection netconfConnection;
private NetconfDeviceInfo deviceInfo;
private Session sshSession;
private boolean connectionActive;
- private List<String> deviceCapabilities =
+ private Iterable<String> onosCapabilities =
Collections.singletonList("urn:ietf:params:netconf:base:1.0");
- private String serverCapabilities;
+
+ /* NOTE: the "serverHelloResponseOld" is deprecated in 1.10.0 and should eventually be removed */
+ @Deprecated
+ private String serverHelloResponseOld;
+ private final Set<String> deviceCapabilities = new LinkedHashSet<>();
private NetconfStreamHandler streamHandler;
private Map<Integer, CompletableFuture<String>> replies;
private List<String> errorReplies;
@@ -178,7 +192,7 @@
@Beta
private void startSubscriptionConnection(String filterSchema) throws NetconfException {
- if (!serverCapabilities.contains("interleave")) {
+ if (!deviceCapabilities.contains(INTERLEAVE_CAPABILITY_STRING)) {
throw new NetconfException("Device" + deviceInfo + "does not support interleave");
}
String reply = sendRequest(createSubscriptionString(filterSchema));
@@ -237,7 +251,20 @@
}
private void sendHello() throws NetconfException {
- serverCapabilities = sendRequest(createHelloString());
+ serverHelloResponseOld = sendRequest(createHelloString());
+ Matcher capabilityMatcher = CAPABILITY_REGEX_PATTERN.matcher(serverHelloResponseOld);
+ while (capabilityMatcher.find()) {
+ deviceCapabilities.add(capabilityMatcher.group(1));
+ }
+ sessionID = String.valueOf(-1);
+ Matcher sessionIDMatcher = SESSION_ID_REGEX_PATTERN.matcher(serverHelloResponseOld);
+ if (sessionIDMatcher.find()) {
+ sessionID = sessionIDMatcher.group(1);
+ } else {
+ throw new NetconfException("Missing SessionID in server hello " +
+ "reponse.");
+ }
+
}
private String createHelloString() {
@@ -246,7 +273,7 @@
hellobuffer.append("\n");
hellobuffer.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n");
hellobuffer.append(" <capabilities>\n");
- deviceCapabilities.forEach(
+ onosCapabilities.forEach(
cap -> hellobuffer.append(" <capability>")
.append(cap)
.append("</capability>\n"));
@@ -594,29 +621,32 @@
@Override
public String getSessionId() {
- if (serverCapabilities.contains("<session-id>")) {
- String[] outer = serverCapabilities.split("<session-id>");
- Preconditions.checkArgument(outer.length != 1,
- "Error in retrieving the session id");
- String[] value = outer[1].split("</session-id>");
- Preconditions.checkArgument(value.length != 1,
- "Error in retrieving the session id");
- return value[0];
- } else {
- return String.valueOf(-1);
- }
+ return sessionID;
}
@Override
+ public Set<String> getDeviceCapabilitiesSet() {
+ return Collections.unmodifiableSet(deviceCapabilities);
+ }
+
+ @Deprecated
+ @Override
public String getServerCapabilities() {
- return serverCapabilities;
+ return serverHelloResponseOld;
+ }
+
+ @Deprecated
+ @Override
+ public void setDeviceCapabilities(List<String> capabilities) {
+ onosCapabilities = capabilities;
}
@Override
- public void setDeviceCapabilities(List<String> capabilities) {
- deviceCapabilities = capabilities;
+ public void setOnosCapabilities(Iterable<String> capabilities) {
+ onosCapabilities = capabilities;
}
+
@Override
public void addDeviceOutputListener(NetconfDeviceOutputEventListener listener) {
streamHandler.addDeviceEventListener(listener);
diff --git a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/NetconfSessionImplTest.java b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/NetconfSessionImplTest.java
index 2dcb14f..86822a6 100644
--- a/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/NetconfSessionImplTest.java
+++ b/protocols/netconf/ctl/src/test/java/org/onosproject/netconf/ctl/NetconfSessionImplTest.java
@@ -15,7 +15,9 @@
*/
package org.onosproject.netconf.ctl;
+import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertFalse;
@@ -24,6 +26,7 @@
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Callable;
@@ -52,6 +55,8 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.google.common.collect.ImmutableList;
+
/**
* Unit tests for NetconfSession.
*
@@ -86,6 +91,20 @@
+ "</edit-config>\n"
+ "</rpc>]]>]]>";
+ static final List<String> DEFAULT_CAPABILITIES = ImmutableList.<String>builder()
+ .add("urn:ietf:params:netconf:base:1.0")
+ .add("urn:ietf:params:netconf:base:1.1")
+ .add("urn:ietf:params:netconf:capability:writable-running:1.0")
+ .add("urn:ietf:params:netconf:capability:candidate:1.0")
+ .add("urn:ietf:params:netconf:capability:startup:1.0")
+ .add("urn:ietf:params:netconf:capability:rollback-on-error:1.0")
+ .add("urn:ietf:params:netconf:capability:interleave:1.0")
+ .add("urn:ietf:params:netconf:capability:notification:1.0")
+ .add("urn:ietf:params:netconf:capability:validate:1.0")
+ .add("urn:ietf:params:netconf:capability:validate:1.1")
+ .build();
+
+
private static NetconfSession session1;
private static NetconfSession session2;
private static SshServer sshServerNetconf;
@@ -93,7 +112,7 @@
@BeforeClass
public static void setUp() throws Exception {
sshServerNetconf = SshServer.setUpDefaultServer();
- List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<NamedFactory<UserAuth>>();
+ List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<>();
userAuthFactories.add(new UserAuthPassword.Factory());
sshServerNetconf.setUserAuthFactories(userAuthFactories);
sshServerNetconf.setPasswordAuthenticator(
@@ -120,11 +139,13 @@
log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session1.getSessionId());
assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("-1"));
assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("0"));
+ assertThat(session1.getDeviceCapabilitiesSet(), containsInAnyOrder(DEFAULT_CAPABILITIES.toArray()));
session2 = new NetconfSessionImpl(deviceInfo);
log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session2.getSessionId());
assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("-1"));
assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("0"));
+ assertThat(session2.getDeviceCapabilitiesSet(), containsInAnyOrder(DEFAULT_CAPABILITIES.toArray()));
}
@AfterClass
@@ -262,8 +283,8 @@
NCCopyConfigCallable testCopyConfig1 = new NCCopyConfigCallable(session1, RUNNING, "candidate");
NCCopyConfigCallable testCopyConfig2 = new NCCopyConfigCallable(session1, RUNNING, "startup");
- FutureTask<Boolean> futureCopyConfig1 = new FutureTask<Boolean>(testCopyConfig1);
- FutureTask<Boolean> futureCopyConfig2 = new FutureTask<Boolean>(testCopyConfig2);
+ FutureTask<Boolean> futureCopyConfig1 = new FutureTask<>(testCopyConfig1);
+ FutureTask<Boolean> futureCopyConfig2 = new FutureTask<>(testCopyConfig2);
ExecutorService executor = Executors.newFixedThreadPool(2);
log.info("Starting concurrent execution of copy-config through same session");
@@ -288,8 +309,8 @@
NCCopyConfigCallable testCopySession1 = new NCCopyConfigCallable(session1, RUNNING, "candidate");
NCCopyConfigCallable testCopySession2 = new NCCopyConfigCallable(session2, RUNNING, "candidate");
- FutureTask<Boolean> futureCopySession1 = new FutureTask<Boolean>(testCopySession1);
- FutureTask<Boolean> futureCopySession2 = new FutureTask<Boolean>(testCopySession2);
+ FutureTask<Boolean> futureCopySession1 = new FutureTask<>(testCopySession1);
+ FutureTask<Boolean> futureCopySession2 = new FutureTask<>(testCopySession2);
ExecutorService executor = Executors.newFixedThreadPool(2);
log.info("Starting concurrent execution of copy-config through 2 different sessions");
@@ -311,20 +332,17 @@
public static String getTestHelloReply(Optional<Long> sessionId) {
+ return getTestHelloReply(DEFAULT_CAPABILITIES, sessionId);
+ }
+
+ public static String getTestHelloReply(Collection<String> capabilities, Optional<Long> sessionId) {
StringBuffer sb = new StringBuffer();
sb.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
sb.append("<capabilities>");
- sb.append("<capability>urn:ietf:params:netconf:base:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:base:1.1</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.0</capability>");
- sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>");
+ capabilities.forEach(capability -> {
+ sb.append("<capability>").append(capability).append("</capability>");
+ });
sb.append("</capabilities>");
if (sessionId.isPresent()) {
sb.append("<session-id>");