Decouple config and netconf subsystems.
[controller.git] / opendaylight / netconf / netconf-impl / src / test / java / org / opendaylight / controller / netconf / impl / ConcurrentClientsTest.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.netconf.impl;
10
11 import static com.google.common.base.Preconditions.checkNotNull;
12 import static org.junit.Assert.assertEquals;
13 import static org.junit.Assert.fail;
14 import static org.mockito.Matchers.any;
15 import static org.mockito.Matchers.anySetOf;
16 import static org.mockito.Mockito.doNothing;
17 import static org.mockito.Mockito.doReturn;
18 import static org.mockito.Mockito.mock;
19
20 import com.google.common.base.Preconditions;
21 import com.google.common.collect.Lists;
22 import com.google.common.collect.Sets;
23 import com.google.common.io.ByteStreams;
24 import io.netty.channel.ChannelFuture;
25 import io.netty.channel.EventLoopGroup;
26 import io.netty.channel.nio.NioEventLoopGroup;
27 import io.netty.util.HashedWheelTimer;
28 import io.netty.util.concurrent.GlobalEventExecutor;
29 import java.io.DataOutputStream;
30 import java.io.InputStream;
31 import java.io.InputStreamReader;
32 import java.net.InetSocketAddress;
33 import java.net.Socket;
34 import java.util.Arrays;
35 import java.util.Collection;
36 import java.util.Collections;
37 import java.util.List;
38 import java.util.Set;
39 import java.util.concurrent.ExecutionException;
40 import java.util.concurrent.ExecutorService;
41 import java.util.concurrent.Executors;
42 import java.util.concurrent.Future;
43 import java.util.concurrent.ThreadFactory;
44 import java.util.concurrent.atomic.AtomicLong;
45 import org.junit.After;
46 import org.junit.AfterClass;
47 import org.junit.Before;
48 import org.junit.BeforeClass;
49 import org.junit.Test;
50 import org.junit.runner.RunWith;
51 import org.junit.runners.Parameterized;
52 import org.opendaylight.controller.config.util.capability.Capability;
53 import org.opendaylight.controller.config.util.xml.DocumentedException;
54 import org.opendaylight.controller.config.util.xml.XmlUtil;
55 import org.opendaylight.controller.netconf.api.NetconfMessage;
56 import org.opendaylight.controller.netconf.api.monitoring.CapabilityListener;
57 import org.opendaylight.controller.netconf.api.monitoring.NetconfMonitoringService;
58 import org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants;
59 import org.opendaylight.controller.netconf.client.NetconfClientDispatcher;
60 import org.opendaylight.controller.netconf.client.NetconfClientDispatcherImpl;
61 import org.opendaylight.controller.netconf.client.SimpleNetconfClientSessionListener;
62 import org.opendaylight.controller.netconf.client.TestingNetconfClient;
63 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfiguration;
64 import org.opendaylight.controller.netconf.client.conf.NetconfClientConfigurationBuilder;
65 import org.opendaylight.controller.netconf.impl.osgi.AggregatedNetconfOperationServiceFactory;
66 import org.opendaylight.controller.netconf.mapping.api.HandlingPriority;
67 import org.opendaylight.controller.netconf.mapping.api.NetconfOperation;
68 import org.opendaylight.controller.netconf.mapping.api.NetconfOperationChainedExecution;
69 import org.opendaylight.controller.netconf.mapping.api.NetconfOperationService;
70 import org.opendaylight.controller.netconf.mapping.api.NetconfOperationServiceFactory;
71 import org.opendaylight.controller.netconf.nettyutil.handler.exi.NetconfStartExiMessage;
72 import org.opendaylight.controller.netconf.util.messages.NetconfHelloMessageAdditionalHeader;
73 import org.opendaylight.controller.netconf.util.messages.NetconfMessageUtil;
74 import org.opendaylight.controller.netconf.util.test.XmlFileLoader;
75 import org.opendaylight.protocol.framework.NeverReconnectStrategy;
76 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev100924.Uri;
77 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.monitoring.rev101004.netconf.state.CapabilitiesBuilder;
78 import org.slf4j.Logger;
79 import org.slf4j.LoggerFactory;
80 import org.w3c.dom.Document;
81
82 @RunWith(Parameterized.class)
83 public class ConcurrentClientsTest {
84     private static final Logger LOG = LoggerFactory.getLogger(ConcurrentClientsTest.class);
85
86     private static ExecutorService clientExecutor;
87
88     private static final int CONCURRENCY = 32;
89     private static final InetSocketAddress netconfAddress = new InetSocketAddress("127.0.0.1", 8303);
90
91     private int nettyThreads;
92     private Class<? extends Runnable> clientRunnable;
93     private Set<String> serverCaps;
94
95     public ConcurrentClientsTest(int nettyThreads, Class<? extends Runnable> clientRunnable, Set<String> serverCaps) {
96         this.nettyThreads = nettyThreads;
97         this.clientRunnable = clientRunnable;
98         this.serverCaps = serverCaps;
99     }
100
101     @Parameterized.Parameters()
102     public static Collection<Object[]> data() {
103         return Arrays.asList(new Object[][]{{4, TestingNetconfClientRunnable.class, NetconfServerSessionNegotiatorFactory.DEFAULT_BASE_CAPABILITIES},
104                                             {1, TestingNetconfClientRunnable.class, NetconfServerSessionNegotiatorFactory.DEFAULT_BASE_CAPABILITIES},
105                                             // empty set of capabilities = only base 1.0 netconf capability
106                                             {4, TestingNetconfClientRunnable.class, Collections.emptySet()},
107                                             {4, TestingNetconfClientRunnable.class, getOnlyExiServerCaps()},
108                                             {4, TestingNetconfClientRunnable.class, getOnlyChunkServerCaps()},
109                                             {4, BlockingClientRunnable.class, getOnlyExiServerCaps()},
110                                             {1, BlockingClientRunnable.class, getOnlyExiServerCaps()},
111         });
112     }
113
114     private EventLoopGroup nettyGroup;
115     private NetconfClientDispatcher netconfClientDispatcher;
116
117     HashedWheelTimer hashedWheelTimer;
118     private TestingNetconfOperation testingNetconfOperation;
119
120     public static NetconfMonitoringService createMockedMonitoringService() {
121         NetconfMonitoringService monitoring = mock(NetconfMonitoringService.class);
122         doNothing().when(monitoring).onSessionUp(any(NetconfServerSession.class));
123         doNothing().when(monitoring).onSessionDown(any(NetconfServerSession.class));
124         doReturn(new AutoCloseable() {
125             @Override
126             public void close() throws Exception {
127
128             }
129         }).when(monitoring).registerListener(any(NetconfMonitoringService.MonitoringListener.class));
130         doNothing().when(monitoring).onCapabilitiesChanged(anySetOf(Capability.class), anySetOf(Capability.class));
131         doReturn(new CapabilitiesBuilder().setCapability(Collections.<Uri>emptyList()).build()).when(monitoring).getCapabilities();
132         return monitoring;
133     }
134
135     @BeforeClass
136     public static void setUpClientExecutor() {
137         clientExecutor = Executors.newFixedThreadPool(CONCURRENCY, new ThreadFactory() {
138             int i = 1;
139
140             @Override
141             public Thread newThread(final Runnable r) {
142                 Thread thread = new Thread(r);
143                 thread.setName("client-" + i++);
144                 thread.setDaemon(true);
145                 return thread;
146             }
147         });
148     }
149
150     @Before
151     public void setUp() throws Exception {
152         hashedWheelTimer = new HashedWheelTimer();
153         nettyGroup = new NioEventLoopGroup(nettyThreads);
154         netconfClientDispatcher = new NetconfClientDispatcherImpl(nettyGroup, nettyGroup, hashedWheelTimer);
155
156         AggregatedNetconfOperationServiceFactory factoriesListener = new AggregatedNetconfOperationServiceFactory();
157
158         testingNetconfOperation = new TestingNetconfOperation();
159         factoriesListener.onAddNetconfOperationServiceFactory(new TestingOperationServiceFactory(testingNetconfOperation));
160
161         SessionIdProvider idProvider = new SessionIdProvider();
162
163         NetconfServerSessionNegotiatorFactory serverNegotiatorFactory = new NetconfServerSessionNegotiatorFactory(
164                 hashedWheelTimer, factoriesListener, idProvider, 5000, createMockedMonitoringService(), serverCaps);
165
166         NetconfServerDispatcherImpl.ServerChannelInitializer serverChannelInitializer = new NetconfServerDispatcherImpl.ServerChannelInitializer(serverNegotiatorFactory);
167         final NetconfServerDispatcherImpl dispatch = new NetconfServerDispatcherImpl(serverChannelInitializer, nettyGroup, nettyGroup);
168
169         ChannelFuture s = dispatch.createServer(netconfAddress);
170         s.await();
171     }
172
173     @After
174     public void tearDown(){
175         hashedWheelTimer.stop();
176         try {
177             nettyGroup.shutdownGracefully().get();
178         } catch (InterruptedException | ExecutionException e) {
179             LOG.warn("Ignoring exception while cleaning up after test", e);
180         }
181     }
182
183     @AfterClass
184     public static void tearDownClientExecutor() {
185         clientExecutor.shutdownNow();
186     }
187
188     @Test(timeout = CONCURRENCY * 1000)
189     public void testConcurrentClients() throws Exception {
190
191         List<Future<?>> futures = Lists.newArrayListWithCapacity(CONCURRENCY);
192
193         for (int i = 0; i < CONCURRENCY; i++) {
194             futures.add(clientExecutor.submit(getInstanceOfClientRunnable()));
195         }
196
197         for (Future<?> future : futures) {
198             try {
199                 future.get();
200             } catch (InterruptedException e) {
201                 throw new IllegalStateException(e);
202             } catch (ExecutionException e) {
203                 LOG.error("Thread for testing client failed", e);
204                 fail("Client failed: " + e.getMessage());
205             }
206         }
207
208         assertEquals(CONCURRENCY, testingNetconfOperation.getMessageCount());
209     }
210
211     public static Set<String> getOnlyExiServerCaps() {
212         return Sets.newHashSet(
213                 XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0,
214                 XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_CAPABILITY_EXI_1_0
215         );
216     }
217
218     public static Set<String> getOnlyChunkServerCaps() {
219         return Sets.newHashSet(
220                 XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_0,
221                 XmlNetconfConstants.URN_IETF_PARAMS_NETCONF_BASE_1_1
222         );
223     }
224
225     public Runnable getInstanceOfClientRunnable() throws Exception {
226         return clientRunnable.getConstructor(ConcurrentClientsTest.class).newInstance(this);
227     }
228
229     /**
230      * Responds to all operations except start-exi and counts all requests
231      */
232     private static class TestingNetconfOperation implements NetconfOperation {
233
234         private final AtomicLong counter = new AtomicLong();
235
236         @Override
237         public HandlingPriority canHandle(Document message) {
238             return XmlUtil.toString(message).contains(NetconfStartExiMessage.START_EXI) ?
239                     HandlingPriority.CANNOT_HANDLE :
240                     HandlingPriority.HANDLE_WITH_MAX_PRIORITY;
241         }
242
243         @Override
244         public Document handle(Document requestMessage, NetconfOperationChainedExecution subsequentOperation) throws DocumentedException {
245             try {
246                 LOG.info("Handling netconf message from test {}", XmlUtil.toString(requestMessage));
247                 counter.getAndIncrement();
248                 return XmlUtil.readXmlToDocument("<test/>");
249             } catch (Exception e) {
250                 throw new RuntimeException(e);
251             }
252         }
253
254         public long getMessageCount() {
255             return counter.get();
256         }
257     }
258
259     /**
260      * Hardcoded operation service factory
261      */
262     private static class TestingOperationServiceFactory implements NetconfOperationServiceFactory {
263         private final NetconfOperation[] operations;
264
265         public TestingOperationServiceFactory(final NetconfOperation... operations) {
266             this.operations = operations;
267         }
268
269         @Override
270         public Set<Capability> getCapabilities() {
271             return Collections.emptySet();
272         }
273
274         @Override
275         public AutoCloseable registerCapabilityListener(final CapabilityListener listener) {
276             return new AutoCloseable(){
277                 @Override
278                 public void close() throws Exception {}
279             };
280         }
281
282         @Override
283         public NetconfOperationService createService(String netconfSessionIdForReporting) {
284             return new NetconfOperationService() {
285
286                 @Override
287                 public Set<NetconfOperation> getNetconfOperations() {
288                     return Sets.newHashSet(operations);
289                 }
290
291                 @Override
292                 public void close() {}
293             };
294         }
295     }
296
297     /**
298      * Pure socket based blocking client
299      */
300     public final class BlockingClientRunnable implements Runnable {
301
302         @Override
303         public void run() {
304             try {
305                 run2();
306             } catch (Exception e) {
307                 throw new IllegalStateException(Thread.currentThread().getName(), e);
308             }
309         }
310
311         private void run2() throws Exception {
312             InputStream clientHello = checkNotNull(XmlFileLoader
313                     .getResourceAsStream("netconfMessages/client_hello.xml"));
314             InputStream getConfig = checkNotNull(XmlFileLoader.getResourceAsStream("netconfMessages/getConfig.xml"));
315
316             Socket clientSocket = new Socket(netconfAddress.getHostString(), netconfAddress.getPort());
317             DataOutputStream outToServer = new DataOutputStream(clientSocket.getOutputStream());
318             InputStreamReader inFromServer = new InputStreamReader(clientSocket.getInputStream());
319
320             StringBuffer sb = new StringBuffer();
321             while (sb.toString().endsWith("]]>]]>") == false) {
322                 sb.append((char) inFromServer.read());
323             }
324             LOG.info(sb.toString());
325
326             outToServer.write(ByteStreams.toByteArray(clientHello));
327             outToServer.write("]]>]]>".getBytes());
328             outToServer.flush();
329             // Thread.sleep(100);
330             outToServer.write(ByteStreams.toByteArray(getConfig));
331             outToServer.write("]]>]]>".getBytes());
332             outToServer.flush();
333             Thread.sleep(100);
334             sb = new StringBuffer();
335             while (sb.toString().endsWith("]]>]]>") == false) {
336                 sb.append((char) inFromServer.read());
337             }
338             LOG.info(sb.toString());
339             clientSocket.close();
340         }
341     }
342
343     /**
344      * TestingNetconfClient based runnable
345      */
346     public final class TestingNetconfClientRunnable implements Runnable {
347
348         @Override
349         public void run() {
350             try {
351                 final TestingNetconfClient netconfClient =
352                         new TestingNetconfClient(Thread.currentThread().getName(), netconfClientDispatcher, getClientConfig());
353                 long sessionId = netconfClient.getSessionId();
354                 LOG.info("Client with session id {}: hello exchanged", sessionId);
355
356                 final NetconfMessage getMessage = XmlFileLoader
357                         .xmlFileToNetconfMessage("netconfMessages/getConfig.xml");
358                 NetconfMessage result = netconfClient.sendRequest(getMessage).get();
359                 LOG.info("Client with session id {}: got result {}", sessionId, result);
360
361                 Preconditions.checkState(NetconfMessageUtil.isErrorMessage(result) == false,
362                         "Received error response: " + XmlUtil.toString(result.getDocument()) + " to request: "
363                                 + XmlUtil.toString(getMessage.getDocument()));
364
365                 netconfClient.close();
366                 LOG.info("Client with session id {}: ended", sessionId);
367             } catch (final Exception e) {
368                 throw new IllegalStateException(Thread.currentThread().getName(), e);
369             }
370         }
371
372         private NetconfClientConfiguration getClientConfig() {
373             final NetconfClientConfigurationBuilder b = NetconfClientConfigurationBuilder.create();
374             b.withAddress(netconfAddress);
375             b.withAdditionalHeader(new NetconfHelloMessageAdditionalHeader("uname", "10.10.10.1", "830", "tcp",
376                     "client"));
377             b.withSessionListener(new SimpleNetconfClientSessionListener());
378             b.withReconnectStrategy(new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE,
379                     NetconfClientConfigurationBuilder.DEFAULT_CONNECTION_TIMEOUT_MILLIS));
380             return b.build();
381         }
382     }
383 }