blob: fe2a0d5a2007ca5374ea84fc2cef1fa2c248b7b5 [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;
Andrei Mihaescuac542ca2017-03-26 21:36:25 +030021import static org.onosproject.netconf.TargetConfig.*;
Sean Condond2c8d472017-02-17 17:09:39 +000022
23import java.util.ArrayList;
24import java.util.Arrays;
25import java.util.List;
26import java.util.Optional;
27import java.util.concurrent.Callable;
28import java.util.concurrent.ExecutorService;
29import java.util.concurrent.Executors;
30import java.util.concurrent.FutureTask;
31import java.util.regex.Pattern;
32
33import org.apache.sshd.SshServer;
34import org.apache.sshd.common.NamedFactory;
35import org.apache.sshd.server.Command;
36import org.apache.sshd.server.PasswordAuthenticator;
37import org.apache.sshd.server.UserAuth;
38import org.apache.sshd.server.auth.UserAuthPassword;
39import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
40import org.apache.sshd.server.session.ServerSession;
41import org.junit.AfterClass;
42import org.junit.BeforeClass;
43import org.junit.Test;
44import org.onlab.junit.TestTools;
45import org.onlab.packet.Ip4Address;
Andrei Mihaescuac542ca2017-03-26 21:36:25 +030046import org.onosproject.netconf.TargetConfig;
Sean Condond2c8d472017-02-17 17:09:39 +000047import org.onosproject.netconf.NetconfDeviceInfo;
48import org.onosproject.netconf.NetconfException;
49import org.onosproject.netconf.NetconfSession;
50import org.slf4j.Logger;
51import org.slf4j.LoggerFactory;
52
53/**
54 * Unit tests for NetconfSession.
55 *
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 NetconfSessionImplTest {
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 private static final String TEST_SERFILE =
68 System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "testkey.ser";
69
70 private static final String SAMPLE_REQUEST =
71 "<some-yang-element xmlns=\"some-namespace\">"
72 + "<some-child-element/>"
73 + "</some-yang-element>";
74
75 private static NetconfSession session1;
76 private static NetconfSession session2;
77 private static SshServer sshServerNetconf;
78
79 @BeforeClass
80 public static void setUp() throws Exception {
81 sshServerNetconf = SshServer.setUpDefaultServer();
82 List<NamedFactory<UserAuth>> userAuthFactories = new ArrayList<NamedFactory<UserAuth>>();
83 userAuthFactories.add(new UserAuthPassword.Factory());
84 sshServerNetconf.setUserAuthFactories(userAuthFactories);
85 sshServerNetconf.setPasswordAuthenticator(
86 new PasswordAuthenticator() {
87 @Override
88 public boolean authenticate(
89 String username,
90 String password,
91 ServerSession session) {
92 return TEST_USERNAME.equals(username) && TEST_PASSWORD.equals(password);
93 }
94 });
95 sshServerNetconf.setPort(PORT_NUMBER);
96 sshServerNetconf.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(TEST_SERFILE));
97 sshServerNetconf.setSubsystemFactories(
98 Arrays.<NamedFactory<Command>>asList(new NetconfSshdTestSubsystem.Factory()));
99 sshServerNetconf.open();
100 log.info("SSH Server opened on port {}", PORT_NUMBER);
101
102 NetconfDeviceInfo deviceInfo = new NetconfDeviceInfo(
103 TEST_USERNAME, TEST_PASSWORD, Ip4Address.valueOf(TEST_HOSTNAME), PORT_NUMBER);
104
105 session1 = new NetconfSessionImpl(deviceInfo);
106 log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session1.getSessionId());
107 assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("-1"));
108 assertTrue("Incorrect sessionId", !session1.getSessionId().equalsIgnoreCase("0"));
109
110 session2 = new NetconfSessionImpl(deviceInfo);
111 log.info("Started NETCONF Session {} with test SSHD server in Unit Test", session2.getSessionId());
112 assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("-1"));
113 assertTrue("Incorrect sessionId", !session2.getSessionId().equalsIgnoreCase("0"));
114 }
115
116 @AfterClass
117 public static void tearDown() throws Exception {
118 if (session1 != null) {
119 session1.close();
120 }
121 if (session2 != null) {
122 session2.close();
123 }
124
125 sshServerNetconf.stop();
126 }
127
128 @Test
129 public void testEditConfigRequest() {
130 log.info("Starting edit-config async");
131 assertNotNull("Incorrect sessionId", session1.getSessionId());
132 try {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300133 assertTrue("NETCONF edit-config command failed", session1.editConfig(RUNNING, null, SAMPLE_REQUEST));
Sean Condond2c8d472017-02-17 17:09:39 +0000134 } catch (NetconfException e) {
135 e.printStackTrace();
136 fail("NETCONF edit-config test failed: " + e.getMessage());
137 }
138 log.info("Finishing edit-config async");
139 }
140
141 @Test
142 public void testCopyConfigRequest() {
143 log.info("Starting copy-config async");
144 assertNotNull("Incorrect sessionId", session1.getSessionId());
145 try {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300146 assertTrue("NETCONF edit-config command failed", session1.copyConfig(RUNNING, "candidate"));
Sean Condond2c8d472017-02-17 17:09:39 +0000147 } catch (NetconfException e) {
148 e.printStackTrace();
149 fail("NETCONF edit-config test failed: " + e.getMessage());
150 }
151 log.info("Finishing copy-config async");
152 }
153
154 @Test
155 public void testGetConfigRequest() {
156 log.info("Starting get-config async");
157 assertNotNull("Incorrect sessionId", session1.getSessionId());
158 try {
159 assertTrue("NETCONF get-config running command failed. ",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300160 GET_REPLY_PATTERN.matcher(session1.getConfig(RUNNING, SAMPLE_REQUEST)).matches());
Sean Condond2c8d472017-02-17 17:09:39 +0000161
162 assertTrue("NETCONF get-config candidate command failed. ",
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300163 GET_REPLY_PATTERN.matcher(session1.getConfig(CANDIDATE, SAMPLE_REQUEST)).matches());
Sean Condond2c8d472017-02-17 17:09:39 +0000164
165 } catch (NetconfException e) {
166 e.printStackTrace();
167 fail("NETCONF get-config test failed: " + e.getMessage());
168 }
169 log.info("Finishing get-config async");
170 }
171
172 @Test
173 public void testGetRequest() {
174 log.info("Starting get async");
175 assertNotNull("Incorrect sessionId", session1.getSessionId());
176 try {
177 assertTrue("NETCONF get running command failed. ",
178 GET_REPLY_PATTERN.matcher(session1.get(SAMPLE_REQUEST, null)).matches());
179
180 } catch (NetconfException e) {
181 e.printStackTrace();
182 fail("NETCONF get test failed: " + e.getMessage());
183 }
184 log.info("Finishing get async");
185 }
186
187 @Test
188 public void testConcurrentSameSessionAccess() throws InterruptedException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300189 NCCopyConfigCallable testCopyConfig1 = new NCCopyConfigCallable(session1, RUNNING, "candidate");
190 NCCopyConfigCallable testCopyConfig2 = new NCCopyConfigCallable(session1, RUNNING, "startup");
Sean Condond2c8d472017-02-17 17:09:39 +0000191
192 FutureTask<Boolean> futureCopyConfig1 = new FutureTask<Boolean>(testCopyConfig1);
193 FutureTask<Boolean> futureCopyConfig2 = new FutureTask<Boolean>(testCopyConfig2);
194
195 ExecutorService executor = Executors.newFixedThreadPool(2);
196 log.info("Starting concurrent execution of copy-config through same session");
197 executor.execute(futureCopyConfig1);
198 executor.execute(futureCopyConfig2);
199
200 int count = 0;
201 while (count < 10) {
202 if (futureCopyConfig1.isDone() && futureCopyConfig2.isDone()) {
203 executor.shutdown();
204 log.info("Finished concurrent same session execution");
205 return;
206 }
207 Thread.sleep(100L);
208 count++;
209 }
210 fail("NETCONF test failed to complete.");
211 }
212
213 @Test
214 public void test2SessionAccess() throws InterruptedException {
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300215 NCCopyConfigCallable testCopySession1 = new NCCopyConfigCallable(session1, RUNNING, "candidate");
216 NCCopyConfigCallable testCopySession2 = new NCCopyConfigCallable(session2, RUNNING, "candidate");
Sean Condond2c8d472017-02-17 17:09:39 +0000217
218 FutureTask<Boolean> futureCopySession1 = new FutureTask<Boolean>(testCopySession1);
219 FutureTask<Boolean> futureCopySession2 = new FutureTask<Boolean>(testCopySession2);
220
221 ExecutorService executor = Executors.newFixedThreadPool(2);
222 log.info("Starting concurrent execution of copy-config through 2 different sessions");
223 executor.execute(futureCopySession1);
224 executor.execute(futureCopySession2);
225
226 int count = 0;
227 while (count < 10) {
228 if (futureCopySession1.isDone() && futureCopySession2.isDone()) {
229 executor.shutdown();
230 log.info("Finished concurrent 2 session execution");
231 return;
232 }
233 Thread.sleep(100L);
234 count++;
235 }
236 fail("NETCONF test failed to complete.");
237 }
238
239
240 public static String getTestHelloReply(Optional<Long> sessionId) {
241 StringBuffer sb = new StringBuffer();
242
243 sb.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
244 sb.append("<capabilities>");
245 sb.append("<capability>urn:ietf:params:netconf:base:1.0</capability>");
246 sb.append("<capability>urn:ietf:params:netconf:base:1.1</capability>");
247 sb.append("<capability>urn:ietf:params:netconf:capability:writable-running:1.0</capability>");
248 sb.append("<capability>urn:ietf:params:netconf:capability:candidate:1.0</capability>");
249 sb.append("<capability>urn:ietf:params:netconf:capability:startup:1.0</capability>");
250 sb.append("<capability>urn:ietf:params:netconf:capability:rollback-on-error:1.0</capability>");
251 sb.append("<capability>urn:ietf:params:netconf:capability:interleave:1.0</capability>");
252 sb.append("<capability>urn:ietf:params:netconf:capability:notification:1.0</capability>");
253 sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.0</capability>");
254 sb.append("<capability>urn:ietf:params:netconf:capability:validate:1.1</capability>");
255 sb.append("</capabilities>");
256 if (sessionId.isPresent()) {
257 sb.append("<session-id>");
258 sb.append(sessionId.get().toString());
259 sb.append("</session-id>");
260 }
261 sb.append("</hello>");
262
263 return sb.toString();
264 }
265
266 public static String getOkReply(Optional<Integer> messageId) {
267 StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
268 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
269 if (messageId.isPresent()) {
270 sb.append("message-id=\"");
271 sb.append(String.valueOf(messageId.get()));
272 sb.append("\">");
273 }
274 sb.append("<ok/>");
275 sb.append("</rpc-reply>");
276 return sb.toString();
277 }
278
279 public static String getGetReply(Optional<Integer> messageId) {
280 StringBuffer sb = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
281 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
282 if (messageId.isPresent()) {
283 sb.append("message-id=\"");
284 sb.append(String.valueOf(messageId.get()));
285 sb.append("\">");
286 }
287 sb.append("<data>\n");
288 sb.append(SAMPLE_REQUEST);
289 sb.append("</data>\n");
290 sb.append("</rpc-reply>");
291 return sb.toString();
292 }
293
294 public static final Pattern HELLO_REQ_PATTERN =
295 Pattern.compile("(<\\?xml).*"
296 + "(<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
297 + "( *)(<capabilities>)\\R?"
298 + "( *)(<capability>urn:ietf:params:netconf:base:1.0</capability>)\\R?"
299 + "( *)(</capabilities>)\\R?"
300 + "(</hello>)\\R? *",
301 Pattern.DOTALL);
302
303 public static final Pattern EDIT_CONFIG_REQ_PATTERN =
304 Pattern.compile("(<\\?xml).*"
305 + "(<rpc message-id=\")[0-9]*(\") *(xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
306 + "(<edit-config>)\\R?"
307 + "(<target>\\R?((<candidate/>)|(<running/>)|(<startup/>))\\R?</target>)\\R?"
308 + "(<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
309 + ".*"
310 + "(</config>)\\R?(</edit-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
311
312 public static final Pattern COPY_CONFIG_REQ_PATTERN =
313 Pattern.compile("(<\\?xml).*"
314 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
315 + "(<copy-config>)\\R?"
316 + "(<target>\\R?((<candidate/>)|(<running/>)|(<startup/>))\\R?</target>)\\R?"
317 + "(<source>)\\R?(<config>)((candidate)|(running)|(startup))(</config>)\\R?(</source>)\\R?"
318 + "(</copy-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
319
320 public static final Pattern GET_CONFIG_REQ_PATTERN =
321 Pattern.compile("(<\\?xml).*"
322 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
323 + "(<get-config>)\\R?"
324 + "(<source>)\\R?((<candidate/>)|(<running/>)|(<startup/>))\\R?(</source>)\\R?"
325 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
326 + "(</get-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
327
328 public static final Pattern GET_REPLY_PATTERN =
329 Pattern.compile("(<\\?xml).*"
330 + "(<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
331 + "(<data>).*(</data>)\\R?"
332 + "(</rpc-reply>)\\R?", Pattern.DOTALL);
333
334 public static final Pattern GET_REQ_PATTERN =
335 Pattern.compile("(<\\?xml).*"
336 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
337 + "(<get>)\\R?"
338 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
339 + "(</get>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
340
341 public class NCCopyConfigCallable implements Callable<Boolean> {
342 private NetconfSession session;
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300343 private TargetConfig target;
Sean Condond2c8d472017-02-17 17:09:39 +0000344 private String source;
345
Andrei Mihaescuac542ca2017-03-26 21:36:25 +0300346 public NCCopyConfigCallable(NetconfSession session, TargetConfig target, String source) {
Sean Condond2c8d472017-02-17 17:09:39 +0000347 this.session = session;
348 this.target = target;
349 this.source = source;
350 }
351
352 @Override
353 public Boolean call() throws Exception {
354 return session.copyConfig(target, source);
355 }
356 }
357}