/* * Copyright 2015-present 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<Intent> 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<JsonNode> 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<JsonNode> 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<JsonNode> 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> constraint = Lists.newArrayList(); List<Host> 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<Host> hosts; private final Random random = new Random(System.currentTimeMillis()); private Set<HostPair> uninstalledOrWithdrawn; private Set<HostPair> installed; private CountDownLatch latch; //used to wait on a batch to be processed. private static final int ITERATIONMAX = 50000000; public RandomInstaller(Optional<JsonNode> 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<Host> constructHostIds(Iterator<JsonNode> elements) { Set<Host> 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<HostPair> hostList = new LinkedList<>(uninstalledOrWithdrawn); Collections.shuffle(hostList); List<HostPair> toInstall = hostList.subList(0, random.nextInt(hostList.size() - 1)); List<HostPair> 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<HostPair> toInstall) { for (HostPair pair : toInstall) { installed.add(pair); uninstalledOrWithdrawn.remove(pair); intentService.submit(pair.h2hIntent()); } } private void uninstallIntents(Collection<HostPair> 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<HostPair> allPairs = Lists.newArrayList(installed); allPairs.addAll(uninstalledOrWithdrawn); for (HostPair pair : allPairs) { intentService.withdraw(pair.h2hIntent()); } } private Set<HostPair> buildPairs(Set<Host> hosts) { Set<HostPair> pairs = Sets.newHashSet(); Iterator<Host> 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<Host> pruneHostsByMasterShip() { return FluentIterable.from(hosts) .filter(hasLocalMaster()) .toSet(); } private Predicate<? super Host> hasLocalMaster() { return host -> 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> 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<JsonNode> { 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<ControllerNode> instances = Sets.newHashSet(clusterService.getNodes()); instances.remove(clusterService.getLocalNode()); Set<NodeId> acceptableNodes = Sets.newHashSet(); if (neighbours >= instances.size()) { instances.forEach(instance -> acceptableNodes.add(instance.id())); } else { Iterator<ControllerNode> nodes = instances.iterator(); for (int i = neighbours; i > 0; i--) { acceptableNodes.add(nodes.next().id()); } } acceptableNodes.add(clusterService.getLocalNode().id()); Set<Device> 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.MAX_PRIORITY); 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; } } }