blob: 2dcb14f02c88a00696f87da421a5f7e81afee4a0 [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;
Shivani Vaidya48df84e2017-04-13 13:48:17 -070021import static org.junit.Assert.assertFalse;
22import static org.onosproject.netconf.TargetConfig.RUNNING;
23import static org.onosproject.netconf.TargetConfig.CANDIDATE;
Sean Condond2c8d472017-02-17 17:09:39 +000024
25import java.util.ArrayList;
26import java.util.Arrays;
27import java.util.List;
28import java.util.Optional;
29import java.util.concurrent.Callable;
30import java.util.concurrent.ExecutorService;
31import java.util.concurrent.Executors;
32import java.util.concurrent.FutureTask;
33import java.util.regex.Pattern;
34
35import org.apache.sshd.SshServer;
36import org.apache.sshd.common.NamedFactory;
37import org.apache.sshd.server.Command;
38import org.apache.sshd.server.PasswordAuthenticator;
39import org.apache.sshd.server.UserAuth;
40import org.apache.sshd.server.auth.UserAuthPassword;
41import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
42import org.apache.sshd.server.session.ServerSession;
43import org.junit.AfterClass;
44import org.junit.BeforeClass;
45import org.junit.Test;
46import org.onlab.junit.TestTools;
47import org.onlab.packet.Ip4Address;
Andrei Mihaescuac542ca2017-03-26 21:36:25 +030048import org.onosproject.netconf.TargetConfig;
Sean Condond2c8d472017-02-17 17:09:39 +000049import org.onosproject.netconf.NetconfDeviceInfo;
50import org.onosproject.netconf.NetconfException;
51import org.onosproject.netconf.NetconfSession;
52import org.slf4j.Logger;
53import org.slf4j.LoggerFactory;
54
55/**
56 * Unit tests for NetconfSession.
57 *
58 * Sets up an SSH Server with Apache SSHD and connects to it using 2 clients
59 * Truly verifies that the NETCONF flows are compliant with a NETCONF server.
60 */
61public class NetconfSessionImplTest {
62 private static final Logger log = LoggerFactory
63 .getLogger(NetconfStreamThread.class);
64
65 private static final int PORT_NUMBER = TestTools.findAvailablePort(50830);
66 private static final String TEST_USERNAME = "netconf";
67 private static final String TEST_PASSWORD = "netconf123";
68 private static final String TEST_HOSTNAME = "127.0.0.1";
Shivani Vaidya48df84e2017-04-13 13:48:17 -070069
Sean Condond2c8d472017-02-17 17:09:39 +000070 private static final String TEST_SERFILE =
71 System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "testkey.ser";
72
73 private static final String SAMPLE_REQUEST =
74 "<some-yang-element xmlns=\"some-namespace\">"
75 + "<some-child-element/>"
76 + "</some-yang-element>";
77
Shivani Vaidya48df84e2017-04-13 13:48:17 -070078 private static final String EDIT_CONFIG_REQUEST =
79 "<?xml version=\"1.0\" encoding=\"UTF-8\"?><rpc message-id=\"6\" "
80 + "xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
81 + "<edit-config>\n"
82 + "<target><running/></target>\n"
83 + "<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
84 + "<some-yang-element xmlns=\"some-namespace\">"
85 + "<some-child-element/></some-yang-element></config>\n"
86 + "</edit-config>\n"
87 + "</rpc>]]>]]>";
88
Sean Condond2c8d472017-02-17 17:09:39 +000089 private static NetconfSession session1;
90 private static NetconfSession session2;
91 private static SshServer sshServerNetconf;
92
93 @BeforeClass
94 public static void setUp() throws Exception {
95 sshServerNetconf = SshServer.setUpDefaultServer();
96 List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<NamedFactory<UserAuth>>();
97 userAuthFactories.add(new UserAuthPassword.Factory());
98 sshServerNetconf.setUserAuthFactories(userAuthFactories);
99 sshServerNetconf.setPasswordAuthenticator(
100 new PasswordAuthenticator() {
101 @Override
102 public boolean authenticate(
103 String username,
104 String password,
105 ServerSession session) {
106 return TEST_USERNAME.equals(username) && TEST_PASSWORD.equals(password);
107 }
108 });
109 sshServerNetconf.setPort(PORT_NUMBER);
110 sshServerNetconf.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(TEST_SERFILE));
111 sshServerNetconf.setSubsystemFactories(
112 Arrays.<NamedFactory<Command>>asList(new NetconfSshdTestSubsystem.Factory()));
113 sshServerNetconf.open();
114 log.info("SSH Server opened on port {}", PORT_NUMBER);
115
116 NetconfDeviceInfo deviceInfo = new NetconfDeviceInfo(
117 TEST_USERNAME, TEST_PASSWORD, Ip4Address.valueOf(TEST_HOSTNAME), PORT_NUMBER);
118
119 session1 = new NetconfSessionImpl(deviceInfo);
120 log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session1.getSessionId());
121 assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("-1"));
122 assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("0"));
123
124 session2 = new NetconfSessionImpl(deviceInfo);
125 log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session2.getSessionId());
126 assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("-1"));
127 assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("0"));
128 }
129
130 @AfterClass
131 public static void tearDown() throws Exception {
132 if (session1 != null) {
133 session1.close();
134 }
135 if (session2 != null) {
136 session2.close();
137 }
138
139 sshServerNetconf.stop();
140 }
141
142 @Test
143 public void testEditConfigRequest() {
144 log.info("Starting edit-config async");
145 assertNotNull("Incorrect sessionId", session1.getSessionId());
146 try {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700147 assertTrue("NETCONF edit-config command failed",
148 session1.editConfig(TargetConfig.RUNNING.toString(),
149 null, SAMPLE_REQUEST));
Sean Condond2c8d472017-02-17 17:09:39 +0000150 } catch (NetconfException e) {
151 e.printStackTrace();
152 fail("NETCONF edit-config test failed: " + e.getMessage());
153 }
154 log.info("Finishing edit-config async");
155 }
156
157 @Test
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700158 public void testEditConfigRequestWithOnlyNewConfiguration() {
159 log.info("Starting edit-config async");
160 assertNotNull("Incorrect sessionId", session1.getSessionId());
161 try {
162 assertTrue("NETCONF edit-config command failed",
163 session1.editConfig(EDIT_CONFIG_REQUEST));
164 } catch (NetconfException e) {
165 e.printStackTrace();
166 fail("NETCONF edit-config test failed: " + e.getMessage());
167 }
168 log.info("Finishing edit-config async");
169 }
170
171 @Test
172 public void testDeleteConfigRequestWithRunningTargetConfiguration() {
173 log.info("Starting delete-config async");
174 assertNotNull("Incorrect sessionId", session1.getSessionId());
175 try {
176 assertFalse("NETCONF delete-config command failed",
177 session1.deleteConfig(TargetConfig.RUNNING));
178 } catch (NetconfException e) {
179 e.printStackTrace();
180 fail("NETCONF delete-config test failed: " + e.getMessage());
181 }
182 log.info("Finishing delete-config async");
183 }
184
185 @Test
Sean Condond2c8d472017-02-17 17:09:39 +0000186 public void testCopyConfigRequest() {
187 log.info("Starting copy-config async");
188 assertNotNull("Incorrect sessionId", session1.getSessionId());
189 try {
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700190 assertTrue("NETCONF copy-config command failed",
191 session1.copyConfig(TargetConfig.RUNNING.toString(),
192 "candidate"));
Sean Condond2c8d472017-02-17 17:09:39 +0000193 } catch (NetconfException e) {
194 e.printStackTrace();
195 fail("NETCONF edit-config test failed: " + e.getMessage());
196 }
197 log.info("Finishing copy-config async");
198 }
199
200 @Test
201 public void testGetConfigRequest() {
202 log.info("Starting get-config async");
203 assertNotNull("Incorrect sessionId", session1.getSessionId());
204 try {
205 assertTrue("NETCONF get-config running command failed. ",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300206 GET_REPLY_PATTERN.matcher(session1.getConfig(RUNNING, SAMPLE_REQUEST)).matches());
Sean Condond2c8d472017-02-17 17:09:39 +0000207
208 assertTrue("NETCONF get-config candidate command failed. ",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300209 GET_REPLY_PATTERN.matcher(session1.getConfig(CANDIDATE, SAMPLE_REQUEST)).matches());
Sean Condond2c8d472017-02-17 17:09:39 +0000210
211 } catch (NetconfException e) {
212 e.printStackTrace();
213 fail("NETCONF get-config test failed: " + e.getMessage());
214 }
215 log.info("Finishing get-config async");
216 }
217
218 @Test
219 public void testGetRequest() {
220 log.info("Starting get async");
221 assertNotNull("Incorrect sessionId", session1.getSessionId());
222 try {
223 assertTrue("NETCONF get running command failed. ",
224 GET_REPLY_PATTERN.matcher(session1.get(SAMPLE_REQUEST, null)).matches());
225
226 } catch (NetconfException e) {
227 e.printStackTrace();
228 fail("NETCONF get test failed: " + e.getMessage());
229 }
230 log.info("Finishing get async");
231 }
232
233 @Test
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700234 public void testLockRequest() {
235 log.info("Starting lock async");
236 assertNotNull("Incorrect sessionId", session1.getSessionId());
237 try {
238 assertTrue("NETCONF lock request failed", session1.lock());
239 } catch (NetconfException e) {
240 e.printStackTrace();
241 fail("NETCONF lock test failed: " + e.getMessage());
242 }
243 log.info("Finishing lock async");
244 }
245
246 @Test
247 public void testUnLockRequest() {
248 log.info("Starting unlock async");
249 assertNotNull("Incorrect sessionId", session1.getSessionId());
250 try {
251 assertTrue("NETCONF unlock request failed", session1.unlock());
252 } catch (NetconfException e) {
253 e.printStackTrace();
254 fail("NETCONF unlock test failed: " + e.getMessage());
255 }
256 log.info("Finishing unlock async");
257 }
258
259
260 @Test
Sean Condond2c8d472017-02-17 17:09:39 +0000261 public void testConcurrentSameSessionAccess() throws InterruptedException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300262 NCCopyConfigCallable testCopyConfig1 = new NCCopyConfigCallable(session1, RUNNING, "candidate");
263 NCCopyConfigCallable testCopyConfig2 = new NCCopyConfigCallable(session1, RUNNING, "startup");
Sean Condond2c8d472017-02-17 17:09:39 +0000264
265 FutureTask<Boolean> futureCopyConfig1 = new FutureTask<Boolean>(testCopyConfig1);
266 FutureTask<Boolean> futureCopyConfig2 = new FutureTask<Boolean>(testCopyConfig2);
267
268 ExecutorService executor = Executors.newFixedThreadPool(2);
269 log.info("Starting concurrent execution of copy-config through same session");
270 executor.execute(futureCopyConfig1);
271 executor.execute(futureCopyConfig2);
272
273 int count = 0;
274 while (count < 10) {
275 if (futureCopyConfig1.isDone() && futureCopyConfig2.isDone()) {
276 executor.shutdown();
277 log.info("Finished concurrent same session execution");
278 return;
279 }
280 Thread.sleep(100L);
281 count++;
282 }
283 fail("NETCONF test failed to complete.");
284 }
285
286 @Test
287 public void test2SessionAccess() throws InterruptedException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300288 NCCopyConfigCallable testCopySession1 = new NCCopyConfigCallable(session1, RUNNING, "candidate");
289 NCCopyConfigCallable testCopySession2 = new NCCopyConfigCallable(session2, RUNNING, "candidate");
Sean Condond2c8d472017-02-17 17:09:39 +0000290
291 FutureTask<Boolean> futureCopySession1 = new FutureTask<Boolean>(testCopySession1);
292 FutureTask<Boolean> futureCopySession2 = new FutureTask<Boolean>(testCopySession2);
293
294 ExecutorService executor = Executors.newFixedThreadPool(2);
295 log.info("Starting concurrent execution of copy-config through 2 different sessions");
296 executor.execute(futureCopySession1);
297 executor.execute(futureCopySession2);
298
299 int count = 0;
300 while (count < 10) {
301 if (futureCopySession1.isDone() && futureCopySession2.isDone()) {
302 executor.shutdown();
303 log.info("Finished concurrent 2 session execution");
304 return;
305 }
306 Thread.sleep(100L);
307 count++;
308 }
309 fail("NETCONF test failed to complete.");
310 }
311
312
313 public static String getTestHelloReply(Optional<Long> sessionId) {
314 StringBuffer sb = new StringBuffer();
315
316 sb.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
317 sb.append("<capabilities>");
318 sb.append("<capability>urn:ietf:params:netconf:base:1.0</capability>");
319 sb.append("<capability>urn:ietf:params:netconf:base:1.1</capability>");
320 sb.append("<capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>");
321 sb.append("<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>");
322 sb.append("<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>");
323 sb.append("<capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>");
324 sb.append("<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>");
325 sb.append("<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>");
326 sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.0</capability>");
327 sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>");
328 sb.append("</capabilities>");
329 if (sessionId.isPresent()) {
330 sb.append("<session-id>");
331 sb.append(sessionId.get().toString());
332 sb.append("</session-id>");
333 }
334 sb.append("</hello>");
335
336 return sb.toString();
337 }
338
339 public static String getOkReply(Optional<Integer> messageId) {
340 StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
341 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
342 if (messageId.isPresent()) {
343 sb.append("message-id=\"");
344 sb.append(String.valueOf(messageId.get()));
345 sb.append("\">");
346 }
347 sb.append("<ok/>");
348 sb.append("</rpc-reply>");
349 return sb.toString();
350 }
351
352 public static String getGetReply(Optional<Integer> messageId) {
353 StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
354 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
355 if (messageId.isPresent()) {
356 sb.append("message-id=\"");
357 sb.append(String.valueOf(messageId.get()));
358 sb.append("\">");
359 }
360 sb.append("<data>\n");
361 sb.append(SAMPLE_REQUEST);
362 sb.append("</data>\n");
363 sb.append("</rpc-reply>");
364 return sb.toString();
365 }
366
367 public static final Pattern HELLO_REQ_PATTERN =
368 Pattern.compile("(<\\?xml).*"
369 + "(<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
370 + "( *)(<capabilities>)\\R?"
371 + "( *)(<capability>urn:ietf:params:netconf:base:1.0</capability>)\\R?"
372 + "( *)(</capabilities>)\\R?"
373 + "(</hello>)\\R? *",
374 Pattern.DOTALL);
375
376 public static final Pattern EDIT_CONFIG_REQ_PATTERN =
377 Pattern.compile("(<\\?xml).*"
378 + "(<rpc message-id=\")[0-9]*(\") *(xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
379 + "(<edit-config>)\\R?"
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700380 + "(<target>\\R?((<" + TargetConfig.CANDIDATE.toString() + "/>)|"
381 + "(<" + TargetConfig.RUNNING.toString() + "/>)|"
382 + "(<" + TargetConfig.STARTUP.toString() + "/>))\\R?</target>)\\R?"
Sean Condond2c8d472017-02-17 17:09:39 +0000383 + "(<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
384 + ".*"
385 + "(</config>)\\R?(</edit-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
386
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700387
388 public static final Pattern LOCK_REQ_PATTERN =
389 Pattern.compile("(<\\?xml).*"
390 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
391 + "message-id=\")[0-9]*(\">)\\R?"
392 + "(<lock>)\\R?"
393 + "(<target>\\R?((<" + TargetConfig.CANDIDATE.toString() + "/>)|"
394 + "(<" + TargetConfig.RUNNING.toString() + "/>)|"
395 + "(<" + TargetConfig.STARTUP.toString() + "/>))\\R?</target>)\\R?"
396 + "(</lock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
397
398 public static final Pattern UNLOCK_REQ_PATTERN =
399 Pattern.compile("(<\\?xml).*"
400 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
401 + "message-id=\")[0-9]*(\">)\\R?"
402 + "(<unlock>)\\R?"
403 + "(<target>\\R?((<" + TargetConfig.CANDIDATE.toString() + "/>)|"
404 + "(<" + TargetConfig.RUNNING.toString() + "/>)|"
405 + "(<" + TargetConfig.STARTUP.toString() + "/>))\\R?</target>)\\R?"
406 + "(</unlock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
407
Sean Condond2c8d472017-02-17 17:09:39 +0000408 public static final Pattern COPY_CONFIG_REQ_PATTERN =
409 Pattern.compile("(<\\?xml).*"
410 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
411 + "(<copy-config>)\\R?"
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700412 + "(<target>\\R?((<" + TargetConfig.CANDIDATE.toString() + "/>)|"
413 + "(<" + TargetConfig.RUNNING.toString() + "/>)|"
414 + "(<" + TargetConfig.STARTUP.toString() + "/>))\\R?</target>)\\R?"
415 + "(<source>)\\R?(<config>)(("
416 + TargetConfig.CANDIDATE.toString() + ")|("
417 + TargetConfig.RUNNING.toString() + ")|("
418 + TargetConfig.STARTUP.toString()
419 + "))(</config>)\\R?(</source>)\\R?"
420 + "(</copy-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
Sean Condond2c8d472017-02-17 17:09:39 +0000421
422 public static final Pattern GET_CONFIG_REQ_PATTERN =
423 Pattern.compile("(<\\?xml).*"
424 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700425 + "(<get-config>)\\R?" + "(<source>)\\R?((<"
426 + TargetConfig.CANDIDATE.toString()
427 + "/>)|(<" + TargetConfig.RUNNING.toString()
428 + "/>)|(<" + TargetConfig.STARTUP.toString()
429 + "/>))\\R?(</source>)\\R?"
Sean Condond2c8d472017-02-17 17:09:39 +0000430 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
431 + "(</get-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
432
433 public static final Pattern GET_REPLY_PATTERN =
434 Pattern.compile("(<\\?xml).*"
435 + "(<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
436 + "(<data>).*(</data>)\\R?"
437 + "(</rpc-reply>)\\R?", Pattern.DOTALL);
438
439 public static final Pattern GET_REQ_PATTERN =
440 Pattern.compile("(<\\?xml).*"
441 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
442 + "(<get>)\\R?"
443 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
444 + "(</get>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
445
446 public class NCCopyConfigCallable implements Callable<Boolean> {
447 private NetconfSession session;
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300448 private TargetConfig target;
Sean Condond2c8d472017-02-17 17:09:39 +0000449 private String source;
450
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300451 public NCCopyConfigCallable(NetconfSession session, TargetConfig target, String source) {
Sean Condond2c8d472017-02-17 17:09:39 +0000452 this.session = session;
453 this.target = target;
454 this.source = source;
455 }
456
457 @Override
458 public Boolean call() throws Exception {
459 return session.copyConfig(target, source);
460 }
461 }
462}