Use text blocks in StressClient
[netconf.git] / netconf / tools / netconf-testtool / src / main / java / org / opendaylight / netconf / test / tool / client / stress / StressClient.java
1 /*
2  * Copyright (c) 2015 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.netconf.test.tool.client.stress;
10
11 import ch.qos.logback.classic.Level;
12 import com.google.common.base.Stopwatch;
13 import com.google.common.io.Files;
14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
15 import io.netty.channel.nio.NioEventLoopGroup;
16 import io.netty.util.HashedWheelTimer;
17 import io.netty.util.Timer;
18 import java.io.IOException;
19 import java.nio.charset.StandardCharsets;
20 import java.util.ArrayList;
21 import java.util.List;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.ExecutorService;
24 import java.util.concurrent.Executors;
25 import java.util.concurrent.Future;
26 import java.util.concurrent.TimeUnit;
27 import java.util.concurrent.TimeoutException;
28 import net.sourceforge.argparse4j.inf.ArgumentParser;
29 import net.sourceforge.argparse4j.inf.ArgumentParserException;
30 import org.opendaylight.netconf.api.messages.NetconfMessage;
31 import org.opendaylight.netconf.api.xml.XmlUtil;
32 import org.opendaylight.netconf.client.NetconfClientDispatcherImpl;
33 import org.opendaylight.netconf.client.mdsal.NetconfDeviceCommunicator;
34 import org.opendaylight.netconf.client.mdsal.api.NetconfSessionPreferences;
35 import org.opendaylight.netconf.client.mdsal.api.RemoteDevice;
36 import org.opendaylight.netconf.nettyutil.handler.ssh.client.AsyncSshHandler;
37 import org.opendaylight.netconf.test.tool.TestToolUtils;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.CommitInput;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.EditConfigInput;
40 import org.opendaylight.yangtools.yang.common.QName;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.w3c.dom.Document;
44 import org.w3c.dom.Element;
45 import org.w3c.dom.Node;
46 import org.xml.sax.SAXException;
47
48 @SuppressFBWarnings("DM_EXIT")
49 public final class StressClient {
50     private static final Logger LOG = LoggerFactory.getLogger(StressClient.class);
51
52     static final QName COMMIT_QNAME = QName.create(CommitInput.QNAME, "commit");
53     public static final NetconfMessage COMMIT_MSG = new NetconfMessage(readString("""
54         <rpc message-id="commit-batch" xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
55             <commit/>
56         </rpc>"""));
57
58     static final QName EDIT_QNAME = QName.create(EditConfigInput.QNAME, "edit-config");
59     static final Document EDIT_CANDIDATE_BLUEPRINT = readString("""
60         <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
61             <edit-config>
62                 <target>
63                     <candidate/>
64                 </target>
65                 <default-operation>none</default-operation>
66                 <config/>
67             </edit-config>
68         </rpc>""");
69     static final Document EDIT_RUNNING_BLUEPRINT  = readString("""
70         <rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
71             <edit-config>
72                 <target>
73                     <running/>
74                 </target>
75                 <default-operation>none</default-operation>
76                 <config/>
77             </edit-config>
78         </rpc>""");
79
80     private static Document readString(final String str) {
81         try {
82             return XmlUtil.readXmlToDocument(str);
83         } catch (SAXException | IOException e) {
84             throw new ExceptionInInitializerError(e);
85         }
86     }
87
88     private static final String MSG_ID_PLACEHOLDER_REGEX = "\\{MSG_ID\\}";
89     private static final String PHYS_ADDR_PLACEHOLDER = "{PHYS_ADDR}";
90
91     private static long macStart = 0xAABBCCDD0000L;
92
93     private static Parameters params;
94
95     private StressClient() {
96
97     }
98
99     public static void main(final String[] args) {
100
101         params = parseArgs(args, Parameters.getParser());
102         params.validate();
103
104         final ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory
105             .getLogger(Logger.ROOT_LOGGER_NAME);
106         root.setLevel(params.debug ? Level.DEBUG : Level.INFO);
107
108         final int threadAmount = params.threadAmount;
109         LOG.info("thread amount: {}", threadAmount);
110         final int requestsPerThread = params.editCount / params.threadAmount;
111         LOG.info("requestsPerThread: {}", requestsPerThread);
112         final int leftoverRequests = params.editCount % params.threadAmount;
113         LOG.info("leftoverRequests: {}", leftoverRequests);
114
115         LOG.info("Preparing messages");
116         // Prepare all msgs up front
117         final List<List<NetconfMessage>> allPreparedMessages = new ArrayList<>(threadAmount);
118         for (int i = 0; i < threadAmount; i++) {
119             if (i != threadAmount - 1) {
120                 allPreparedMessages.add(new ArrayList<>(requestsPerThread));
121             } else {
122                 allPreparedMessages.add(new ArrayList<>(requestsPerThread + leftoverRequests));
123             }
124         }
125
126
127         final String editContentString;
128         try {
129             editContentString = Files.asCharSource(params.editContent, StandardCharsets.UTF_8).read();
130         } catch (final IOException e) {
131             throw new IllegalArgumentException("Cannot read content of " + params.editContent, e);
132         }
133
134         for (int i = 0; i < threadAmount; i++) {
135             final List<NetconfMessage> preparedMessages = allPreparedMessages.get(i);
136             int padding = 0;
137             if (i == threadAmount - 1) {
138                 padding = leftoverRequests;
139             }
140             for (int j = 0; j < requestsPerThread + padding; j++) {
141                 LOG.debug("id: {}", i * requestsPerThread + j);
142                 preparedMessages.add(prepareMessage(i * requestsPerThread + j, editContentString));
143             }
144         }
145
146         final NioEventLoopGroup nioGroup = new NioEventLoopGroup();
147         final Timer timer = new HashedWheelTimer();
148
149         final NetconfClientDispatcherImpl netconfClientDispatcher = configureClientDispatcher(nioGroup, timer);
150
151         final List<StressClientCallable> callables = new ArrayList<>(threadAmount);
152         for (final List<NetconfMessage> messages : allPreparedMessages) {
153             callables.add(new StressClientCallable(params, netconfClientDispatcher, messages));
154         }
155
156         final ExecutorService executorService = Executors.newFixedThreadPool(threadAmount);
157
158         LOG.info("Starting stress test");
159         final Stopwatch started = Stopwatch.createStarted();
160         try {
161             final List<Future<Boolean>> futures = executorService.invokeAll(callables);
162             for (final Future<Boolean> future : futures) {
163                 try {
164                     future.get(4L, TimeUnit.MINUTES);
165                 } catch (ExecutionException | TimeoutException e) {
166                     throw new IllegalStateException(e);
167                 }
168             }
169             executorService.shutdownNow();
170         } catch (final InterruptedException e) {
171             throw new IllegalStateException("Unable to execute requests", e);
172         }
173         started.stop();
174
175         LOG.info("FINISHED. Execution time: {}", started);
176         LOG.info("Requests per second: {}", params.editCount * 1000.0 / started.elapsed(TimeUnit.MILLISECONDS));
177
178         // Cleanup
179         timer.stop();
180         try {
181             nioGroup.shutdownGracefully().get(20L, TimeUnit.SECONDS);
182         } catch (InterruptedException | ExecutionException | TimeoutException e) {
183             LOG.warn("Unable to close executor properly", e);
184         }
185         //stop the underlying ssh thread that gets spawned if we use ssh
186         if (params.ssh) {
187             AsyncSshHandler.DEFAULT_CLIENT.stop();
188         }
189     }
190
191     static NetconfMessage prepareMessage(final int id, final String editContentString) {
192         final Document msg = XmlUtil.createDocumentCopy(
193             params.candidateDatastore ? EDIT_CANDIDATE_BLUEPRINT : EDIT_RUNNING_BLUEPRINT);
194         msg.getDocumentElement().setAttribute("message-id", Integer.toString(id));
195         final NetconfMessage netconfMessage = new NetconfMessage(msg);
196
197         final Element editContentElement;
198         try {
199             // Insert message id where needed
200             String specificEditContent = editContentString.replaceAll(MSG_ID_PLACEHOLDER_REGEX, Integer.toString(id));
201
202             final StringBuilder stringBuilder = new StringBuilder(specificEditContent);
203             int idx = stringBuilder.indexOf(PHYS_ADDR_PLACEHOLDER);
204             while (idx != -1) {
205                 stringBuilder.replace(idx, idx + PHYS_ADDR_PLACEHOLDER.length(), TestToolUtils.getMac(macStart++));
206                 idx = stringBuilder.indexOf(PHYS_ADDR_PLACEHOLDER);
207             }
208             specificEditContent = stringBuilder.toString();
209
210             editContentElement = XmlUtil.readXmlToElement(specificEditContent);
211             final Node config = ((Element) msg.getDocumentElement().getElementsByTagName("edit-config").item(0))
212                     .getElementsByTagName("config").item(0);
213             config.appendChild(msg.importNode(editContentElement, true));
214         } catch (final IOException | SAXException e) {
215             throw new IllegalArgumentException("Edit content file is unreadable", e);
216         }
217
218         return netconfMessage;
219     }
220
221     private static NetconfClientDispatcherImpl configureClientDispatcher(final NioEventLoopGroup nioGroup,
222             final Timer timer) {
223         final NetconfClientDispatcherImpl netconfClientDispatcher;
224         if (params.exi) {
225             if (params.legacyFraming) {
226                 netconfClientDispatcher = ConfigurableClientDispatcher.createLegacyExi(nioGroup, nioGroup, timer);
227             } else {
228                 netconfClientDispatcher = ConfigurableClientDispatcher.createChunkedExi(nioGroup, nioGroup, timer);
229             }
230         } else if (params.legacyFraming) {
231             netconfClientDispatcher = ConfigurableClientDispatcher.createLegacy(nioGroup, nioGroup, timer);
232         } else {
233             netconfClientDispatcher = ConfigurableClientDispatcher.createChunked(nioGroup, nioGroup, timer);
234         }
235         return netconfClientDispatcher;
236     }
237
238     private static Parameters parseArgs(final String[] args, final ArgumentParser parser) {
239         final Parameters opt = new Parameters();
240         try {
241             parser.parseArgs(args, opt);
242             return opt;
243         } catch (final ArgumentParserException e) {
244             parser.handleError(e);
245         }
246
247         System.exit(1);
248         return null;
249     }
250
251     static class LoggingRemoteDevice implements RemoteDevice<NetconfDeviceCommunicator> {
252         @Override
253         public void onRemoteSessionUp(final NetconfSessionPreferences remoteSessionCapabilities,
254                                       final NetconfDeviceCommunicator netconfDeviceCommunicator) {
255             LOG.info("Session established");
256         }
257
258         @Override
259         public void onRemoteSessionDown() {
260             LOG.info("Session down");
261         }
262
263         @Override
264         public void onNotification(final NetconfMessage notification) {
265             LOG.info("Notification received: {}", notification);
266         }
267     }
268 }