From 13d05bc8458758ee39cb829098241e89616717ee Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Wed, 9 Sep 2015 22:15:21 -0700 Subject: ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60 Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd --- framework/src/onos/apps/test/demo/pom.xml | 121 ++++ .../main/java/org/onosproject/demo/DemoAPI.java | 49 ++ .../java/org/onosproject/demo/DemoInstaller.java | 608 +++++++++++++++++++++ .../java/org/onosproject/demo/DemoResource.java | 101 ++++ .../java/org/onosproject/demo/package-info.java | 20 + .../apps/test/demo/src/main/webapp/WEB-INF/web.xml | 44 ++ .../onos/apps/test/distributed-primitives/pom.xml | 70 +++ .../DistributedPrimitivesTest.java | 57 ++ .../cli/CounterTestIncrementCommand.java | 100 ++++ .../cli/SetTestAddCommand.java | 81 +++ .../cli/SetTestGetCommand.java | 108 ++++ .../cli/SetTestRemoveCommand.java | 109 ++++ .../cli/TransactionalMapTestGetCommand.java | 73 +++ .../cli/TransactionalMapTestPutCommand.java | 81 +++ .../distributedprimitives/cli/package-info.java | 20 + .../distributedprimitives/package-info.java | 20 + .../resources/OSGI-INF/blueprint/shell-config.xml | 39 ++ framework/src/onos/apps/test/election/pom.xml | 65 +++ .../org/onosproject/election/ElectionTest.java | 124 +++++ .../election/cli/ElectionTestLeaderCommand.java | 54 ++ .../election/cli/ElectionTestRunCommand.java | 39 ++ .../election/cli/ElectionTestWithdrawCommand.java | 40 ++ .../org/onosproject/election/cli/package-info.java | 20 + .../org/onosproject/election/package-info.java | 20 + .../resources/OSGI-INF/blueprint/shell-config.xml | 30 + framework/src/onos/apps/test/intent-perf/pom.xml | 59 ++ .../intentperf/IntentPerfCollector.java | 234 ++++++++ .../intentperf/IntentPerfInstaller.java | 596 ++++++++++++++++++++ .../intentperf/IntentPerfListCommand.java | 91 +++ .../intentperf/IntentPerfStartCommand.java | 33 ++ .../intentperf/IntentPerfStopCommand.java | 33 ++ .../org/onosproject/intentperf/IntentPerfUi.java | 194 +++++++ .../org/onosproject/intentperf/package-info.java | 20 + .../resources/OSGI-INF/blueprint/shell-config.xml | 28 + .../resources/app/view/intentPerf/intentPerf.css | 55 ++ .../resources/app/view/intentPerf/intentPerf.html | 26 + .../resources/app/view/intentPerf/intentPerf.js | 307 +++++++++++ .../test/intent-perf/src/main/resources/css.html | 1 + .../test/intent-perf/src/main/resources/dev.html | 27 + .../test/intent-perf/src/main/resources/js.html | 1 + .../src/onos/apps/test/messaging-perf/pom.xml | 60 ++ .../messagingperf/MessagingPerfApp.java | 378 +++++++++++++ .../onosproject/messagingperf/package-info.java | 20 + framework/src/onos/apps/test/pom.xml | 42 ++ 44 files changed, 4298 insertions(+) create mode 100644 framework/src/onos/apps/test/demo/pom.xml create mode 100644 framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoAPI.java create mode 100644 framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoInstaller.java create mode 100644 framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoResource.java create mode 100644 framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/package-info.java create mode 100644 framework/src/onos/apps/test/demo/src/main/webapp/WEB-INF/web.xml create mode 100644 framework/src/onos/apps/test/distributed-primitives/pom.xml create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/DistributedPrimitivesTest.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/CounterTestIncrementCommand.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestAddCommand.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestGetCommand.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestRemoveCommand.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestGetCommand.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestPutCommand.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/package-info.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/package-info.java create mode 100644 framework/src/onos/apps/test/distributed-primitives/src/main/resources/OSGI-INF/blueprint/shell-config.xml create mode 100644 framework/src/onos/apps/test/election/pom.xml create mode 100644 framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/ElectionTest.java create mode 100644 framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestLeaderCommand.java create mode 100644 framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestRunCommand.java create mode 100644 framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestWithdrawCommand.java create mode 100644 framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/package-info.java create mode 100644 framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/package-info.java create mode 100644 framework/src/onos/apps/test/election/src/main/resources/OSGI-INF/blueprint/shell-config.xml create mode 100644 framework/src/onos/apps/test/intent-perf/pom.xml create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfListCommand.java create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStartCommand.java create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStopCommand.java create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/package-info.java create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/resources/OSGI-INF/blueprint/shell-config.xml create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.html create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/resources/css.html create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/resources/dev.html create mode 100644 framework/src/onos/apps/test/intent-perf/src/main/resources/js.html create mode 100644 framework/src/onos/apps/test/messaging-perf/pom.xml create mode 100644 framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/MessagingPerfApp.java create mode 100644 framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/package-info.java create mode 100644 framework/src/onos/apps/test/pom.xml (limited to 'framework/src/onos/apps/test') diff --git a/framework/src/onos/apps/test/demo/pom.xml b/framework/src/onos/apps/test/demo/pom.xml new file mode 100644 index 00000000..27dd6675 --- /dev/null +++ b/framework/src/onos/apps/test/demo/pom.xml @@ -0,0 +1,121 @@ + + + + 4.0.0 + + + org.onosproject + onos-apps-test + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-app-demo + bundle + + Flow throughput test application + + + org.onosproject.demo + /onos/demo + ONOS Flow Throughput Test App API + + APIs for interacting with the flow throughput test application. + + org.onosproject.demo + + + + + org.osgi + org.osgi.compendium + + + org.onosproject + onlab-rest + ${project.version} + + + + org.onosproject + onos-rest + ${project.version} + + + + com.sun.jersey + jersey-servlet + + + com.fasterxml.jackson.core + jackson-databind + + + + com.fasterxml.jackson.core + jackson-annotations + + + + org.osgi + org.osgi.core + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + <_wab>src/main/webapp/ + + WEB-INF/classes/apidoc/swagger.json=target/swagger.json, + {maven-resources} + + + ${project.groupId}.${project.artifactId} + + + org.slf4j, + org.osgi.framework, + javax.ws.rs,javax.ws.rs.core, + com.sun.jersey.api.core, + com.sun.jersey.spi.container.servlet, + com.sun.jersey.server.impl.container.servlet, + com.fasterxml.jackson.databind, + com.fasterxml.jackson.databind.node, + org.apache.commons.lang.math.*, + com.google.common.*, + org.onlab.packet.*, + org.onlab.rest.*, + org.onosproject.*, + org.onlab.util.*, + org.jboss.netty.util.* + + ${web.context} + + + + + + + diff --git a/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoAPI.java b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoAPI.java new file mode 100644 index 00000000..4197bb60 --- /dev/null +++ b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoAPI.java @@ -0,0 +1,49 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.demo; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.Optional; + +/** + * Simple demo api interface. + */ +public interface DemoAPI { + + enum InstallType { MESH, RANDOM }; + + /** + * Tests flow subsystem based on the parameters supplied. + * + * @param params the test parameters + * @return JSON representation + */ + JsonNode flowTest(Optional params); + + /** + * Installs intents based on the installation type. + * @param type the installation type. + * @param runParams run params + */ + void setup(InstallType type, Optional runParams); + + /** + * Uninstalls all existing intents. + */ + void tearDown(); + +} diff --git a/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoInstaller.java b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoInstaller.java new file mode 100644 index 00000000..27d1ca96 --- /dev/null +++ b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoInstaller.java @@ -0,0 +1,608 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.demo; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +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.concurrent.TimeoutException; + +import org.apache.commons.lang.math.RandomUtils; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.MacAddress; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.Device; +import org.onosproject.net.Host; +import org.onosproject.net.HostId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.host.HostService; +import org.onosproject.net.intent.Constraint; +import org.onosproject.net.intent.HostToHostIntent; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentService; +import org.slf4j.Logger; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.base.Predicate; +import com.google.common.base.Stopwatch; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.ThreadFactoryBuilder; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Application to set up demos. + */ +@Component(immediate = true) +@Service +public class DemoInstaller implements DemoAPI { + + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentService intentService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected HostService hostService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected FlowRuleService flowService; + + private ExecutorService worker; + + private ExecutorService installWorker; + + private ApplicationId appId; + + private final Set existingIntents = new HashSet<>(); + private RandomInstaller randomInstaller; + + private ObjectMapper mapper = new ObjectMapper(); + + + + @Activate + public void activate() { + String nodeId = clusterService.getLocalNode().ip().toString(); + appId = coreService.registerApplication("org.onosproject.demo.installer." + + nodeId); + worker = Executors.newFixedThreadPool(1, + new ThreadFactoryBuilder() + .setNameFormat("demo-app-worker") + .build()); + log.info("Started with Application ID {}", appId.id()); + } + + @Deactivate + public void deactivate() { + shutdownAndAwaitTermination(worker); + if (installWorker != null && !installWorker.isShutdown()) { + shutdownAndAwaitTermination(installWorker); + } + log.info("Stopped"); + } + + @Override + public JsonNode flowTest(Optional params) { + int flowsPerDevice = 1000; + int neighbours = 0; + boolean remove = true; + if (params.isPresent()) { + flowsPerDevice = params.get().get("flowsPerDevice").asInt(); + neighbours = params.get().get("neighbours").asInt(); + remove = params.get().get("remove").asBoolean(); + } + + Future future = worker.submit(new FlowTest(flowsPerDevice, neighbours, remove)); + + try { + return future.get(10, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + ObjectNode node = mapper.createObjectNode(); + node.put("Error", e.getMessage()); + return node; + } + } + + @Override + public void setup(InstallType type, Optional runParams) { + switch (type) { + case MESH: + log.debug("Installing mesh intents"); + worker.execute(new MeshInstaller()); + break; + case RANDOM: + //check that we do not have a random installer running + if (installWorker == null || installWorker.isShutdown()) { + installWorker = Executors.newFixedThreadPool(1, + new ThreadFactoryBuilder() + .setNameFormat("random-worker") + .build()); + log.debug("Installing random sequence of intents"); + randomInstaller = new RandomInstaller(runParams); + installWorker.execute(randomInstaller); + } else { + log.warn("Random installer is already running"); + } + break; + default: + throw new IllegalArgumentException("What is it you want exactly?"); + } + } + + @Override + public void tearDown() { + worker.submit(new UnInstaller()); + } + + + /** + * Simply installs a mesh of intents from all the hosts existing in the network. + */ + private class MeshInstaller implements Runnable { + + @Override + public void run() { + TrafficSelector selector = DefaultTrafficSelector.emptySelector(); + TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); + List constraint = Lists.newArrayList(); + List hosts = Lists.newArrayList(hostService.getHosts()); + while (!hosts.isEmpty()) { + Host src = hosts.remove(0); + for (Host dst : hosts) { + HostToHostIntent intent = HostToHostIntent.builder() + .appId(appId) + .one(src.id()) + .two(dst.id()) + .selector(selector) + .treatment(treatment) + .constraints(constraint) + .build(); + existingIntents.add(intent); + intentService.submit(intent); + } + } + } + } + + /** + * Randomly installs and withdraws intents. + */ + private class RandomInstaller implements Runnable { + + private final boolean isLocal; + private final Set hosts; + + private final Random random = new Random(System.currentTimeMillis()); + + private Set uninstalledOrWithdrawn; + private Set installed; + + private CountDownLatch latch; + + //used to wait on a batch to be processed. + private static final int ITERATIONMAX = 50000000; + + + public RandomInstaller(Optional runParams) { + /* + Check if we have params and honour them. Otherwise + set defaults to processing only local stuff and + all local hosts. + */ + if (runParams.isPresent()) { + JsonNode node = runParams.get(); + isLocal = node.get("local").asBoolean(); + hosts = node.get("hosts") == null ? Sets.newHashSet(hostService.getHosts()) : + constructHostIds(node.get("hosts").elements()); + } else { + isLocal = true; + hosts = Sets.newHashSet(hostService.getHosts()); + } + + //construct list of intents. + installed = Sets.newHashSet(); + if (isLocal) { + uninstalledOrWithdrawn = buildPairs(pruneHostsByMasterShip()); + } else { + uninstalledOrWithdrawn = buildPairs(hosts); + } + + } + + private Set constructHostIds(Iterator elements) { + Set hostIds = Sets.newHashSet(); + JsonNode n; + while (elements.hasNext()) { + n = elements.next(); + hostIds.add(hostService.getHost(HostId.hostId(n.textValue()))); + } + return hostIds; + } + + @Override + public void run() { + if (!installWorker.isShutdown()) { + randomize(); + latch = new CountDownLatch(1); + try { + trackIntents(); + } catch (InterruptedException e) { + shutdown(); + } + } + + } + + + /** + * Check whether the previously submitted batch is in progress + * and if yes submit the next one. If things hang, wait for at + * most 5 seconds and bail. + * @throws InterruptedException if the thread go interupted + */ + private void trackIntents() throws InterruptedException { + //FIXME + // TODO generate keys for each set of intents to allow manager to throttle + // TODO may also look into the store to see how many operations are pending + + //if everything is good proceed. + if (!installWorker.isShutdown()) { + installWorker.execute(this); + } + + } + + public void shutdown() { + log.warn("Shutting down random installer!"); + cleanUp(); + } + + + /** + * Shuffle the uninstalled and installed list (separately) and select + * a random number of them and install or uninstall them respectively. + */ + private void randomize() { + List hostList = new LinkedList<>(uninstalledOrWithdrawn); + Collections.shuffle(hostList); + List toInstall = hostList.subList(0, + random.nextInt(hostList.size() - 1)); + List toRemove; + if (!installed.isEmpty()) { + hostList = new LinkedList<>(installed); + Collections.shuffle(hostList); + toRemove = hostList.subList(0, + random.nextInt(hostList.size() - 1)); + uninstallIntents(toRemove); + } + installIntents(toInstall); + + } + + private void installIntents(List toInstall) { + for (HostPair pair : toInstall) { + installed.add(pair); + uninstalledOrWithdrawn.remove(pair); + intentService.submit(pair.h2hIntent()); + } + } + + private void uninstallIntents(Collection toRemove) { + for (HostPair pair : toRemove) { + installed.remove(pair); + uninstalledOrWithdrawn.add(pair); + intentService.withdraw(pair.h2hIntent()); + } + } + + /** + * Take everything and remove it all. + */ + private void cleanUp() { + List allPairs = Lists.newArrayList(installed); + allPairs.addAll(uninstalledOrWithdrawn); + for (HostPair pair : allPairs) { + intentService.withdraw(pair.h2hIntent()); + } + } + + + private Set buildPairs(Set hosts) { + Set pairs = Sets.newHashSet(); + Iterator it = Sets.newHashSet(hosts).iterator(); + while (it.hasNext()) { + Host src = it.next(); + it.remove(); + for (Host dst : hosts) { + pairs.add(new HostPair(src, dst)); + } + } + return pairs; + } + + private Set pruneHostsByMasterShip() { + return FluentIterable.from(hosts) + .filter(hasLocalMaster()) + .toSet(); + + } + + private Predicate hasLocalMaster() { + return new Predicate() { + @Override + public boolean apply(Host host) { + return mastershipService.getLocalRole( + host.location().deviceId()).equals(MastershipRole.MASTER); + } + }; + } + + + /** + * Simple class representing a pair of hosts and precomputes the associated + * h2h intent. + */ + private class HostPair { + + private final Host src; + private final Host dst; + + private final TrafficSelector selector = DefaultTrafficSelector.emptySelector(); + private final TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); + private final List constraint = Lists.newArrayList(); + private final HostToHostIntent intent; + + public HostPair(Host src, Host dst) { + this.src = src; + this.dst = dst; + this.intent = HostToHostIntent.builder() + .appId(appId) + .one(src.id()) + .two(dst.id()) + .selector(selector) + .treatment(treatment) + .constraints(constraint) + .build(); + } + + public HostToHostIntent h2hIntent() { + return intent; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + HostPair hostPair = (HostPair) o; + + return Objects.equals(src, hostPair.src) && + Objects.equals(dst, hostPair.dst); + + } + + @Override + public int hashCode() { + return Objects.hash(src, dst); + } + + + } + + } + + /** + * Remove anything that is running and clear it all out. + */ + private class UnInstaller implements Runnable { + @Override + public void run() { + if (!existingIntents.isEmpty()) { + clearExistingIntents(); + } + + if (installWorker != null && !installWorker.isShutdown()) { + shutdownAndAwaitTermination(installWorker); + randomInstaller.shutdown(); + } + } + + private void clearExistingIntents() { + for (Intent i : existingIntents) { + intentService.withdraw(i); + } + existingIntents.clear(); + } + } + + /** + * Shutdown a pool cleanly if possible. + * + * @param pool an executorService + */ + private void shutdownAndAwaitTermination(ExecutorService pool) { + pool.shutdown(); // Disable new tasks from being submitted + try { + // Wait a while for existing tasks to terminate + if (!pool.awaitTermination(10, TimeUnit.SECONDS)) { + pool.shutdownNow(); // Cancel currently executing tasks + // Wait a while for tasks to respond to being cancelled + if (!pool.awaitTermination(10, TimeUnit.SECONDS)) { + log.error("Pool did not terminate"); + } + } + } catch (Exception ie) { + // (Re-)Cancel if current thread also interrupted + pool.shutdownNow(); + // Preserve interrupt status + Thread.currentThread().interrupt(); + } + } + + private class FlowTest implements Callable { + private final int flowPerDevice; + private final int neighbours; + private final boolean remove; + private FlowRuleOperations.Builder adds; + private FlowRuleOperations.Builder removes; + + public FlowTest(int flowsPerDevice, int neighbours, boolean remove) { + this.flowPerDevice = flowsPerDevice; + this.neighbours = neighbours; + this.remove = remove; + prepareInstallation(); + } + + private void prepareInstallation() { + Set instances = Sets.newHashSet(clusterService.getNodes()); + instances.remove(clusterService.getLocalNode()); + Set acceptableNodes = Sets.newHashSet(); + if (neighbours >= instances.size()) { + instances.forEach(instance -> acceptableNodes.add(instance.id())); + } else { + Iterator nodes = instances.iterator(); + for (int i = neighbours; i > 0; i--) { + acceptableNodes.add(nodes.next().id()); + } + } + acceptableNodes.add(clusterService.getLocalNode().id()); + + Set devices = Sets.newHashSet(); + for (Device dev : deviceService.getDevices()) { + if (acceptableNodes.contains( + mastershipService.getMasterFor(dev.id()))) { + devices.add(dev); + } + } + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .setOutput(PortNumber.portNumber(RandomUtils.nextInt())).build(); + TrafficSelector.Builder sbuilder; + FlowRuleOperations.Builder rules = FlowRuleOperations.builder(); + FlowRuleOperations.Builder remove = FlowRuleOperations.builder(); + + for (Device d : devices) { + for (int i = 0; i < this.flowPerDevice; i++) { + sbuilder = DefaultTrafficSelector.builder(); + + sbuilder.matchEthSrc(MacAddress.valueOf(RandomUtils.nextInt() * i)) + .matchEthDst(MacAddress.valueOf((Integer.MAX_VALUE - i) * RandomUtils.nextInt())); + + + int randomPriority = RandomUtils.nextInt(); + FlowRule f = DefaultFlowRule.builder() + .forDevice(d.id()) + .withSelector(sbuilder.build()) + .withTreatment(treatment) + .withPriority(randomPriority) + .fromApp(appId) + .makeTemporary(10) + .build(); + rules.add(f); + remove.remove(f); + + } + } + + this.adds = rules; + this.removes = remove; + } + + @Override + public JsonNode call() throws Exception { + ObjectNode node = mapper.createObjectNode(); + CountDownLatch latch = new CountDownLatch(1); + flowService.apply(adds.build(new FlowRuleOperationsContext() { + + private final Stopwatch timer = Stopwatch.createStarted(); + + @Override + public void onSuccess(FlowRuleOperations ops) { + + long elapsed = timer.elapsed(TimeUnit.MILLISECONDS); + node.put("elapsed", elapsed); + + + latch.countDown(); + } + })); + + latch.await(10, TimeUnit.SECONDS); + if (this.remove) { + flowService.apply(removes.build()); + } + return node; + } + } +} + + diff --git a/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoResource.java b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoResource.java new file mode 100644 index 00000000..e0c97dd8 --- /dev/null +++ b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/DemoResource.java @@ -0,0 +1,101 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.demo; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.onlab.rest.BaseResource; + +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.io.IOException; +import java.io.InputStream; +import java.util.Optional; + +/** + * Rest API for demos. + */ +@Path("intents") +public class DemoResource extends BaseResource { + + + /** + * Start the flow test. + * + * @param input JSON describing how to run the flow test + * @return response code OK + * @throws IOException if the JSON processing fails + */ + @POST + @Path("flowTest") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response flowTest(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode cfg = mapper.readTree(input); + DemoAPI demo = get(DemoAPI.class); + return Response.ok(demo.flowTest(Optional.ofNullable(cfg)).toString()).build(); + } + + /** + * Set up the flow test. + * + * @param input JSON describing how to configure the flow test + * @return response code OK + * @throws IOException if the JSON processing fails + */ + @POST + @Path("setup") + @Consumes(MediaType.APPLICATION_JSON) + @Produces(MediaType.APPLICATION_JSON) + public Response setup(InputStream input) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + JsonNode cfg = mapper.readTree(input); + if (!cfg.has("type")) { + return Response.status(Response.Status.BAD_REQUEST) + .entity("Expected type field containing either mesh or random.").build(); + } + + + DemoAPI.InstallType type = DemoAPI.InstallType.valueOf( + cfg.get("type").asText().toUpperCase()); + DemoAPI demo = get(DemoAPI.class); + demo.setup(type, Optional.ofNullable(cfg.get("runParams"))); + + return Response.ok(mapper.createObjectNode().toString()).build(); + } + + /** + * Tear down the flow test. + * + * @return response code OK + */ + @GET + @Path("teardown") + @Produces(MediaType.APPLICATION_JSON) + public Response tearDown() { + ObjectMapper mapper = new ObjectMapper(); + DemoAPI demo = get(DemoAPI.class); + demo.tearDown(); + return Response.ok(mapper.createObjectNode().toString()).build(); + } + +} diff --git a/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/package-info.java b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/package-info.java new file mode 100644 index 00000000..9724c44c --- /dev/null +++ b/framework/src/onos/apps/test/demo/src/main/java/org/onosproject/demo/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Demo applications live here. + */ +package org.onosproject.demo; diff --git a/framework/src/onos/apps/test/demo/src/main/webapp/WEB-INF/web.xml b/framework/src/onos/apps/test/demo/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000..29716df7 --- /dev/null +++ b/framework/src/onos/apps/test/demo/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,44 @@ + + + + ONOS DEMO APP API v1.0 + + + JAX-RS Service + com.sun.jersey.spi.container.servlet.ServletContainer + + com.sun.jersey.config.property.resourceConfigClass + com.sun.jersey.api.core.ClassNamesResourceConfig + + + com.sun.jersey.config.property.classnames + + org.onosproject.demo.DemoResource + + + 1 + + + + JAX-RS Service + /* + + + diff --git a/framework/src/onos/apps/test/distributed-primitives/pom.xml b/framework/src/onos/apps/test/distributed-primitives/pom.xml new file mode 100644 index 00000000..e0376ee7 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/pom.xml @@ -0,0 +1,70 @@ + + + + + 4.0.0 + + + org.onosproject + onos-apps-test + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-app-distributed-primitives + bundle + + ONOS app to test distributed primitives + + + org.onosproject.distributedprimitives + + + + + + org.onosproject + onos-api + ${project.version} + test + tests + + + + org.onosproject + onos-cli + ${project.version} + + + org.onosproject + onos-core-dist + 1.3.0-SNAPSHOT + + + org.osgi + org.osgi.core + + + org.apache.karaf.shell + org.apache.karaf.shell.console + + + + + diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/DistributedPrimitivesTest.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/DistributedPrimitivesTest.java new file mode 100644 index 00000000..f0892282 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/DistributedPrimitivesTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.distributedprimitives; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.slf4j.Logger; + +import static org.slf4j.LoggerFactory.getLogger; + + +/** + * Simple application to test distributed primitives. + */ +@Component(immediate = true) +public class DistributedPrimitivesTest { + + private final Logger log = getLogger(getClass()); + + private static final String APP_NAME = "org.onosproject.distributedprimitives"; + private ApplicationId appId; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + + @Activate + protected void activate() { + + log.info("Distributed-Primitives-test app started"); + appId = coreService.registerApplication(APP_NAME); + } + + @Deactivate + protected void deactivate() { + + log.info("Distributed-Primitives-test app Stopped"); + } +} diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/CounterTestIncrementCommand.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/CounterTestIncrementCommand.java new file mode 100644 index 00000000..d8e8e0be --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/CounterTestIncrementCommand.java @@ -0,0 +1,100 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.distributedprimitives.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.commands.Option; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.store.service.AsyncAtomicCounter; +import org.onosproject.store.service.StorageService; +import org.slf4j.Logger; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * CLI command to increment a distributed counter. + */ +@Command(scope = "onos", name = "counter-test-increment", + description = "Increment a distributed counter") +public class CounterTestIncrementCommand extends AbstractShellCommand { + + private final Logger log = getLogger(getClass()); + + @Option(name = "-i", aliases = "--inMemory", description = "use in memory map?", + required = false, multiValued = false) + private boolean inMemory = false; + + @Option(name = "-g", aliases = "--getFirst", description = "get the counter's value before adding", + required = false, multiValued = false) + private boolean getFirst = false; + + @Argument(index = 0, name = "counter", + description = "Counter name", + required = true, multiValued = false) + String counter = null; + + @Argument(index = 1, name = "delta", + description = "Long to add to the counter", + required = false, multiValued = false) + Long delta = null; + + AsyncAtomicCounter atomicCounter; + + + @Override + protected void execute() { + StorageService storageService = get(StorageService.class); + if (inMemory) { + atomicCounter = storageService.atomicCounterBuilder() + .withName(counter) + .withPartitionsDisabled() + .buildAsyncCounter(); + } else { + atomicCounter = storageService.atomicCounterBuilder() + .withName(counter) + .buildAsyncCounter(); + } + CompletableFuture result; + if (delta != null) { + if (getFirst) { + result = atomicCounter.getAndAdd(delta); + } else { + result = atomicCounter.addAndGet(delta); + } + } else { + if (getFirst) { + result = atomicCounter.getAndIncrement(); + } else { + result = atomicCounter.incrementAndGet(); + } + } + try { + print("%s was updated to %d", counter, result.get(3, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + return; + } catch (ExecutionException e) { + e.printStackTrace(); + } catch (TimeoutException e) { + e.printStackTrace(); + } + } +} diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestAddCommand.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestAddCommand.java new file mode 100644 index 00000000..0ccc2d3c --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestAddCommand.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.distributedprimitives.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.onlab.util.KryoNamespace; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.store.service.Serializer; +import org.onosproject.store.service.StorageService; + +import java.util.HashSet; +import java.util.Set; + +/** + * CLI command to add elements to a distributed set. + */ +@Command(scope = "onos", name = "set-test-add", + description = "Add to a distributed set") +public class SetTestAddCommand extends AbstractShellCommand { + + @Argument(index = 0, name = "setName", + description = "set name", + required = true, multiValued = false) + String setName = null; + + @Argument(index = 1, name = "values", + description = "Value(s) to add to the set", + required = true, multiValued = true) + String[] values = null; + + Set set; + Set toAdd = new HashSet(); + + + Serializer serializer = Serializer.using( + new KryoNamespace.Builder().register(KryoNamespaces.BASIC).build()); + + + @Override + protected void execute() { + StorageService storageService = get(StorageService.class); + set = storageService.setBuilder() + .withName(setName) + .withSerializer(serializer) + .build(); + + // Add a single element to the set + if (values.length == 1) { + if (set.add(values[0])) { + print("[%s] was added to the set %s", values[0], setName); + } else { + print("[%s] was already in set %s", values[0], setName); + } + } else if (values.length >= 1) { + // Add multiple elements to a set + for (String value : values) { + toAdd.add(value); + } + if (set.addAll(toAdd)) { + print("%s was added to the set %s", toAdd, setName); + } else { + print("%s was already in set %s", toAdd, setName); + } + } + } +} diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestGetCommand.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestGetCommand.java new file mode 100644 index 00000000..792eab90 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestGetCommand.java @@ -0,0 +1,108 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.distributedprimitives.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.commands.Option; +import org.onlab.util.KryoNamespace; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.store.service.Serializer; +import org.onosproject.store.service.StorageService; + +import java.util.HashSet; +import java.util.Set; + +/** + * CLI command to get the elements in a distributed set. + */ +@Command(scope = "onos", name = "set-test-get", + description = "Get the elements in a distributed set") +public class SetTestGetCommand extends AbstractShellCommand { + + @Option(name = "-s", aliases = "--size", description = "Also show the size of the set?", + required = false, multiValued = false) + private boolean size = false; + + @Argument(index = 0, name = "setName", + description = "set name", + required = true, multiValued = false) + String setName = null; + + @Argument(index = 1, name = "values", + description = "Check if the set contains these value(s)", + required = false, multiValued = true) + String[] values = null; + + Set set; + Set toCheck = new HashSet(); + String output = new String(); + + Serializer serializer = Serializer.using( + new KryoNamespace.Builder().register(KryoNamespaces.BASIC).build()); + + + @Override + protected void execute() { + StorageService storageService = get(StorageService.class); + set = storageService.setBuilder() + .withName(setName) + .withSerializer(serializer) + .build(); + + // Print the set size + if (size) { + print("There are %d items in set %s:", set.size(), setName); + } else { + print("Items in set %s:", setName); + } + // Print the set + if (set.isEmpty()) { + print("[]"); + } else { + for (String e : set.toArray(new String[set.size()])) { + if (output.isEmpty()) { + output += e; + } else { + output += ", " + e; + } + } + print("[%s]", output); + } + // Check if given values are in the set + if (values == null) { + return; + } else if (values.length == 1) { + // contains + if (set.contains(values[0])) { + print("Set %s contains the value %s", setName, values[0]); + } else { + print("Set %s did not contain the value %s", setName, values[0]); + } + } else if (values.length > 1) { + //containsAll + for (String value : values) { + toCheck.add(value); + } + if (set.containsAll(toCheck)) { + print("Set %s contains the the subset %s", setName, toCheck); + } else { + print("Set %s did not contain the the subset %s", setName, toCheck); + } + } + } +} diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestRemoveCommand.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestRemoveCommand.java new file mode 100644 index 00000000..7e3a3e82 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/SetTestRemoveCommand.java @@ -0,0 +1,109 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.distributedprimitives.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.commands.Option; +import org.onlab.util.KryoNamespace; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.store.service.Serializer; +import org.onosproject.store.service.StorageService; + +import java.util.HashSet; +import java.util.Set; + +/** + * CLI command to remove elements from a distributed set. + */ +@Command(scope = "onos", name = "set-test-remove", + description = "Remove from a distributed set") +public class SetTestRemoveCommand extends AbstractShellCommand { + + @Option(name = "-r", aliases = "--retain", + description = "Only keep the given values in the set (if they already exist in the set)", + required = false, multiValued = false) + private boolean retain = false; + + @Option(name = "-c", aliases = "--clear", description = "Clear the set of all values", + required = false, multiValued = false) + private boolean clear = false; + + @Argument(index = 0, name = "setName", + description = "set name", + required = true, multiValued = false) + String setName = null; + + @Argument(index = 1, name = "values", + description = "Value(s) to remove from the set", + required = false, multiValued = true) + String[] values = null; + + Set set; + Set givenValues = new HashSet(); + Serializer serializer = Serializer.using( + new KryoNamespace.Builder().register(KryoNamespaces.BASIC).build()); + + + @Override + protected void execute() { + StorageService storageService = get(StorageService.class); + set = storageService.setBuilder() + .withName(setName) + .withSerializer(serializer) + .build(); + + if (clear) { + set.clear(); + print("Set %s cleared", setName); + return; + } + + if (values == null) { + print("Error executing command: No value given"); + return; + } + + if (retain) { // Keep only the given values + for (String value : values) { + givenValues.add(value); + } + if (set.retainAll(givenValues)) { + print("%s was pruned to contain only elements of set %s", setName, givenValues); + } else { + print("%s was not changed by retaining only elements of the set %s", setName, givenValues); + } + } else if (values.length == 1) { + // Remove a single element from the set + if (set.remove(values[0])) { + print("[%s] was removed from the set %s", values[0], setName); + } else { + print("[%s] was not in set %s", values[0], setName); + } + } else if (values.length >= 1) { + // Remove multiple elements from a set + for (String value : values) { + givenValues.add(value); + } + if (set.removeAll(givenValues)) { + print("%s was removed from the set %s", givenValues, setName); + } else { + print("No element of %s was in set %s", givenValues, setName); + } + } + } +} diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestGetCommand.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestGetCommand.java new file mode 100644 index 00000000..e41ccc8d --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestGetCommand.java @@ -0,0 +1,73 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.distributedprimitives.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.commands.Option; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.store.service.Serializer; +import org.onosproject.store.service.StorageService; +import org.onosproject.store.service.TransactionContext; +import org.onosproject.store.service.TransactionalMap; + +/** + * CLI command to get a value associated with a specific key in a transactional map. + */ +@Command(scope = "onos", name = "transactional-map-test-get", + description = "Get a value associated with a specific key in a transactional map") +public class TransactionalMapTestGetCommand extends AbstractShellCommand { + + @Option(name = "-i", aliases = "--inMemory", description = "use in memory map?", + required = false, multiValued = false) + private boolean inMemory = false; + + @Argument(index = 0, name = "key", + description = "Key to get the value of", + required = true, multiValued = false) + private String key = null; + + TransactionalMap map; + String mapName = "Test-Map"; + Serializer serializer = Serializer.using(KryoNamespaces.BASIC); + + @Override + protected void execute() { + StorageService storageService = get(StorageService.class); + TransactionContext context; + if (inMemory) { + context = storageService.transactionContextBuilder().withPartitionsDisabled().build(); + } else { + context = storageService.transactionContextBuilder().build(); + } + context.begin(); + try { + map = context.getTransactionalMap(mapName, serializer); + String response = map.get(key); + context.commit(); + + if (response == null) { + print("Key %s not found.", key); + } else { + print("Key-value pair (%s, %s) found.", key, response); + } + } catch (Exception e) { + context.abort(); + throw e; + } + } +} diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestPutCommand.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestPutCommand.java new file mode 100644 index 00000000..0e0e44a7 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/TransactionalMapTestPutCommand.java @@ -0,0 +1,81 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.distributedprimitives.cli; + +import org.apache.karaf.shell.commands.Argument; +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.commands.Option; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.store.service.Serializer; +import org.onosproject.store.service.StorageService; +import org.onosproject.store.service.TransactionContext; +import org.onosproject.store.service.TransactionalMap; + +/** + * CLI command to put a value into a transactional map. + */ +@Command(scope = "onos", name = "transactional-map-test-put", + description = "Put a value into a transactional map") +public class TransactionalMapTestPutCommand extends AbstractShellCommand { + + @Option(name = "-i", aliases = "--inMemory", description = "use in memory map?", + required = false, multiValued = false) + private boolean inMemory = false; + + @Argument(index = 0, name = "numKeys", + description = "Number of keys to put the value into", + required = true, multiValued = false) + private int numKeys = 1; + + @Argument(index = 1, name = "value", + description = "Value to map with the keys in the map", + required = true, multiValued = false) + private String value = null; + + TransactionalMap map; + String prefix = "Key"; + String mapName = "Test-Map"; + Serializer serializer = Serializer.using(KryoNamespaces.BASIC); + + @Override + protected void execute() { + StorageService storageService = get(StorageService.class); + TransactionContext context; + if (inMemory) { + context = storageService.transactionContextBuilder().withPartitionsDisabled().build(); + } else { + context = storageService.transactionContextBuilder().build(); + } + context.begin(); + try { + map = context.getTransactionalMap(mapName, serializer); + for (int i = 1; i <= numKeys; i++) { + String key = prefix + i; + String response = map.put(key, value); + if (response == null) { + print("Created Key %s with value %s.", key, value); + } else { + print("Put %s into key %s. The old value was %s.", value, key, response); + } + } + context.commit(); + } catch (Exception e) { + context.abort(); + throw e; + } + } +} diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/package-info.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/package-info.java new file mode 100644 index 00000000..53ed8056 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/cli/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Distributed Primitives test command-line handlers. + */ +package org.onosproject.distributedprimitives.cli; diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/package-info.java b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/package-info.java new file mode 100644 index 00000000..73c41818 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/java/org/onosproject/distributedprimitives/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Sample application for use in various experiments with distributed primitives. + */ +package org.onosproject.distributedprimitives; diff --git a/framework/src/onos/apps/test/distributed-primitives/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/framework/src/onos/apps/test/distributed-primitives/src/main/resources/OSGI-INF/blueprint/shell-config.xml new file mode 100644 index 00000000..295171d7 --- /dev/null +++ b/framework/src/onos/apps/test/distributed-primitives/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/framework/src/onos/apps/test/election/pom.xml b/framework/src/onos/apps/test/election/pom.xml new file mode 100644 index 00000000..4c18baee --- /dev/null +++ b/framework/src/onos/apps/test/election/pom.xml @@ -0,0 +1,65 @@ + + + + + 4.0.0 + + + org.onosproject + onos-apps-test + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-app-election + bundle + + Master election test application + + + org.onosproject.election + + + + + org.onosproject + onos-api + ${project.version} + test + tests + + + + org.onosproject + onos-cli + ${project.version} + + + + org.osgi + org.osgi.core + + + org.apache.karaf.shell + org.apache.karaf.shell.console + + + + + diff --git a/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/ElectionTest.java b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/ElectionTest.java new file mode 100644 index 00000000..b2a72075 --- /dev/null +++ b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/ElectionTest.java @@ -0,0 +1,124 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.election; + +import static org.slf4j.LoggerFactory.getLogger; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.onosproject.cluster.ClusterService; +import org.onosproject.core.CoreService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.LeadershipEvent; +import org.onosproject.cluster.LeadershipEventListener; +import org.onosproject.cluster.LeadershipService; +import org.onosproject.core.ApplicationId; + +import org.slf4j.Logger; + + +/** + * Simple application to test leadership election. + */ +@Component(immediate = true) +public class ElectionTest { + + private final Logger log = getLogger(getClass()); + + private static final String ELECTION_APP = "org.onosproject.election"; + private ApplicationId appId; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected LeadershipService leadershipService; + + private LeadershipEventListener leadershipEventListener = + new InnerLeadershipEventListener(); + + private ControllerNode localControllerNode; + + + @Activate + protected void activate() { + log.info("Election-test app started"); + + appId = coreService.registerApplication(ELECTION_APP); + + localControllerNode = clusterService.getLocalNode(); + + leadershipService.addListener(leadershipEventListener); + } + + @Deactivate + protected void deactivate() { + + leadershipService.withdraw(appId.name()); + leadershipService.removeListener(leadershipEventListener); + + log.info("Election-test app Stopped"); + } + + /** + * A listener for Leadership Events. + */ + private class InnerLeadershipEventListener + implements LeadershipEventListener { + + @Override + public void event(LeadershipEvent event) { + + + if (event.type().equals(LeadershipEvent.Type.CANDIDATES_CHANGED)) { + return; + } + if (!event.subject().topic().equals(appId.name())) { + return; // Not our topic: ignore + } + + //only log what pertains to us + log.debug("Leadership Event: time = {} type = {} event = {}", + event.time(), event.type(), event); + + if (!event.subject().leader().equals( + localControllerNode.id())) { + return; // The event is not about this instance: ignore + } + + switch (event.type()) { + case LEADER_ELECTED: + log.info("Election-test app leader elected"); + break; + case LEADER_BOOTED: + log.info("Election-test app lost election"); + break; + case LEADER_REELECTED: + log.debug("Election-test app was re-elected"); + break; + default: + break; + } + } + } + +} diff --git a/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestLeaderCommand.java b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestLeaderCommand.java new file mode 100644 index 00000000..5b3fab3f --- /dev/null +++ b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestLeaderCommand.java @@ -0,0 +1,54 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.election.cli; + +import org.onosproject.cluster.NodeId; +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cluster.LeadershipService; + +/** + * CLI command to get the current leader for the Election test application. + */ +@Command(scope = "onos", name = "election-test-leader", + description = "Get the current leader for the Election test application") +public class ElectionTestLeaderCommand extends AbstractShellCommand { + + private NodeId leader; + private static final String ELECTION_APP = "org.onosproject.election"; + + @Override + protected void execute() { + LeadershipService service = get(LeadershipService.class); + + //print the current leader + leader = service.getLeader(ELECTION_APP); + printLeader(leader); + } + + /** + * Prints the leader. + * + * @param leader the leader to print + */ + private void printLeader(NodeId leader) { + if (leader != null) { + print("The current leader for the Election app is %s.", leader); + } else { + print("There is currently no leader elected for the Election app"); + } + } +} diff --git a/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestRunCommand.java b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestRunCommand.java new file mode 100644 index 00000000..5ea8ceb5 --- /dev/null +++ b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestRunCommand.java @@ -0,0 +1,39 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.election.cli; + +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cluster.LeadershipService; + +/** + * CLI command to run for leadership of the Election test application. + */ +@Command(scope = "onos", name = "election-test-run", + description = "Run for leader of the Election test application") +public class ElectionTestRunCommand extends AbstractShellCommand { + + private static final String ELECTION_APP = "org.onosproject.election"; + + @Override + protected void execute() { + LeadershipService service = get(LeadershipService.class); + + service.runForLeadership(ELECTION_APP); + //print the current leader + print("Entering leadership elections for the Election app."); + } +} diff --git a/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestWithdrawCommand.java b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestWithdrawCommand.java new file mode 100644 index 00000000..0732a1fa --- /dev/null +++ b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/ElectionTestWithdrawCommand.java @@ -0,0 +1,40 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.election.cli; + +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.cluster.LeadershipService; + +/** + * CLI command to withdraw the local node from leadership election for + * the Election test application. + */ +@Command(scope = "onos", name = "election-test-withdraw", + description = "Withdraw node from leadership election for the Election test application") +public class ElectionTestWithdrawCommand extends AbstractShellCommand { + + private static final String ELECTION_APP = "org.onosproject.election"; + + @Override + protected void execute() { + LeadershipService service = get(LeadershipService.class); + + service.withdraw(ELECTION_APP); + //print the current leader + print("Withdrawing from leadership elections for the Election app."); + } +} diff --git a/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/package-info.java b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/package-info.java new file mode 100644 index 00000000..6c366378 --- /dev/null +++ b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/cli/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Election test command-line handlers. + */ +package org.onosproject.election.cli; diff --git a/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/package-info.java b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/package-info.java new file mode 100644 index 00000000..fcc48894 --- /dev/null +++ b/framework/src/onos/apps/test/election/src/main/java/org/onosproject/election/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014-2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Sample application for use in various experiments. + */ +package org.onosproject.election; diff --git a/framework/src/onos/apps/test/election/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/framework/src/onos/apps/test/election/src/main/resources/OSGI-INF/blueprint/shell-config.xml new file mode 100644 index 00000000..e9f05b43 --- /dev/null +++ b/framework/src/onos/apps/test/election/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + diff --git a/framework/src/onos/apps/test/intent-perf/pom.xml b/framework/src/onos/apps/test/intent-perf/pom.xml new file mode 100644 index 00000000..7d325394 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/pom.xml @@ -0,0 +1,59 @@ + + + + 4.0.0 + + + org.onosproject + onos-apps-test + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-app-intent-perf + bundle + + Intent performance test application + + + org.onosproject.intentperf + + + + + org.onosproject + onos-cli + ${project.version} + + + org.osgi + org.osgi.compendium + + + org.apache.karaf.shell + org.apache.karaf.shell.console + + + + org.osgi + org.osgi.core + + + + diff --git a/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java new file mode 100644 index 00000000..8c160e85 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfCollector.java @@ -0,0 +1,234 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.intentperf; + +import com.google.common.collect.ImmutableList; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.store.cluster.messaging.ClusterCommunicationService; +import org.onosproject.store.cluster.messaging.ClusterMessage; +import org.onosproject.store.cluster.messaging.ClusterMessageHandler; +import org.onosproject.store.cluster.messaging.MessageSubject; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static org.onlab.util.SharedExecutors.getPoolThreadExecutor; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Collects and distributes performance samples. + */ +@Component(immediate = true) +@Service(value = IntentPerfCollector.class) +public class IntentPerfCollector { + + private static final long SAMPLE_TIME_WINDOW_MS = 5_000; + private final Logger log = getLogger(getClass()); + + private static final int MAX_SAMPLES = 1_000; + + private final List samples = new LinkedList<>(); + + private static final MessageSubject SAMPLE = new MessageSubject("intent-perf-sample"); + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterCommunicationService communicationService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected IntentPerfUi ui; + + // Auxiliary structures used to accrue data for normalized time interval + // across all nodes. + private long newestTime; + private Sample overall; + private Sample current; + + private ControllerNode[] nodes; + private Map nodeToIndex; + + private NodeId nodeId; + + @Activate + public void activate() { + nodeId = clusterService.getLocalNode().id(); + + communicationService.addSubscriber(SAMPLE, new InternalSampleCollector(), + getPoolThreadExecutor()); + + nodes = clusterService.getNodes().toArray(new ControllerNode[]{}); + Arrays.sort(nodes, (a, b) -> a.id().toString().compareTo(b.id().toString())); + + nodeToIndex = new HashMap<>(); + for (int i = 0; i < nodes.length; i++) { + nodeToIndex.put(nodes[i].id(), i); + } + + clearSamples(); + ui.setCollector(this); + log.info("Started"); + } + + @Deactivate + public void deactivate() { + communicationService.removeSubscriber(SAMPLE); + log.info("Stopped"); + } + + /** + * Clears all previously accumulated data. + */ + public void clearSamples() { + newestTime = 0; + overall = new Sample(0, nodes.length); + current = new Sample(0, nodes.length); + samples.clear(); + } + + + /** + * Records a sample point of data about intent operation rate. + * + * @param overallRate overall rate + * @param currentRate current rate + */ + public void recordSample(double overallRate, double currentRate) { + long now = System.currentTimeMillis(); + addSample(now, nodeId, overallRate, currentRate); + broadcastSample(now, nodeId, overallRate, currentRate); + } + + /** + * Returns set of node ids as headers. + * + * @return node id headers + */ + public List getSampleHeaders() { + List headers = new ArrayList<>(); + for (ControllerNode node : nodes) { + headers.add(node.id().toString()); + } + return headers; + } + + /** + * Returns set of all accumulated samples normalized to the local set of + * samples. + * + * @return accumulated samples + */ + public synchronized List getSamples() { + return ImmutableList.copyOf(samples); + } + + /** + * Returns overall throughput performance for each of the cluster nodes. + * + * @return overall intent throughput + */ + public synchronized Sample getOverall() { + return overall; + } + + // Records a new sample to our collection of samples + private synchronized void addSample(long time, NodeId nodeId, + double overallRate, double currentRate) { + Sample fullSample = createCurrentSampleIfNeeded(time); + setSampleData(current, nodeId, currentRate); + setSampleData(overall, nodeId, overallRate); + pruneSamplesIfNeeded(); + + if (fullSample != null && ui != null) { + ui.reportSample(fullSample); + } + } + + private Sample createCurrentSampleIfNeeded(long time) { + Sample oldSample = time - newestTime > SAMPLE_TIME_WINDOW_MS || current.isComplete() ? current : null; + if (oldSample != null) { + newestTime = time; + current = new Sample(time, nodes.length); + if (oldSample.time > 0) { + samples.add(oldSample); + } + } + return oldSample; + } + + private void setSampleData(Sample sample, NodeId nodeId, double data) { + Integer index = nodeToIndex.get(nodeId); + if (index != null) { + sample.data[index] = data; + } + } + + private void pruneSamplesIfNeeded() { + if (samples.size() > MAX_SAMPLES) { + samples.remove(0); + } + } + + // Performance data sample. + static class Sample { + final long time; + final double[] data; + + public Sample(long time, int nodeCount) { + this.time = time; + this.data = new double[nodeCount]; + Arrays.fill(data, -1); + } + + public boolean isComplete() { + for (int i = 0; i < data.length; i++) { + if (data[i] < 0) { + return false; + } + } + return true; + } + } + + private void broadcastSample(long time, NodeId nodeId, double overallRate, double currentRate) { + String data = String.format("%d|%f|%f", time, overallRate, currentRate); + communicationService.broadcast(data, SAMPLE, str -> str.getBytes()); + } + + private class InternalSampleCollector implements ClusterMessageHandler { + @Override + public void handle(ClusterMessage message) { + String[] fields = new String(message.payload()).split("\\|"); + log.debug("Received sample from {}: {}", message.sender(), fields); + addSample(Long.parseLong(fields[0]), message.sender(), + Double.parseDouble(fields[1]), Double.parseDouble(fields[2])); + } + } +} diff --git a/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java new file mode 100644 index 00000000..de9e9f21 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfInstaller.java @@ -0,0 +1,596 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.intentperf; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Sets; +import org.apache.commons.lang.math.RandomUtils; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.packet.MacAddress; +import org.onlab.util.Counter; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.ControllerNode; +import org.onosproject.cluster.NodeId; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.mastership.MastershipService; +import org.onosproject.net.ConnectPoint; +import org.onosproject.net.Device; +import org.onosproject.net.PortNumber; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.intent.Intent; +import org.onosproject.net.intent.IntentEvent; +import org.onosproject.net.intent.IntentListener; +import org.onosproject.net.intent.IntentService; +import org.onosproject.net.intent.Key; +import org.onosproject.net.intent.PartitionService; +import org.onosproject.net.intent.PointToPointIntent; +import org.onosproject.store.cluster.messaging.ClusterCommunicationService; +import org.onosproject.store.cluster.messaging.ClusterMessage; +import org.onosproject.store.cluster.messaging.ClusterMessageHandler; +import org.onosproject.store.cluster.messaging.MessageSubject; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Dictionary; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; +import static java.lang.System.currentTimeMillis; +import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY; +import static org.onlab.util.Tools.*; +import static org.onosproject.net.intent.IntentEvent.Type.*; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Application to test sustained intent throughput. + */ +@Component(immediate = true) +@Service(value = IntentPerfInstaller.class) +public class IntentPerfInstaller { + + private final Logger log = getLogger(getClass()); + + private static final int DEFAULT_NUM_WORKERS = 1; + + private static final int DEFAULT_NUM_KEYS = 40000; + private static final int DEFAULT_GOAL_CYCLE_PERIOD = 1000; //ms + + private static final int DEFAULT_NUM_NEIGHBORS = 0; + + private static final int START_DELAY = 5_000; // ms + private static final int REPORT_PERIOD = 1_000; //ms + + private static final String START = "start"; + private static final String STOP = "stop"; + private static final MessageSubject CONTROL = new MessageSubject("intent-perf-ctl"); + + //FIXME add path length + + @Property(name = "numKeys", intValue = DEFAULT_NUM_KEYS, + label = "Number of keys (i.e. unique intents) to generate per instance") + private int numKeys = DEFAULT_NUM_KEYS; + + //TODO implement numWorkers property +// @Property(name = "numThreads", intValue = DEFAULT_NUM_WORKERS, +// label = "Number of installer threads per instance") +// private int numWokers = DEFAULT_NUM_WORKERS; + + @Property(name = "cyclePeriod", intValue = DEFAULT_GOAL_CYCLE_PERIOD, + label = "Goal for cycle period (in ms)") + private int cyclePeriod = DEFAULT_GOAL_CYCLE_PERIOD; + + @Property(name = "numNeighbors", intValue = DEFAULT_NUM_NEIGHBORS, + label = "Number of neighbors to generate intents for") + private int numNeighbors = DEFAULT_NUM_NEIGHBORS; + + @Reference(cardinality = MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = MANDATORY_UNARY) + protected IntentService intentService; + + @Reference(cardinality = MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = MANDATORY_UNARY) + protected DeviceService deviceService; + + @Reference(cardinality = MANDATORY_UNARY) + protected MastershipService mastershipService; + + @Reference(cardinality = MANDATORY_UNARY) + protected PartitionService partitionService; + + @Reference(cardinality = MANDATORY_UNARY) + protected ComponentConfigService configService; + + @Reference(cardinality = MANDATORY_UNARY) + protected IntentPerfCollector sampleCollector; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ClusterCommunicationService communicationService; + + private ExecutorService messageHandlingExecutor; + + private ExecutorService workers; + private ApplicationId appId; + private Listener listener; + private boolean stopped = true; + + private Timer reportTimer; + + // FIXME this variable isn't shared properly between multiple worker threads + private int lastKey = 0; + + private IntentPerfUi perfUi; + private NodeId nodeId; + private TimerTask reporterTask; + + @Activate + public void activate(ComponentContext context) { + configService.registerProperties(getClass()); + + nodeId = clusterService.getLocalNode().id(); + appId = coreService.registerApplication("org.onosproject.intentperf." + nodeId.toString()); + + // TODO: replace with shared timer + reportTimer = new Timer("onos-intent-perf-reporter"); + workers = Executors.newFixedThreadPool(DEFAULT_NUM_WORKERS, groupedThreads("onos/intent-perf", "worker-%d")); + + // disable flow backups for testing + configService.setProperty("org.onosproject.store.flow.impl.DistributedFlowRuleStore", + "backupEnabled", "false"); + + // TODO: replace with shared executor + messageHandlingExecutor = Executors.newSingleThreadExecutor( + groupedThreads("onos/perf", "command-handler")); + + communicationService.addSubscriber(CONTROL, new InternalControl(), + messageHandlingExecutor); + + listener = new Listener(); + intentService.addListener(listener); + + // TODO: investigate why this seems to be necessary for configs to get picked up on initial activation + modify(context); + } + + @Deactivate + public void deactivate() { + stopTestRun(); + + configService.unregisterProperties(getClass(), false); + messageHandlingExecutor.shutdown(); + communicationService.removeSubscriber(CONTROL); + + if (listener != null) { + reportTimer.cancel(); + intentService.removeListener(listener); + listener = null; + reportTimer = null; + } + } + + @Modified + public void modify(ComponentContext context) { + if (context == null) { + logConfig("Reconfigured"); + return; + } + + Dictionary properties = context.getProperties(); + int newNumKeys, newCyclePeriod, newNumNeighbors; + try { + String s = get(properties, "numKeys"); + newNumKeys = isNullOrEmpty(s) ? numKeys : Integer.parseInt(s.trim()); + + s = get(properties, "cyclePeriod"); + newCyclePeriod = isNullOrEmpty(s) ? cyclePeriod : Integer.parseInt(s.trim()); + + s = get(properties, "numNeighbors"); + newNumNeighbors = isNullOrEmpty(s) ? numNeighbors : Integer.parseInt(s.trim()); + + } catch (NumberFormatException | ClassCastException e) { + log.warn("Malformed configuration detected; using defaults", e); + newNumKeys = DEFAULT_NUM_KEYS; + newCyclePeriod = DEFAULT_GOAL_CYCLE_PERIOD; + newNumNeighbors = DEFAULT_NUM_NEIGHBORS; + } + + if (newNumKeys != numKeys || newCyclePeriod != cyclePeriod || newNumNeighbors != numNeighbors) { + numKeys = newNumKeys; + cyclePeriod = newCyclePeriod; + numNeighbors = newNumNeighbors; + logConfig("Reconfigured"); + } + } + + public void start() { + if (stopped) { + stopped = false; + communicationService.broadcast(START, CONTROL, str -> str.getBytes()); + startTestRun(); + } + } + + public void stop() { + if (!stopped) { + communicationService.broadcast(STOP, CONTROL, str -> str.getBytes()); + stopTestRun(); + } + } + + private void logConfig(String prefix) { + log.info("{} with appId {}; numKeys = {}; cyclePeriod = {} ms; numNeighbors={}", + prefix, appId.id(), numKeys, cyclePeriod, numNeighbors); + } + + private void startTestRun() { + sampleCollector.clearSamples(); + + // adjust numNeighbors and generate list of neighbors + numNeighbors = Math.min(clusterService.getNodes().size() - 1, numNeighbors); + + // Schedule reporter task on report period boundary + reporterTask = new ReporterTask(); + reportTimer.scheduleAtFixedRate(reporterTask, + REPORT_PERIOD - currentTimeMillis() % REPORT_PERIOD, + REPORT_PERIOD); + + // Submit workers + stopped = false; + for (int i = 0; i < DEFAULT_NUM_WORKERS; i++) { + workers.submit(new Submitter(createIntents(numKeys, /*FIXME*/ 2, lastKey))); + } + log.info("Started test run"); + } + + private void stopTestRun() { + if (reporterTask != null) { + reporterTask.cancel(); + reporterTask = null; + } + + try { + workers.awaitTermination(5 * cyclePeriod, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + log.warn("Failed to stop worker", e); + } + + sampleCollector.recordSample(0, 0); + sampleCollector.recordSample(0, 0); + stopped = true; + + log.info("Stopped test run"); + } + + private List getNeighbors() { + List nodes = clusterService.getNodes().stream() + .map(ControllerNode::id) + .collect(Collectors.toCollection(ArrayList::new)); + // sort neighbors by id + Collections.sort(nodes, (node1, node2) -> + node1.toString().compareTo(node2.toString())); + // rotate the local node to index 0 + Collections.rotate(nodes, -1 * nodes.indexOf(clusterService.getLocalNode().id())); + log.debug("neighbors (raw): {}", nodes); //TODO remove + // generate the sub-list that will contain local node and selected neighbors + nodes = nodes.subList(0, numNeighbors + 1); + log.debug("neighbors: {}", nodes); //TODO remove + return nodes; + } + + private Intent createIntent(Key key, long mac, NodeId node, Multimap devices) { + // choose a random device for which this node is master + List deviceList = devices.get(node).stream().collect(Collectors.toList()); + Device device = deviceList.get(RandomUtils.nextInt(deviceList.size())); + + //FIXME we currently ignore the path length and always use the same device + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthDst(MacAddress.valueOf(mac)).build(); + TrafficTreatment treatment = DefaultTrafficTreatment.emptyTreatment(); + ConnectPoint ingress = new ConnectPoint(device.id(), PortNumber.portNumber(1)); + ConnectPoint egress = new ConnectPoint(device.id(), PortNumber.portNumber(2)); + + return PointToPointIntent.builder() + .appId(appId) + .key(key) + .selector(selector) + .treatment(treatment) + .ingressPoint(ingress) + .egressPoint(egress) + .build(); + } + + /** + * Creates a specified number of intents for testing purposes. + * + * @param numberOfKeys number of intents + * @param pathLength path depth + * @param firstKey first key to attempt + * @return set of intents + */ + private Set createIntents(int numberOfKeys, int pathLength, int firstKey) { + List neighbors = getNeighbors(); + + Multimap devices = ArrayListMultimap.create(); + deviceService.getAvailableDevices() + .forEach(device -> devices.put(mastershipService.getMasterFor(device.id()), device)); + + // ensure that we have at least one device per neighbor + neighbors.forEach(node -> checkState(devices.get(node).size() > 0, + "There are no devices for {}", node)); + + // TODO pull this outside so that createIntent can use it + // prefix based on node id for keys generated on this instance + long keyPrefix = ((long) clusterService.getLocalNode().ip().getIp4Address().toInt()) << 32; + + int maxKeysPerNode = (int) Math.ceil((double) numberOfKeys / neighbors.size()); + Multimap intents = ArrayListMultimap.create(); + + for (int count = 0, k = firstKey; count < numberOfKeys; k++) { + Key key = Key.of(keyPrefix + k, appId); + + NodeId leader = partitionService.getLeader(key); + if (!neighbors.contains(leader) || intents.get(leader).size() >= maxKeysPerNode) { + // Bail if we are not sending to this node or we have enough for this node + continue; + } + intents.put(leader, createIntent(key, keyPrefix + k, leader, devices)); + + // Bump up the counter and remember this as the last key used. + count++; + lastKey = k; + if (count % 1000 == 0) { + log.info("Building intents... {} (attempt: {})", count, lastKey); + } + } + checkState(intents.values().size() == numberOfKeys, + "Generated wrong number of intents"); + log.info("Created {} intents", numberOfKeys); + intents.keySet().forEach(node -> log.info("\t{}\t{}", node, intents.get(node).size())); + + return Sets.newHashSet(intents.values()); + } + + // Submits intent operations. + final class Submitter implements Runnable { + + private long lastDuration; + private int lastCount; + + private Set intents = Sets.newHashSet(); + private Set submitted = Sets.newHashSet(); + private Set withdrawn = Sets.newHashSet(); + + private Submitter(Set intents) { + this.intents = intents; + lastCount = numKeys / 4; + lastDuration = 1_000; // 1 second + } + + @Override + public void run() { + prime(); + while (!stopped) { + try { + cycle(); + } catch (Exception e) { + log.warn("Exception during cycle", e); + } + } + clear(); + } + + private Iterable subset(Set intents) { + List subset = Lists.newArrayList(intents); + Collections.shuffle(subset); + return subset.subList(0, lastCount); + } + + // Submits the specified intent. + private void submit(Intent intent) { + intentService.submit(intent); + submitted.add(intent); + withdrawn.remove(intent); //TODO could check result here... + } + + // Withdraws the specified intent. + private void withdraw(Intent intent) { + intentService.withdraw(intent); + withdrawn.add(intent); + submitted.remove(intent); //TODO could check result here... + } + + // Primes the cycle. + private void prime() { + int i = 0; + withdrawn.addAll(intents); + for (Intent intent : intents) { + submit(intent); + // only submit half of the intents to start + if (i++ >= intents.size() / 2) { + break; + } + } + } + + private void clear() { + submitted.forEach(this::withdraw); + } + + // Runs a single operation cycle. + private void cycle() { + //TODO consider running without rate adjustment + adjustRates(); + + long start = currentTimeMillis(); + subset(submitted).forEach(this::withdraw); + subset(withdrawn).forEach(this::submit); + long delta = currentTimeMillis() - start; + + if (delta > cyclePeriod * 3 || delta < 0) { + log.warn("Cycle took {} ms", delta); + } + + int difference = cyclePeriod - (int) delta; + if (difference > 0) { + delay(difference); + } + + lastDuration = delta; + } + + int cycleCount = 0; + + private void adjustRates() { + + int addDelta = Math.max(1000 - cycleCount, 10); + double multRatio = Math.min(0.8 + cycleCount * 0.0002, 0.995); + + //FIXME need to iron out the rate adjustment + //FIXME we should taper the adjustments over time + //FIXME don't just use the lastDuration, take an average + if (++cycleCount % 5 == 0) { //TODO: maybe use a timer (we should do this every 5-10 sec) + if (listener.requestThroughput() - listener.processedThroughput() <= 2000 && //was 500 + lastDuration <= cyclePeriod) { + lastCount = Math.min(lastCount + addDelta, intents.size() / 2); + } else { + lastCount *= multRatio; + } + log.info("last count: {}, last duration: {} ms (sub: {} vs inst: {})", + lastCount, lastDuration, listener.requestThroughput(), listener.processedThroughput()); + } + + } + } + + // Event listener to monitor throughput. + final class Listener implements IntentListener { + + private final Counter runningTotal = new Counter(); + private volatile Map counters; + + private volatile double processedThroughput = 0; + private volatile double requestThroughput = 0; + + public Listener() { + counters = initCounters(); + } + + private Map initCounters() { + Map map = Maps.newHashMap(); + for (IntentEvent.Type type : IntentEvent.Type.values()) { + map.put(type, new Counter()); + } + return map; + } + + public double processedThroughput() { + return processedThroughput; + } + + public double requestThroughput() { + return requestThroughput; + } + + @Override + public void event(IntentEvent event) { + if (event.subject().appId().equals(appId)) { + counters.get(event.type()).add(1); + } + } + + public void report() { + Map reportCounters = counters; + counters = initCounters(); + + // update running total and latest throughput + Counter installed = reportCounters.get(INSTALLED); + Counter withdrawn = reportCounters.get(WITHDRAWN); + processedThroughput = installed.throughput() + withdrawn.throughput(); + runningTotal.add(installed.total() + withdrawn.total()); + + Counter installReq = reportCounters.get(INSTALL_REQ); + Counter withdrawReq = reportCounters.get(WITHDRAW_REQ); + requestThroughput = installReq.throughput() + withdrawReq.throughput(); + + // build the string to report + StringBuilder stringBuilder = new StringBuilder(); + for (IntentEvent.Type type : IntentEvent.Type.values()) { + Counter counter = reportCounters.get(type); + stringBuilder.append(format("%s=%.2f;", type, counter.throughput())); + } + log.info("Throughput: OVERALL={}; CURRENT={}; {}", + format("%.2f", runningTotal.throughput()), + format("%.2f", processedThroughput), + stringBuilder); + + sampleCollector.recordSample(runningTotal.throughput(), + processedThroughput); + } + } + + private class InternalControl implements ClusterMessageHandler { + @Override + public void handle(ClusterMessage message) { + String cmd = new String(message.payload()); + log.info("Received command {}", cmd); + if (cmd.equals(START)) { + startTestRun(); + } else { + stopTestRun(); + } + } + } + + private class ReporterTask extends TimerTask { + @Override + public void run() { + //adjustRates(); // FIXME we currently adjust rates in the cycle thread + listener.report(); + } + } + +} diff --git a/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfListCommand.java b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfListCommand.java new file mode 100644 index 00000000..a5567125 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfListCommand.java @@ -0,0 +1,91 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.intentperf; + +import org.apache.karaf.shell.commands.Command; +import org.apache.karaf.shell.commands.Option; +import org.onosproject.cli.AbstractShellCommand; +import org.onosproject.intentperf.IntentPerfCollector.Sample; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +/** + * Displays accumulated performance metrics. + */ +@Command(scope = "onos", name = "intent-perf", + description = "Displays accumulated performance metrics") +public class IntentPerfListCommand extends AbstractShellCommand { + + @Option(name = "-s", aliases = "--summary", description = "Output just summary", + required = false, multiValued = false) + private boolean summary = false; + + @Override + protected void execute() { + if (summary) { + printSummary(); + } else { + printSamples(); + } + } + + private void printSummary() { + IntentPerfCollector collector = get(IntentPerfCollector.class); + List headers = collector.getSampleHeaders(); + Sample overall = collector.getOverall(); + double total = 0; + print("%12s: %14s", "Node ID", "Overall Rate"); + for (int i = 0; i < overall.data.length; i++) { + if (overall.data[i] >= 0) { + print("%12s: %14.2f", headers.get(i), overall.data[i]); + total += overall.data[i]; + } else { + print("%12s: %14s", headers.get(i), " "); + } + } + print("%12s: %14.2f", "total", total); + } + + private void printSamples() { + IntentPerfCollector collector = get(IntentPerfCollector.class); + List headers = collector.getSampleHeaders(); + List samples = collector.getSamples(); + + System.out.print(String.format("%10s ", "Time")); + for (String header : headers) { + System.out.print(String.format("%12s ", header)); + } + System.out.println(String.format("%12s", "Total")); + + SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); + for (Sample sample : samples) { + double total = 0; + System.out.print(String.format("%10s ", sdf.format(new Date(sample.time)))); + for (int i = 0; i < sample.data.length; i++) { + if (sample.data[i] >= 0) { + System.out.print(String.format("%12.2f ", sample.data[i])); + total += sample.data[i]; + } else { + System.out.print(String.format("%12s ", " ")); + } + } + System.out.println(String.format("%12.2f", total)); + } + } + +} diff --git a/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStartCommand.java b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStartCommand.java new file mode 100644 index 00000000..35491539 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStartCommand.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.intentperf; + +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; + +/** + * Starts intent performance test run. + */ +@Command(scope = "onos", name = "intent-perf-start", + description = "Starts intent performance test run") +public class IntentPerfStartCommand extends AbstractShellCommand { + + @Override + protected void execute() { + get(IntentPerfInstaller.class).start(); + } + +} diff --git a/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStopCommand.java b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStopCommand.java new file mode 100644 index 00000000..ac45cd82 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfStopCommand.java @@ -0,0 +1,33 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.intentperf; + +import org.apache.karaf.shell.commands.Command; +import org.onosproject.cli.AbstractShellCommand; + +/** + * Stops intent performance test run. + */ +@Command(scope = "onos", name = "intent-perf-stop", + description = "Stops intent performance test run") +public class IntentPerfStopCommand extends AbstractShellCommand { + + @Override + protected void execute() { + get(IntentPerfInstaller.class).stop(); + } + +} diff --git a/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java new file mode 100644 index 00000000..a44ead57 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/IntentPerfUi.java @@ -0,0 +1,194 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.onosproject.intentperf; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.osgi.ServiceDirectory; +import org.onosproject.intentperf.IntentPerfCollector.Sample; +import org.onosproject.ui.RequestHandler; +import org.onosproject.ui.UiConnection; +import org.onosproject.ui.UiExtension; +import org.onosproject.ui.UiExtensionService; +import org.onosproject.ui.UiMessageHandler; +import org.onosproject.ui.UiView; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static java.util.Collections.synchronizedSet; +import static org.onosproject.ui.UiView.Category.OTHER; + +/** + * Mechanism to stream data to the GUI. + */ +@Component(immediate = true, enabled = true) +@Service(value = IntentPerfUi.class) +public class IntentPerfUi { + + private static final String INTENT_PERF_START = "intentPerfStart"; + private static final String INTENT_PERF_STOP = "intentPerfStop"; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected UiExtensionService uiExtensionService; + + private final Set handlers = synchronizedSet(new HashSet<>()); + + private List views = ImmutableList.of( + new UiView(OTHER, "intentPerf", "Intent Performance") + ); + + private UiExtension uiExtension = + new UiExtension.Builder(getClass().getClassLoader(), views) + .messageHandlerFactory(this::newHandlers) + .build(); + + private IntentPerfCollector collector; + + @Activate + protected void activate() { + uiExtensionService.register(uiExtension); + } + + @Deactivate + protected void deactivate() { + uiExtensionService.unregister(uiExtension); + } + + /** + * Reports a single sample of performance data. + * + * @param sample performance sample + */ + public void reportSample(Sample sample) { + synchronized (handlers) { + handlers.forEach(h -> h.send(sample)); + } + } + + /** + * Binds the sample collector. + * + * @param collector list of headers for future samples + */ + public void setCollector(IntentPerfCollector collector) { + this.collector = collector; + } + + // Creates and returns session specific message handler. + private Collection newHandlers() { + return ImmutableList.of(new StreamingControl()); + } + + + // UI Message handlers for turning on/off reporting to a session. + private class StreamingControl extends UiMessageHandler { + + private boolean streamingEnabled = false; + + @Override + protected Collection createRequestHandlers() { + return ImmutableSet.of( + new IntentPerfStart(), + new IntentPerfStop() + ); + } + + @Override + public void init(UiConnection connection, ServiceDirectory directory) { + super.init(connection, directory); + handlers.add(this); + } + + @Override + public void destroy() { + super.destroy(); + handlers.remove(this); + } + + private void send(Sample sample) { + if (streamingEnabled) { + connection().sendMessage("intentPerfSample", 0, sampleNode(sample)); + } + } + + + private ObjectNode sampleNode(Sample sample) { + ObjectNode sampleNode = objectNode(); + ArrayNode an = arrayNode(); + sampleNode.put("time", sample.time); + sampleNode.set("data", an); + + for (double d : sample.data) { + an.add(d); + } + return sampleNode; + } + + // ====================================================================== + + private final class IntentPerfStart extends RequestHandler { + + private IntentPerfStart() { + super(INTENT_PERF_START); + } + + @Override + public void process(long sid, ObjectNode payload) { + streamingEnabled = true; + sendInitData(); + } + + private void sendInitData() { + ObjectNode rootNode = MAPPER.createObjectNode(); + ArrayNode an = MAPPER.createArrayNode(); + ArrayNode sn = MAPPER.createArrayNode(); + rootNode.set("headers", an); + rootNode.set("samples", sn); + + collector.getSampleHeaders().forEach(an::add); + collector.getSamples().forEach(s -> sn.add(sampleNode(s))); + sendMessage("intentPerfInit", 0, rootNode); + } + } + + // ====================================================================== + + private final class IntentPerfStop extends RequestHandler { + + private IntentPerfStop() { + super(INTENT_PERF_STOP); + } + + @Override + public void process(long sid, ObjectNode payload) { + streamingEnabled = false; + } + } + + } + +} diff --git a/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/package-info.java b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/package-info.java new file mode 100644 index 00000000..75d40e49 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/java/org/onosproject/intentperf/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Performance test application that induces steady load on the intent subsystem. + */ +package org.onosproject.intentperf; \ No newline at end of file diff --git a/framework/src/onos/apps/test/intent-perf/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/framework/src/onos/apps/test/intent-perf/src/main/resources/OSGI-INF/blueprint/shell-config.xml new file mode 100644 index 00000000..fc46d1de --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/resources/OSGI-INF/blueprint/shell-config.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + diff --git a/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css b/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css new file mode 100644 index 00000000..15e95d5e --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.css @@ -0,0 +1,55 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + ONOS GUI -- Intent Perf View -- CSS file + */ + +svg { + font: 12px sans-serif; +} + +.line,.lineTotal { + fill: none; + stroke-width: 2px; +} + +.axis path, +.axis line { + fill: none; + stroke-width: 2px; + shape-rendering: crispEdges; +} + +.light .axis path, +.light .axis line, +.light .lineTotal { + stroke: #333; +} + +.light .axis text { + fill: #333; +} + +.dark .axis path, +.dark .axis line, +.dark .lineTotal { + stroke: #eee; +} + +.dark .axis text { + fill: #eee; +} diff --git a/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.html b/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.html new file mode 100644 index 00000000..6cdbc665 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.html @@ -0,0 +1,26 @@ + + + +
+

Intent Performance View

+ +
+
+
diff --git a/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js b/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js new file mode 100644 index 00000000..94304c73 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/resources/app/view/intentPerf/intentPerf.js @@ -0,0 +1,307 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + ONOS GUI -- Intent Performance View Module + */ +(function () { + 'use strict'; + + // injected refs + var $log, tbs, ts, wss, sus, flash, fs, mast; + + // internal state + var handlerMap, + openListener, + theSample = [], + graph; + + // ========================== + + function createGraph(h, samples) { + var stopped = false, + n = 243, + duration = 750, + now = new Date(Date.now() - duration), + headers = h, + data = []; + + var dim = fs.windowSize(mast.mastHeight()); + var margin, width, height, x, y; + var svg, axis; + + var lines = [], + paths = []; + + var transition = d3.select({}).transition() + .duration(duration) + .ease("linear"); + + svg = d3.select("#intent-perf-chart").append("p").append("svg") + .attr("id", "intent-perf-svg") + .append("g") + .attr("id", "intent-perf-svg-g"); + + svg.append("defs").append("clipPath") + .attr("id", "intent-perf-clip") + .append("rect"); + + axis = svg.append("g") + .attr("class", "x axis") + .attr("id", "intent-perf-x"); + + svg.append("g").attr("class", "y axis") + .attr("id", "intent-perf-yl"); + + svg.append("g") + .attr("class", "y axis") + .attr("id", "intent-perf-yr"); + + resize(dim); + + headers.forEach(function (h, li) { + // Prime the data to match the headers and zero it out. + data[li] = d3.range(n).map(function() { return 0 }); + + if (li < headers.length - 1) { + samples.forEach(function (s, i) { + var di = dataIndex(s.time); + if (di >= 0) { + data[li][di] = s.data[li]; + } + }); + + data[li].forEach(function (d, i) { + if (!d && i > 0) { + data[li][i] = data[li][i - 1]; + } + }); + } else { + data[li].forEach(function (t, i) { + for (var si = 0; si < headers.length - 1; si++) { + data[li][i] = data[si][i]; + } + }); + } + + // Create the lines + lines[li] = d3.svg.line() + .interpolate("basis") + .x(function(d, i) { return x(now - (n - 1 - i) * duration); }) + .y(function(d, i) { return y(d); }); + + // Create the SVG paths + paths[li] = svg.append("g") + .attr("clip-path", "url(#intent-perf-clip)") + .append("path") + .datum(function () { return data[li]; }) + .attr("id", "line" + li); + + if (li < headers.length - 1) { + paths[li].attr("class", "line").style("stroke", lineColor(li)); + } else { + paths[li].attr("class", "lineTotal"); + } + }); + + function dataIndex(time) { + var delta = now.getTime() - time; + var di = Math.round(n - 2 - (delta / duration)); + // $log.info('now=' + now.getTime() + '; then=' + time + '; delta=' + delta + '; di=' + di + ';'); + return di >= n || di < 0 ? -1 : di; + } + + function lineColor(li) { + return sus.cat7().getColor(li, false, ts.theme()); + } + + function tick() { + if (stopped) { + return; + } + + transition = transition.each(function() { + // update the domains + now = new Date(); + x.domain([now - (n - 2) * duration, now - duration]); + + data.forEach(function (d, li) { + // push the new most recent sample onto the back + d.push(theSample[li]); + + // redraw the line and slide it left + paths[li].attr("d", lines[li]).attr("transform", null); + paths[li].transition() + .attr("transform", "translate(" + x(now - (n - 1) * duration) + ")"); + + // pop the old data point off the front + d.shift(); + }); + + // slide the x-axis left + axis.call(x.axis); + }).transition().each("start", tick); + } + + function start() { + stopped = false; + headers.forEach(function (h, li) { + theSample[li] = data[li][n-1]; + }); + tick(); + } + + function stop() { + headers.forEach(function (h, li) { + theSample[li] = 0; + }); + // Schedule delayed stop to allow 0s to render. + setTimeout(function () { stopped = true; }, 1000); + } + + function resize(dim) { + margin = {top: 20, right: 90, bottom: 20, left: 70}; + width = dim.width - margin.right - margin.left; + height = 480 - margin.top - margin.bottom; + + x = d3.time.scale() + .domain([now - (n - 2) * duration, now - duration]) + .range([0, width]); + + y = d3.scale.linear() + .domain([0, 200000]) + .range([height, 0]); + + d3.select("#intent-perf-svg") + .attr("width", width + margin.left + margin.right) + .attr("height", height + margin.top + margin.bottom); + d3.select("#intent-perf-svg-g") + .attr("transform", "translate(" + margin.left + "," + margin.top + ")"); + + d3.select("#intent-perf-clip rect") + .attr("width", width) + .attr("height", height); + + d3.select("#intent-perf-x") + .attr("transform", "translate(0," + height + ")") + .call(x.axis = d3.svg.axis().scale(x).orient("bottom")); + + d3.select("#intent-perf-yl") + .call(d3.svg.axis().scale(y).orient("left")) + d3.select("#intent-perf-yr") + .attr("transform", "translate(" + width + " ,0)") + .call(d3.svg.axis().scale(y).orient("right")) + } + + return { + start: start, + stop: stop, + resize: resize + }; + } + + + function wsOpen(host, url) { + $log.debug('IntentPerf: web socket open - cluster node:', host, 'URL:', url); + // Request batch of initial data from the new server + wss.sendEvent('intentPerfStart'); + } + + function createAndInitGraph(d) { + if (!graph) { + d.headers.push("total"); + graph = createGraph(d.headers, d.samples); + } + graph.start(); + } + + function graphResized(dim) { + $log.info("Resized: " + dim.width + "x" + dim.height); + if (graph) { + graph.resize(dim); + } + } + + function recordSample(sample) { + var total = 0; + sample.data.forEach(function (d, i) { + theSample[i] = d; + total = total + d; + }); + theSample[sample.data.length] = total; + } + + function createHandlerMap() { + handlerMap = { + intentPerfInit: createAndInitGraph, + intentPerfSample: recordSample + }; + } + + // define the controller + + angular.module('ovIntentPerf', ['onosUtil']) + .controller('OvIntentPerfCtrl', + ['$scope', '$log', 'ToolbarService', 'WebSocketService', + 'ThemeService', 'FlashService', 'SvgUtilService', 'FnService', + 'MastService', + + function ($scope, _$log_, _tbs_, _wss_, _ts_, _flash_, _sus_, _fs_, _mast_) { + var self = this; + + $log = _$log_; + tbs = _tbs_; + wss = _wss_; + ts = _ts_; + flash = _flash_; + sus = _sus_; + fs = _fs_; + mast = _mast_; + + createHandlerMap(); + + self.notifyResize = function () { + graphResized(fs.windowSize(mast.mastHeight())); + }; + + function start() { + openListener = wss.addOpenListener(wsOpen); + wss.bindHandlers(handlerMap); + wss.sendEvent('intentPerfStart'); + $log.debug('intentPerf comms started'); + } + + function stop() { + graph.stop(); + wss.sendEvent('intentPerfStop'); + wss.unbindHandlers(handlerMap); + wss.removeOpenListener(openListener); + openListener = null; + graph = null; + $log.debug('intentPerf comms stopped'); + } + + // Cleanup on destroyed scope.. + $scope.$on('$destroy', function () { + $log.log('OvIntentPerfCtrl is saying Buh-Bye!'); + stop(); + }); + + $log.log('OvIntentPerfCtrl has been created'); + + start(); + }]); +}()); diff --git a/framework/src/onos/apps/test/intent-perf/src/main/resources/css.html b/framework/src/onos/apps/test/intent-perf/src/main/resources/css.html new file mode 100644 index 00000000..06dd7e8b --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/resources/css.html @@ -0,0 +1 @@ + diff --git a/framework/src/onos/apps/test/intent-perf/src/main/resources/dev.html b/framework/src/onos/apps/test/intent-perf/src/main/resources/dev.html new file mode 100644 index 00000000..d0b59f46 --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/resources/dev.html @@ -0,0 +1,27 @@ + + + + + Dev View + + + + +
+ + + \ No newline at end of file diff --git a/framework/src/onos/apps/test/intent-perf/src/main/resources/js.html b/framework/src/onos/apps/test/intent-perf/src/main/resources/js.html new file mode 100644 index 00000000..e8bf551f --- /dev/null +++ b/framework/src/onos/apps/test/intent-perf/src/main/resources/js.html @@ -0,0 +1 @@ + diff --git a/framework/src/onos/apps/test/messaging-perf/pom.xml b/framework/src/onos/apps/test/messaging-perf/pom.xml new file mode 100644 index 00000000..66a7c306 --- /dev/null +++ b/framework/src/onos/apps/test/messaging-perf/pom.xml @@ -0,0 +1,60 @@ + + + + 4.0.0 + + + org.onosproject + onos-apps-test + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-app-messaging-perf + bundle + + Messaging performance test application + + + org.onosproject.messagingperf + + + + + org.onosproject + onos-api + ${project.version} + + + org.onosproject + onos-core-serializers + ${project.version} + + + org.osgi + org.osgi.compendium + + + + org.osgi + org.osgi.core + + + + diff --git a/framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/MessagingPerfApp.java b/framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/MessagingPerfApp.java new file mode 100644 index 00000000..24697933 --- /dev/null +++ b/framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/MessagingPerfApp.java @@ -0,0 +1,378 @@ +package org.onosproject.messagingperf; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.apache.felix.scr.annotations.ReferenceCardinality.MANDATORY_UNARY; +import static org.onlab.util.Tools.get; +import static org.onlab.util.Tools.groupedThreads; +import static org.slf4j.LoggerFactory.getLogger; + +import java.util.Dictionary; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.IntStream; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.Service; +import org.onlab.util.BoundedThreadPool; +import org.onlab.util.KryoNamespace; +import org.onosproject.cfg.ComponentConfigService; +import org.onosproject.cluster.ClusterService; +import org.onosproject.cluster.NodeId; +import org.onosproject.core.CoreService; +import org.onosproject.store.cluster.messaging.ClusterCommunicationService; +import org.onosproject.store.cluster.messaging.MessageSubject; +import org.onosproject.store.serializers.KryoNamespaces; +import org.onosproject.store.serializers.KryoSerializer; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * Application for measuring cluster messaging performance. + */ +@Component(immediate = true, enabled = true) +@Service(value = MessagingPerfApp.class) +public class MessagingPerfApp { + private final Logger log = getLogger(getClass()); + + @Reference(cardinality = MANDATORY_UNARY) + protected ClusterService clusterService; + + @Reference(cardinality = MANDATORY_UNARY) + protected ClusterCommunicationService communicationService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected CoreService coreService; + + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) + protected ComponentConfigService configService; + + private static final MessageSubject TEST_UNICAST_MESSAGE_TOPIC = + new MessageSubject("net-perf-unicast-message"); + + private static final MessageSubject TEST_REQUEST_REPLY_TOPIC = + new MessageSubject("net-perf-rr-message"); + + private static final int DEFAULT_SENDER_THREAD_POOL_SIZE = 2; + private static final int DEFAULT_RECEIVER_THREAD_POOL_SIZE = 2; + + @Property(name = "totalSenderThreads", intValue = DEFAULT_SENDER_THREAD_POOL_SIZE, + label = "Number of sender threads") + protected int totalSenderThreads = DEFAULT_SENDER_THREAD_POOL_SIZE; + + @Property(name = "totalReceiverThreads", intValue = DEFAULT_RECEIVER_THREAD_POOL_SIZE, + label = "Number of receiver threads") + protected int totalReceiverThreads = DEFAULT_RECEIVER_THREAD_POOL_SIZE; + + @Property(name = "serializationOn", boolValue = true, + label = "Turn serialization on/off") + private boolean serializationOn = true; + + @Property(name = "receiveOnIOLoopThread", boolValue = false, + label = "Set this to true to handle message on IO thread") + private boolean receiveOnIOLoopThread = false; + + protected int reportIntervalSeconds = 1; + + private Executor messageReceivingExecutor; + + private ExecutorService messageSendingExecutor = + BoundedThreadPool.newFixedThreadPool(totalSenderThreads, + groupedThreads("onos/messaging-perf-test", "sender-%d")); + + private final ScheduledExecutorService reporter = + Executors.newSingleThreadScheduledExecutor( + groupedThreads("onos/net-perf-test", "reporter")); + + private AtomicInteger received = new AtomicInteger(0); + private AtomicInteger sent = new AtomicInteger(0); + private AtomicInteger attempted = new AtomicInteger(0); + private AtomicInteger completed = new AtomicInteger(0); + + protected static final KryoSerializer SERIALIZER = new KryoSerializer() { + @Override + protected void setupKryoPool() { + serializerPool = KryoNamespace.newBuilder() + .register(KryoNamespaces.BASIC) + .register(KryoNamespaces.MISC) + .register(byte[].class) + .register(Data.class) + .build(); + } + }; + + private final Data data = new Data().withStringField("test") + .withListField(Lists.newArrayList("1", "2", "3")) + .withSetField(Sets.newHashSet("1", "2", "3")); + private final byte[] dataBytes = SERIALIZER.encode(new Data().withStringField("test") + .withListField(Lists.newArrayList("1", "2", "3")) + .withSetField(Sets.newHashSet("1", "2", "3"))); + + private Function encoder; + private Function decoder; + + @Activate + public void activate(ComponentContext context) { + configService.registerProperties(getClass()); + setupCodecs(); + messageReceivingExecutor = receiveOnIOLoopThread + ? MoreExecutors.directExecutor() + : Executors.newFixedThreadPool( + totalReceiverThreads, + groupedThreads("onos/net-perf-test", "receiver-%d")); + registerMessageHandlers(); + startTest(); + reporter.scheduleWithFixedDelay(this::reportPerformance, + reportIntervalSeconds, + reportIntervalSeconds, + TimeUnit.SECONDS); + logConfig("Started"); + } + + @Deactivate + public void deactivate(ComponentContext context) { + configService.unregisterProperties(getClass(), false); + stopTest(); + reporter.shutdown(); + unregisterMessageHandlers(); + log.info("Stopped."); + } + + @Modified + public void modified(ComponentContext context) { + if (context == null) { + totalSenderThreads = DEFAULT_SENDER_THREAD_POOL_SIZE; + totalReceiverThreads = DEFAULT_RECEIVER_THREAD_POOL_SIZE; + serializationOn = true; + receiveOnIOLoopThread = false; + return; + } + + Dictionary properties = context.getProperties(); + + int newTotalSenderThreads = totalSenderThreads; + int newTotalReceiverThreads = totalReceiverThreads; + boolean newSerializationOn = serializationOn; + boolean newReceiveOnIOLoopThread = receiveOnIOLoopThread; + try { + String s = get(properties, "totalSenderThreads"); + newTotalSenderThreads = isNullOrEmpty(s) + ? totalSenderThreads : Integer.parseInt(s.trim()); + + s = get(properties, "totalReceiverThreads"); + newTotalReceiverThreads = isNullOrEmpty(s) + ? totalReceiverThreads : Integer.parseInt(s.trim()); + + s = get(properties, "serializationOn"); + newSerializationOn = isNullOrEmpty(s) + ? serializationOn : Boolean.parseBoolean(s.trim()); + + s = get(properties, "receiveOnIOLoopThread"); + newReceiveOnIOLoopThread = isNullOrEmpty(s) + ? receiveOnIOLoopThread : Boolean.parseBoolean(s.trim()); + + } catch (NumberFormatException | ClassCastException e) { + return; + } + + boolean modified = newTotalSenderThreads != totalSenderThreads || + newTotalReceiverThreads != totalReceiverThreads || + newSerializationOn != serializationOn || + newReceiveOnIOLoopThread != receiveOnIOLoopThread; + + // If nothing has changed, simply return. + if (!modified) { + return; + } + + totalSenderThreads = newTotalSenderThreads; + totalReceiverThreads = newTotalReceiverThreads; + serializationOn = newSerializationOn; + if (!receiveOnIOLoopThread && newReceiveOnIOLoopThread != receiveOnIOLoopThread) { + ((ExecutorService) messageReceivingExecutor).shutdown(); + } + receiveOnIOLoopThread = newReceiveOnIOLoopThread; + + // restart test. + + stopTest(); + unregisterMessageHandlers(); + setupCodecs(); + messageSendingExecutor = + BoundedThreadPool.newFixedThreadPool( + totalSenderThreads, + groupedThreads("onos/net-perf-test", "sender-%d")); + messageReceivingExecutor = receiveOnIOLoopThread + ? MoreExecutors.directExecutor() + : Executors.newFixedThreadPool( + totalReceiverThreads, + groupedThreads("onos/net-perf-test", "receiver-%d")); + + registerMessageHandlers(); + startTest(); + + logConfig("Reconfigured"); + } + + + private void logConfig(String prefix) { + log.info("{} with senderThreadPoolSize = {}; receivingThreadPoolSize = {}" + + " serializationOn = {}, receiveOnIOLoopThread = {}", + prefix, + totalSenderThreads, + totalReceiverThreads, + serializationOn, + receiveOnIOLoopThread); + } + + private void setupCodecs() { + encoder = serializationOn ? SERIALIZER::encode : d -> dataBytes; + decoder = serializationOn ? SERIALIZER::decode : b -> data; + } + + private void registerMessageHandlers() { + communicationService.addSubscriber( + TEST_UNICAST_MESSAGE_TOPIC, + decoder, + d -> { received.incrementAndGet(); }, + messageReceivingExecutor); + + communicationService.addSubscriber( + TEST_REQUEST_REPLY_TOPIC, + decoder, + Function.identity(), + encoder, + messageReceivingExecutor); + } + + private void unregisterMessageHandlers() { + communicationService.removeSubscriber(TEST_UNICAST_MESSAGE_TOPIC); + communicationService.removeSubscriber(TEST_REQUEST_REPLY_TOPIC); + } + + private void startTest() { + IntStream.range(0, totalSenderThreads).forEach(i -> requestReply()); + } + + private void stopTest() { + messageSendingExecutor.shutdown(); + } + + private void requestReply() { + try { + attempted.incrementAndGet(); + CompletableFuture response = + communicationService.sendAndReceive( + data, + TEST_REQUEST_REPLY_TOPIC, + encoder, + decoder, + randomPeer()); + response.whenComplete((result, error) -> { + if (Objects.equals(data, result)) { + completed.incrementAndGet(); + } + messageSendingExecutor.submit(this::requestReply); + }); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void unicast() { + try { + sent.incrementAndGet(); + communicationService.unicast( + data, + TEST_UNICAST_MESSAGE_TOPIC, + encoder, + randomPeer()); + } catch (Exception e) { + e.printStackTrace(); + } + messageSendingExecutor.submit(this::unicast); + } + + private void broadcast() { + try { + sent.incrementAndGet(); + communicationService.broadcast( + data, + TEST_UNICAST_MESSAGE_TOPIC, + encoder); + } catch (Exception e) { + e.printStackTrace(); + } + messageSendingExecutor.submit(this::broadcast); + } + + private NodeId randomPeer() { + return clusterService.getNodes() + .stream() + .filter(node -> clusterService.getLocalNode().equals(node)) + .findAny() + .get() + .id(); + } + + private void reportPerformance() { + log.info("Attempted: {} Completed: {}", attempted.getAndSet(0), completed.getAndSet(0)); + } + + private static class Data { + private String stringField; + private List listField; + private Set setField; + + public Data withStringField(String value) { + stringField = value; + return this; + } + + public Data withListField(List value) { + listField = ImmutableList.copyOf(value); + return this; + } + + public Data withSetField(Set value) { + setField = ImmutableSet.copyOf(value); + return this; + } + + @Override + public int hashCode() { + return Objects.hash(stringField, listField, setField); + } + + @Override + public boolean equals(Object other) { + if (other instanceof Data) { + Data that = (Data) other; + return Objects.equals(this.stringField, that.stringField) && + Objects.equals(this.listField, that.listField) && + Objects.equals(this.setField, that.setField); + } + return false; + } + } +} diff --git a/framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/package-info.java b/framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/package-info.java new file mode 100644 index 00000000..98d682af --- /dev/null +++ b/framework/src/onos/apps/test/messaging-perf/src/main/java/org/onosproject/messagingperf/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Performance test application for the cluster messaging subsystem. + */ +package org.onosproject.messagingperf; diff --git a/framework/src/onos/apps/test/pom.xml b/framework/src/onos/apps/test/pom.xml new file mode 100644 index 00000000..41e1f201 --- /dev/null +++ b/framework/src/onos/apps/test/pom.xml @@ -0,0 +1,42 @@ + + + + 4.0.0 + + + org.onosproject + onos-apps + 1.3.0-SNAPSHOT + ../pom.xml + + + onos-apps-test + pom + + ONOS test applications + + + election + intent-perf + messaging-perf + demo + distributed-primitives + + + -- cgit 1.2.3-korg