/* * Copyright (C) 2019 Ericsson Software Technology AB. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.netconf.test.tool; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol.SSH; import static org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol.TCP; import static org.xmlunit.assertj.XmlAssert.assertThat; import com.google.common.collect.ImmutableMap; import java.io.File; import java.net.InetAddress; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.opendaylight.netconf.api.messages.NetconfMessage; import org.opendaylight.netconf.api.xml.XmlUtil; import org.opendaylight.netconf.auth.AuthProvider; import org.opendaylight.netconf.client.NetconfClientFactory; import org.opendaylight.netconf.client.NetconfClientFactoryImpl; import org.opendaylight.netconf.client.NetconfClientSession; import org.opendaylight.netconf.client.NetconfClientSessionListener; import org.opendaylight.netconf.client.SimpleNetconfClientSessionListener; import org.opendaylight.netconf.client.conf.NetconfClientConfiguration; import org.opendaylight.netconf.client.conf.NetconfClientConfiguration.NetconfClientProtocol; import org.opendaylight.netconf.client.conf.NetconfClientConfigurationBuilder; import org.opendaylight.netconf.common.impl.DefaultNetconfTimer; import org.opendaylight.netconf.test.tool.config.Configuration; import org.opendaylight.netconf.test.tool.config.ConfigurationBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.password.grouping.password.type.CleartextPasswordBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.Host; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.IetfInetUtil; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.inet.types.rev130715.PortNumber; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev231228.netconf.client.initiate.stack.grouping.transport.ssh.ssh.TcpClientParametersBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.netconf.client.rev231228.netconf.client.listen.stack.grouping.transport.ssh.ssh.SshClientParametersBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ClientIdentityBuilder; import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.client.identity.PasswordBuilder; import org.opendaylight.yangtools.yang.common.Uint16; import org.w3c.dom.Document; public class TestToolTest { private static final long RESPONSE_TIMEOUT_MS = 30_000; private static final int RANDOM_PORT = 0; private static final String USERNAME = "username"; private static final String PASSWORD = "pa$$W0rd"; private static final AuthProvider AUTH_PROVIDER = (user, passw) -> USERNAME.equals(user) && PASSWORD.equals(passw); private static final File CUSTOM_RPC_CONFIG = new File("src/test/resources/customrpc.xml"); private static final String RFC7950_4_2_9_REQUEST = """ example-fw-2.3 """; private static final String RFC7950_4_2_9_RESPONSE = """ The image example-fw-2.3 is being installed. """; private static final String RFC7950_7_15_3_REQUEST = """ apache-1 2014-07-29T13:42:00Z """; private static final String RFC7950_7_15_3_RESPONSE = """ 2014-07-29T13:42:12Z """; private static final String GET_SCHEMAS_REQUEST = """ """; private static final Map PREFIX_2_URI = ImmutableMap.of( "base10", "urn:ietf:params:xml:ns:netconf:base:1.0", "ncmon", "urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring" ); private static DefaultNetconfTimer timer; private static NetconfClientFactory clientFactory; private static NetconfDeviceSimulator tcpDeviceSimulator; private static NetconfDeviceSimulator sshDeviceSimulator; private static int tcpDevicePort; private static int sshDevicePort; @BeforeAll static void beforeAll() { timer = new DefaultNetconfTimer(); clientFactory = new NetconfClientFactoryImpl(timer); tcpDeviceSimulator = new NetconfDeviceSimulator(getSimulatorConfig(TCP)); tcpDevicePort = startSimulator(tcpDeviceSimulator); sshDeviceSimulator = new NetconfDeviceSimulator(getSimulatorConfig(SSH)); sshDevicePort = startSimulator(sshDeviceSimulator); } @AfterAll public static void afterAll() throws Exception { stopSimulator(tcpDeviceSimulator); tcpDeviceSimulator = null; stopSimulator(sshDeviceSimulator); sshDeviceSimulator = null; clientFactory.close(); timer.close(); } @ParameterizedTest(name = "Custom RPC -- RFC7950 {0}") @MethodSource("customRpcArgs") void customRpc(final String ignoredTestDesc, final NetconfClientProtocol protocol, final String requestXml, final String responseXml) throws Exception { final var docResponse = sendRequest(protocol, requestXml); assertThat(docResponse).and(responseXml).ignoreWhitespace().areIdentical(); } private static Stream customRpcArgs() { return Stream.of( // # test descriptor, protocol, request, expected response Arguments.of("#7.15.3 @TCP", TCP, RFC7950_7_15_3_REQUEST, RFC7950_7_15_3_RESPONSE), Arguments.of("#7.15.3 @SSH", SSH, RFC7950_7_15_3_REQUEST, RFC7950_7_15_3_RESPONSE), Arguments.of("#4.2.9 @TCP", TCP, RFC7950_4_2_9_REQUEST, RFC7950_4_2_9_RESPONSE), Arguments.of("#4.2.9 @SSH", SSH, RFC7950_4_2_9_REQUEST, RFC7950_4_2_9_RESPONSE) ); } @ParameterizedTest(name = "Get Schemas @{0}") @MethodSource("getSchemasArgs") void getSchemas(final NetconfClientProtocol protocol) throws Exception { final var docResponse = sendRequest(protocol, GET_SCHEMAS_REQUEST); final var expectedYangResources = Configuration.DEFAULT_YANG_RESOURCES; assertEquals(4, expectedYangResources.size()); assertThat(docResponse) .withNamespaceContext(PREFIX_2_URI) .valueByXPath("count(//base10:rpc-reply/base10:data/ncmon:netconf-state/ncmon:schemas/ncmon:schema)") .isEqualTo(expectedYangResources.size()); } private static List getSchemasArgs() { return List.of(SSH, TCP); } private static Document sendRequest(final NetconfClientProtocol protocol, final String xml) throws Exception { final var sessionListener = new SimpleNetconfClientSessionListener(); final int port = SSH == protocol ? sshDevicePort : tcpDevicePort; final var clientConfig = getClientConfig(port, protocol, sessionListener); final var request = new NetconfMessage(XmlUtil.readXmlToDocument(xml)); NetconfMessage response; try (NetconfClientSession ignored = clientFactory.createClient(clientConfig) .get(RESPONSE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) { response = sessionListener.sendRequest(request).get(RESPONSE_TIMEOUT_MS, TimeUnit.MILLISECONDS); } assertNotNull(response); return response.getDocument(); } /** * Runs a simulator. * * @param simulator simulator instance * @return The TCP port number to access the launched simulator. */ private static int startSimulator(final NetconfDeviceSimulator simulator) { final var openDevices = simulator.start(); if (openDevices != null && !openDevices.isEmpty()) { return openDevices.get(0); } throw new IllegalStateException("Could not start device simulator"); } private static void stopSimulator(final NetconfDeviceSimulator simulator) { if (simulator != null) { simulator.close(); } } @SuppressWarnings("deprecation") private static Configuration getSimulatorConfig(final NetconfClientProtocol protocol) { return new ConfigurationBuilder() .setStartingPort(RANDOM_PORT) .setDeviceCount(1) .setRpcConfigFile(CUSTOM_RPC_CONFIG) .setSsh(SSH == protocol) .setAuthProvider(AUTH_PROVIDER) .build(); } private static NetconfClientConfiguration getClientConfig(final int port, final NetconfClientProtocol protocol, final NetconfClientSessionListener sessionListener) { return NetconfClientConfigurationBuilder.create() .withTcpParameters( new TcpClientParametersBuilder() .setRemoteAddress(new Host(IetfInetUtil.ipAddressFor(InetAddress.getLoopbackAddress()))) .setRemotePort(new PortNumber(Uint16.valueOf(port))) .build()) .withSshParameters( new SshClientParametersBuilder() .setClientIdentity(new ClientIdentityBuilder() .setUsername(USERNAME) .setPassword(new PasswordBuilder().setPasswordType( new CleartextPasswordBuilder().setCleartextPassword(PASSWORD).build() ).build()).build()).build()) .withSessionListener(sessionListener) .withProtocol(protocol) .build(); } }