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