blob: be0c916dcb357ea9f367901d9022603d3bbcf57e [file] [log] [blame]
Sean Condond2c8d472017-02-17 17:09:39 +00001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
Sean Condond2c8d472017-02-17 17:09:39 +00003 *
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 */
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070016package org.onosproject.netconf.ctl.impl;
Sean Condond2c8d472017-02-17 17:09:39 +000017
Kamil Stasiak9f59f442017-05-02 11:02:24 +020018
Sean Condond2c8d472017-02-17 17:09:39 +000019import java.io.BufferedReader;
Sean Condond2c8d472017-02-17 17:09:39 +000020import java.io.InputStream;
Sean Condond2c8d472017-02-17 17:09:39 +000021import java.io.OutputStream;
22import java.io.PrintWriter;
Kamil Stasiak9f59f442017-05-02 11:02:24 +020023import java.io.UnsupportedEncodingException;
24import java.io.IOException;
25import java.io.InputStreamReader;
26import java.io.EOFException;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070027import java.nio.Buffer;
28import java.nio.ByteBuffer;
Sean Condond2c8d472017-02-17 17:09:39 +000029import java.util.Collection;
30import java.util.Optional;
31import java.util.concurrent.ExecutorService;
32import java.util.concurrent.Future;
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -070033import java.util.regex.Pattern;
Sean Condond2c8d472017-02-17 17:09:39 +000034
Kamil Stasiak9f59f442017-05-02 11:02:24 +020035import org.apache.commons.lang3.tuple.Pair;
Sean Condond2c8d472017-02-17 17:09:39 +000036import org.apache.sshd.common.NamedFactory;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070037import org.apache.sshd.common.util.threads.ThreadUtils;
Sean Condond2c8d472017-02-17 17:09:39 +000038import org.apache.sshd.server.Command;
39import org.apache.sshd.server.Environment;
40import org.apache.sshd.server.ExitCallback;
41import org.apache.sshd.server.SessionAware;
42import org.apache.sshd.server.session.ServerSession;
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -070043import org.onosproject.netconf.DatastoreId;
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070044import org.onosproject.netconf.ctl.impl.NetconfStreamThread.NetconfMessageState;
Sean Condond2c8d472017-02-17 17:09:39 +000045import org.slf4j.Logger;
46import org.slf4j.LoggerFactory;
47
48/**
49 * Mocks a NETCONF Device to test the NETCONF Southbound Interface etc.
50 *
51 * Implements the 'netconf' subsystem on Apache SSH (Mina).
52 * See SftpSubsystem for an example of another subsystem
53 */
54public class NetconfSshdTestSubsystem extends Thread implements Command, Runnable, SessionAware {
55
56 protected final Logger log = LoggerFactory.getLogger(getClass());
57
58 public static class Factory implements NamedFactory<Command> {
59
60 public static final String NAME = "netconf";
61
62 private final ExecutorService executors;
63 private final boolean shutdownExecutor;
64
65 public Factory() {
66 this(null);
67 }
68
69 /**
70 * @param executorService The {@link ExecutorService} to be used by
71 * the {@link SftpSubsystem} command when starting execution. If
72 * {@code null} then a single-threaded ad-hoc service is used.
73 * <B>Note:</B> the service will <U>not</U> be shutdown when the
74 * subsystem is closed - unless it is the ad-hoc service, which will be
75 * shutdown regardless
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -070076 * @see #Factory(ExecutorService, boolean)
Sean Condond2c8d472017-02-17 17:09:39 +000077 */
78 public Factory(ExecutorService executorService) {
79 this(executorService, false);
80 }
81
82 /**
83 * @param executorService The {@link ExecutorService} to be used by
84 * the {@link SftpSubsystem} command when starting execution. If
85 * {@code null} then a single-threaded ad-hoc service is used.
86 * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
87 * will be called when subsystem terminates - unless it is the ad-hoc
88 * service, which will be shutdown regardless
89 */
90 public Factory(ExecutorService executorService, boolean shutdownOnExit) {
91 executors = executorService;
92 shutdownExecutor = shutdownOnExit;
93 }
94
95 public ExecutorService getExecutorService() {
96 return executors;
97 }
98
99 public boolean isShutdownOnExit() {
100 return shutdownExecutor;
101 }
102
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700103 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000104 public Command create() {
105 return new NetconfSshdTestSubsystem(getExecutorService(), isShutdownOnExit());
106 }
107
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700108 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000109 public String getName() {
110 return NAME;
111 }
112 }
113
114 /**
115 * Properties key for the maximum of available open handles per session.
116 */
117 private static final String CLOSE_SESSION = "<close-session";
118 private static final String END_PATTERN = "]]>]]>";
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200119 private static final String HASH = "#";
120 private static final String LF = "\n";
121 private static final String MSGLEN_REGEX_PATTERN = "\n#\\d+\n";
122 private static final String MSGLEN_PART_REGEX_PATTERN = "\\d+\n";
123 private static final String CHUNKED_END_REGEX_PATTERN = "\n##\n";
Sean Condond2c8d472017-02-17 17:09:39 +0000124
125 private ExecutorService executors;
126 private boolean shutdownExecutor;
127 private ExitCallback callback;
128 private ServerSession session;
129 private InputStream in;
130 private OutputStream out;
131 private OutputStream err;
132 private Environment env;
133 private Future<?> pendingFuture;
134 private boolean closed = false;
135 private NetconfMessageState state;
136 private PrintWriter outputStream;
137
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700138 private static final String SAMPLE_REQUEST =
139 "<some-yang-element xmlns=\"some-namespace\">"
140 + "<some-child-element/>"
141 + "</some-yang-element>";
142 public static final Pattern GET_REQ_PATTERN =
143 Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
144 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
145 + "(<get>)\\R?"
146 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
147 + "(</get>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
148 public static final Pattern GET_CONFIG_REQ_PATTERN =
149 Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
150 + "(<rpc message-id=\")[0-9]*(\" xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
151 + "(<get-config>)\\R?" + "(<source>)\\R?((<"
152 + DatastoreId.CANDIDATE.toString()
153 + "/>)|(<" + DatastoreId.RUNNING.toString()
154 + "/>)|(<" + DatastoreId.STARTUP.toString()
155 + "/>))\\R?(</source>)\\R?"
156 + "(<filter type=\"subtree\">).*(</filter>)\\R?"
157 + "(</get-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
158 public static final Pattern COPY_CONFIG_REQ_PATTERN =
159 Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
160 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" message-id=\")[0-9]*(\">)\\R?"
161 + "(<copy-config>)\\R?"
162 + "(<target>\\R?"
163 + "("
164 + "(<" + DatastoreId.CANDIDATE.toString() + "/>)|"
165 + "(<" + DatastoreId.RUNNING.toString() + "/>)|"
166 + "(<" + DatastoreId.STARTUP.toString() + "/>)"
167 + ")\\R?"
168 + "</target>)\\R?"
169 + "(<source>)\\R?"
170 + "("
171 + "(<config>)(.*)(</config>)|"
172 + "(<" + DatastoreId.CANDIDATE.toString() + "/>)|"
173 + "(<" + DatastoreId.RUNNING.toString() + "/>)|"
174 + "(<" + DatastoreId.STARTUP.toString() + "/>)"
175 + ")\\R?"
176 + "(</source>)\\R?"
177 + "(</copy-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
178 public static final Pattern UNLOCK_REQ_PATTERN =
179 Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
180 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
181 + "message-id=\")[0-9]*(\">)\\R?"
182 + "(<unlock>)\\R?"
183 + "(<target>\\R?((<" + DatastoreId.CANDIDATE.toString() + "/>)|"
184 + "(<" + DatastoreId.RUNNING.toString() + "/>)|"
185 + "(<" + DatastoreId.STARTUP.toString() + "/>))\\R?</target>)\\R?"
186 + "(</unlock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
187 public static final Pattern LOCK_REQ_PATTERN =
188 Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
189 + "(<rpc xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" "
190 + "message-id=\")[0-9]*(\">)\\R?"
191 + "(<lock>)\\R?"
192 + "(<target>\\R?((<" + DatastoreId.CANDIDATE.toString() + "/>)|"
193 + "(<" + DatastoreId.RUNNING.toString() + "/>)|"
194 + "(<" + DatastoreId.STARTUP.toString() + "/>))\\R?</target>)\\R?"
195 + "(</lock>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
196 public static final Pattern EDIT_CONFIG_REQ_PATTERN =
197 Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
198 + "(<rpc message-id=\")[0-9]*(\") *(xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
199 + "(<edit-config>)\\R?"
200 + "(<target>\\R?((<" + DatastoreId.CANDIDATE.toString() + "/>)|"
201 + "(<" + DatastoreId.RUNNING.toString() + "/>)|"
202 + "(<" + DatastoreId.STARTUP.toString() + "/>))\\R?</target>)\\R?"
203 + "(<config xmlns:nc=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
204 + ".*"
205 + "(</config>)\\R?(</edit-config>)\\R?(</rpc>)\\R?", Pattern.DOTALL);
206 public static final Pattern HELLO_REQ_PATTERN_1_1 =
207 Pattern.compile("(<\\?xml version=\"1.0\" encoding=\"UTF-8\"\\?>)\\R?"
208 + "(<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
209 + "( *)(<capabilities>)\\R?"
210 + "( *)(<capability>urn:ietf:params:netconf:base:1.0</capability>)\\R?"
211 + "( *)(<capability>urn:ietf:params:netconf:base:1.1</capability>)\\R?"
212 + "( *)(</capabilities>)\\R?"
213 + "(</hello>)\\R? *",
214 Pattern.DOTALL);
215 public static final Pattern HELLO_REQ_PATTERN =
216 Pattern.compile("(<\\?xml).*"
217 + "(<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">)\\R?"
218 + "( *)(<capabilities>)\\R?"
219 + "( *)(<capability>urn:ietf:params:netconf:base:1.0</capability>)\\R?"
220 + "( *)(</capabilities>)\\R?"
221 + "(</hello>)\\R? *",
222 Pattern.DOTALL);
223
Sean Condond2c8d472017-02-17 17:09:39 +0000224 public NetconfSshdTestSubsystem() {
225 this(null);
226 }
227
228 /**
229 * @param executorService The {@link ExecutorService} to be used by
230 * the {@link SftpSubsystem} command when starting execution. If
231 * {@code null} then a single-threaded ad-hoc service is used.
232 * <b>Note:</b> the service will <U>not</U> be shutdown when the
233 * subsystem is closed - unless it is the ad-hoc service
234 * @see #SftpSubsystem(ExecutorService, boolean)
235 */
236 public NetconfSshdTestSubsystem(ExecutorService executorService) {
237 this(executorService, false);
238 }
239
240 /**
241 * @param executorService The {@link ExecutorService} to be used by
242 * the {@link SftpSubsystem} command when starting execution. If
243 * {@code null} then a single-threaded ad-hoc service is used.
244 * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
245 * will be called when subsystem terminates - unless it is the ad-hoc
246 * service, which will be shutdown regardless
247 * @see ThreadUtils#newSingleThreadExecutor(String)
248 */
249 public NetconfSshdTestSubsystem(ExecutorService executorService, boolean shutdownOnExit) {
250 executors = executorService;
251 if (executorService == null) {
252 executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
253 shutdownExecutor = true; // we always close the ad-hoc executor service
254 } else {
255 shutdownExecutor = shutdownOnExit;
256 }
257 }
258
259 @Override
260 public void setSession(ServerSession session) {
261 this.session = session;
262 }
263
264 @Override
265 public void run() {
266 BufferedReader bufferReader = new BufferedReader(new InputStreamReader(in));
267 boolean socketClosed = false;
268 try {
269 StringBuilder deviceRequestBuilder = new StringBuilder();
270 while (!socketClosed) {
271 int cInt = bufferReader.read();
272 if (cInt == -1) {
273 log.info("Netconf client sent error");
274 socketClosed = true;
275 }
276 char c = (char) cInt;
277 state = state.evaluateChar(c);
278 deviceRequestBuilder.append(c);
279 if (state == NetconfMessageState.END_PATTERN) {
280 String deviceRequest = deviceRequestBuilder.toString();
281 if (deviceRequest.equals(END_PATTERN)) {
282 socketClosed = true;
283 this.interrupt();
284 } else {
285 deviceRequest = deviceRequest.replace(END_PATTERN, "");
286 Optional<Integer> messageId = NetconfStreamThread.getMsgId(deviceRequest);
287 log.info("Client Request on session {}. MsgId {}: {}",
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700288 session.getSessionId(), messageId, deviceRequest);
Sean Condond2c8d472017-02-17 17:09:39 +0000289 synchronized (outputStream) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200290
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700291 if (HELLO_REQ_PATTERN.matcher(deviceRequest).matches()) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200292
Sean Condond2c8d472017-02-17 17:09:39 +0000293 String helloReply =
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700294 getTestHelloReply(Optional.of(ByteBuffer.wrap(
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200295 session.getSessionId()).asLongBuffer().get()), false);
Sean Condond2c8d472017-02-17 17:09:39 +0000296 outputStream.write(helloReply + END_PATTERN);
297 outputStream.flush();
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700298 } else if (HELLO_REQ_PATTERN_1_1.matcher(deviceRequest).matches()) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200299
300 String helloReply =
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700301 getTestHelloReply(Optional.of(ByteBuffer.wrap(
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200302 session.getSessionId()).asLongBuffer().get()), true);
303 outputStream.write(helloReply + END_PATTERN);
Sean Condond2c8d472017-02-17 17:09:39 +0000304 outputStream.flush();
305 } else {
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700306 Pair<String, Boolean> replyClosedPair = dealWithRequest(deviceRequest, messageId);
307 String reply = replyClosedPair.getLeft();
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200308 if (reply != null) {
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700309 Boolean newSockedClosed = replyClosedPair.getRight();
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200310 socketClosed = newSockedClosed.booleanValue();
311 outputStream.write(reply + END_PATTERN);
312 outputStream.flush();
313 }
Sean Condond2c8d472017-02-17 17:09:39 +0000314 }
315 }
316 deviceRequestBuilder.setLength(0);
317 }
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200318 } else if (state == NetconfMessageState.END_CHUNKED_PATTERN) {
319 String deviceRequest = deviceRequestBuilder.toString();
320 if (!validateChunkedFraming(deviceRequest)) {
321 log.error("Netconf client send badly framed message {}",
322 deviceRequest);
323 } else {
324 deviceRequest = deviceRequest.replaceAll(MSGLEN_REGEX_PATTERN, "");
325 deviceRequest = deviceRequest.replaceAll(CHUNKED_END_REGEX_PATTERN, "");
326 Optional<Integer> messageId = NetconfStreamThread.getMsgId(deviceRequest);
327 log.info("Client Request on session {}. MsgId {}: {}",
328 session.getSessionId(), messageId, deviceRequest);
329
330 synchronized (outputStream) {
331
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700332 if (HELLO_REQ_PATTERN.matcher(deviceRequest).matches()) {
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200333 String helloReply =
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700334 getTestHelloReply(Optional.of(ByteBuffer.wrap(
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200335 session.getSessionId()).asLongBuffer().get()), true);
336 outputStream.write(helloReply + END_PATTERN);
337 outputStream.flush();
338 } else {
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700339 Pair<String, Boolean> replyClosedPair = dealWithRequest(deviceRequest, messageId);
340 String reply = replyClosedPair.getLeft();
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200341 if (reply != null) {
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700342 Boolean newSockedClosed = replyClosedPair.getRight();
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200343 socketClosed = newSockedClosed.booleanValue();
344 outputStream.write(formatChunkedMessage(reply));
345 outputStream.flush();
346 }
347 }
348 }
349
350 }
351 deviceRequestBuilder.setLength(0);
Sean Condond2c8d472017-02-17 17:09:39 +0000352 }
353 }
354 } catch (Throwable t) {
355 if (!socketClosed && !(t instanceof EOFException)) { // Ignore
356 log.error("Exception caught in NETCONF Server subsystem", t.getMessage());
357 }
358 } finally {
359 try {
360 bufferReader.close();
361 } catch (IOException ioe) {
362 log.error("Could not close DataInputStream", ioe);
363 }
364
365 callback.onExit(0);
366 }
367 }
368
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200369 private boolean validateChunkedFraming(String reply) {
370 String[] strs = reply.split(LF + HASH);
371 int strIndex = 0;
372 while (strIndex < strs.length) {
373 String str = strs[strIndex];
374 if ((str.equals(HASH + LF))) {
375 return true;
376 }
377 if (!str.equals("")) {
378 try {
379 if (str.equals(LF)) {
380 return false;
381 }
382 int len = Integer.parseInt(str.split(LF)[0]);
383 if (str.split(MSGLEN_PART_REGEX_PATTERN)[1].getBytes("UTF-8").length != len) {
384 return false;
385 }
386 } catch (NumberFormatException e) {
387 return false;
388 } catch (UnsupportedEncodingException e) {
389 e.printStackTrace();
390 }
391 }
392 strIndex++;
393 }
394 return true;
395 }
396
397 private Pair<String, Boolean> dealWithRequest(String deviceRequest, Optional<Integer> messageId) {
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700398 if (EDIT_CONFIG_REQ_PATTERN.matcher(deviceRequest).matches()
399 || COPY_CONFIG_REQ_PATTERN.matcher(deviceRequest).matches()
400 || LOCK_REQ_PATTERN.matcher(deviceRequest).matches()
401 || UNLOCK_REQ_PATTERN.matcher(deviceRequest).matches()) {
402 return Pair.of(getOkReply(messageId), false);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200403
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700404 } else if (GET_CONFIG_REQ_PATTERN.matcher(deviceRequest).matches()
405 || GET_REQ_PATTERN.matcher(deviceRequest).matches()) {
406 return Pair.of(getGetReply(messageId), false);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200407 } else if (deviceRequest.contains(CLOSE_SESSION)) {
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700408 return Pair.of(getOkReply(messageId), true);
Kamil Stasiak9f59f442017-05-02 11:02:24 +0200409 } else {
410 log.error("Unexpected NETCONF message structure on session {} : {}",
411 ByteBuffer.wrap(
412 session.getSessionId()).asLongBuffer().get(), deviceRequest);
413 return null;
414 }
415 }
416
417 private String formatChunkedMessage(String message) {
418 if (message.endsWith(END_PATTERN)) {
419 message = message.split(END_PATTERN)[0];
420 }
421 if (!message.startsWith(LF + HASH)) {
422 try {
423 message = LF + HASH + message.getBytes("UTF-8").length + LF + message + LF + HASH + HASH + LF;
424 } catch (UnsupportedEncodingException e) {
425 e.printStackTrace();
426 }
427 }
428 return message;
429 }
430
431
Sean Condond2c8d472017-02-17 17:09:39 +0000432 @Override
433 public void setInputStream(InputStream in) {
434 this.in = in;
435 }
436
437 @Override
438 public void setOutputStream(OutputStream out) {
439 this.out = out;
440 }
441
442 @Override
443 public void setErrorStream(OutputStream err) {
444 this.err = err;
445 }
446
447 @Override
448 public void setExitCallback(ExitCallback callback) {
449 this.callback = callback;
450 }
451
452 @Override
453 public void start(Environment env) throws IOException {
454 this.env = env;
455 state = NetconfMessageState.NO_MATCHING_PATTERN;
456 outputStream = new PrintWriter(out, false);
457 try {
458 pendingFuture = executors.submit(this);
459 } catch (RuntimeException e) { // e.g., RejectedExecutionException
460 log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.getMessage(), e);
461 throw new IOException(e);
462 }
463 }
464
465 @Override
466 public void interrupt() {
467 // if thread has not completed, cancel it
468 if ((pendingFuture != null) && (!pendingFuture.isDone())) {
469 boolean result = pendingFuture.cancel(true);
470 // TODO consider waiting some reasonable (?) amount of time for cancellation
471 if (log.isDebugEnabled()) {
472 log.debug("interrupt() - cancel pending future=" + result);
473 }
474 }
475
476 pendingFuture = null;
477
478 if ((executors != null) && shutdownExecutor) {
479 Collection<Runnable> runners = executors.shutdownNow();
480 if (log.isDebugEnabled()) {
481 log.debug("interrupt() - shutdown executor service - runners count=" +
482 runners.size());
483 }
484 }
485
486 executors = null;
487
488 if (!closed) {
489 if (log.isDebugEnabled()) {
490 log.debug("interrupt() - mark as closed");
491 }
492
493 closed = true;
494 }
495 outputStream.close();
496 }
497
498 @Override
499 public void destroy() {
500 //Handled by interrupt
501 }
502
503 protected void process(Buffer buffer) throws IOException {
504 log.warn("Receieved buffer:" + buffer);
505 }
Yuta HIGUCHI5233dec2018-05-02 15:22:37 -0700506
507 public static String getTestHelloReply(Collection<String> capabilities, Optional<Long> sessionId) {
508 StringBuilder sb = new StringBuilder();
509
510 sb.append("<hello xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">");
511 sb.append("<capabilities>");
512 capabilities.forEach(capability -> {
513 sb.append("<capability>").append(capability).append("</capability>");
514 });
515 sb.append("</capabilities>");
516 if (sessionId.isPresent()) {
517 sb.append("<session-id>");
518 sb.append(sessionId.get().toString());
519 sb.append("</session-id>");
520 }
521 sb.append("</hello>");
522
523 return sb.toString();
524 }
525
526 public static String getTestHelloReply(Optional<Long> sessionId, boolean useChunkedFraming) {
527 if (useChunkedFraming) {
528 return getTestHelloReply(NetconfSessionMinaImplTest.DEFAULT_CAPABILITIES_1_1, sessionId);
529 } else {
530 return getTestHelloReply(NetconfSessionMinaImplTest.DEFAULT_CAPABILITIES, sessionId);
531 }
532 }
533
534 public static String getGetReply(Optional<Integer> messageId) {
535 StringBuilder sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
536 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
537 if (messageId.isPresent()) {
538 sb.append("message-id=\"");
539 sb.append(String.valueOf(messageId.get()));
540 sb.append("\">");
541 }
542 sb.append("<data>\n");
543 sb.append(SAMPLE_REQUEST);
544 sb.append("</data>\n");
545 sb.append("</rpc-reply>");
546 return sb.toString();
547 }
548
549 public static String getOkReply(Optional<Integer> messageId) {
550 StringBuilder sb = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
551 sb.append("<rpc-reply xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\" ");
552 if (messageId.isPresent()) {
553 sb.append("message-id=\"");
554 sb.append(String.valueOf(messageId.get()));
555 sb.append("\">");
556 }
557 sb.append("<ok/>");
558 sb.append("</rpc-reply>");
559 return sb.toString();
560 }
561}