alshabib
Committed by Gerrit Code Review

random demo intent installer

Change-Id: I1ac2f5a6b7efbc7c940c6d7c371af4f5befcd676

using a timer to pace ourselves

Change-Id: Ia58698f950bbbc958ad002ed56dfe54b90f317ab

all good blasts intents

Change-Id: Ia85df5ad211c01d22d4088403d789b3d6a2292f7

clean up

Change-Id: I1a6dde05f57d0e4866d3255fc28836dfa7e7c190
...@@ -109,7 +109,9 @@ ...@@ -109,7 +109,9 @@
109 com.google.common.*, 109 com.google.common.*,
110 org.onlab.packet.*, 110 org.onlab.packet.*,
111 org.onlab.rest.*, 111 org.onlab.rest.*,
112 - org.onlab.onos.* 112 + org.onlab.onos.*,
113 + org.onlab.util.*,
114 + org.jboss.netty.util.*
113 </Import-Package> 115 </Import-Package>
114 <Web-ContextPath>${web.context}</Web-ContextPath> 116 <Web-ContextPath>${web.context}</Web-ContextPath>
115 </instructions> 117 </instructions>
......
1 package org.onlab.onos.demo; 1 package org.onlab.onos.demo;
2 2
3 +import com.fasterxml.jackson.databind.JsonNode;
4 +
5 +import java.util.Optional;
6 +
3 /** 7 /**
4 * Simple demo api interface. 8 * Simple demo api interface.
5 */ 9 */
...@@ -11,7 +15,7 @@ public interface DemoAPI { ...@@ -11,7 +15,7 @@ public interface DemoAPI {
11 * Installs intents based on the installation type. 15 * Installs intents based on the installation type.
12 * @param type the installation type. 16 * @param type the installation type.
13 */ 17 */
14 - void setup(InstallType type); 18 + void setup(InstallType type, Optional<JsonNode> runParams);
15 19
16 /** 20 /**
17 * Uninstalls all existing intents. 21 * Uninstalls all existing intents.
......
...@@ -15,7 +15,11 @@ ...@@ -15,7 +15,11 @@
15 */ 15 */
16 package org.onlab.onos.demo; 16 package org.onlab.onos.demo;
17 17
18 +import com.fasterxml.jackson.databind.JsonNode;
19 +import com.google.common.base.Predicate;
20 +import com.google.common.collect.FluentIterable;
18 import com.google.common.collect.Lists; 21 import com.google.common.collect.Lists;
22 +import com.google.common.collect.Sets;
19 import com.google.common.util.concurrent.ThreadFactoryBuilder; 23 import com.google.common.util.concurrent.ThreadFactoryBuilder;
20 import org.apache.felix.scr.annotations.Activate; 24 import org.apache.felix.scr.annotations.Activate;
21 import org.apache.felix.scr.annotations.Component; 25 import org.apache.felix.scr.annotations.Component;
...@@ -23,9 +27,14 @@ import org.apache.felix.scr.annotations.Deactivate; ...@@ -23,9 +27,14 @@ import org.apache.felix.scr.annotations.Deactivate;
23 import org.apache.felix.scr.annotations.Reference; 27 import org.apache.felix.scr.annotations.Reference;
24 import org.apache.felix.scr.annotations.ReferenceCardinality; 28 import org.apache.felix.scr.annotations.ReferenceCardinality;
25 import org.apache.felix.scr.annotations.Service; 29 import org.apache.felix.scr.annotations.Service;
30 +
31 +import org.onlab.onos.cluster.ClusterService;
26 import org.onlab.onos.core.ApplicationId; 32 import org.onlab.onos.core.ApplicationId;
27 import org.onlab.onos.core.CoreService; 33 import org.onlab.onos.core.CoreService;
34 +import org.onlab.onos.mastership.MastershipService;
28 import org.onlab.onos.net.Host; 35 import org.onlab.onos.net.Host;
36 +import org.onlab.onos.net.HostId;
37 +import org.onlab.onos.net.MastershipRole;
29 import org.onlab.onos.net.flow.DefaultTrafficSelector; 38 import org.onlab.onos.net.flow.DefaultTrafficSelector;
30 import org.onlab.onos.net.flow.DefaultTrafficTreatment; 39 import org.onlab.onos.net.flow.DefaultTrafficTreatment;
31 import org.onlab.onos.net.flow.TrafficSelector; 40 import org.onlab.onos.net.flow.TrafficSelector;
...@@ -34,15 +43,27 @@ import org.onlab.onos.net.host.HostService; ...@@ -34,15 +43,27 @@ import org.onlab.onos.net.host.HostService;
34 import org.onlab.onos.net.intent.Constraint; 43 import org.onlab.onos.net.intent.Constraint;
35 import org.onlab.onos.net.intent.HostToHostIntent; 44 import org.onlab.onos.net.intent.HostToHostIntent;
36 import org.onlab.onos.net.intent.Intent; 45 import org.onlab.onos.net.intent.Intent;
46 +import org.onlab.onos.net.intent.IntentBatchService;
47 +import org.onlab.onos.net.intent.IntentOperations;
37 import org.onlab.onos.net.intent.IntentService; 48 import org.onlab.onos.net.intent.IntentService;
38 import org.slf4j.Logger; 49 import org.slf4j.Logger;
39 50
40 51
52 +import java.util.Collection;
53 +import java.util.Collections;
41 import java.util.HashSet; 54 import java.util.HashSet;
55 +import java.util.Iterator;
56 +import java.util.LinkedList;
42 import java.util.List; 57 import java.util.List;
58 +import java.util.Objects;
59 +import java.util.Optional;
60 +import java.util.Random;
43 import java.util.Set; 61 import java.util.Set;
62 +import java.util.concurrent.CountDownLatch;
44 import java.util.concurrent.ExecutorService; 63 import java.util.concurrent.ExecutorService;
45 import java.util.concurrent.Executors; 64 import java.util.concurrent.Executors;
65 +import java.util.concurrent.TimeUnit;
66 +
46 67
47 import static org.slf4j.LoggerFactory.getLogger; 68 import static org.slf4j.LoggerFactory.getLogger;
48 69
...@@ -64,17 +85,31 @@ public class DemoInstaller implements DemoAPI { ...@@ -64,17 +85,31 @@ public class DemoInstaller implements DemoAPI {
64 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) 85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
65 protected HostService hostService; 86 protected HostService hostService;
66 87
88 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 + protected MastershipService mastershipService;
90 +
91 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 + protected IntentBatchService intentBatchService;
93 +
94 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 + protected ClusterService clusterService;
96 +
67 private ExecutorService worker; 97 private ExecutorService worker;
68 98
99 + private ExecutorService randomWorker;
100 +
69 private ApplicationId appId; 101 private ApplicationId appId;
70 102
71 private final Set<Intent> existingIntents = new HashSet<>(); 103 private final Set<Intent> existingIntents = new HashSet<>();
104 + private RandomInstaller randomInstaller;
72 105
73 106
74 107
75 @Activate 108 @Activate
76 public void activate() { 109 public void activate() {
77 - appId = coreService.registerApplication("org.onlab.onos.demo.installer"); 110 + String nodeId = clusterService.getLocalNode().ip().toString();
111 + appId = coreService.registerApplication("org.onlab.onos.demo.installer."
112 + + nodeId);
78 worker = Executors.newFixedThreadPool(1, 113 worker = Executors.newFixedThreadPool(1,
79 new ThreadFactoryBuilder() 114 new ThreadFactoryBuilder()
80 .setNameFormat("demo-app-worker") 115 .setNameFormat("demo-app-worker")
...@@ -84,19 +119,34 @@ public class DemoInstaller implements DemoAPI { ...@@ -84,19 +119,34 @@ public class DemoInstaller implements DemoAPI {
84 119
85 @Deactivate 120 @Deactivate
86 public void deactivate() { 121 public void deactivate() {
87 - worker.shutdownNow(); 122 + shutdownAndAwaitTermination(worker);
123 + if (!randomWorker.isShutdown()) {
124 + shutdownAndAwaitTermination(randomWorker);
125 + }
88 log.info("Stopped"); 126 log.info("Stopped");
89 } 127 }
90 128
91 @Override 129 @Override
92 - public void setup(InstallType type) { 130 + public void setup(InstallType type, Optional<JsonNode> runParams) {
93 switch (type) { 131 switch (type) {
94 case MESH: 132 case MESH:
95 log.debug("Installing mesh intents"); 133 log.debug("Installing mesh intents");
96 worker.execute(new MeshInstaller()); 134 worker.execute(new MeshInstaller());
97 break; 135 break;
98 case RANDOM: 136 case RANDOM:
99 - throw new IllegalArgumentException("Not yet implemented."); 137 + //check that we do not have a random installer running
138 + if (randomWorker == null || randomWorker.isShutdown()) {
139 + randomWorker = Executors.newFixedThreadPool(1,
140 + new ThreadFactoryBuilder()
141 + .setNameFormat("random-worker")
142 + .build());
143 + log.debug("Installing random sequence of intents");
144 + randomInstaller = new RandomInstaller(runParams);
145 + randomWorker.execute(randomInstaller);
146 + } else {
147 + log.warn("Random installer is already running");
148 + }
149 + break;
100 default: 150 default:
101 throw new IllegalArgumentException("What is it you want exactly?"); 151 throw new IllegalArgumentException("What is it you want exactly?");
102 } 152 }
...@@ -108,6 +158,9 @@ public class DemoInstaller implements DemoAPI { ...@@ -108,6 +158,9 @@ public class DemoInstaller implements DemoAPI {
108 } 158 }
109 159
110 160
161 + /**
162 + * Simply installs a mesh of intents from all the hosts existing in the network.
163 + */
111 private class MeshInstaller implements Runnable { 164 private class MeshInstaller implements Runnable {
112 165
113 @Override 166 @Override
...@@ -129,15 +182,295 @@ public class DemoInstaller implements DemoAPI { ...@@ -129,15 +182,295 @@ public class DemoInstaller implements DemoAPI {
129 } 182 }
130 } 183 }
131 184
185 + /**
186 + * Randomly installs and withdraws intents.
187 + */
188 + private class RandomInstaller implements Runnable {
189 +
190 + private final boolean isLocal;
191 + private final Set<Host> hosts;
192 +
193 + private final Random random = new Random(System.currentTimeMillis());
194 +
195 + private Set<HostPair> uninstalledOrWithdrawn;
196 + private Set<HostPair> installed;
197 +
198 + private CountDownLatch latch;
199 +
200 + //used to wait on a batch to be processed.
201 + private static final int ITERATIONMAX = 50000000;
202 +
203 +
204 + public RandomInstaller(Optional<JsonNode> runParams) {
205 + /*
206 + Check if we have params and honour them. Otherwise
207 + set defaults to processing only local stuff and
208 + all local hosts.
209 + */
210 + if (runParams.isPresent()) {
211 + JsonNode node = runParams.get();
212 + isLocal = node.get("local").asBoolean();
213 + hosts = node.get("hosts") == null ? Sets.newHashSet(hostService.getHosts()) :
214 + constructHostIds(node.get("hosts").elements());
215 + } else {
216 + isLocal = true;
217 + hosts = Sets.newHashSet(hostService.getHosts());
218 + }
219 +
220 + //construct list of intents.
221 + installed = Sets.newHashSet();
222 + if (isLocal) {
223 + uninstalledOrWithdrawn = buildPairs(pruneHostsByMasterShip());
224 + } else {
225 + uninstalledOrWithdrawn = buildPairs(hosts);
226 + }
227 +
228 + }
229 +
230 + private Set<Host> constructHostIds(Iterator<JsonNode> elements) {
231 + Set<Host> hostIds = Sets.newHashSet();
232 + JsonNode n;
233 + while (elements.hasNext()) {
234 + n = elements.next();
235 + hostIds.add(hostService.getHost(HostId.hostId(n.textValue())));
236 + }
237 + return hostIds;
238 + }
239 +
240 + @Override
241 + public void run() {
242 + if (!randomWorker.isShutdown()) {
243 + randomize();
244 + latch = new CountDownLatch(1);
245 + try {
246 + trackIntents();
247 + } catch (InterruptedException e) {
248 + shutdown();
249 + }
250 + }
251 +
252 + }
253 +
254 +
255 + /**
256 + * Check whether the previously submitted batch is in progress
257 + * and if yes submit the next one. If things hang, wait for at
258 + * most 5 seconds and bail.
259 + * @throws InterruptedException if the thread go interupted
260 + */
261 + private void trackIntents() throws InterruptedException {
262 + int count = 0;
263 + while (!latch.await(100, TimeUnit.NANOSECONDS)) {
264 + if (intentBatchService.getPendingOperations().isEmpty()) {
265 + latch.countDown();
266 + }
267 + count++;
268 + if (count > ITERATIONMAX) {
269 + log.warn("A batch is stuck processing. current : {}" +
270 + ", pending : {}",
271 + intentBatchService.getCurrentOperations(),
272 + intentBatchService.getPendingOperations());
273 + shutdownAndAwaitTermination(randomWorker);
274 + }
275 + }
276 + //if everyting is good proceed.
277 + if (!randomWorker.isShutdown()) {
278 + randomWorker.execute(this);
279 + }
280 +
281 + }
282 +
283 + public void shutdown() {
284 + log.warn("Shutting down random installer!");
285 + cleanUp();
286 + }
287 +
288 +
289 + /**
290 + * Shuffle the uninstalled and installed list (separately) and select
291 + * a random number of them and install or uninstall them respectively.
292 + */
293 + private void randomize() {
294 + List<HostPair> hostList = new LinkedList<>(uninstalledOrWithdrawn);
295 + Collections.shuffle(hostList);
296 + List<HostPair> toInstall = hostList.subList(0,
297 + random.nextInt(hostList.size() - 1));
298 + List<HostPair> toRemove;
299 + if (!installed.isEmpty()) {
300 + hostList = new LinkedList<>(installed);
301 + Collections.shuffle(hostList);
302 + toRemove = hostList.subList(0,
303 + random.nextInt(hostList.size() - 1));
304 + uninstallIntents(toRemove);
305 + }
306 + installIntents(toInstall);
307 +
308 + }
309 +
310 + private void installIntents(List<HostPair> toInstall) {
311 + IntentOperations.Builder builder = IntentOperations.builder();
312 + for (HostPair pair : toInstall) {
313 + installed.add(pair);
314 + uninstalledOrWithdrawn.remove(pair);
315 + builder.addSubmitOperation(pair.h2hIntent());
316 + }
317 + intentBatchService.addIntentOperations(builder.build());
318 + }
132 319
320 + private void uninstallIntents(Collection<HostPair> toRemove) {
321 + IntentOperations.Builder builder = IntentOperations.builder();
322 + for (HostPair pair : toRemove) {
323 + installed.remove(pair);
324 + uninstalledOrWithdrawn.add(pair);
325 + builder.addWithdrawOperation(pair.h2hIntent().id());
326 + }
327 + intentBatchService.addIntentOperations(builder.build());
328 + }
329 +
330 + /**
331 + * Take everything and remove it all.
332 + */
333 + private void cleanUp() {
334 + List<HostPair> allPairs = Lists.newArrayList(installed);
335 + allPairs.addAll(uninstalledOrWithdrawn);
336 + IntentOperations.Builder builder = IntentOperations.builder();
337 + for (HostPair pair : allPairs) {
338 + builder.addWithdrawOperation(pair.h2hIntent().id());
339 + }
340 + intentBatchService.addIntentOperations(builder.build());
341 + }
342 +
343 +
344 + private Set<HostPair> buildPairs(Set<Host> hosts) {
345 + Set<HostPair> pairs = Sets.newHashSet();
346 + Iterator<Host> it = Sets.newHashSet(hosts).iterator();
347 + while (it.hasNext()) {
348 + Host src = it.next();
349 + it.remove();
350 + for (Host dst : hosts) {
351 + pairs.add(new HostPair(src, dst));
352 + }
353 + }
354 + return pairs;
355 + }
356 +
357 + private Set<Host> pruneHostsByMasterShip() {
358 + return FluentIterable.from(hosts)
359 + .filter(hasLocalMaster())
360 + .toSet();
361 +
362 + }
363 +
364 + private Predicate<? super Host> hasLocalMaster() {
365 + return new Predicate<Host>() {
366 + @Override
367 + public boolean apply(Host host) {
368 + return mastershipService.getLocalRole(
369 + host.location().deviceId()).equals(MastershipRole.MASTER);
370 + }
371 + };
372 + }
373 +
374 +
375 + /**
376 + * Simple class representing a pair of hosts and precomputes the associated
377 + * h2h intent.
378 + */
379 + private class HostPair {
380 +
381 + private final Host src;
382 + private final Host dst;
383 +
384 + private final TrafficSelector selector = DefaultTrafficSelector.builder().build();
385 + private final TrafficTreatment treatment = DefaultTrafficTreatment.builder().build();
386 + private final List<Constraint> constraint = Lists.newArrayList();
387 + private final HostToHostIntent intent;
388 +
389 + public HostPair(Host src, Host dst) {
390 + this.src = src;
391 + this.dst = dst;
392 + this.intent = new HostToHostIntent(appId, src.id(), dst.id(),
393 + selector, treatment, constraint);
394 + }
395 +
396 + public HostToHostIntent h2hIntent() {
397 + return intent;
398 + }
399 +
400 + @Override
401 + public boolean equals(Object o) {
402 + if (this == o) {
403 + return true;
404 + }
405 + if (o == null || getClass() != o.getClass()) {
406 + return false;
407 + }
408 +
409 + HostPair hostPair = (HostPair) o;
410 +
411 + return Objects.equals(src, hostPair.src) &&
412 + Objects.equals(dst, hostPair.dst);
413 +
414 + }
415 +
416 + @Override
417 + public int hashCode() {
418 + return Objects.hash(src, dst);
419 + }
420 +
421 +
422 + }
423 +
424 + }
425 +
426 + /**
427 + * Remove anything that is running and clear it all out.
428 + */
133 private class UnInstaller implements Runnable { 429 private class UnInstaller implements Runnable {
134 @Override 430 @Override
135 public void run() { 431 public void run() {
432 + if (!existingIntents.isEmpty()) {
433 + clearExistingIntents();
434 + }
435 +
436 + if (randomWorker != null && !randomWorker.isShutdown()) {
437 + shutdownAndAwaitTermination(randomWorker);
438 + randomInstaller.shutdown();
439 + }
440 + }
441 +
442 + private void clearExistingIntents() {
136 for (Intent i : existingIntents) { 443 for (Intent i : existingIntents) {
137 intentService.withdraw(i); 444 intentService.withdraw(i);
138 } 445 }
446 + existingIntents.clear();
139 } 447 }
140 } 448 }
449 +
450 + /**
451 + * Shutdown a pool cleanly if possible.
452 + *
453 + * @param pool an executorService
454 + */
455 + private void shutdownAndAwaitTermination(ExecutorService pool) {
456 + pool.shutdown(); // Disable new tasks from being submitted
457 + try {
458 + // Wait a while for existing tasks to terminate
459 + if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
460 + pool.shutdownNow(); // Cancel currently executing tasks
461 + // Wait a while for tasks to respond to being cancelled
462 + if (!pool.awaitTermination(10, TimeUnit.SECONDS)) {
463 + log.error("Pool did not terminate");
464 + }
465 + }
466 + } catch (Exception ie) {
467 + // (Re-)Cancel if current thread also interrupted
468 + pool.shutdownNow();
469 + // Preserve interrupt status
470 + Thread.currentThread().interrupt();
471 + }
472 + }
473 +
141 } 474 }
142 475
143 476
......
...@@ -13,6 +13,7 @@ import javax.ws.rs.core.MediaType; ...@@ -13,6 +13,7 @@ import javax.ws.rs.core.MediaType;
13 import javax.ws.rs.core.Response; 13 import javax.ws.rs.core.Response;
14 import java.io.IOException; 14 import java.io.IOException;
15 import java.io.InputStream; 15 import java.io.InputStream;
16 +import java.util.Optional;
16 17
17 /** 18 /**
18 * Rest API for demos. 19 * Rest API for demos.
...@@ -33,10 +34,11 @@ public class DemoResource extends BaseResource { ...@@ -33,10 +34,11 @@ public class DemoResource extends BaseResource {
33 .entity("Expected type field containing either mesh or random.").build(); 34 .entity("Expected type field containing either mesh or random.").build();
34 } 35 }
35 36
37 +
36 DemoAPI.InstallType type = DemoAPI.InstallType.valueOf( 38 DemoAPI.InstallType type = DemoAPI.InstallType.valueOf(
37 cfg.get("type").asText().toUpperCase()); 39 cfg.get("type").asText().toUpperCase());
38 DemoAPI demo = get(DemoAPI.class); 40 DemoAPI demo = get(DemoAPI.class);
39 - demo.setup(type); 41 + demo.setup(type, Optional.ofNullable(cfg.get("runParams")));
40 42
41 return Response.ok(mapper.createObjectNode().toString()).build(); 43 return Response.ok(mapper.createObjectNode().toString()).build();
42 } 44 }
......
...@@ -231,6 +231,7 @@ ...@@ -231,6 +231,7 @@
231 <feature name="onos-app-demo" version="1.0.0" 231 <feature name="onos-app-demo" version="1.0.0"
232 description="ONOS demo applications"> 232 description="ONOS demo applications">
233 <feature>onos-api</feature> 233 <feature>onos-api</feature>
234 + <bundle>mvn:org.onlab.onos/onlab-misc/1.0.0-SNAPSHOT</bundle>
234 <bundle>mvn:org.onlab.onos/onos-app-demo/1.0.0-SNAPSHOT</bundle> 235 <bundle>mvn:org.onlab.onos/onos-app-demo/1.0.0-SNAPSHOT</bundle>
235 </feature> 236 </feature>
236 237
......