::
- usage: netconf testtool [-h] [--edit-content EDIT-CONTENT] [--async-requests {true,false}] [--thread-amount THREAD-AMOUNT] [--throttle THROTTLE]
- [--auth AUTH AUTH] [--controller-destination CONTROLLER-DESTINATION] [--device-count DEVICES-COUNT]
- [--devices-per-port DEVICES-PER-PORT] [--schemas-dir SCHEMAS-DIR] [--notification-file NOTIFICATION-FILE]
- [--initial-config-xml-file INITIAL-CONFIG-XML-FILE] [--starting-port STARTING-PORT]
+ usage: netconf testtool [-h] [--edit-content EDIT-CONTENT] [--async-requests {true,false}]
+ [--thread-amount THREAD-AMOUNT] [--throttle THROTTLE]
+ [--controller-auth-username CONTROLLER-AUTH-USERNAME]
+ [--controller-auth-password CONTROLLER-AUTH-PASSWORD]
+ [--controller-ip CONTROLLER-IP] [--controller-port CONTROLLER-PORT]
+ [--device-count DEVICES-COUNT] [--devices-per-port DEVICES-PER-PORT]
+ [--schemas-dir SCHEMAS-DIR] [--notification-file NOTIFICATION-FILE]
+ [--initial-config-xml-file INITIAL-CONFIG-XML-FILE]
+ [--starting-port STARTING-PORT]
[--generate-config-connection-timeout GENERATE-CONFIG-CONNECTION-TIMEOUT]
- [--generate-config-address GENERATE-CONFIG-ADDRESS] [--generate-configs-batch-size GENERATE-CONFIGS-BATCH-SIZE]
- [--distribution-folder DISTRO-FOLDER] [--ssh {true,false}] [--exi {true,false}] [--debug {true,false}]
- [--md-sal {true,false}] [--time-out TIME-OUT] [-ip IP] [--thread-pool-size THREAD-POOL-SIZE] [--rpc-config RPC-CONFIG]
+ [--generate-config-address GENERATE-CONFIG-ADDRESS]
+ [--generate-configs-batch-size GENERATE-CONFIGS-BATCH-SIZE]
+ [--distribution-folder DISTRO-FOLDER] [--ssh {true,false}]
+ [--exi {true,false}] [--debug {true,false}] [--md-sal {true,false}]
+ [--time-out TIME-OUT] [-ip IP] [--thread-pool-size THREAD-POOL-SIZE]
+ [--rpc-config RPC-CONFIG]
netconf testtool
--async-requests {true,false}
--thread-amount THREAD-AMOUNT
The number of threads to use for configuring devices.
- --throttle THROTTLE Maximum amount of async requests that can be open at a time, with mutltiple threads this gets divided among all threads
- --auth AUTH AUTH Username and password for HTTP basic authentication in order username password.
- --controller-destination CONTROLLER-DESTINATION
- Ip address and port of controller. Must be in following format <ip>:<port> if available it will be used for spawning
- netconf connectors via topology configuration as a part of URI. Example (http://<controller
- destination>/restconf/config/network-topology:network-topology/topology/topology-netconf/node/<node-id>)otherwise it will
- just start simulated devices and skip the execution of PUT requests
+ --throttle THROTTLE Maximum amount of async requests that can be open at a time, with
+ mutltiple threads this gets divided among all threads
+ --controller-auth-username CONTROLLER-AUTH-USERNAME
+ Username for HTTP basic authentication to destination controller.
+ --controller-auth-password CONTROLLER-AUTH-PASSWORD
+ Password for HTTP basic authentication to destination controller.
+ --controller-ip CONTROLLER-IP
+ Ip of controller if available it will be used for spawning netconf
+ connectors via topology configuration as a part of URI(http://<controller-
+ ip>:<controller-port>/restconf/config/...) otherwise it will just start
+ simulated devices and skip the execution of PATCH requests
+ --controller-port CONTROLLER-PORT
+ Port of controller if available it will be used for spawning netconf
+ connectors via topology configuration as a part of URI(http://<controller-
+ ip>:<controller-port>/restconf/config/...) otherwise it will just start
+ simulated devices and skip the execution of PATCH requests
--device-count DEVICES-COUNT
- Number of simulated netconf devices to spin. This is the number of actual ports open for the devices.
+ Number of simulated netconf devices to spin. This is the number of actual
+ ports open for the devices.
--devices-per-port DEVICES-PER-PORT
- Amount of config files generated per port to spoof more devices than are actually running
+ Amount of config files generated per port to spoof more devices than are
+ actually running
--schemas-dir SCHEMAS-DIR
- Directory containing yang schemas to describe simulated devices. Some schemas e.g. netconf monitoring and inet types are
- included by default
+ Directory containing yang schemas to describe simulated devices. Some
+ schemas e.g. netconf monitoring and inet types are included by default
--notification-file NOTIFICATION-FILE
- Xml file containing notifications that should be sent to clients after create subscription is called
+ Xml file containing notifications that should be sent to clients after
+ create subscription is called
--initial-config-xml-file INITIAL-CONFIG-XML-FILE
- Xml file containing initial simulatted configuration to be returned via get-config rpc
+ Xml file containing initial simulatted configuration to be returned via
+ get-config rpc
--starting-port STARTING-PORT
- First port for simulated device. Each other device will have previous+1 port number
+ First port for simulated device. Each other device will have previous+1
+ port number
--generate-config-connection-timeout GENERATE-CONFIG-CONNECTION-TIMEOUT
Timeout to be generated in initial config files
--generate-config-address GENERATE-CONFIG-ADDRESS
--exi {true,false} Whether to use exi to transport xml content
--debug {true,false} Whether to use debug log level instead of INFO
--md-sal {true,false} Whether to use md-sal datastore instead of default simulated datastore.
- --time-out TIME-OUT the maximum time in seconds for executing each PUT request
- -ip IP Ip address which will be used for creating a socket address.It can either be a machine name, such as java.sun.com, or a
- textual representation of its IP address.
+ --time-out TIME-OUT the maximum time in seconds for executing each PATCH request
+ -ip IP Ip address which will be used for creating a socket address.It can either
+ be a machine name, such as java.sun.com, or a textual representation of
+ its IP address.
--thread-pool-size THREAD-POOL-SIZE
- The number of threads to keep in the pool, when creating a device simulator. Even if they are idle.
+ The number of threads to keep in the pool, when creating a device
+ simulator. Even if they are idle.
--rpc-config RPC-CONFIG
- Rpc config file. It can be used to define custom rpc behavior, or override the default one.Usable for testing buggy device
- behavior.
+ Rpc config file. It can be used to define custom rpc behavior, or
+ override the default one.Usable for testing buggy device behavior.
+
Supported operations
*/
package org.opendaylight.netconf.test.tool;
+import com.google.common.collect.Lists;
import java.io.IOException;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.Semaphore;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class Execution implements Callable<Void> {
+final class Execution implements Callable<Void> {
+ private static final Logger LOG = LoggerFactory.getLogger(Execution.class);
+ private static final String NETCONF_TOPOLOGY_DESTINATION =
+ "http://%s:%s/restconf/config/network-topology:network-topology/topology/topology-netconf";
- private final ArrayList<HttpRequest> payloads;
private final HttpClient httpClient;
- private static final Logger LOG = LoggerFactory.getLogger(Execution.class);
- private final boolean invokeAsync;
+ private final String destination;
+ private final List<Integer> openDevices;
+ private final TesttoolParameters params;
private final Semaphore semaphore;
- private final int throttle;
-
- static final class DestToPayload {
-
- private final String destination;
- private final String payload;
-
- DestToPayload(final String destination, final String payload) {
- this.destination = destination;
- this.payload = payload;
- }
-
- public String getDestination() {
- return destination;
- }
-
- public String getPayload() {
- return payload;
- }
- }
- public Execution(final TesttoolParameters params, final ArrayList<DestToPayload> payloads) {
- this.invokeAsync = params.async;
- this.throttle = params.throttle / params.threadAmount;
-
- if (params.async && params.threadAmount > 1) {
- LOG.info("Throttling per thread: {}", this.throttle);
- }
- this.semaphore = new Semaphore(this.throttle);
+ private final int throttle;
+ private final boolean isAsync;
- this.httpClient = HttpClient.newBuilder()
+ Execution(final List<Integer> openDevices, final TesttoolParameters params) {
+ httpClient = HttpClient.newBuilder()
.authenticator(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
}
})
.build();
- this.payloads = new ArrayList<>();
- for (DestToPayload payload : payloads) {
- HttpRequest request = HttpRequest.newBuilder(URI.create(payload.getDestination()))
- .POST(BodyPublishers.ofString(payload.getPayload(), StandardCharsets.UTF_8))
- .header("Content-Type", "application/json")
- .header("Accept", "application/json")
- .build();
+ destination = String.format(Locale.ROOT, NETCONF_TOPOLOGY_DESTINATION,
+ params.controllerIp, params.controllerPort);
+ this.openDevices = openDevices;
+ this.params = params;
+
+ throttle = params.throttle / params.threadAmount;
+ isAsync = params.async;
- this.payloads.add(request);
+ if (params.async && params.threadAmount > 1) {
+ LOG.info("Throttling per thread: {}", throttle);
}
+ semaphore = new Semaphore(throttle);
}
- private void invokeSync() {
- LOG.info("Begin sending sync requests");
- for (HttpRequest request : payloads) {
- try {
- HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
-
- if (response.statusCode() != 200 && response.statusCode() != 204) {
- if (response.statusCode() == 409) {
- LOG.warn("Request failed, status code: {} - one or more of the devices"
- + " is already configured, skipping the whole batch", response.statusCode());
- } else {
- LOG.warn("Status code: {}", response.statusCode());
- LOG.warn("url: {}", request.uri());
- LOG.warn("body: {}", response.body());
- }
- }
- } catch (InterruptedException | IOException e) {
- LOG.warn("Failed to execute request", e);
- }
+ @Override
+ public Void call() {
+ final List<HttpRequest> requests = prepareRequests();
+ if (isAsync) {
+ this.sendAsync(requests);
+ } else {
+ this.sendSync(requests);
}
- LOG.info("End sending sync requests");
+ return null;
}
- private void invokeAsync() {
- LOG.info("Begin sending async requests");
+ private List<HttpRequest> prepareRequests() {
+ final List<List<Integer>> batches = Lists.partition(openDevices, params.generateConfigBatchSize);
+ return batches.stream()
+ .map(b -> PayloadCreator.createStringPayload(b, params))
+ .map(this::prepareRequest)
+ .collect(Collectors.toList());
+ }
- for (final HttpRequest request : payloads) {
+ private void sendAsync(final List<HttpRequest> requests) {
+ LOG.info("Begin sending async requests");
+ for (final HttpRequest request : requests) {
try {
semaphore.acquire();
- } catch (InterruptedException e) {
+ } catch (final InterruptedException e) {
LOG.warn("Semaphore acquire interrupted");
}
httpClient.sendAsync(request, BodyHandlers.ofString()).whenComplete((response, error) -> {
- if (response.statusCode() != 200 && response.statusCode() != 204) {
- if (response.statusCode() == 409) {
- LOG.warn("Request failed, status code: {} - one or more of the devices"
- + " is already configured, skipping the whole batch", response.statusCode());
- } else {
- LOG.warn("Request failed, status code: {}", response.statusCode());
- LOG.warn("request: {}", request);
- }
+ if (response.statusCode() != 200) {
+ LOG.warn("Unexpected status code: {} for request to uri: {} with body: {}",
+ response.statusCode(), request.uri(), response.body());
}
semaphore.release();
});
}
LOG.info("Requests sent, waiting for responses");
-
try {
semaphore.acquire(this.throttle);
- } catch (InterruptedException e) {
+ } catch (final InterruptedException e) {
LOG.warn("Semaphore acquire interrupted");
}
-
LOG.info("Responses received, ending...");
}
- @Override
- public Void call() {
- if (invokeAsync) {
- this.invokeAsync();
- } else {
- this.invokeSync();
+ private void sendSync(final List<HttpRequest> requests) {
+ LOG.info("Begin sending sync requests");
+ for (final HttpRequest request : requests) {
+ try {
+ final HttpResponse<String> response = httpClient.send(request, BodyHandlers.ofString());
+ if (response.statusCode() != 200) {
+ LOG.warn("Unexpected status code: {} for request to uri: {} with body: {}",
+ response.statusCode(), request.uri(), response.body());
+ }
+ } catch (final InterruptedException | IOException e) {
+ LOG.error("Failed to execute request: {}", request, e);
+ throw new RuntimeException("Failed to execute request", e);
+ }
}
- return null;
+ LOG.info("End sending sync requests");
+ }
+
+ private HttpRequest prepareRequest(final String payload) {
+ LOG.info("Creating request to: {} with payload: {}", destination, payload);
+ return HttpRequest.newBuilder(URI.create(destination))
+ .method("PATCH", BodyPublishers.ofString(payload, StandardCharsets.UTF_8))
+ .header("Content-Type", "application/json")
+ .header("Accept", "application/json")
+ .build();
}
}
* 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 ch.qos.logback.classic.Level;
import com.google.common.base.Stopwatch;
+import com.google.common.collect.Lists;
+import com.google.common.math.IntMath;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-import java.util.ArrayList;
+import java.math.RoundingMode;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
import org.opendaylight.netconf.test.tool.config.Configuration;
import org.opendaylight.netconf.test.tool.config.ConfigurationBuilder;
import org.slf4j.Logger;
private static final Logger LOG = LoggerFactory.getLogger(Main.class);
private Main() {
-
+ // hidden on purpose
}
@SuppressWarnings("checkstyle:IllegalCatch")
}
//if ODL controller ip is not set NETCONF devices will be started, but not registered at the controller
if (params.controllerIp != null) {
- final ArrayList<ArrayList<Execution.DestToPayload>> allThreadsPayloads = params
- .getThreadsPayloads(openDevices);
- final ArrayList<Execution> executions = new ArrayList<>();
- for (ArrayList<Execution.DestToPayload> payloads : allThreadsPayloads) {
- executions.add(new Execution(params, payloads));
- }
+ final List<Execution> executionThreads = divideDevicesForThreads(openDevices, params);
final ExecutorService executorService = Executors.newFixedThreadPool(params.threadAmount);
final Stopwatch time = Stopwatch.createStarted();
- List<Future<Void>> futures = executorService.invokeAll(executions, params.timeOut, TimeUnit.SECONDS);
+ final List<Future<Void>> futures = executorService.invokeAll(executionThreads,
+ params.timeOut, TimeUnit.SECONDS);
int threadNum = 0;
- for (Future<Void> future : futures) {
+ for (final Future<Void> future : futures) {
threadNum++;
if (future.isCancelled()) {
- LOG.info("{}. thread timed out.",threadNum);
+ LOG.info("{}. thread timed out.", threadNum);
} else {
try {
future.get();
}
}
time.stop();
- LOG.info("Time spent with configuration of devices: {}.",time);
+ LOG.info("Time spent with configuration of devices: {}.", time);
}
- } catch (RuntimeException | InterruptedException e) {
+ } catch (final RuntimeException | InterruptedException e) {
LOG.error("Unhandled exception", e);
netconfDeviceSimulator.close();
System.exit(1);
}
}
}
+
+ private static List<Execution> divideDevicesForThreads(final List<Integer> openDevices,
+ final TesttoolParameters params) {
+ final int devicesPerThread = IntMath.divide(openDevices.size(), params.threadAmount, RoundingMode.UP);
+ return Lists.partition(openDevices, devicesPerThread).stream()
+ .map(t -> new Execution(t, params))
+ .collect(Collectors.toList());
+ }
}
--- /dev/null
+/*
+ * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. 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.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import static org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gson.stream.JsonWriter;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.List;
+import org.opendaylight.mdsal.binding.runtime.spi.BindingRuntimeHelpers;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.$YangModuleInfoImpl;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.NetconfNodeFields;
+import org.opendaylight.yang.gen.v1.urn.opendaylight.netconf.node.topology.rev150114.netconf.node.credentials.Credentials;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.NetworkTopology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.Topology;
+import org.opendaylight.yang.gen.v1.urn.tbd.params.xml.ns.yang.network.topology.rev131021.network.topology.topology.Node;
+import org.opendaylight.yangtools.yang.common.QName;
+import org.opendaylight.yangtools.yang.common.Uint16;
+import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactory;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONCodecFactorySupplier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONNormalizedNodeStreamWriter;
+import org.opendaylight.yangtools.yang.data.codec.gson.JsonWriterFactory;
+import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
+import org.opendaylight.yangtools.yang.model.api.SchemaPath;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+final class PayloadCreator {
+ private static final Logger LOG = LoggerFactory.getLogger(PayloadCreator.class);
+
+ private static final EffectiveModelContext NETWORK_TOPOLOGY_SCHEMA_CONTEXT = BindingRuntimeHelpers
+ .createEffectiveModel(ImmutableList.of($YangModuleInfoImpl.getInstance()));
+ private static final JSONCodecFactory NETWORK_TOPOLOGY_JSON_CODEC_FACTORY = JSONCodecFactorySupplier
+ .DRAFT_LHOTKA_NETMOD_YANG_JSON_02.getShared(NETWORK_TOPOLOGY_SCHEMA_CONTEXT);
+
+ private static final QName TOPOLOGY_ID_QNAME = QName.create(Topology.QNAME, "topology-id").intern();
+ private static final QName NODE_ID_QNAME = QName.create(Node.QNAME, "node-id").intern();
+
+ private static final NodeIdentifier PORT_NODE_IDENTIFIER = NodeIdentifier
+ .create(QName.create(NetconfNodeFields.QNAME, "port"));
+ private static final NodeIdentifier HOST_NODE_IDENTIFIER = NodeIdentifier
+ .create(QName.create(NetconfNodeFields.QNAME,"host"));
+ private static final NodeIdentifier USERNAME_NODE_IDENTIFIER = NodeIdentifier
+ .create(QName.create(NetconfNodeFields.QNAME,"username"));
+ private static final NodeIdentifier PASSWORD_NODE_IDENTIFIER = NodeIdentifier
+ .create(QName.create(NetconfNodeFields.QNAME, "password"));
+ private static final NodeIdentifier CREDENTIALS_NODE_IDENTIFIER = NodeIdentifier.create(Credentials.QNAME);
+ private static final NodeIdentifier TCP_ONLY_NODE_IDENTIFIER = NodeIdentifier
+ .create(QName.create(NetconfNodeFields.QNAME, "tcp-only"));
+ private static final NodeIdentifier KEEPALIVE_DELAY_NODE_IDENTIFIER = NodeIdentifier
+ .create(QName.create(NetconfNodeFields.QNAME, "keepalive-delay"));
+ private static final NodeIdentifier SCHEMALESS_NODE_IDENTIFIER = NodeIdentifier
+ .create(QName.create(NetconfNodeFields.QNAME, "schemaless"));
+ private static final String DEFAULT_TOPOLOGY_ID = "topology-netconf";
+ private static final String DEFAULT_NODE_PASSWORD = "admin";
+ private static final String DEFAULT_NODE_USERNAME = "admin";
+
+ private static final boolean DEFAULT_NODE_SCHEMALESS = false;
+ private static final int DEFAULT_NODE_KEEPALIVE_DELAY = 0;
+ private static final int DEFAULT_REQUEST_PAYLOAD_INDENTATION = 2;
+
+ private PayloadCreator() {
+ // hidden on purpose
+ }
+
+ static String createStringPayload(final List<Integer> devices, final TesttoolParameters parameters) {
+ return normalizedNodeToString(createNormalizedNodePayload(devices, parameters));
+ }
+
+ private static String normalizedNodeToString(final NormalizedNode node) {
+ final StringWriter writer = new StringWriter();
+ final JsonWriter jsonWriter = JsonWriterFactory.createJsonWriter(writer, DEFAULT_REQUEST_PAYLOAD_INDENTATION);
+ final NormalizedNodeStreamWriter jsonStream = JSONNormalizedNodeStreamWriter.createExclusiveWriter(
+ NETWORK_TOPOLOGY_JSON_CODEC_FACTORY, SchemaPath.create(true, NetworkTopology.QNAME),
+ null, jsonWriter);
+ try (NormalizedNodeWriter nodeWriter = NormalizedNodeWriter.forStreamWriter(jsonStream)) {
+ nodeWriter.write(node);
+ } catch (final IOException e) {
+ LOG.error("Failed to serialize node: {} to JSON", node, e);
+ throw new RuntimeException("Failed to serialize node to JSON", e);
+ }
+ return writer.toString();
+ }
+
+ private static NormalizedNode createNormalizedNodePayload(final List<Integer> devices,
+ final TesttoolParameters parameters) {
+ final var nodeBuilder = Builders.mapBuilder().withNodeIdentifier(NodeIdentifier.create(Node.QNAME));
+ for (final Integer device : devices) {
+ nodeBuilder.withChild(Builders.mapEntryBuilder()
+ .withNodeIdentifier(NodeIdentifierWithPredicates.of(Node.QNAME, NODE_ID_QNAME,
+ createNodeID(device)))
+ .withChild(leafPort(device))
+ .withChild(leafHost(parameters.generateConfigsAddress))
+ .withChild(containerCredentials(DEFAULT_NODE_USERNAME, DEFAULT_NODE_PASSWORD))
+ .withChild(leafTcpOnly(!parameters.ssh))
+ .withChild(leafKeepaliveDelay(DEFAULT_NODE_KEEPALIVE_DELAY))
+ .withChild(leafSchemaless(DEFAULT_NODE_SCHEMALESS))
+ .build());
+ }
+
+ return Builders.mapBuilder()
+ .withNodeIdentifier(NodeIdentifier.create(Topology.QNAME))
+ .withChild(Builders.mapEntryBuilder().withNodeIdentifier(NodeIdentifierWithPredicates
+ .of(Topology.QNAME, TOPOLOGY_ID_QNAME, DEFAULT_TOPOLOGY_ID))
+ .withChild(nodeBuilder.build())
+ .build())
+ .build();
+ }
+
+ private static String createNodeID(final Integer port) {
+ return String.format("%d-sim-device", port);
+ }
+
+ private static LeafNode<Uint16> leafPort(final int port) {
+ return Builders.<Uint16>leafBuilder()
+ .withNodeIdentifier(PORT_NODE_IDENTIFIER)
+ .withValue(Uint16.valueOf(port))
+ .build();
+ }
+
+ private static LeafNode<String> leafHost(final String host) {
+ return Builders.<String>leafBuilder()
+ .withNodeIdentifier(HOST_NODE_IDENTIFIER)
+ .withValue(host)
+ .build();
+ }
+
+ private static ChoiceNode containerCredentials(final String username, final String password) {
+ return Builders.choiceBuilder().withNodeIdentifier(CREDENTIALS_NODE_IDENTIFIER)
+ .withChild(Builders.<String>leafBuilder()
+ .withNodeIdentifier(USERNAME_NODE_IDENTIFIER)
+ .withValue(username)
+ .build())
+ .withChild(Builders.<String>leafBuilder()
+ .withNodeIdentifier(PASSWORD_NODE_IDENTIFIER)
+ .withValue(password)
+ .build())
+ .build();
+ }
+
+ private static LeafNode<Boolean> leafTcpOnly(final Boolean tcpOnly) {
+ return Builders.<Boolean>leafBuilder()
+ .withNodeIdentifier(TCP_ONLY_NODE_IDENTIFIER)
+ .withValue(tcpOnly)
+ .build();
+ }
+
+ private static LeafNode<Integer> leafKeepaliveDelay(final Integer keepaliveDelay) {
+ return Builders.<Integer>leafBuilder()
+ .withNodeIdentifier(KEEPALIVE_DELAY_NODE_IDENTIFIER)
+ .withValue(keepaliveDelay)
+ .build();
+ }
+
+ private static LeafNode<Boolean> leafSchemaless(final Boolean schemaless) {
+ return Builders.<Boolean>leafBuilder()
+ .withNodeIdentifier(SCHEMALESS_NODE_IDENTIFIER)
+ .withValue(schemaless)
+ .build();
+ }
+}
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
-import com.google.common.io.CharStreams;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.Iterator;
import java.util.List;
import java.util.StringJoiner;
import java.util.concurrent.TimeUnit;
import net.sourceforge.argparse4j.inf.ArgumentParserException;
import org.opendaylight.yangtools.yang.common.YangConstants;
-@SuppressFBWarnings({"DM_EXIT", "DM_DEFAULT_ENCODING"})
-public class TesttoolParameters {
-
- private static final String HOST_KEY = "{HOST}";
- private static final String PORT_KEY = "{PORT}";
- private static final String TCP_ONLY = "{TCP_ONLY}";
- private static final String RESTCONF_NETCONF_TOPOLOGY_PATH_TEMPLATE =
- "http://%s:%s/restconf/config/network-topology:network-topology/topology/topology-netconf/";
+@SuppressFBWarnings({"DM_EXIT"})
+public final class TesttoolParameters {
private static final Pattern YANG_FILENAME_PATTERN = Pattern
- .compile("(?<name>.*)@(?<revision>\\d{4}-\\d{2}-\\d{2})\\.yang");
+ .compile("(?<name>.*)@(?<revision>\\d{4}-\\d{2}-\\d{2})\\.yang");
private static final Pattern REVISION_DATE_PATTERN = Pattern.compile("revision\\s+\"?(\\d{4}-\\d{2}-\\d{2})\"?");
- private static final String RESOURCE = "/config-template.json";
@Arg(dest = "async")
public boolean async;
@Arg(dest = "thread-amount")
.help("Ip of controller if available it will be used for spawning netconf connectors via topology"
+ " configuration as a part of"
+ " URI(http://<controller-ip>:<controller-port>/restconf/config/...)"
- + " otherwise it will just start simulated devices and skip the execution of PUT requests")
+ + " otherwise it will just start simulated devices and skip the execution of PATCH requests")
.dest("controller-ip");
parser.addArgument("--controller-port")
.help("Port of controller if available it will be used for spawning netconf connectors via topology "
+ "configuration as a part of"
+ " URI(http://<controller-ip>:<controller-port>/restconf/config/...) "
- + "otherwise it will just start simulated devices and skip the execution of PUT requests")
+ + "otherwise it will just start simulated devices and skip the execution of PATCH requests")
.dest("controller-port");
parser.addArgument("--device-count")
parser.addArgument("--time-out")
.type(long.class)
.setDefault(20)
- .help("the maximum time in seconds for executing each PUT request")
+ .help("the maximum time in seconds for executing each PATCH request")
.dest("time-out");
parser.addArgument("-ip")
return parser;
}
- public static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
+ static TesttoolParameters parseArgs(final String[] args, final ArgumentParser parser) {
final TesttoolParameters opt = new TesttoolParameters();
try {
parser.parseArgs(args, opt);
return null;
}
- private static String modifyMessage(final StringBuilder payloadBuilder, final int payloadPosition, final int size) {
- if (size == 1) {
- return payloadBuilder.toString();
- }
-
- if (payloadPosition == 0) {
- payloadBuilder.insert(payloadBuilder.toString().indexOf('{', 2), "[");
- payloadBuilder.replace(payloadBuilder.length() - 1, payloadBuilder.length(), ",");
- } else if (payloadPosition + 1 == size) {
- payloadBuilder.delete(0, payloadBuilder.toString().indexOf(':') + 1);
- payloadBuilder.insert(payloadBuilder.toString().indexOf('}', 2) + 1, "]");
- } else {
- payloadBuilder.delete(0, payloadBuilder.toString().indexOf(':') + 1);
- payloadBuilder.replace(payloadBuilder.length() - 2, payloadBuilder.length() - 1, ",");
- payloadBuilder.deleteCharAt(payloadBuilder.toString().lastIndexOf('}'));
- }
- return payloadBuilder.toString();
- }
-
@SuppressWarnings("checkstyle:regexpSinglelineJava")
void validate() {
if (controllerIp != null) {
checkArgument(deviceCount > 0, "Device count has to be > 0");
checkArgument(startingPort > 1023, "Starting port has to be > 1023");
- checkArgument(devicesPerPort > 0, "Atleast one device per port needed");
+ checkArgument(devicesPerPort > 0, "At least one device per port needed");
if (schemasDir != null) {
checkArgument(schemasDir.exists(), "Schemas dir has to exist");
return null;
}
-
- public ArrayList<ArrayList<Execution.DestToPayload>> getThreadsPayloads(final List<Integer> openDevices) {
- final String editContentString;
- try {
- final InputStream stream = TesttoolParameters.class.getResourceAsStream(RESOURCE);
- editContentString = CharStreams.toString(new InputStreamReader(stream, StandardCharsets.UTF_8));
- } catch (final IOException e) {
- throw new IllegalArgumentException("Cannot read content of " + RESOURCE, e);
- }
-
- int from;
- int to;
- Iterator<Integer> iterator;
-
- final ArrayList<ArrayList<Execution.DestToPayload>> allThreadsPayloads = new ArrayList<>();
- if (generateConfigBatchSize > 1) {
-
- final int batchedRequests = openDevices.size() / generateConfigBatchSize;
- final int batchedRequestsPerThread = batchedRequests / threadAmount;
- final int leftoverBatchedRequests = batchedRequests % threadAmount;
- final int leftoverRequests = openDevices.size() - batchedRequests * generateConfigBatchSize;
-
- //FIXME Move this to validate() and rename it to init() or create init() and move there.
- //FIXME Make it field.
- final String restconfNetconfTopologyPath = String.format(RESTCONF_NETCONF_TOPOLOGY_PATH_TEMPLATE,
- controllerIp, controllerPort);
-
- for (int l = 0; l < threadAmount; l++) {
- from = l * batchedRequests * batchedRequestsPerThread;
- to = from + batchedRequests * batchedRequestsPerThread;
- iterator = openDevices.subList(from, to).iterator();
- allThreadsPayloads.add(createBatchedPayloads(batchedRequestsPerThread, iterator, editContentString,
- restconfNetconfTopologyPath));
- }
- ArrayList<Execution.DestToPayload> payloads = null;
- if (leftoverBatchedRequests > 0) {
- from = threadAmount * batchedRequests * batchedRequestsPerThread;
- to = from + batchedRequests * batchedRequestsPerThread;
- iterator = openDevices.subList(from, to).iterator();
- payloads = createBatchedPayloads(leftoverBatchedRequests, iterator, editContentString,
- restconfNetconfTopologyPath);
- }
- String payload = "";
-
- for (int j = 0; j < leftoverRequests; j++) {
- from = openDevices.size() - leftoverRequests;
- to = openDevices.size();
- iterator = openDevices.subList(from, to).iterator();
- final StringBuilder payloadBuilder = new StringBuilder(
- prepareMessage(iterator.next(), editContentString));
- payload += modifyMessage(payloadBuilder, j, leftoverRequests);
- }
- if (leftoverRequests > 0 || leftoverBatchedRequests > 0) {
-
- if (payloads != null) {
- payloads.add(new Execution.DestToPayload(restconfNetconfTopologyPath, payload));
- }
- allThreadsPayloads.add(payloads);
- }
- } else {
- final int requestPerThreads = openDevices.size() / threadAmount;
- final int leftoverRequests = openDevices.size() % threadAmount;
-
- for (int i = 0; i < threadAmount; i++) {
- from = i * requestPerThreads;
- to = from + requestPerThreads;
- iterator = openDevices.subList(from, to).iterator();
- allThreadsPayloads.add(createPayloads(iterator, editContentString));
- }
-
- if (leftoverRequests > 0) {
- from = threadAmount * requestPerThreads;
- to = from + leftoverRequests;
- iterator = openDevices.subList(from, to).iterator();
- allThreadsPayloads.add(createPayloads(iterator, editContentString));
- }
- }
- return allThreadsPayloads;
- }
-
- private String prepareMessage(final int openDevice, final String editContentString) {
- final StringBuilder messageBuilder = new StringBuilder(editContentString);
-
- if (editContentString.contains(HOST_KEY)) {
- messageBuilder.replace(messageBuilder.indexOf(HOST_KEY),
- messageBuilder.indexOf(HOST_KEY) + HOST_KEY.length(),
- generateConfigsAddress);
- }
- if (editContentString.contains(PORT_KEY)) {
- while (messageBuilder.indexOf(PORT_KEY) != -1) {
- messageBuilder.replace(messageBuilder.indexOf(PORT_KEY),
- messageBuilder.indexOf(PORT_KEY) + PORT_KEY.length(),
- Integer.toString(openDevice));
- }
- }
- if (editContentString.contains(TCP_ONLY)) {
- messageBuilder.replace(messageBuilder.indexOf(TCP_ONLY),
- messageBuilder.indexOf(TCP_ONLY) + TCP_ONLY.length(),
- Boolean.toString(!ssh));
- }
- return messageBuilder.toString();
- }
-
- private ArrayList<Execution.DestToPayload> createPayloads(final Iterator<Integer> openDevices,
- final String editContentString) {
- final ArrayList<Execution.DestToPayload> payloads = new ArrayList<>();
-
- while (openDevices.hasNext()) {
- //FIXME Move this to validate() and rename it to init() or create init() and move there.
- //FIXME Make it field.
- final String restconfNetconfTopologyPath = String.format(RESTCONF_NETCONF_TOPOLOGY_PATH_TEMPLATE,
- controllerIp, controllerPort);
- payloads.add(new Execution.DestToPayload(
- restconfNetconfTopologyPath, prepareMessage(openDevices.next(), editContentString)));
- }
- return payloads;
- }
-
- private ArrayList<Execution.DestToPayload> createBatchedPayloads(final int batchedRequestsCount,
- final Iterator<Integer> openDevices, final String editContentString, final String destination) {
- final ArrayList<Execution.DestToPayload> payloads = new ArrayList<>();
-
- for (int i = 0; i < batchedRequestsCount; i++) {
- StringBuilder payload = new StringBuilder();
- for (int j = 0; j < generateConfigBatchSize; j++) {
- final StringBuilder payloadBuilder = new StringBuilder(
- prepareMessage(openDevices.next(), editContentString));
- payload.append(modifyMessage(payloadBuilder, j, generateConfigBatchSize));
- }
- payloads.add(new Execution.DestToPayload(destination, payload.toString()));
- }
- return payloads;
- }
-
@Override
public String toString() {
final List<Field> fields = Arrays.asList(this.getClass().getDeclaredFields());
+++ /dev/null
-/*
- * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. 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.model;
-
-public class Node {
- private String nodeId;
- private String host;
- private Integer port;
- private String username;
- private String password;
- private Boolean tcpOnly;
- private Integer keepaliveDelay;
- private Boolean schemaless;
-
- public Node() {
-
- }
-
- public Node(final String nodeId, final String host, final Integer port, final String username,
- final String password, final Boolean tcpOnly, final Integer keepaliveDelay, final Boolean schemaless) {
- this.nodeId = nodeId;
- this.host = host;
- this.port = port;
- this.username = username;
- this.password = password;
- this.tcpOnly = tcpOnly;
- this.keepaliveDelay = keepaliveDelay;
- this.schemaless = schemaless;
- }
-
- public String getNodeId() {
- return nodeId;
- }
-
- public void setNodeId(final String nodeId) {
- this.nodeId = nodeId;
- }
-
- public String getUsername() {
- return username;
- }
-
- public void setUsername(final String username) {
- this.username = username;
- }
-
- public String getPassword() {
- return password;
- }
-
- public void setPassword(final String password) {
- this.password = password;
- }
-
- public String getHost() {
- return host;
- }
-
- public void setHost(final String host) {
- this.host = host;
- }
-
- public Integer getKeepaliveDelay() {
- return keepaliveDelay;
- }
-
- public void setKeepaliveDelay(final Integer keepaliveDelay) {
- this.keepaliveDelay = keepaliveDelay;
- }
-
- public Integer getPort() {
- return port;
- }
-
- public void setPort(final Integer port) {
- this.port = port;
- }
-
- public Boolean getSchemaless() {
- return schemaless;
- }
-
- public void setSchemaless(final Boolean schemaless) {
- this.schemaless = schemaless;
- }
-
- public Boolean getTcpOnly() {
- return tcpOnly;
- }
-
- public void setTcpOnly(final Boolean tcpOnly) {
- this.tcpOnly = tcpOnly;
- }
-}
\ No newline at end of file
+++ /dev/null
-/*
- * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. 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.model;
-
-public class Payload {
- private Topology topology;
-
- public Payload() {
-
- }
-
- public Payload(final Topology topology) {
- this.topology = topology;
- }
-
- public Topology getTopology() {
- return topology;
- }
-
- public void setTopology(Topology topology) {
- this.topology = topology;
- }
-}
+++ /dev/null
-/*
- * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. 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.model;
-
-import com.google.gson.annotations.SerializedName;
-import java.util.ArrayList;
-import java.util.List;
-
-public class Topology {
- private String topologyId;
-
- @SerializedName("node")
- private List<Node> nodeList = new ArrayList<>();
-
- public Topology() {
-
- }
-
- public Topology(final String topologyId) {
- this.topologyId = topologyId;
- }
-
- public void addNode(final Node node) {
- this.nodeList.add(node);
- }
-
- public String getTopologyId() {
- return topologyId;
- }
-
- public void setTopologyId(String topologyId) {
- this.topologyId = topologyId;
- }
-
- public List<Node> getNodeList() {
- return nodeList;
- }
-
- public void setNodeList(List<Node> nodeList) {
- this.nodeList = nodeList;
- }
-}
+++ /dev/null
-{
- "node": {
- "node-id": "{PORT}-sim-device",
- "host": "{HOST}",
- "port": "{PORT}",
- "username": "admin",
- "password": "admin",
- "tcp-only": "{TCP_ONLY}",
- "keepalive-delay": "0"
- }
-}
+++ /dev/null
-<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
- <node-id>{PORT}-sim-device</node-id>
- <host xmlns="urn:opendaylight:netconf-node-topology">{HOST}</host>
- <port xmlns="urn:opendaylight:netconf-node-topology">{PORT}</port>
- <username xmlns="urn:opendaylight:netconf-node-topology">admin</username>
- <password xmlns="urn:opendaylight:netconf-node-topology">admin</password>
- <tcp-only xmlns="urn:opendaylight:netconf-node-topology">{TCP_ONLY}</tcp-only>
- <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">0</keepalive-delay>
-</node>
\ No newline at end of file