c821ad6990fa61be9df42b45d1fb5f9d6728349d
[netconf.git] / netconf / tools / netconf-testtool / src / test / java / org / opendaylight / netconf / test / tool / TestToolTest.java
1 /*
2  * Copyright (C) 2019 Ericsson Software Technology AB. 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;
10
11 import static org.junit.Assert.assertNotNull;
12 import static org.xmlunit.assertj.XmlAssert.assertThat;
13
14 import io.netty.channel.nio.NioEventLoopGroup;
15 import io.netty.util.HashedWheelTimer;
16 import io.netty.util.concurrent.DefaultThreadFactory;
17 import io.netty.util.concurrent.GlobalEventExecutor;
18 import java.io.File;
19 import java.net.InetSocketAddress;
20 import java.util.concurrent.ConcurrentHashMap;
21 import java.util.concurrent.TimeUnit;
22 import java.util.regex.Pattern;
23 import org.junit.AfterClass;
24 import org.junit.BeforeClass;
25 import org.junit.Rule;
26 import org.junit.Test;
27 import org.opendaylight.netconf.api.NetconfMessage;
28 import org.opendaylight.netconf.api.xml.XmlUtil;
29 import org.opendaylight.netconf.auth.AuthProvider;
30 import org.opendaylight.netconf.client.NetconfClientDispatcher;
31 import org.opendaylight.netconf.client.NetconfClientDispatcherImpl;
32 import org.opendaylight.netconf.client.NetconfClientSession;
33 import org.opendaylight.netconf.client.NetconfClientSessionListener;
34 import org.opendaylight.netconf.client.SimpleNetconfClientSessionListener;
35 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration;
36 import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol;
37 import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder;
38 import org.opendaylight.netconf.nettyutil.NeverReconnectStrategy;
39 import org.opendaylight.netconf.nettyutil.handler.ssh.authentication.LoginPasswordHandler;
40 import org.opendaylight.netconf.test.tool.config.Configuration;
41 import org.opendaylight.netconf.test.tool.config.ConfigurationBuilder;
42 import org.w3c.dom.Document;
43
44 @SuppressWarnings("SameParameterValue")
45 public class TestToolTest {
46
47     private static final long RECEIVE_TIMEOUT_MS = 5_000;
48     private static final int RANDOM_PORT = 0;
49
50     private static final User ADMIN_USER = new User("admin", "admin");
51     private static final File CUSTOM_RPC_CONFIG = new File("src/test/resources/customrpc.xml");
52     private static final Configuration SSH_SIMULATOR_CONFIG = getSimulatorConfig(NetconfClientProtocol.SSH,
53         ADMIN_USER);
54     private static final Configuration TCP_SIMULATOR_CONFIG = getSimulatorConfig(NetconfClientProtocol.SSH,
55         ADMIN_USER);
56
57     private static NioEventLoopGroup nettyGroup;
58     private static NetconfClientDispatcherImpl dispatcher;
59
60
61     @Rule
62     public LogPropertyCatcher logPropertyCatcher =
63         new LogPropertyCatcher(Pattern.compile("(start\\(\\) listen on auto-allocated port="
64             + "|Simulated TCP device started on (/0:0:0:0:0:0:0:0|/0.0.0.0):)(\\d+)"));
65
66     private static final String XML_REQUEST_RFC7950_SECTION_4_2_9 = "<rpc message-id=\"101\"\n"
67         + "          xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
68         + "       <activate-software-image xmlns=\"http://example.com/system\">\n"
69         + "         <image-name>example-fw-2.3</image-name>\n"
70         + "       </activate-software-image>\n"
71         + "     </rpc>";
72     private static final String EXPECTED_XML_RESPONSE_RFC7950_SECTION_4_2_9 = "<rpc-reply message-id=\"101\"\n"
73         + "                xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
74         + "       <status xmlns=\"http://example.com/system\">\n"
75         + "         The image example-fw-2.3 is being installed.\n"
76         + "       </status>\n"
77         + "     </rpc-reply>";
78     private static final String XML_REQUEST_RFC7950_SECTION_7_15_3 = "<rpc message-id=\"101\"\n"
79         + "          xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
80         + "       <action xmlns=\"urn:ietf:params:xml:ns:yang:1\">\n"
81         + "         <server xmlns=\"urn:example:server-farm\">\n"
82         + "           <name>apache-1</name>\n"
83         + "           <reset>\n"
84         + "             <reset-at>2014-07-29T13:42:00Z</reset-at>\n"
85         + "           </reset>\n"
86         + "         </server>\n"
87         + "       </action>\n"
88         + "     </rpc>";
89     private static final String EXPECTED_XML_RESPONSE_RFC7950_SECTION_7_15_3 = "<rpc-reply message-id=\"101\"\n"
90         + "           xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n"
91         + "  <reset-finished-at xmlns=\"urn:example:server-farm\">\n"
92         + "    2014-07-29T13:42:12Z\n"
93         + "  </reset-finished-at>\n"
94         + "</rpc-reply>";
95
96     @BeforeClass
97     public static void setUpClass() {
98         HashedWheelTimer hashedWheelTimer = new HashedWheelTimer();
99         nettyGroup = new NioEventLoopGroup(1, new DefaultThreadFactory(NetconfClientDispatcher.class));
100         dispatcher = new NetconfClientDispatcherImpl(nettyGroup, nettyGroup, hashedWheelTimer);
101     }
102
103     @AfterClass
104     public static void cleanUpClass()
105         throws InterruptedException {
106         nettyGroup.shutdownGracefully().sync();
107     }
108
109     @Test
110     public void customRpcOverSsh()
111         throws Exception {
112         Document docResponse = invokeRpc(SSH_SIMULATOR_CONFIG, XML_REQUEST_RFC7950_SECTION_7_15_3);
113         assertThat(docResponse)
114             .and(EXPECTED_XML_RESPONSE_RFC7950_SECTION_7_15_3)
115             .ignoreWhitespace()
116             .areIdentical();
117     }
118
119     @Test
120     public void customRpcOverTcp()
121         throws Exception {
122         Document docResponse = invokeRpc(TCP_SIMULATOR_CONFIG, XML_REQUEST_RFC7950_SECTION_4_2_9);
123         assertThat(docResponse)
124             .and(EXPECTED_XML_RESPONSE_RFC7950_SECTION_4_2_9)
125             .ignoreWhitespace()
126             .areIdentical();
127     }
128
129     private Document invokeRpc(Configuration simulatorConfig, String xmlRequest)
130         throws Exception {
131         // GIVEN
132         int localPort = launchSimulator(simulatorConfig);
133         SimpleNetconfClientSessionListener sessionListener = new SimpleNetconfClientSessionListener();
134         NetconfClientConfiguration clientConfig = getClientConfig("localhost", localPort,
135             simulatorConfig, sessionListener);
136         Document docRequest = XmlUtil.readXmlToDocument(xmlRequest);
137         NetconfMessage request = new NetconfMessage(docRequest);
138
139         // WHEN
140         NetconfMessage response;
141         try (NetconfClientSession ignored = dispatcher.createClient(clientConfig).get()) {
142             response = sessionListener.sendRequest(request)
143                 .get(RECEIVE_TIMEOUT_MS, TimeUnit.MILLISECONDS);
144         }
145
146         // THEN
147         assertNotNull(response);
148         return response.getDocument();
149     }
150
151     private static final ConcurrentHashMap<Configuration, Integer> CACHED_SIMULATORS = new ConcurrentHashMap<>();
152
153     /**
154      * Retrieves a previously launched simulator or launches a new one using the given configuration.
155      *
156      * @param configuration The simulator configuration.
157      * @return The TCP port number to access the launched simulator.
158      */
159     private int launchSimulator(Configuration configuration) {
160         return CACHED_SIMULATORS.computeIfAbsent(configuration, cfg -> {
161             NetconfDeviceSimulator simulator = new NetconfDeviceSimulator(cfg);
162             simulator.start();
163             return logPropertyCatcher.getLastValue()
164                 .map(Integer::parseInt)
165                 .orElseThrow(() -> new IllegalArgumentException("Unable to capture auto-allocated port from log"));
166         });
167     }
168
169     @SuppressWarnings("deprecation")
170     private static Configuration getSimulatorConfig(NetconfClientProtocol protocol, User user) {
171         return new ConfigurationBuilder()
172             .setStartingPort(RANDOM_PORT)
173             .setRpcConfigFile(CUSTOM_RPC_CONFIG)
174             .setSsh(protocol == NetconfClientProtocol.SSH)
175             .setAuthProvider(new InMemoryAuthenticationProvider(user))
176             .build();
177     }
178
179     @SuppressWarnings("deprecation")
180     private static NetconfClientConfiguration getClientConfig(String host, int port,
181                                                               Configuration simulatorConfig,
182                                                               NetconfClientSessionListener sessionListener) {
183         User user = ((InMemoryAuthenticationProvider) simulatorConfig.getAuthProvider()).user;
184         return NetconfClientConfigurationBuilder.create()
185             .withAddress(new InetSocketAddress(host, port))
186             .withSessionListener(sessionListener)
187             .withReconnectStrategy(new NeverReconnectStrategy(GlobalEventExecutor.INSTANCE,
188                 NetconfClientConfigurationBuilder.DEFAULT_CONNECTION_TIMEOUT_MILLIS))
189             .withProtocol(simulatorConfig.isSsh() ? NetconfClientProtocol.SSH : NetconfClientProtocol.TCP)
190             .withAuthHandler(new LoginPasswordHandler(user.username, user.password))
191             .build();
192     }
193
194     private static final class User {
195         private final String username;
196         private final String password;
197
198         private User(String username, String password) {
199             this.username = username;
200             this.password = password;
201         }
202     }
203
204     private static final class InMemoryAuthenticationProvider implements AuthProvider {
205
206         private final User user;
207
208         private InMemoryAuthenticationProvider(User user) {
209             this.user = user;
210         }
211
212         @Override
213         public boolean authenticated(String username, String password) {
214             return user.username.equals(username) && user.password.equals(password);
215         }
216     }
217 }