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