blob: 661c290474ed821a05594e9f48d6b78ad1c49415 [file] [log] [blame]
Sean Condond2c8d472017-02-17 17:09:39 +00001/*
2 * Copyright 2017-present Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.netconf.ctl;
17
18import static org.junit.Assert.assertNotNull;
19import static org.junit.Assert.assertTrue;
20import static org.junit.Assert.fail;
21
22import java.util.ArrayList;
23import java.util.Arrays;
24import java.util.List;
25import java.util.Optional;
26import java.util.concurrent.Callable;
27import java.util.concurrent.ExecutorService;
28import java.util.concurrent.Executors;
29import java.util.concurrent.FutureTask;
30import java.util.regex.Pattern;
31
32import org.apache.sshd.SshServer;
33import org.apache.sshd.common.NamedFactory;
34import org.apache.sshd.server.Command;
35import org.apache.sshd.server.PasswordAuthenticator;
36import org.apache.sshd.server.UserAuth;
37import org.apache.sshd.server.auth.UserAuthPassword;
38import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
39import org.apache.sshd.server.session.ServerSession;
40import org.junit.AfterClass;
41import org.junit.BeforeClass;
42import org.junit.Test;
43import org.onlab.junit.TestTools;
44import org.onlab.packet.Ip4Address;
45import org.onosproject.netconf.NetconfDeviceInfo;
46import org.onosproject.netconf.NetconfException;
47import org.onosproject.netconf.NetconfSession;
48import org.slf4j.Logger;
49import org.slf4j.LoggerFactory;
50
51/**
52 * Unit tests for NetconfSession.
53 *
54 * Sets up an SSH Server with Apache SSHD and connects to it using 2 clients
55 * Truly verifies that the NETCONF flows are compliant with a NETCONF server.
56 */
57public class NetconfSessionImplTest {
58 private static final Logger log = LoggerFactory
59 .getLogger(NetconfStreamThread.class);
60
61 private static final int PORT_NUMBER = TestTools.findAvailablePort(50830);
62 private static final String TEST_USERNAME = "netconf";
63 private static final String TEST_PASSWORD = "netconf123";
64 private static final String TEST_HOSTNAME = "127.0.0.1";
65 private static final String TEST_SERFILE =
66 System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "testkey.ser";
67
68 private static final String SAMPLE_REQUEST =
69 "<some-yang-element xmlns=\"some-namespace\">"
70 + "<some-child-element/>"
71 + "</some-yang-element>";
72
73 private static NetconfSession session1;
74 private static NetconfSession session2;
75 private static SshServer sshServerNetconf;
76
77 @BeforeClass
78 public static void setUp() throws Exception {
79 sshServerNetconf = SshServer.setUpDefaultServer();
80 List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<NamedFactory<UserAuth>>();
81 userAuthFactories.add(new UserAuthPassword.Factory());
82 sshServerNetconf.setUserAuthFactories(userAuthFactories);
83 sshServerNetconf.setPasswordAuthenticator(
84 new PasswordAuthenticator() {
85 @Override
86 public boolean authenticate(
87 String username,
88 String password,
89 ServerSession session) {
90 return TEST_USERNAME.equals(username) && TEST_PASSWORD.equals(password);
91 }
92 });
93 sshServerNetconf.setPort(PORT_NUMBER);
94 sshServerNetconf.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(TEST_SERFILE));
95 sshServerNetconf.setSubsystemFactories(
96 Arrays.<NamedFactory<Command>>asList(new NetconfSshdTestSubsystem.Factory()));
97 sshServerNetconf.open();
98 log.info("SSH Server opened on port {}", PORT_NUMBER);
99
100 NetconfDeviceInfo deviceInfo = new NetconfDeviceInfo(
101 TEST_USERNAME, TEST_PASSWORD, Ip4Address.valueOf(TEST_HOSTNAME), PORT_NUMBER);
102
103 session1 = new NetconfSessionImpl(deviceInfo);
104 log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session1.getSessionId());
105 assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("-1"));
106 assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("0"));
107
108 session2 = new NetconfSessionImpl(deviceInfo);
109 log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session2.getSessionId());
110 assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("-1"));
111 assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("0"));
112 }
113
114 @AfterClass
115 public static void tearDown() throws Exception {
116 if (session1 != null) {
117 session1.close();
118 }
119 if (session2 != null) {
120 session2.close();
121 }
122
123 sshServerNetconf.stop();
124 }
125
126 @Test
127 public void testEditConfigRequest() {
128 log.info("Starting edit-config async");
129 assertNotNull("Incorrect sessionId", session1.getSessionId());
130 try {
131 assertTrue("NETCONF edit-config command failed", session1.editConfig("running", null, SAMPLE_REQUEST));
132 } catch (NetconfException e) {
133 e.printStackTrace();
134 fail("NETCONF edit-config test failed: " + e.getMessage());
135 }
136 log.info("Finishing edit-config async");
137 }
138
139 @Test
140 public void testCopyConfigRequest() {
141 log.info("Starting copy-config async");
142 assertNotNull("Incorrect sessionId", session1.getSessionId());
143 try {
144 assertTrue("NETCONF edit-config command failed", session1.copyConfig("running", "candidate"));
145 } catch (NetconfException e) {
146 e.printStackTrace();
147 fail("NETCONF edit-config test failed: " + e.getMessage());
148 }
149 log.info("Finishing copy-config async");
150 }
151
152 @Test
153 public void testGetConfigRequest() {
154 log.info("Starting get-config async");
155 assertNotNull("Incorrect sessionId", session1.getSessionId());
156 try {
157 assertTrue("NETCONF get-config running command failed. ",
158 GET_REPLY_PATTERN.matcher(session1.getConfig("running", SAMPLE_REQUEST)).matches());
159
160 assertTrue("NETCONF get-config candidate command failed. ",
161 GET_REPLY_PATTERN.matcher(session1.getConfig("candidate", SAMPLE_REQUEST)).matches());
162
163 } catch (NetconfException e) {
164 e.printStackTrace();
165 fail("NETCONF get-config test failed: " + e.getMessage());
166 }
167 log.info("Finishing get-config async");
168 }
169
170 @Test
171 public void testGetRequest() {
172 log.info("Starting get async");
173 assertNotNull("Incorrect sessionId", session1.getSessionId());
174 try {
175 assertTrue("NETCONF get running command failed. ",
176 GET_REPLY_PATTERN.matcher(session1.get(SAMPLE_REQUEST, null)).matches());
177
178 } catch (NetconfException e) {
179 e.printStackTrace();
180 fail("NETCONF get test failed: " + e.getMessage());
181 }
182 log.info("Finishing get async");
183 }
184
185 @Test
186 public void testConcurrentSameSessionAccess() throws InterruptedException {
187 NCCopyConfigCallable testCopyConfig1 = new NCCopyConfigCallable(session1, "running", "candidate");
188 NCCopyConfigCallable testCopyConfig2 = new NCCopyConfigCallable(session1, "candidate", "startup");
189
190 FutureTask<Boolean> futureCopyConfig1 = new FutureTask<Boolean>(testCopyConfig1);
191 FutureTask<Boolean> futureCopyConfig2 = new FutureTask<Boolean>(testCopyConfig2);
192
193 ExecutorService executor = Executors.newFixedThreadPool(2);
194 log.info("Starting concurrent execution of copy-config through same session");
195 executor.execute(futureCopyConfig1);
196 executor.execute(futureCopyConfig2);
197
198 int count = 0;
199 while (count < 10) {
200 if (futureCopyConfig1.isDone() && futureCopyConfig2.isDone()) {
201 executor.shutdown();
202 log.info("Finished concurrent same session execution");
203 return;
204 }
205 Thread.sleep(100L);
206 count++;
207 }
208 fail("NETCONF test failed to complete.");
209 }
210
211 @Test
212 public void test2SessionAccess() throws InterruptedException {
213 NCCopyConfigCallable testCopySession1 = new NCCopyConfigCallable(session1, "running", "candidate");
214 NCCopyConfigCallable testCopySession2 = new NCCopyConfigCallable(session2, "running", "candidate");
215
216 FutureTask<Boolean> futureCopySession1 = new FutureTask<Boolean>(testCopySession1);
217 FutureTask<Boolean> futureCopySession2 = new FutureTask<Boolean>(testCopySession2);
218
219 ExecutorService executor = Executors.newFixedThreadPool(2);
220 log.info("Starting concurrent execution of copy-config through 2 different sessions");
221 executor.execute(futureCopySession1);
222 executor.execute(futureCopySession2);
223
224 int count = 0;
225 while (count < 10) {
226 if (futureCopySession1.isDone() && futureCopySession2.isDone()) {
227 executor.shutdown();
228 log.info("Finished concurrent 2 session execution");
229 return;
230 }
231 Thread.sleep(100L);
232 count++;
233 }
234 fail("NETCONF test failed to complete.");
235 }
236
237
238 public static String getTestHelloReply(Optional<Long> sessionId) {
239 StringBuffer sb = new StringBuffer();
240
241 sb.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
242 sb.append("<capabilities>");
243 sb.append("<capability>urn:ietf:params:netconf:base:1.0</capability>");
244 sb.append("<capability>urn:ietf:params:netconf:base:1.1</capability>");
245 sb.append("<capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>");
246 sb.append("<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>");
247 sb.append("<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>");
248 sb.append("<capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>");
249 sb.append("<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>");
250 sb.append("<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>");
251 sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.0</capability>");
252 sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>");
253 sb.append("</capabilities>");
254 if (sessionId.isPresent()) {
255 sb.append("<session-id>");
256 sb.append(sessionId.get().toString());
257 sb.append("</session-id>");
258 }
259 sb.append("</hello>");
260
261 return sb.toString();
262 }
263
264 public static String getOkReply(Optional<Integer> messageId) {
265 StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
266 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
267 if (messageId.isPresent()) {
268 sb.append("message-id=\"");
269 sb.append(String.valueOf(messageId.get()));
270 sb.append("\">");
271 }
272 sb.append("<ok/>");
273 sb.append("</rpc-reply>");
274 return sb.toString();
275 }
276
277 public static String getGetReply(Optional<Integer> messageId) {
278 StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
279 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
280 if (messageId.isPresent()) {
281 sb.append("message-id=\"");
282 sb.append(String.valueOf(messageId.get()));
283 sb.append("\">");
284 }
285 sb.append("<data>\n");
286 sb.append(SAMPLE_REQUEST);
287 sb.append("</data>\n");
288 sb.append("</rpc-reply>");
289 return sb.toString();
290 }
291
292 public static final Pattern HELLO_REQ_PATTERN =
293 Pattern.compile("(<\\?xml).*"
294 + "(<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
295 + "( *)(<capabilities>)\\R?"
296 + "( *)(<capability>urn:ietf:params:netconf:base:1.0</capability>)\\R?"
297 + "( *)(</capabilities>)\\R?"
298 + "(</hello>)\\R? *",
299 Pattern.DOTALL);
300
301 public static final Pattern EDIT_CONFIG_REQ_PATTERN =
302 Pattern.compile("(<\\?xml).*"
303 + "(<rpc message-id=\")[0-9]*(\") *(xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
304 + "(<edit-config>)\\R?"
305 + "(<target>\\R?((<candidate/>)|(<running/>)|(<startup/>))\\R?</target>)\\R?"
306 + "(<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
307 + ".*"
308 + "(</config>)\\R?(</edit-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
309
310 public static final Pattern COPY_CONFIG_REQ_PATTERN =
311 Pattern.compile("(<\\?xml).*"
312 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
313 + "(<copy-config>)\\R?"
314 + "(<target>\\R?((<candidate/>)|(<running/>)|(<startup/>))\\R?</target>)\\R?"
315 + "(<source>)\\R?(<config>)((candidate)|(running)|(startup))(</config>)\\R?(</source>)\\R?"
316 + "(</copy-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
317
318 public static final Pattern GET_CONFIG_REQ_PATTERN =
319 Pattern.compile("(<\\?xml).*"
320 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
321 + "(<get-config>)\\R?"
322 + "(<source>)\\R?((<candidate/>)|(<running/>)|(<startup/>))\\R?(</source>)\\R?"
323 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
324 + "(</get-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
325
326 public static final Pattern GET_REPLY_PATTERN =
327 Pattern.compile("(<\\?xml).*"
328 + "(<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
329 + "(<data>).*(</data>)\\R?"
330 + "(</rpc-reply>)\\R?", Pattern.DOTALL);
331
332 public static final Pattern GET_REQ_PATTERN =
333 Pattern.compile("(<\\?xml).*"
334 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
335 + "(<get>)\\R?"
336 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
337 + "(</get>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
338
339 public class NCCopyConfigCallable implements Callable<Boolean> {
340 private NetconfSession session;
341 private String target;
342 private String source;
343
344 public NCCopyConfigCallable(NetconfSession session, String target, String source) {
345 this.session = session;
346 this.target = target;
347 this.source = source;
348 }
349
350 @Override
351 public Boolean call() throws Exception {
352 return session.copyConfig(target, source);
353 }
354 }
355}