blob: cc1cbf959628223937c5ebc131d78c73deadef39 [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 */
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070016package org.onosproject.netconf.ctl.impl;
Sean Condond2c8d472017-02-17 17:09:39 +000017
18import java.io.BufferedReader;
19import java.io.EOFException;
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.InputStreamReader;
23import java.io.OutputStream;
24import java.io.PrintWriter;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070025import java.nio.Buffer;
26import java.nio.ByteBuffer;
Sean Condond2c8d472017-02-17 17:09:39 +000027import java.util.Collection;
28import java.util.Optional;
29import java.util.concurrent.ExecutorService;
30import java.util.concurrent.Future;
31
32import org.apache.sshd.common.NamedFactory;
Andrea Campanella7bbe7b12017-05-03 16:03:38 -070033import org.apache.sshd.common.util.threads.ThreadUtils;
Sean Condond2c8d472017-02-17 17:09:39 +000034import org.apache.sshd.server.Command;
35import org.apache.sshd.server.Environment;
36import org.apache.sshd.server.ExitCallback;
37import org.apache.sshd.server.SessionAware;
38import org.apache.sshd.server.session.ServerSession;
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070039import org.onosproject.netconf.ctl.impl.NetconfStreamThread.NetconfMessageState;
Sean Condond2c8d472017-02-17 17:09:39 +000040import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
43/**
44 * Mocks a NETCONF Device to test the NETCONF Southbound Interface etc.
45 *
46 * Implements the 'netconf' subsystem on Apache SSH (Mina).
47 * See SftpSubsystem for an example of another subsystem
48 */
49public class NetconfSshdTestSubsystem extends Thread implements Command, Runnable, SessionAware {
50
51 protected final Logger log = LoggerFactory.getLogger(getClass());
52
53 public static class Factory implements NamedFactory<Command> {
54
55 public static final String NAME = "netconf";
56
57 private final ExecutorService executors;
58 private final boolean shutdownExecutor;
59
60 public Factory() {
61 this(null);
62 }
63
64 /**
65 * @param executorService The {@link ExecutorService} to be used by
66 * the {@link SftpSubsystem} command when starting execution. If
67 * {@code null} then a single-threaded ad-hoc service is used.
68 * <B>Note:</B> the service will <U>not</U> be shutdown when the
69 * subsystem is closed - unless it is the ad-hoc service, which will be
70 * shutdown regardless
71 * @see Factory(ExecutorService, boolean)}
72 */
73 public Factory(ExecutorService executorService) {
74 this(executorService, false);
75 }
76
77 /**
78 * @param executorService The {@link ExecutorService} to be used by
79 * the {@link SftpSubsystem} command when starting execution. If
80 * {@code null} then a single-threaded ad-hoc service is used.
81 * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
82 * will be called when subsystem terminates - unless it is the ad-hoc
83 * service, which will be shutdown regardless
84 */
85 public Factory(ExecutorService executorService, boolean shutdownOnExit) {
86 executors = executorService;
87 shutdownExecutor = shutdownOnExit;
88 }
89
90 public ExecutorService getExecutorService() {
91 return executors;
92 }
93
94 public boolean isShutdownOnExit() {
95 return shutdownExecutor;
96 }
97
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -070098 @Override
Sean Condond2c8d472017-02-17 17:09:39 +000099 public Command create() {
100 return new NetconfSshdTestSubsystem(getExecutorService(), isShutdownOnExit());
101 }
102
Yuta HIGUCHIe3ae8212017-04-20 10:18:41 -0700103 @Override
Sean Condond2c8d472017-02-17 17:09:39 +0000104 public String getName() {
105 return NAME;
106 }
107 }
108
109 /**
110 * Properties key for the maximum of available open handles per session.
111 */
112 private static final String CLOSE_SESSION = "<close-session";
113 private static final String END_PATTERN = "]]>]]>";
114
115 private ExecutorService executors;
116 private boolean shutdownExecutor;
117 private ExitCallback callback;
118 private ServerSession session;
119 private InputStream in;
120 private OutputStream out;
121 private OutputStream err;
122 private Environment env;
123 private Future<?> pendingFuture;
124 private boolean closed = false;
125 private NetconfMessageState state;
126 private PrintWriter outputStream;
127
128 public NetconfSshdTestSubsystem() {
129 this(null);
130 }
131
132 /**
133 * @param executorService The {@link ExecutorService} to be used by
134 * the {@link SftpSubsystem} command when starting execution. If
135 * {@code null} then a single-threaded ad-hoc service is used.
136 * <b>Note:</b> the service will <U>not</U> be shutdown when the
137 * subsystem is closed - unless it is the ad-hoc service
138 * @see #SftpSubsystem(ExecutorService, boolean)
139 */
140 public NetconfSshdTestSubsystem(ExecutorService executorService) {
141 this(executorService, false);
142 }
143
144 /**
145 * @param executorService The {@link ExecutorService} to be used by
146 * the {@link SftpSubsystem} command when starting execution. If
147 * {@code null} then a single-threaded ad-hoc service is used.
148 * @param shutdownOnExit If {@code true} the {@link ExecutorService#shutdownNow()}
149 * will be called when subsystem terminates - unless it is the ad-hoc
150 * service, which will be shutdown regardless
151 * @see ThreadUtils#newSingleThreadExecutor(String)
152 */
153 public NetconfSshdTestSubsystem(ExecutorService executorService, boolean shutdownOnExit) {
154 executors = executorService;
155 if (executorService == null) {
156 executors = ThreadUtils.newSingleThreadExecutor(getClass().getSimpleName());
157 shutdownExecutor = true; // we always close the ad-hoc executor service
158 } else {
159 shutdownExecutor = shutdownOnExit;
160 }
161 }
162
163 @Override
164 public void setSession(ServerSession session) {
165 this.session = session;
166 }
167
168 @Override
169 public void run() {
170 BufferedReader bufferReader = new BufferedReader(new InputStreamReader(in));
171 boolean socketClosed = false;
172 try {
173 StringBuilder deviceRequestBuilder = new StringBuilder();
174 while (!socketClosed) {
175 int cInt = bufferReader.read();
176 if (cInt == -1) {
177 log.info("Netconf client sent error");
178 socketClosed = true;
179 }
180 char c = (char) cInt;
181 state = state.evaluateChar(c);
182 deviceRequestBuilder.append(c);
183 if (state == NetconfMessageState.END_PATTERN) {
184 String deviceRequest = deviceRequestBuilder.toString();
185 if (deviceRequest.equals(END_PATTERN)) {
186 socketClosed = true;
187 this.interrupt();
188 } else {
189 deviceRequest = deviceRequest.replace(END_PATTERN, "");
190 Optional<Integer> messageId = NetconfStreamThread.getMsgId(deviceRequest);
191 log.info("Client Request on session {}. MsgId {}: {}",
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700192 session.getSessionId(), messageId, deviceRequest);
Sean Condond2c8d472017-02-17 17:09:39 +0000193 synchronized (outputStream) {
194 if (NetconfSessionImplTest.HELLO_REQ_PATTERN.matcher(deviceRequest).matches()) {
195 String helloReply =
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700196 NetconfSessionImplTest.getTestHelloReply(Optional.of(ByteBuffer.wrap(
197 session.getSessionId()).asLongBuffer().get()));
Sean Condond2c8d472017-02-17 17:09:39 +0000198 outputStream.write(helloReply + END_PATTERN);
199 outputStream.flush();
200 } else if (NetconfSessionImplTest.EDIT_CONFIG_REQ_PATTERN.matcher(deviceRequest).matches()
Shivani Vaidya48df84e2017-04-13 13:48:17 -0700201 || NetconfSessionImplTest.COPY_CONFIG_REQ_PATTERN.matcher(deviceRequest).matches()
202 || NetconfSessionImplTest.LOCK_REQ_PATTERN.matcher(deviceRequest).matches()
203 || NetconfSessionImplTest.UNLOCK_REQ_PATTERN.matcher(deviceRequest).matches()) {
Sean Condond2c8d472017-02-17 17:09:39 +0000204 outputStream.write(NetconfSessionImplTest.getOkReply(messageId) + END_PATTERN);
205 outputStream.flush();
206 } else if (NetconfSessionImplTest.GET_CONFIG_REQ_PATTERN.matcher(deviceRequest).matches()
207 || NetconfSessionImplTest.GET_REQ_PATTERN.matcher(deviceRequest).matches()) {
208 outputStream.write(NetconfSessionImplTest.getGetReply(messageId) + END_PATTERN);
209 outputStream.flush();
210 } else if (deviceRequest.contains(CLOSE_SESSION)) {
211 socketClosed = true;
212 outputStream.write(NetconfSessionImplTest.getOkReply(messageId) + END_PATTERN);
213 outputStream.flush();
214 } else {
215 log.error("Unexpected NETCONF message structure on session {} : {}",
Andrea Campanella7bbe7b12017-05-03 16:03:38 -0700216 ByteBuffer.wrap(
217 session.getSessionId()).asLongBuffer().get(), deviceRequest);
Sean Condond2c8d472017-02-17 17:09:39 +0000218 }
219 }
220 deviceRequestBuilder.setLength(0);
221 }
222 }
223 }
224 } catch (Throwable t) {
225 if (!socketClosed && !(t instanceof EOFException)) { // Ignore
226 log.error("Exception caught in NETCONF Server subsystem", t.getMessage());
227 }
228 } finally {
229 try {
230 bufferReader.close();
231 } catch (IOException ioe) {
232 log.error("Could not close DataInputStream", ioe);
233 }
234
235 callback.onExit(0);
236 }
237 }
238
239 @Override
240 public void setInputStream(InputStream in) {
241 this.in = in;
242 }
243
244 @Override
245 public void setOutputStream(OutputStream out) {
246 this.out = out;
247 }
248
249 @Override
250 public void setErrorStream(OutputStream err) {
251 this.err = err;
252 }
253
254 @Override
255 public void setExitCallback(ExitCallback callback) {
256 this.callback = callback;
257 }
258
259 @Override
260 public void start(Environment env) throws IOException {
261 this.env = env;
262 state = NetconfMessageState.NO_MATCHING_PATTERN;
263 outputStream = new PrintWriter(out, false);
264 try {
265 pendingFuture = executors.submit(this);
266 } catch (RuntimeException e) { // e.g., RejectedExecutionException
267 log.error("Failed (" + e.getClass().getSimpleName() + ") to start command: " + e.getMessage(), e);
268 throw new IOException(e);
269 }
270 }
271
272 @Override
273 public void interrupt() {
274 // if thread has not completed, cancel it
275 if ((pendingFuture != null) && (!pendingFuture.isDone())) {
276 boolean result = pendingFuture.cancel(true);
277 // TODO consider waiting some reasonable (?) amount of time for cancellation
278 if (log.isDebugEnabled()) {
279 log.debug("interrupt() - cancel pending future=" + result);
280 }
281 }
282
283 pendingFuture = null;
284
285 if ((executors != null) && shutdownExecutor) {
286 Collection<Runnable> runners = executors.shutdownNow();
287 if (log.isDebugEnabled()) {
288 log.debug("interrupt() - shutdown executor service - runners count=" +
289 runners.size());
290 }
291 }
292
293 executors = null;
294
295 if (!closed) {
296 if (log.isDebugEnabled()) {
297 log.debug("interrupt() - mark as closed");
298 }
299
300 closed = true;
301 }
302 outputStream.close();
303 }
304
305 @Override
306 public void destroy() {
307 //Handled by interrupt
308 }
309
310 protected void process(Buffer buffer) throws IOException {
311 log.warn("Receieved buffer:" + buffer);
312 }
313}