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