Carmelo Cascone
Committed by Thomas Vachuska

Added BMv2 demo apps (onos1.6 cherry-pick)

Change-Id: I19484a826acce724c1fcd5b6e9910d724bda686f
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2016-present Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +
18 +<project xmlns="http://maven.apache.org/POM/4.0.0"
19 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21 + <modelVersion>4.0.0</modelVersion>
22 +
23 + <parent>
24 + <artifactId>onos-app-bmv2-demo</artifactId>
25 + <groupId>org.onosproject</groupId>
26 + <version>1.7.0-SNAPSHOT</version>
27 + <relativePath>../pom.xml</relativePath>
28 + </parent>
29 +
30 + <artifactId>onos-app-bmv2-demo-common</artifactId>
31 +
32 + <packaging>bundle</packaging>
33 +
34 + <dependencies>
35 + <dependency>
36 + <groupId>org.onosproject</groupId>
37 + <artifactId>onos-bmv2-protocol-api</artifactId>
38 + <version>${project.version}</version>
39 + </dependency>
40 + </dependencies>
41 +</project>
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.common;
18 +
19 +import com.google.common.collect.ImmutableList;
20 +import com.google.common.collect.ImmutableMap;
21 +import com.google.common.collect.ImmutableSet;
22 +import com.google.common.collect.Lists;
23 +import com.google.common.collect.Maps;
24 +import com.google.common.collect.Sets;
25 +import org.apache.felix.scr.annotations.Activate;
26 +import org.apache.felix.scr.annotations.Component;
27 +import org.apache.felix.scr.annotations.Deactivate;
28 +import org.apache.felix.scr.annotations.Reference;
29 +import org.apache.felix.scr.annotations.ReferenceCardinality;
30 +import org.onosproject.app.ApplicationAdminService;
31 +import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
32 +import org.onosproject.bmv2.api.service.Bmv2DeviceContextService;
33 +import org.onosproject.core.ApplicationId;
34 +import org.onosproject.core.CoreService;
35 +import org.onosproject.net.ConnectPoint;
36 +import org.onosproject.net.Device;
37 +import org.onosproject.net.DeviceId;
38 +import org.onosproject.net.Host;
39 +import org.onosproject.net.Port;
40 +import org.onosproject.net.device.DeviceEvent;
41 +import org.onosproject.net.device.DeviceListener;
42 +import org.onosproject.net.device.DeviceService;
43 +import org.onosproject.net.flow.DefaultFlowRule;
44 +import org.onosproject.net.flow.FlowRule;
45 +import org.onosproject.net.flow.FlowRuleOperations;
46 +import org.onosproject.net.flow.FlowRuleService;
47 +import org.onosproject.net.host.HostEvent;
48 +import org.onosproject.net.host.HostListener;
49 +import org.onosproject.net.host.HostService;
50 +import org.onosproject.net.topology.Topology;
51 +import org.onosproject.net.topology.TopologyEvent;
52 +import org.onosproject.net.topology.TopologyGraph;
53 +import org.onosproject.net.topology.TopologyListener;
54 +import org.onosproject.net.topology.TopologyService;
55 +import org.onosproject.net.topology.TopologyVertex;
56 +import org.slf4j.Logger;
57 +
58 +import java.util.Collection;
59 +import java.util.List;
60 +import java.util.Map;
61 +import java.util.Set;
62 +import java.util.concurrent.ExecutorService;
63 +import java.util.concurrent.Executors;
64 +import java.util.concurrent.TimeUnit;
65 +import java.util.stream.Collectors;
66 +import java.util.stream.Stream;
67 +
68 +import static com.google.common.base.Preconditions.checkNotNull;
69 +import static java.util.stream.Collectors.toSet;
70 +import static java.util.stream.Stream.concat;
71 +import static org.onlab.util.Tools.groupedThreads;
72 +import static org.onosproject.net.device.DeviceEvent.Type.*;
73 +import static org.onosproject.net.host.HostEvent.Type.HOST_ADDED;
74 +import static org.slf4j.LoggerFactory.getLogger;
75 +
76 +/**
77 + * Abstract implementation of an app providing fabric connectivity for a 2-stage Clos topology of BMv2 devices.
78 + */
79 +@Component(immediate = true)
80 +public abstract class AbstractUpgradableFabricApp {
81 +
82 + private static final Map<String, AbstractUpgradableFabricApp> APP_HANDLES = Maps.newConcurrentMap();
83 +
84 + private static final int NUM_LEAFS = 3;
85 + private static final int NUM_SPINES = 3;
86 + private static final int FLOW_PRIORITY = 100;
87 +
88 + private static final int CLEANUP_SLEEP = 1000;
89 +
90 + protected final Logger log = getLogger(getClass());
91 +
92 + private final TopologyListener topologyListener = new InternalTopologyListener();
93 + private final DeviceListener deviceListener = new InternalDeviceListener();
94 + private final HostListener hostListener = new InternalHostListener();
95 +
96 + private final ExecutorService executorService = Executors
97 + .newFixedThreadPool(8, groupedThreads("onos/bmv2-demo-app", "bmv2-app-task", log));
98 +
99 + private final String appName;
100 + private final String configurationName;
101 +
102 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
103 + protected TopologyService topologyService;
104 +
105 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
106 + protected DeviceService deviceService;
107 +
108 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
109 + private HostService hostService;
110 +
111 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
112 + private FlowRuleService flowRuleService;
113 +
114 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 + private ApplicationAdminService appService;
116 +
117 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 + private CoreService coreService;
119 +
120 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121 + private Bmv2DeviceContextService bmv2ContextService;
122 +
123 + private boolean appActive = false;
124 + private boolean appFreezed = false;
125 +
126 + private boolean otherAppFound = false;
127 + private AbstractUpgradableFabricApp otherApp;
128 +
129 + private boolean flowRuleGenerated = false;
130 + private ApplicationId appId;
131 +
132 + private Bmv2DeviceContext bmv2Context;
133 +
134 + private Set<DeviceId> leafSwitches;
135 + private Set<DeviceId> spineSwitches;
136 +
137 + private Map<DeviceId, List<FlowRule>> deviceFlowRules;
138 + private Map<DeviceId, Boolean> rulesInstalled;
139 +
140 + /**
141 + * Creates a new Bmv2 Fabric Component.
142 + *
143 + * @param appName app name
144 + * @param configurationName a common name for the P4 program / BMv2 configuration used by this app
145 + * @param context a BMv2 device context to be used on devices
146 + */
147 + protected AbstractUpgradableFabricApp(String appName, String configurationName, Bmv2DeviceContext context) {
148 + this.appName = checkNotNull(appName);
149 + this.configurationName = checkNotNull(configurationName);
150 + this.bmv2Context = checkNotNull(context);
151 + }
152 +
153 + @Activate
154 + public void activate() {
155 + log.info("Starting...");
156 +
157 + appActive = true;
158 + appFreezed = false;
159 +
160 + if (APP_HANDLES.size() > 0) {
161 + if (APP_HANDLES.size() > 1) {
162 + throw new IllegalStateException("Found more than 1 active app handles");
163 + }
164 + otherAppFound = true;
165 + otherApp = APP_HANDLES.values().iterator().next();
166 + log.info("Found other fabric app active, signaling to freeze to {}...", otherApp.appName);
167 + otherApp.setAppFreezed(true);
168 + }
169 +
170 + APP_HANDLES.put(appName, this);
171 +
172 + appId = coreService.registerApplication(appName);
173 +
174 + topologyService.addListener(topologyListener);
175 + deviceService.addListener(deviceListener);
176 + hostService.addListener(hostListener);
177 +
178 + bmv2ContextService.registerInterpreterClassLoader(bmv2Context.interpreter().getClass(),
179 + this.getClass().getClassLoader());
180 +
181 + init();
182 +
183 + log.info("STARTED", appId.id());
184 + }
185 +
186 + @Deactivate
187 + public void deactivate() {
188 + log.info("Stopping...");
189 + try {
190 + executorService.shutdown();
191 + executorService.awaitTermination(5, TimeUnit.SECONDS);
192 + } catch (InterruptedException e) {
193 + List<Runnable> runningTasks = executorService.shutdownNow();
194 + log.warn("Unable to stop the following tasks: {}", runningTasks);
195 + }
196 + deviceService.removeListener(deviceListener);
197 + topologyService.removeListener(topologyListener);
198 + hostService.removeListener(hostListener);
199 + flowRuleService.removeFlowRulesById(appId);
200 +
201 + appActive = false;
202 + APP_HANDLES.remove(appName);
203 +
204 + log.info("STOPPED");
205 + }
206 +
207 + private void init() {
208 +
209 + // Reset any previous state
210 + synchronized (this) {
211 + flowRuleGenerated = Boolean.FALSE;
212 + leafSwitches = Sets.newHashSet();
213 + spineSwitches = Sets.newHashSet();
214 + deviceFlowRules = Maps.newConcurrentMap();
215 + rulesInstalled = Maps.newConcurrentMap();
216 + }
217 +
218 + // Start flow rules generator...
219 + spawnTask(() -> generateFlowRules(topologyService.currentTopology(), Sets.newHashSet(hostService.getHosts())));
220 + }
221 +
222 + private void setAppFreezed(boolean appFreezed) {
223 + this.appFreezed = appFreezed;
224 + if (appFreezed) {
225 + log.info("Freezing...");
226 + } else {
227 + log.info("Unfreezing...!");
228 + }
229 + }
230 +
231 + /**
232 + * Perform device initialization. Returns true if the operation was successful, false otherwise.
233 + *
234 + * @param deviceId a device id
235 + * @return a boolean value
236 + */
237 + public abstract boolean initDevice(DeviceId deviceId);
238 +
239 + /**
240 + * Generates a list of flow rules for the given leaf switch, source host, destination hosts, spine switches and
241 + * topology.
242 + *
243 + * @param leaf a leaf device id
244 + * @param srcHost a source host
245 + * @param dstHosts a collection of destination hosts
246 + * @param spines a collection of spine device IDs
247 + * @param topology a topology
248 + * @return a list of flow rules
249 + * @throws FlowRuleGeneratorException if flow rules cannot be generated
250 + */
251 + public abstract List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
252 + Collection<DeviceId> spines, Topology topology)
253 + throws FlowRuleGeneratorException;
254 +
255 + /**
256 + * Generates a list of flow rules for the given spine switch, destination hosts and topology.
257 + *
258 + * @param deviceId a spine device id
259 + * @param dstHosts a collection of destination hosts
260 + * @param topology a topology
261 + * @return a list of flow rules
262 + * @throws FlowRuleGeneratorException if flow rules cannot be generated
263 + */
264 + public abstract List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topology)
265 + throws FlowRuleGeneratorException;
266 +
267 + private void deployRoutine() {
268 + if (otherAppFound && otherApp.appActive) {
269 + log.info("Starting update routine...");
270 + updateRoutine();
271 + appService.deactivate(otherApp.appId);
272 + } else {
273 + Stream.concat(leafSwitches.stream(), spineSwitches.stream())
274 + .map(deviceService::getDevice)
275 + .forEach(device -> spawnTask(() -> deployDevice(device)));
276 + }
277 + }
278 +
279 + private void updateRoutine() {
280 + Stream.concat(leafSwitches.stream(), spineSwitches.stream())
281 + .forEach(did -> spawnTask(() -> {
282 + cleanUpDevice(did);
283 + try {
284 + Thread.sleep(CLEANUP_SLEEP);
285 + } catch (InterruptedException e) {
286 + log.warn("Cleanup sleep interrupted!");
287 + Thread.interrupted();
288 + }
289 + deployDevice(deviceService.getDevice(did));
290 + }));
291 + }
292 +
293 + private void cleanUpDevice(DeviceId deviceId) {
294 + List<FlowRule> flowRulesToRemove = Lists.newArrayList();
295 + flowRuleService.getFlowEntries(deviceId).forEach(fe -> {
296 + if (fe.appId() == otherApp.appId.id()) {
297 + flowRulesToRemove.add(fe);
298 + }
299 + });
300 + if (flowRulesToRemove.size() > 0) {
301 + log.info("Cleaning {} old flow rules from {}...", flowRulesToRemove.size(), deviceId);
302 + removeFlowRules(flowRulesToRemove);
303 + }
304 + }
305 +
306 + /**
307 + * Executes a device deploy.
308 + *
309 + * @param device a device
310 + */
311 + public void deployDevice(Device device) {
312 + // Serialize executions per device ID using a concurrent map.
313 + rulesInstalled.compute(device.id(), (did, deployed) -> {
314 + Bmv2DeviceContext deviceContext = bmv2ContextService.getContext(device.id());
315 + if (deviceContext == null) {
316 + log.error("Unable to get context for device {}", device.id());
317 + return deployed;
318 + } else if (!deviceContext.equals(bmv2Context)) {
319 + log.info("Swapping configuration to {} on device {}...", configurationName, device.id());
320 + bmv2ContextService.triggerConfigurationSwap(device.id(), bmv2Context);
321 + return deployed;
322 + }
323 +
324 + List<FlowRule> rules = deviceFlowRules.get(device.id());
325 + if (initDevice(device.id())) {
326 + if (deployed == null && rules != null && rules.size() > 0) {
327 + log.info("Installing rules for {}...", did);
328 + installFlowRules(rules);
329 + return true;
330 + }
331 + } else {
332 + log.warn("Filed to initialize device {}", device.id());
333 + if (deployed != null && rules != null && rules.size() > 0) {
334 + log.info("Removing rules for {}...", did);
335 + removeFlowRules(rules);
336 + return null;
337 + }
338 + }
339 +
340 + return deployed;
341 + });
342 + }
343 +
344 + private void spawnTask(Runnable task) {
345 + executorService.execute(task);
346 + }
347 +
348 +
349 + private void installFlowRules(Collection<FlowRule> rules) {
350 + FlowRuleOperations.Builder opsBuilder = FlowRuleOperations.builder();
351 + rules.forEach(opsBuilder::add);
352 + flowRuleService.apply(opsBuilder.build());
353 + }
354 +
355 + private void removeFlowRules(Collection<FlowRule> rules) {
356 + FlowRuleOperations.Builder opsBuilder = FlowRuleOperations.builder();
357 + rules.forEach(opsBuilder::remove);
358 + flowRuleService.apply(opsBuilder.build());
359 + }
360 +
361 + /**
362 + * Generates the flow rules to provide host-to-host connectivity for the given topology and hosts.
363 + *
364 + * @param topo a topology
365 + * @param hosts a collection of hosts
366 + */
367 + private synchronized void generateFlowRules(Topology topo, Collection<Host> hosts) {
368 +
369 + if (flowRuleGenerated) {
370 + log.debug("Flow rules have been already generated, aborting...");
371 + return;
372 + }
373 +
374 + log.debug("Starting flow rules generator...");
375 +
376 + TopologyGraph graph = topologyService.getGraph(topo);
377 + Set<DeviceId> spines = Sets.newHashSet();
378 + Set<DeviceId> leafs = Sets.newHashSet();
379 + graph.getVertexes().stream()
380 + .map(TopologyVertex::deviceId)
381 + .forEach(did -> (isSpine(did, topo) ? spines : leafs).add(did));
382 +
383 + if (spines.size() != NUM_SPINES || leafs.size() != NUM_LEAFS) {
384 + log.info("Invalid leaf/spine switches count, aborting... > leafCount={}, spineCount={}",
385 + spines.size(), leafs.size());
386 + return;
387 + }
388 +
389 + for (DeviceId did : spines) {
390 + int portCount = deviceService.getPorts(did).size();
391 + // Expected port count: num leafs + 1 redundant leaf link
392 + if (portCount != (NUM_LEAFS + 1)) {
393 + log.info("Invalid port count for spine, aborting... > deviceId={}, portCount={}", did, portCount);
394 + return;
395 + }
396 + }
397 + for (DeviceId did : leafs) {
398 + int portCount = deviceService.getPorts(did).size();
399 + // Expected port count: num spines + host port + 1 redundant spine link
400 + if (portCount != (NUM_SPINES + 2)) {
401 + log.info("Invalid port count for leaf, aborting... > deviceId={}, portCount={}", did, portCount);
402 + return;
403 + }
404 + }
405 +
406 + // Check hosts, number and exactly one per leaf
407 + Map<DeviceId, Host> hostMap = Maps.newHashMap();
408 + hosts.forEach(h -> hostMap.put(h.location().deviceId(), h));
409 + if (hosts.size() != NUM_LEAFS || !leafs.equals(hostMap.keySet())) {
410 + log.info("Wrong host configuration, aborting... > hostCount={}, hostMapz={}", hosts.size(), hostMap);
411 + return;
412 + }
413 +
414 + List<FlowRule> newFlowRules = Lists.newArrayList();
415 +
416 + try {
417 + for (DeviceId deviceId : leafs) {
418 + Host srcHost = hostMap.get(deviceId);
419 + Set<Host> dstHosts = hosts.stream().filter(h -> h != srcHost).collect(toSet());
420 + newFlowRules.addAll(generateLeafRules(deviceId, srcHost, dstHosts, spines, topo));
421 + }
422 + for (DeviceId deviceId : spines) {
423 + newFlowRules.addAll(generateSpineRules(deviceId, hosts, topo));
424 + }
425 + } catch (FlowRuleGeneratorException e) {
426 + log.warn("Exception while executing flow rule generator: ", e.toString());
427 + return;
428 + }
429 +
430 + if (newFlowRules.size() == 0) {
431 + // Something went wrong
432 + log.error("0 flow rules generated, BUG?");
433 + return;
434 + }
435 +
436 + // All good!
437 + // Divide flow rules per device id...
438 + ImmutableMap.Builder<DeviceId, List<FlowRule>> mapBuilder = ImmutableMap.builder();
439 + concat(spines.stream(), leafs.stream())
440 + .map(deviceId -> ImmutableList.copyOf(newFlowRules
441 + .stream()
442 + .filter(fr -> fr.deviceId().equals(deviceId))
443 + .iterator()))
444 + .forEach(frs -> mapBuilder.put(frs.get(0).deviceId(), frs));
445 + this.deviceFlowRules = mapBuilder.build();
446 +
447 + this.leafSwitches = ImmutableSet.copyOf(leafs);
448 + this.spineSwitches = ImmutableSet.copyOf(spines);
449 +
450 + // Avoid other executions to modify the generated flow rules.
451 + flowRuleGenerated = true;
452 +
453 + log.info("DONE! Generated {} flow rules for {} devices...", newFlowRules.size(), spines.size() + leafs.size());
454 +
455 + // Deploy configuration.
456 + spawnTask(this::deployRoutine);
457 + }
458 +
459 + /**
460 + * Returns a new, pre-configured flow rule builder.
461 + *
462 + * @param did a device id
463 + * @param tableName a table name
464 + * @return a new flow rule builder
465 + */
466 + protected FlowRule.Builder flowRuleBuilder(DeviceId did, String tableName) throws FlowRuleGeneratorException {
467 + Map<String, Integer> tableMap = bmv2Context.interpreter().tableIdMap().inverse();
468 + if (tableMap.get(tableName) == null) {
469 + throw new FlowRuleGeneratorException("Unknown table " + tableName);
470 + }
471 + return DefaultFlowRule.builder()
472 + .forDevice(did)
473 + .forTable(tableMap.get(tableName))
474 + .fromApp(appId)
475 + .withPriority(FLOW_PRIORITY)
476 + .makePermanent();
477 + }
478 +
479 + private List<Port> getHostPorts(DeviceId deviceId, Topology topology) {
480 + // Get all non-fabric ports.
481 + return deviceService
482 + .getPorts(deviceId)
483 + .stream()
484 + .filter(p -> !isFabricPort(p, topology))
485 + .collect(Collectors.toList());
486 + }
487 +
488 + private boolean isSpine(DeviceId deviceId, Topology topology) {
489 + // True if all ports are fabric.
490 + return getHostPorts(deviceId, topology).size() == 0;
491 + }
492 +
493 + protected boolean isFabricPort(Port port, Topology topology) {
494 + // True if the port connects this device to another infrastructure device.
495 + return topologyService.isInfrastructure(topology, new ConnectPoint(port.element().id(), port.number()));
496 + }
497 +
498 + /**
499 + * A listener of topology events that executes a flow rule generation task each time a device is added.
500 + */
501 + private class InternalTopologyListener implements TopologyListener {
502 +
503 + @Override
504 + public void event(TopologyEvent event) {
505 + spawnTask(() -> generateFlowRules(event.subject(), Sets.newHashSet(hostService.getHosts())));
506 + }
507 +
508 + @Override
509 + public boolean isRelevant(TopologyEvent event) {
510 + return !appFreezed &&
511 + // If at least one reason is of type DEVICE_ADDED.
512 + event.reasons().stream().
513 + filter(r -> r instanceof DeviceEvent)
514 + .filter(r -> ((DeviceEvent) r).type() == DEVICE_ADDED)
515 + .findAny()
516 + .isPresent();
517 + }
518 + }
519 +
520 + /**
521 + * A listener of device events that executes a device deploy task each time a device is added, updated or
522 + * re-connects.
523 + */
524 + private class InternalDeviceListener implements DeviceListener {
525 + @Override
526 + public void event(DeviceEvent event) {
527 + spawnTask(() -> deployDevice(event.subject()));
528 + }
529 +
530 + @Override
531 + public boolean isRelevant(DeviceEvent event) {
532 + return !appFreezed &&
533 + (event.type() == DEVICE_ADDED ||
534 + event.type() == DEVICE_UPDATED ||
535 + (event.type() == DEVICE_AVAILABILITY_CHANGED &&
536 + deviceService.isAvailable(event.subject().id())));
537 + }
538 + }
539 +
540 + /**
541 + * A listener of host events that generates flow rules each time a new host is added.
542 + */
543 + private class InternalHostListener implements HostListener {
544 + @Override
545 + public void event(HostEvent event) {
546 + spawnTask(() -> generateFlowRules(topologyService.currentTopology(),
547 + Sets.newHashSet(hostService.getHosts())));
548 + }
549 +
550 + @Override
551 + public boolean isRelevant(HostEvent event) {
552 + return !appFreezed && event.type() == HOST_ADDED;
553 + }
554 + }
555 +
556 + /**
557 + * An exception occurred while generating flow rules for this fabric.
558 + */
559 + public class FlowRuleGeneratorException extends Exception {
560 +
561 + public FlowRuleGeneratorException() {
562 + }
563 +
564 + public FlowRuleGeneratorException(String msg) {
565 + super(msg);
566 + }
567 +
568 + public FlowRuleGeneratorException(Exception cause) {
569 + super(cause);
570 + }
571 + }
572 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/**
18 + * Bmv2 demo app common classes.
19 + */
20 +package org.onosproject.bmv2.demo.app.common;
...\ No newline at end of file ...\ No newline at end of file
1 +<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 +
3 +<!--
4 + ~ Copyright 2016-present Open Networking Laboratory
5 + ~
6 + ~ Licensed under the Apache License, Version 2.0 (the "License");
7 + ~ you may not use this file except in compliance with the License.
8 + ~ You may obtain a copy of the License at
9 + ~
10 + ~ http://www.apache.org/licenses/LICENSE-2.0
11 + ~
12 + ~ Unless required by applicable law or agreed to in writing, software
13 + ~ distributed under the License is distributed on an "AS IS" BASIS,
14 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 + ~ See the License for the specific language governing permissions and
16 + ~ limitations under the License.
17 + -->
18 +
19 +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
20 + <feature name="${project.artifactId}" version="${project.version}"
21 + description="${project.description}">
22 + <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
23 + <bundle>mvn:${project.groupId}/onos-app-bmv2-demo-common/${project.version}</bundle>
24 + </feature>
25 +</features>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2016-present Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +
18 +<project xmlns="http://maven.apache.org/POM/4.0.0"
19 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21 + <modelVersion>4.0.0</modelVersion>
22 +
23 + <parent>
24 + <artifactId>onos-app-bmv2-demo</artifactId>
25 + <groupId>org.onosproject</groupId>
26 + <version>1.7.0-SNAPSHOT</version>
27 + <relativePath>../pom.xml</relativePath>
28 + </parent>
29 +
30 + <artifactId>onos-app-bmv2-demo-ecmp</artifactId>
31 +
32 + <packaging>bundle</packaging>
33 +
34 + <properties>
35 + <onos.app.name>org.onosproject.bmv2-ecmp-fabric</onos.app.name>
36 + <onos.app.title>P4/BMv2 Demo Fabric App v1 (ECMP)</onos.app.title>
37 + <onos.app.category>Traffic Steering</onos.app.category>
38 + <onos.app.url>http://onosproject.org</onos.app.url>
39 + <onos.app.readme>P4/BMv2 demo application with ECMP support for a 2-stage clos fabric topology</onos.app.readme>
40 + </properties>
41 +
42 + <dependencies>
43 + <dependency>
44 + <groupId>org.onosproject</groupId>
45 + <artifactId>onos-app-bmv2-demo-common</artifactId>
46 + <version>${project.version}</version>
47 + </dependency>
48 + </dependencies>
49 +
50 +</project>
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.ecmp;
18 +
19 +import com.eclipsesource.json.Json;
20 +import com.eclipsesource.json.JsonObject;
21 +import com.google.common.collect.Lists;
22 +import org.apache.commons.lang3.tuple.Pair;
23 +import org.apache.felix.scr.annotations.Component;
24 +import org.onosproject.bmv2.api.context.Bmv2Configuration;
25 +import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
26 +import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
27 +import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
28 +import org.onosproject.net.DeviceId;
29 +import org.onosproject.net.Host;
30 +import org.onosproject.net.Path;
31 +import org.onosproject.net.Port;
32 +import org.onosproject.net.PortNumber;
33 +import org.onosproject.net.flow.DefaultTrafficSelector;
34 +import org.onosproject.net.flow.DefaultTrafficTreatment;
35 +import org.onosproject.net.flow.FlowRule;
36 +import org.onosproject.net.flow.TrafficTreatment;
37 +import org.onosproject.net.flow.criteria.ExtensionSelector;
38 +import org.onosproject.net.flow.instructions.ExtensionTreatment;
39 +import org.onosproject.net.topology.DefaultTopologyVertex;
40 +import org.onosproject.net.topology.Topology;
41 +import org.onosproject.net.topology.TopologyGraph;
42 +
43 +import java.io.BufferedReader;
44 +import java.io.IOException;
45 +import java.io.InputStreamReader;
46 +import java.util.Collection;
47 +import java.util.Iterator;
48 +import java.util.List;
49 +import java.util.Set;
50 +import java.util.stream.Collectors;
51 +
52 +import static java.util.stream.Collectors.toSet;
53 +import static org.onlab.packet.EthType.EtherType.IPV4;
54 +import static org.onosproject.bmv2.demo.app.ecmp.EcmpGroupTreatmentBuilder.groupIdOf;
55 +import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.ECMP_GROUP_TABLE;
56 +import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.TABLE0;
57 +
58 +/**
59 + * Implementation of an upgradable fabric app for the ECMP configuration.
60 + */
61 +@Component(immediate = true)
62 +public class EcmpFabricApp extends AbstractUpgradableFabricApp {
63 +
64 + private static final String APP_NAME = "org.onosproject.bmv2-ecmp-fabric";
65 + private static final String MODEL_NAME = "ECMP";
66 + private static final String JSON_CONFIG_PATH = "/ecmp.json";
67 +
68 + private static final Bmv2Configuration ECMP_CONFIGURATION = loadConfiguration();
69 + private static final EcmpInterpreter ECMP_INTERPRETER = new EcmpInterpreter();
70 + protected static final Bmv2DeviceContext ECMP_CONTEXT = new Bmv2DeviceContext(ECMP_CONFIGURATION, ECMP_INTERPRETER);
71 +
72 + public EcmpFabricApp() {
73 + super(APP_NAME, MODEL_NAME, ECMP_CONTEXT);
74 + }
75 +
76 + @Override
77 + public boolean initDevice(DeviceId deviceId) {
78 + // Nothing to do.
79 + return true;
80 + }
81 +
82 + @Override
83 + public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
84 + Collection<DeviceId> availableSpines, Topology topo)
85 + throws FlowRuleGeneratorException {
86 +
87 + // Get ports which connect this leaf switch to hosts.
88 + Set<PortNumber> hostPorts = deviceService.getPorts(leaf)
89 + .stream()
90 + .filter(port -> !isFabricPort(port, topo))
91 + .map(Port::number)
92 + .collect(Collectors.toSet());
93 +
94 + // Get ports which connect this leaf to the given available spines.
95 + TopologyGraph graph = topologyService.getGraph(topo);
96 + Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
97 + .stream()
98 + .filter(e -> availableSpines.contains(e.dst().deviceId()))
99 + .map(e -> e.link().src().port())
100 + .collect(Collectors.toSet());
101 +
102 + if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
103 + log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
104 + hostPorts.size(), fabricPorts.size());
105 + throw new FlowRuleGeneratorException();
106 + }
107 + PortNumber hostPort = hostPorts.iterator().next();
108 +
109 + List<FlowRule> rules = Lists.newArrayList();
110 +
111 + TrafficTreatment treatment;
112 + if (fabricPorts.size() > 1) {
113 + // Do ECMP.
114 + Pair<ExtensionTreatment, List<FlowRule>> result = provisionEcmpTreatment(leaf, fabricPorts);
115 + rules.addAll(result.getRight());
116 + ExtensionTreatment extTreatment = result.getLeft();
117 + treatment = DefaultTrafficTreatment.builder().extension(extTreatment, leaf).build();
118 + } else {
119 + // Output on port.
120 + PortNumber outPort = fabricPorts.iterator().next();
121 + treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
122 + }
123 +
124 + // From srHost to dstHosts.
125 + for (Host dstHost : dstHosts) {
126 + FlowRule rule = flowRuleBuilder(leaf, TABLE0)
127 + .withSelector(
128 + DefaultTrafficSelector.builder()
129 + .matchInPort(hostPort)
130 + .matchEthType(IPV4.ethType().toShort())
131 + .matchEthSrc(srcHost.mac())
132 + .matchEthDst(dstHost.mac())
133 + .build())
134 + .withTreatment(treatment)
135 + .build();
136 + rules.add(rule);
137 + }
138 +
139 + // From fabric ports to this leaf host.
140 + for (PortNumber port : fabricPorts) {
141 + FlowRule rule = flowRuleBuilder(leaf, TABLE0)
142 + .withSelector(
143 + DefaultTrafficSelector.builder()
144 + .matchInPort(port)
145 + .matchEthType(IPV4.ethType().toShort())
146 + .matchEthDst(srcHost.mac())
147 + .build())
148 + .withTreatment(
149 + DefaultTrafficTreatment.builder()
150 + .setOutput(hostPort)
151 + .build())
152 + .build();
153 + rules.add(rule);
154 + }
155 +
156 + return rules;
157 + }
158 +
159 + @Override
160 + public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
161 + throws FlowRuleGeneratorException {
162 +
163 + List<FlowRule> rules = Lists.newArrayList();
164 +
165 + // for each host
166 + for (Host dstHost : dstHosts) {
167 +
168 + Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
169 +
170 + if (paths.size() == 0) {
171 + log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
172 + throw new FlowRuleGeneratorException();
173 + }
174 +
175 + TrafficTreatment treatment;
176 +
177 + if (paths.size() == 1) {
178 + // Only one path, do output on port.
179 + PortNumber port = paths.iterator().next().src().port();
180 + treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
181 + } else {
182 + // Multiple paths, do ECMP.
183 + Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
184 + Pair<ExtensionTreatment, List<FlowRule>> result = provisionEcmpTreatment(deviceId, portNumbers);
185 + rules.addAll(result.getRight());
186 + treatment = DefaultTrafficTreatment.builder().extension(result.getLeft(), deviceId).build();
187 + }
188 +
189 + FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
190 + .withSelector(
191 + DefaultTrafficSelector.builder()
192 + .matchEthType(IPV4.ethType().toShort())
193 + .matchEthDst(dstHost.mac())
194 + .build())
195 + .withTreatment(treatment)
196 + .build();
197 +
198 + rules.add(rule);
199 + }
200 +
201 + return rules;
202 + }
203 +
204 + private Pair<ExtensionTreatment, List<FlowRule>> provisionEcmpTreatment(DeviceId deviceId,
205 + Set<PortNumber> fabricPorts)
206 + throws FlowRuleGeneratorException {
207 +
208 + // Install ECMP group table entries that map from hash values to actual fabric ports...
209 + int groupId = groupIdOf(deviceId, fabricPorts);
210 + int groupSize = fabricPorts.size();
211 + Iterator<PortNumber> portIterator = fabricPorts.iterator();
212 + List<FlowRule> rules = Lists.newArrayList();
213 + for (short i = 0; i < groupSize; i++) {
214 + ExtensionSelector extSelector = new EcmpGroupTableSelectorBuilder()
215 + .withGroupId(groupId)
216 + .withSelector(i)
217 + .build();
218 + FlowRule rule = flowRuleBuilder(deviceId, ECMP_GROUP_TABLE)
219 + .withSelector(
220 + DefaultTrafficSelector.builder()
221 + .extension(extSelector, deviceId)
222 + .build())
223 + .withTreatment(
224 + DefaultTrafficTreatment.builder()
225 + .setOutput(portIterator.next())
226 + .build())
227 + .build();
228 + rules.add(rule);
229 + }
230 +
231 + ExtensionTreatment extTreatment = new EcmpGroupTreatmentBuilder()
232 + .withGroupId(groupId)
233 + .withGroupSize(groupSize)
234 + .build();
235 +
236 + return Pair.of(extTreatment, rules);
237 + }
238 +
239 + private static Bmv2Configuration loadConfiguration() {
240 + try {
241 + JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
242 + EcmpFabricApp.class.getResourceAsStream(JSON_CONFIG_PATH)))).asObject();
243 + return Bmv2DefaultConfiguration.parse(json);
244 + } catch (IOException e) {
245 + throw new RuntimeException("Unable to load configuration", e);
246 + }
247 + }
248 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.ecmp;
18 +
19 +import com.google.common.collect.ImmutableMap;
20 +import org.onlab.util.ImmutableByteSequence;
21 +import org.onosproject.bmv2.api.context.Bmv2HeaderTypeModel;
22 +import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
23 +import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
24 +import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
25 +import org.onosproject.net.flow.criteria.ExtensionSelector;
26 +
27 +import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
28 +import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
29 +import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
30 +
31 +/**
32 + * Builder of ECMP group table extension selector.
33 + */
34 +public class EcmpGroupTableSelectorBuilder {
35 +
36 + private int groupId;
37 + private int selector;
38 +
39 + /**
40 + * Sets the ECMP group ID.
41 + *
42 + * @param groupId an integer value
43 + * @return this
44 + */
45 + public EcmpGroupTableSelectorBuilder withGroupId(int groupId) {
46 + this.groupId = groupId;
47 + return this;
48 + }
49 +
50 + /**
51 + * Sets the ECMP selector.
52 + *
53 + * @param selector an integer value
54 + * @return this
55 + */
56 + public EcmpGroupTableSelectorBuilder withSelector(int selector) {
57 + this.selector = selector;
58 + return this;
59 + }
60 +
61 + /**
62 + * Returns a new extension selector.
63 + *
64 + * @return an extension selector
65 + */
66 + public ExtensionSelector build() {
67 + Bmv2HeaderTypeModel headerTypeModel = ECMP_CONTEXT.configuration().headerType(ECMP_METADATA_T);
68 + int groupIdBitWidth = headerTypeModel.field(GROUP_ID).bitWidth();
69 + int selectorBitWidth = headerTypeModel.field(SELECTOR).bitWidth();
70 +
71 + try {
72 + ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId),
73 + groupIdBitWidth);
74 + ImmutableByteSequence selectorBs = fitByteSequence(ImmutableByteSequence.copyFrom(selector),
75 + selectorBitWidth);
76 +
77 + Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
78 + Bmv2ExactMatchParam hashMatch = new Bmv2ExactMatchParam(selectorBs);
79 +
80 + return new Bmv2ExtensionSelector(ImmutableMap.of(
81 + ECMP_METADATA + "." + GROUP_ID, groupIdMatch,
82 + ECMP_METADATA + "." + SELECTOR, hashMatch));
83 +
84 + } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
85 + throw new RuntimeException(e);
86 + }
87 + }
88 +}
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.ecmp;
18 +
19 +import com.google.common.collect.Maps;
20 +import org.onlab.util.ImmutableByteSequence;
21 +import org.onosproject.bmv2.api.context.Bmv2ActionModel;
22 +import org.onosproject.bmv2.api.runtime.Bmv2Action;
23 +import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
24 +import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
25 +import org.onosproject.net.DeviceId;
26 +import org.onosproject.net.PortNumber;
27 +import org.onosproject.net.flow.instructions.ExtensionTreatment;
28 +
29 +import java.util.Map;
30 +import java.util.Set;
31 +
32 +import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
33 +import static org.onosproject.bmv2.demo.app.ecmp.EcmpFabricApp.ECMP_CONTEXT;
34 +import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
35 +
36 +/**
37 + * Builder of ECMP extension treatments.
38 + */
39 +public class EcmpGroupTreatmentBuilder {
40 +
41 + private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
42 + private int groupId;
43 + private int groupSize;
44 +
45 + /**
46 + * Sets the group ID.
47 + *
48 + * @param groupId an integer value
49 + * @return this
50 + */
51 + public EcmpGroupTreatmentBuilder withGroupId(int groupId) {
52 + this.groupId = groupId;
53 + return this;
54 + }
55 +
56 + /**
57 + * Sets the group size.
58 + *
59 + * @param groupSize an integer value
60 + * @return this
61 + */
62 + public EcmpGroupTreatmentBuilder withGroupSize(int groupSize) {
63 + this.groupSize = groupSize;
64 + return this;
65 + }
66 +
67 + /**
68 + * Returns a new extension treatment.
69 + *
70 + * @return an extension treatment
71 + */
72 + public ExtensionTreatment build() {
73 + Bmv2ActionModel actionModel = ECMP_CONTEXT.configuration().action(ECMP_GROUP);
74 + int groupIdBitWidth = actionModel.runtimeData(GROUP_ID).bitWidth();
75 + int groupSizeBitWidth = actionModel.runtimeData(GROUP_SIZE).bitWidth();
76 +
77 + try {
78 + ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
79 + ImmutableByteSequence groupSizeBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupSize),
80 + groupSizeBitWidth);
81 +
82 + return new Bmv2ExtensionTreatment(Bmv2Action.builder()
83 + .withName(ECMP_GROUP)
84 + .addParameter(groupIdBs)
85 + .addParameter(groupSizeBs)
86 + .build());
87 +
88 + } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
89 + throw new RuntimeException(e);
90 + }
91 + }
92 +
93 + /**
94 + * Returns a group ID for the given device and set of ports.
95 + *
96 + * @param deviceId a device ID
97 + * @param ports a set of ports
98 + * @return an integer value
99 + */
100 + public static int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
101 + DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
102 + // Counts the number of unique portNumber sets for each deviceId.
103 + // Each distinct set of portNumbers will have a unique ID.
104 + return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
105 + (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
106 + }
107 +}
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.ecmp;
18 +
19 +import com.google.common.collect.ImmutableBiMap;
20 +import org.onlab.util.ImmutableByteSequence;
21 +import org.onosproject.bmv2.api.context.Bmv2Configuration;
22 +import org.onosproject.bmv2.api.context.Bmv2Interpreter;
23 +import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
24 +import org.onosproject.bmv2.api.runtime.Bmv2Action;
25 +import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
26 +import org.onosproject.net.PortNumber;
27 +import org.onosproject.net.flow.TrafficTreatment;
28 +import org.onosproject.net.flow.criteria.Criterion;
29 +import org.onosproject.net.flow.instructions.Instruction;
30 +
31 +import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
32 +import static org.onosproject.net.PortNumber.CONTROLLER;
33 +import static org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
34 +
35 +/**
36 + * Implementation of a BMv2 interpreter for the ecmp.json configuration.
37 + */
38 +public class EcmpInterpreter implements Bmv2Interpreter {
39 +
40 + protected static final String ECMP_METADATA_T = "ecmp_metadata_t";
41 + protected static final String ECMP_METADATA = "ecmp_metadata";
42 + protected static final String SELECTOR = "selector";
43 + protected static final String GROUP_ID = "groupId";
44 + protected static final String GROUP_SIZE = "groupSize";
45 + protected static final String ECMP_GROUP = "ecmp_group";
46 + protected static final String ECMP_GROUP_TABLE = "ecmp_group_table";
47 + protected static final String TABLE0 = "table0";
48 + protected static final String SEND_TO_CPU = "send_to_cpu";
49 + protected static final String DROP = "_drop";
50 + protected static final String SET_EGRESS_PORT = "set_egress_port";
51 + protected static final String PORT = "port";
52 +
53 + private static final ImmutableBiMap<Criterion.Type, String> CRITERION_TYPE_MAP = ImmutableBiMap.of(
54 + Criterion.Type.IN_PORT, "standard_metadata.ingress_port",
55 + Criterion.Type.ETH_DST, "ethernet.dstAddr",
56 + Criterion.Type.ETH_SRC, "ethernet.srcAddr",
57 + Criterion.Type.ETH_TYPE, "ethernet.etherType");
58 +
59 + private static final ImmutableBiMap<Integer, String> TABLE_ID_MAP = ImmutableBiMap.of(
60 + 0, TABLE0,
61 + 1, ECMP_GROUP_TABLE);
62 +
63 + @Override
64 + public ImmutableBiMap<Integer, String> tableIdMap() {
65 + return TABLE_ID_MAP;
66 + }
67 +
68 + @Override
69 + public ImmutableBiMap<Criterion.Type, String> criterionTypeMap() {
70 + return CRITERION_TYPE_MAP;
71 + }
72 +
73 + @Override
74 + public Bmv2Action mapTreatment(TrafficTreatment treatment, Bmv2Configuration configuration)
75 + throws Bmv2InterpreterException {
76 +
77 + if (treatment.allInstructions().size() == 0) {
78 + // No instructions means drop for us.
79 + return actionWithName(DROP);
80 + } else if (treatment.allInstructions().size() > 1) {
81 + // Otherwise, we understand treatments with only 1 instruction.
82 + throw new Bmv2InterpreterException("Treatment has multiple instructions");
83 + }
84 +
85 + Instruction instruction = treatment.allInstructions().get(0);
86 +
87 + switch (instruction.type()) {
88 + case OUTPUT:
89 + OutputInstruction outInstruction = (OutputInstruction) instruction;
90 + PortNumber port = outInstruction.port();
91 + if (!port.isLogical()) {
92 + return buildEgressAction(port, configuration);
93 + } else if (port.equals(CONTROLLER)) {
94 + return actionWithName(SEND_TO_CPU);
95 + } else {
96 + throw new Bmv2InterpreterException("Egress on logical port not supported: " + port);
97 + }
98 + case NOACTION:
99 + return actionWithName(DROP);
100 + default:
101 + throw new Bmv2InterpreterException("Instruction type not supported: " + instruction.type().name());
102 + }
103 + }
104 +
105 + private static Bmv2Action buildEgressAction(PortNumber port, Bmv2Configuration configuration)
106 + throws Bmv2InterpreterException {
107 +
108 + int portBitWidth = configuration.action(SET_EGRESS_PORT).runtimeData(PORT).bitWidth();
109 +
110 + try {
111 + ImmutableByteSequence portBs = fitByteSequence(ImmutableByteSequence.copyFrom(port.toLong()), portBitWidth);
112 + return Bmv2Action.builder()
113 + .withName(SET_EGRESS_PORT)
114 + .addParameter(portBs)
115 + .build();
116 + } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
117 + throw new Bmv2InterpreterException(e.getMessage());
118 + }
119 + }
120 +
121 + private static Bmv2Action actionWithName(String name) {
122 + return Bmv2Action.builder().withName(name).build();
123 + }
124 +}
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/**
18 + * BMv2 demo app for the ECMP configuration.
19 + */
20 +package org.onosproject.bmv2.demo.app.ecmp;
...\ No newline at end of file ...\ No newline at end of file
1 +/Users/carmelo/workspace/onos-p4-dev/p4src/build/ecmp.json
...\ No newline at end of file ...\ No newline at end of file
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2014-present Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +<project xmlns="http://maven.apache.org/POM/4.0.0"
18 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
19 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
20 + <modelVersion>4.0.0</modelVersion>
21 +
22 + <parent>
23 + <groupId>org.onosproject</groupId>
24 + <artifactId>onos-apps</artifactId>
25 + <version>1.7.0-SNAPSHOT</version>
26 + <relativePath>../pom.xml</relativePath>
27 + </parent>
28 +
29 + <artifactId>onos-app-bmv2-demo</artifactId>
30 +
31 + <packaging>pom</packaging>
32 +
33 + <modules>
34 + <module>common</module>
35 + <module>ecmp</module>
36 + <module>wcmp</module>
37 + </modules>
38 +
39 +</project>
1 +<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2 +
3 +<!--
4 + ~ Copyright 2016-present Open Networking Laboratory
5 + ~
6 + ~ Licensed under the Apache License, Version 2.0 (the "License");
7 + ~ you may not use this file except in compliance with the License.
8 + ~ You may obtain a copy of the License at
9 + ~
10 + ~ http://www.apache.org/licenses/LICENSE-2.0
11 + ~
12 + ~ Unless required by applicable law or agreed to in writing, software
13 + ~ distributed under the License is distributed on an "AS IS" BASIS,
14 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 + ~ See the License for the specific language governing permissions and
16 + ~ limitations under the License.
17 + -->
18 +
19 +<features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}">
20 + <feature name="${project.artifactId}" version="${project.version}"
21 + description="${project.description}">
22 + <bundle>mvn:${project.groupId}/${project.artifactId}/${project.version}</bundle>
23 + <bundle>mvn:${project.groupId}/onos-app-bmv2-demo-common/${project.version}</bundle>
24 + </feature>
25 +</features>
1 +<?xml version="1.0" encoding="UTF-8"?>
2 +<!--
3 + ~ Copyright 2016-present Open Networking Laboratory
4 + ~
5 + ~ Licensed under the Apache License, Version 2.0 (the "License");
6 + ~ you may not use this file except in compliance with the License.
7 + ~ You may obtain a copy of the License at
8 + ~
9 + ~ http://www.apache.org/licenses/LICENSE-2.0
10 + ~
11 + ~ Unless required by applicable law or agreed to in writing, software
12 + ~ distributed under the License is distributed on an "AS IS" BASIS,
13 + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 + ~ See the License for the specific language governing permissions and
15 + ~ limitations under the License.
16 + -->
17 +
18 +<project xmlns="http://maven.apache.org/POM/4.0.0"
19 + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
21 + <modelVersion>4.0.0</modelVersion>
22 +
23 + <parent>
24 + <artifactId>onos-app-bmv2-demo</artifactId>
25 + <groupId>org.onosproject</groupId>
26 + <version>1.7.0-SNAPSHOT</version>
27 + <relativePath>../pom.xml</relativePath>
28 + </parent>
29 +
30 + <artifactId>onos-app-bmv2-demo-wcmp</artifactId>
31 +
32 + <packaging>bundle</packaging>
33 +
34 + <properties>
35 + <onos.app.name>org.onosproject.bmv2-wcmp-fabric</onos.app.name>
36 + <onos.app.title>P4/BMv2 Demo Fabric App v2 (WCMP)</onos.app.title>
37 + <onos.app.category>Traffic Steering</onos.app.category>
38 + <onos.app.url>http://onosproject.org</onos.app.url>
39 + <onos.app.readme>P4/BMv2 demo application with WCMP support for a 2-stage clos fabric topology</onos.app.readme>
40 + </properties>
41 +
42 + <dependencies>
43 + <dependency>
44 + <groupId>org.onosproject</groupId>
45 + <artifactId>onos-app-bmv2-demo-common</artifactId>
46 + <version>${project.version}</version>
47 + </dependency>
48 + </dependencies>
49 +
50 +</project>
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.wcmp;
18 +
19 +import com.eclipsesource.json.Json;
20 +import com.eclipsesource.json.JsonObject;
21 +import com.google.common.collect.Lists;
22 +import com.google.common.collect.Maps;
23 +import com.google.common.collect.Sets;
24 +import org.apache.commons.lang3.tuple.Pair;
25 +import org.apache.felix.scr.annotations.Component;
26 +import org.apache.felix.scr.annotations.Reference;
27 +import org.apache.felix.scr.annotations.ReferenceCardinality;
28 +import org.onosproject.bmv2.api.context.Bmv2Configuration;
29 +import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
30 +import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
31 +import org.onosproject.bmv2.api.runtime.Bmv2Action;
32 +import org.onosproject.bmv2.api.runtime.Bmv2DeviceAgent;
33 +import org.onosproject.bmv2.api.runtime.Bmv2RuntimeException;
34 +import org.onosproject.bmv2.api.service.Bmv2Controller;
35 +import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
36 +import org.onosproject.net.DeviceId;
37 +import org.onosproject.net.Host;
38 +import org.onosproject.net.Path;
39 +import org.onosproject.net.PortNumber;
40 +import org.onosproject.net.flow.DefaultTrafficSelector;
41 +import org.onosproject.net.flow.DefaultTrafficTreatment;
42 +import org.onosproject.net.flow.FlowRule;
43 +import org.onosproject.net.flow.TrafficTreatment;
44 +import org.onosproject.net.flow.criteria.ExtensionSelector;
45 +import org.onosproject.net.flow.instructions.ExtensionTreatment;
46 +import org.onosproject.net.topology.DefaultTopologyVertex;
47 +import org.onosproject.net.topology.Topology;
48 +import org.onosproject.net.topology.TopologyGraph;
49 +
50 +import java.io.BufferedReader;
51 +import java.io.IOException;
52 +import java.io.InputStreamReader;
53 +import java.util.Collection;
54 +import java.util.List;
55 +import java.util.Map;
56 +import java.util.Set;
57 +import java.util.stream.Collectors;
58 +
59 +import static java.util.stream.Collectors.toSet;
60 +import static org.onlab.packet.EthType.EtherType.IPV4;
61 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.groupIdOf;
62 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpGroupTreatmentBuilder.toPrefixLengths;
63 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.TABLE0;
64 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.WCMP_GROUP_TABLE;
65 +
66 +/**
67 + * Implementation of an upgradable fabric app for the WCMP configuration.
68 + */
69 +@Component(immediate = true)
70 +public class WcmpFabricApp extends AbstractUpgradableFabricApp {
71 +
72 + private static final String APP_NAME = "org.onosproject.bmv2-wcmp-fabric";
73 + private static final String MODEL_NAME = "WCMP";
74 + private static final String JSON_CONFIG_PATH = "/wcmp.json";
75 +
76 + private static final double MULTI_PORT_WEIGHT_COEFFICIENT = 0.85;
77 +
78 + private static final Bmv2Configuration WCMP_CONFIGURATION = loadConfiguration();
79 + private static final WcmpInterpreter WCMP_INTERPRETER = new WcmpInterpreter();
80 + protected static final Bmv2DeviceContext WCMP_CONTEXT = new Bmv2DeviceContext(WCMP_CONFIGURATION, WCMP_INTERPRETER);
81 +
82 + @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
83 + private Bmv2Controller bmv2Controller;
84 +
85 + /**
86 + * TODO.
87 + */
88 + public WcmpFabricApp() {
89 + super(APP_NAME, MODEL_NAME, WCMP_CONTEXT);
90 + }
91 +
92 +
93 + @Override
94 + public boolean initDevice(DeviceId deviceId) {
95 + try {
96 + Bmv2DeviceAgent agent = bmv2Controller.getAgent(deviceId);
97 + for (Map.Entry<String, Bmv2Action> entry : WCMP_INTERPRETER.defaultActionsMap().entrySet()) {
98 + agent.setTableDefaultAction(entry.getKey(), entry.getValue());
99 + }
100 + return true;
101 + } catch (Bmv2RuntimeException e) {
102 + log.error("Unable to init device {}: {}", deviceId, e.explain());
103 + return false;
104 + }
105 + }
106 +
107 + @Override
108 + public List<FlowRule> generateLeafRules(DeviceId deviceId, Host srcHost, Collection<Host> dstHosts,
109 + Collection<DeviceId> availableSpines, Topology topo)
110 + throws FlowRuleGeneratorException {
111 +
112 + Set<PortNumber> hostPortNumbers = Sets.newHashSet();
113 + Set<PortNumber> fabricPortNumbers = Sets.newHashSet();
114 + deviceService.getPorts(deviceId)
115 + .forEach(p -> (isFabricPort(p, topo) ? fabricPortNumbers : hostPortNumbers).add(p.number()));
116 +
117 + if (hostPortNumbers.size() != 1 || fabricPortNumbers.size() == 0) {
118 + log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
119 + hostPortNumbers.size(), fabricPortNumbers.size());
120 + throw new FlowRuleGeneratorException();
121 + }
122 + PortNumber hostPort = hostPortNumbers.iterator().next();
123 +
124 + TopologyGraph graph = topologyService.getGraph(topo);
125 + // Map key: spine device id, value: leaf switch ports which connect to spine in the key.
126 + Map<DeviceId, Set<PortNumber>> spineToPortsMap = Maps.newHashMap();
127 + graph.getEdgesFrom(new DefaultTopologyVertex(deviceId)).forEach(edge -> {
128 + spineToPortsMap.putIfAbsent(edge.dst().deviceId(), Sets.newHashSet());
129 + spineToPortsMap.get(edge.dst().deviceId()).add(edge.link().src().port());
130 + });
131 +
132 + double baseWeight = 1d / spineToPortsMap.size();
133 +
134 + int numSinglePorts = (int) spineToPortsMap.values().stream().filter(s -> s.size() == 1).count();
135 + int numMultiPorts = spineToPortsMap.size() - numSinglePorts;
136 +
137 + // Reduce weight portion assigned to multi-ports to mitigate flow assignment imbalance (measured empirically).
138 + double multiPortBaseWeight = baseWeight * MULTI_PORT_WEIGHT_COEFFICIENT;
139 + double excess = (baseWeight - multiPortBaseWeight) * numMultiPorts;
140 + double singlePortBaseWeight = baseWeight + (excess / numSinglePorts);
141 +
142 + Map<PortNumber, Double> weighedPortNumbers = Maps.newHashMap();
143 + spineToPortsMap.forEach((did, portSet) -> {
144 + double base = (portSet.size() == 1) ? singlePortBaseWeight : multiPortBaseWeight;
145 + double weight = base / portSet.size();
146 + portSet.forEach(portNumber -> weighedPortNumbers.put(portNumber, weight));
147 + });
148 +
149 + List<FlowRule> rules = Lists.newArrayList();
150 +
151 +
152 + Pair<ExtensionTreatment, List<FlowRule>> result = provisionWcmpTreatment(deviceId, weighedPortNumbers);
153 + ExtensionTreatment wcmpTreatment = result.getLeft();
154 + rules.addAll(result.getRight());
155 +
156 + // From src host to dst hosts, WCMP to all fabric ports.
157 + for (Host dstHost : dstHosts) {
158 + FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
159 + .withSelector(
160 + DefaultTrafficSelector.builder()
161 + .matchInPort(hostPort)
162 + .matchEthType(IPV4.ethType().toShort())
163 + .matchEthSrc(srcHost.mac())
164 + .matchEthDst(dstHost.mac())
165 + .build())
166 + .withTreatment(
167 + DefaultTrafficTreatment.builder()
168 + .extension(wcmpTreatment, deviceId)
169 + .build())
170 + .build();
171 + rules.add(rule);
172 + }
173 +
174 + // From fabric ports to src host.
175 + for (PortNumber port : fabricPortNumbers) {
176 + FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
177 + .withSelector(
178 + DefaultTrafficSelector.builder()
179 + .matchInPort(port)
180 + .matchEthType(IPV4.ethType().toShort())
181 + .matchEthDst(srcHost.mac())
182 + .build())
183 + .withTreatment(
184 + DefaultTrafficTreatment.builder()
185 + .setOutput(hostPort)
186 + .build())
187 + .build();
188 + rules.add(rule);
189 + }
190 +
191 + return rules;
192 + }
193 +
194 + @Override
195 + public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
196 + throws FlowRuleGeneratorException {
197 +
198 + List<FlowRule> rules = Lists.newArrayList();
199 +
200 + for (Host dstHost : dstHosts) {
201 +
202 + Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
203 +
204 + if (paths.size() == 0) {
205 + log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
206 + throw new FlowRuleGeneratorException();
207 + }
208 +
209 + TrafficTreatment treatment;
210 +
211 + if (paths.size() == 1) {
212 + // Only one path.
213 + PortNumber port = paths.iterator().next().src().port();
214 + treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
215 + } else {
216 + // Multiple paths, do WCMP.
217 + Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
218 + double weight = 1d / portNumbers.size();
219 + // Same weight for all ports.
220 + Map<PortNumber, Double> weightedPortNumbers = portNumbers.stream()
221 + .collect(Collectors.toMap(p -> p, p -> weight));
222 + Pair<ExtensionTreatment, List<FlowRule>> result = provisionWcmpTreatment(deviceId, weightedPortNumbers);
223 + rules.addAll(result.getRight());
224 + treatment = DefaultTrafficTreatment.builder().extension(result.getLeft(), deviceId).build();
225 + }
226 +
227 + FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
228 + .withSelector(
229 + DefaultTrafficSelector.builder()
230 + .matchEthType(IPV4.ethType().toShort())
231 + .matchEthDst(dstHost.mac())
232 + .build())
233 + .withTreatment(treatment)
234 + .build();
235 +
236 + rules.add(rule);
237 + }
238 +
239 + return rules;
240 + }
241 +
242 + private Pair<ExtensionTreatment, List<FlowRule>> provisionWcmpTreatment(DeviceId deviceId,
243 + Map<PortNumber, Double> weightedFabricPorts)
244 + throws FlowRuleGeneratorException {
245 +
246 + // Install WCMP group table entries that map from hash values to fabric ports.
247 +
248 + int groupId = groupIdOf(deviceId, weightedFabricPorts);
249 + List<PortNumber> portNumbers = Lists.newArrayList();
250 + List<Double> weights = Lists.newArrayList();
251 + weightedFabricPorts.forEach((p, w) -> {
252 + portNumbers.add(p);
253 + weights.add(w);
254 + });
255 + List<Integer> prefixLengths;
256 + try {
257 + prefixLengths = toPrefixLengths(weights);
258 + } catch (WcmpGroupTreatmentBuilder.WcmpGroupException e) {
259 + throw new FlowRuleGeneratorException(e);
260 + }
261 +
262 + List<FlowRule> rules = Lists.newArrayList();
263 + for (int i = 0; i < portNumbers.size(); i++) {
264 + ExtensionSelector extSelector = new WcmpGroupTableSelectorBuilder()
265 + .withGroupId(groupId)
266 + .withPrefixLength(prefixLengths.get(i))
267 + .build();
268 + FlowRule rule = flowRuleBuilder(deviceId, WCMP_GROUP_TABLE)
269 + .withSelector(DefaultTrafficSelector.builder()
270 + .extension(extSelector, deviceId)
271 + .build())
272 + .withTreatment(
273 + DefaultTrafficTreatment.builder()
274 + .setOutput(portNumbers.get(i))
275 + .build())
276 + .build();
277 + rules.add(rule);
278 + }
279 +
280 + ExtensionTreatment extTreatment = new WcmpGroupTreatmentBuilder().withGroupId(groupId).build();
281 +
282 + return Pair.of(extTreatment, rules);
283 + }
284 +
285 + private static Bmv2Configuration loadConfiguration() {
286 + try {
287 + JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
288 + WcmpFabricApp.class.getResourceAsStream(JSON_CONFIG_PATH)))).asObject();
289 + return Bmv2DefaultConfiguration.parse(json);
290 + } catch (IOException e) {
291 + throw new RuntimeException("Unable to load configuration", e);
292 + }
293 + }
294 +}
...\ No newline at end of file ...\ No newline at end of file
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.wcmp;
18 +
19 +import com.google.common.collect.ImmutableMap;
20 +import org.onlab.util.ImmutableByteSequence;
21 +import org.onosproject.bmv2.api.runtime.Bmv2ExactMatchParam;
22 +import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
23 +import org.onosproject.bmv2.api.runtime.Bmv2LpmMatchParam;
24 +import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
25 +import org.onosproject.net.flow.criteria.ExtensionSelector;
26 +
27 +import static com.google.common.base.Preconditions.checkArgument;
28 +import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
29 +import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.roundToBytes;
30 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
31 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
32 +
33 +/**
34 + * Builder of WCMP group table extension selector.
35 + */
36 +public final class WcmpGroupTableSelectorBuilder {
37 +
38 + private int groupId;
39 + private int prefixLength;
40 +
41 + /**
42 + * Sets the WCMP group ID.
43 + *
44 + * @param groupId an integer value
45 + * @return this
46 + */
47 + public WcmpGroupTableSelectorBuilder withGroupId(int groupId) {
48 + this.groupId = groupId;
49 + return this;
50 + }
51 +
52 + /**
53 + * Sets the WCMP selector's prefix length.
54 + *
55 + * @param prefixLength an integer value
56 + * @return this
57 + */
58 + public WcmpGroupTableSelectorBuilder withPrefixLength(int prefixLength) {
59 + this.prefixLength = prefixLength;
60 + return this;
61 + }
62 +
63 + /**
64 + * Returns a new extension selector.
65 + *
66 + * @return an extension selector
67 + */
68 + public ExtensionSelector build() {
69 +
70 + final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
71 + final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
72 + final ImmutableByteSequence ones = ImmutableByteSequence.ofOnes(roundToBytes(selectorBitWidth));
73 +
74 + checkArgument(prefixLength >= 1 && prefixLength <= selectorBitWidth,
75 + "prefix length must be between 1 and " + selectorBitWidth);
76 + try {
77 + ImmutableByteSequence groupIdBs = fitByteSequence(ImmutableByteSequence.copyFrom(groupId), groupIdBitWidth);
78 + Bmv2ExactMatchParam groupIdMatch = new Bmv2ExactMatchParam(groupIdBs);
79 + Bmv2LpmMatchParam selectorMatch = new Bmv2LpmMatchParam(ones, prefixLength);
80 +
81 + return new Bmv2ExtensionSelector(ImmutableMap.of(
82 + WCMP_META + "." + GROUP_ID, groupIdMatch,
83 + WCMP_META + "." + SELECTOR, selectorMatch));
84 +
85 + } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
86 + throw new RuntimeException(e);
87 + }
88 + }
89 +}
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.wcmp;
18 +
19 +import com.google.common.collect.ImmutableList;
20 +import com.google.common.collect.Lists;
21 +import com.google.common.collect.Maps;
22 +import org.onlab.util.ImmutableByteSequence;
23 +import org.onosproject.bmv2.api.runtime.Bmv2Action;
24 +import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
25 +import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
26 +import org.onosproject.net.DeviceId;
27 +import org.onosproject.net.PortNumber;
28 +import org.onosproject.net.flow.instructions.ExtensionTreatment;
29 +
30 +import java.util.Collections;
31 +import java.util.List;
32 +import java.util.Map;
33 +
34 +import static com.google.common.base.Preconditions.checkArgument;
35 +import static java.util.stream.Collectors.toList;
36 +import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
37 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpFabricApp.WCMP_CONTEXT;
38 +import static org.onosproject.bmv2.demo.app.wcmp.WcmpInterpreter.*;
39 +
40 +/**
41 + * Builder of WCMP extension treatment.
42 + */
43 +public final class WcmpGroupTreatmentBuilder {
44 +
45 + private static final double MAX_ERROR = 0.0001;
46 +
47 + private static final Map<DeviceId, Map<Map<PortNumber, Double>, Integer>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
48 +
49 + private int groupId;
50 +
51 + /**
52 + * Sets the WCMP group ID.
53 + *
54 + * @param groupId an integer value
55 + * @return this
56 + */
57 + public WcmpGroupTreatmentBuilder withGroupId(int groupId) {
58 + this.groupId = groupId;
59 + return this;
60 + }
61 +
62 + /**
63 + * Returns a new extension treatment.
64 + *
65 + * @return an extension treatment
66 + */
67 + public ExtensionTreatment build() {
68 + checkArgument(groupId >= 0, "group id must be a non-zero positive integer");
69 + ImmutableByteSequence groupIdBs = ImmutableByteSequence.copyFrom(groupId);
70 + final int groupIdBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(GROUP_ID).bitWidth();
71 + try {
72 + groupIdBs = fitByteSequence(groupIdBs, groupIdBitWidth);
73 + return new Bmv2ExtensionTreatment(
74 + Bmv2Action.builder()
75 + .withName(WCMP_GROUP)
76 + .addParameter(groupIdBs)
77 + .build());
78 + } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
79 + throw new RuntimeException(e);
80 + }
81 + }
82 +
83 + public static int groupIdOf(DeviceId did, Map<PortNumber, Double> weightedPorts) {
84 + DEVICE_GROUP_ID_MAP.putIfAbsent(did, Maps.newHashMap());
85 + // Counts the number of unique portNumber sets for each device ID.
86 + // Each distinct set of portNumbers will have a unique ID.
87 + return DEVICE_GROUP_ID_MAP.get(did).computeIfAbsent(weightedPorts,
88 + (pp) -> DEVICE_GROUP_ID_MAP.get(did).size() + 1);
89 + }
90 +
91 + public static List<Integer> toPrefixLengths(List<Double> weigths) throws WcmpGroupException {
92 +
93 + double weightSum = weigths.stream()
94 + .mapToDouble(Double::doubleValue)
95 + .map(WcmpGroupTreatmentBuilder::roundDouble)
96 + .sum();
97 +
98 + if (Math.abs(weightSum - 1) > MAX_ERROR) {
99 + throw new WcmpGroupException("weights sum is expected to be 1, found was " + weightSum);
100 + }
101 +
102 + final int selectorBitWidth = WCMP_CONTEXT.configuration().headerType(WCMP_META_T).field(SELECTOR).bitWidth();
103 + final int availableBits = selectorBitWidth - 1;
104 +
105 + List<Long> prefixDiffs = weigths.stream().map(w -> Math.round(w * availableBits)).collect(toList());
106 +
107 + final long bitSum = prefixDiffs.stream().mapToLong(Long::longValue).sum();
108 + final long error = availableBits - bitSum;
109 +
110 + if (error != 0) {
111 + // Lazy intuition here is that the error can be absorbed by the longest prefixDiff with the minor impact.
112 + Long maxDiff = Collections.max(prefixDiffs);
113 + int idx = prefixDiffs.indexOf(maxDiff);
114 + prefixDiffs.remove(idx);
115 + prefixDiffs.add(idx, maxDiff + error);
116 + }
117 + List<Integer> prefixLengths = Lists.newArrayList();
118 +
119 + int prefix = 1;
120 + for (Long p : prefixDiffs) {
121 + prefixLengths.add(prefix);
122 + prefix += p;
123 + }
124 + return ImmutableList.copyOf(prefixLengths);
125 + }
126 +
127 + private static double roundDouble(double n) {
128 + // 5 digits precision.
129 + return (double) Math.round(n * 100000d) / 100000d;
130 + }
131 +
132 + public static class WcmpGroupException extends Exception {
133 + public WcmpGroupException(String s) {
134 + }
135 + }
136 +}
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +package org.onosproject.bmv2.demo.app.wcmp;
18 +
19 +import com.google.common.collect.ImmutableBiMap;
20 +import org.onlab.util.ImmutableByteSequence;
21 +import org.onosproject.bmv2.api.context.Bmv2Configuration;
22 +import org.onosproject.bmv2.api.context.Bmv2Interpreter;
23 +import org.onosproject.bmv2.api.context.Bmv2InterpreterException;
24 +import org.onosproject.bmv2.api.runtime.Bmv2Action;
25 +import org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils;
26 +import org.onosproject.net.PortNumber;
27 +import org.onosproject.net.flow.TrafficTreatment;
28 +import org.onosproject.net.flow.criteria.Criterion;
29 +import org.onosproject.net.flow.instructions.Instruction;
30 +import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
31 +
32 +import java.util.Map;
33 +
34 +import static org.onosproject.bmv2.api.utils.Bmv2TranslatorUtils.fitByteSequence;
35 +import static org.onosproject.net.PortNumber.CONTROLLER;
36 +
37 +/**
38 + * Implementation of a BMv2 interpreter for the wcmp.json configuration.
39 + */
40 +public final class WcmpInterpreter implements Bmv2Interpreter {
41 +
42 + protected static final String WCMP_META_T = "wcmp_meta_t";
43 + protected static final String WCMP_META = "wcmp_meta";
44 + protected static final String SELECTOR = "selector";
45 + protected static final String GROUP_ID = "groupId";
46 + protected static final String WCMP_GROUP = "wcmp_group";
47 + protected static final String WCMP_SET_SELECTOR = "wcmp_set_selector";
48 + protected static final String WCMP_SET_SELECTOR_TABLE = "wcmp_set_selector_table";
49 + protected static final String WCMP_GROUP_TABLE = "wcmp_group_table";
50 + protected static final String TABLE0 = "table0";
51 + protected static final String SEND_TO_CPU = "send_to_cpu";
52 + protected static final String DROP = "_drop";
53 + protected static final String SET_EGRESS_PORT = "set_egress_port";
54 + protected static final String PORT = "port";
55 +
56 + private static final ImmutableBiMap<Criterion.Type, String> CRITERION_TYPE_MAP = ImmutableBiMap.of(
57 + Criterion.Type.IN_PORT, "standard_metadata.ingress_port",
58 + Criterion.Type.ETH_DST, "ethernet.dstAddr",
59 + Criterion.Type.ETH_SRC, "ethernet.srcAddr",
60 + Criterion.Type.ETH_TYPE, "ethernet.etherType");
61 +
62 + private static final ImmutableBiMap<Integer, String> TABLE_ID_MAP = ImmutableBiMap.of(
63 + 0, TABLE0,
64 + 1, WCMP_GROUP_TABLE);
65 +
66 + private static final Map<String, Bmv2Action> DEFAULT_ACTIONS_MAP = ImmutableBiMap.of(
67 + WCMP_SET_SELECTOR_TABLE, actionWithName(WCMP_SET_SELECTOR));
68 +
69 + @Override
70 + public ImmutableBiMap<Integer, String> tableIdMap() {
71 + return TABLE_ID_MAP;
72 + }
73 +
74 + @Override
75 + public ImmutableBiMap<Criterion.Type, String> criterionTypeMap() {
76 + return CRITERION_TYPE_MAP;
77 + }
78 +
79 + public Map<String, Bmv2Action> defaultActionsMap() {
80 + return DEFAULT_ACTIONS_MAP;
81 + }
82 +
83 + @Override
84 + public Bmv2Action mapTreatment(TrafficTreatment treatment, Bmv2Configuration configuration)
85 + throws Bmv2InterpreterException {
86 +
87 + if (treatment.allInstructions().size() == 0) {
88 + // No instructions means drop for us.
89 + return actionWithName(DROP);
90 + } else if (treatment.allInstructions().size() > 1) {
91 + // Otherwise, we understand treatments with only 1 instruction.
92 + throw new Bmv2InterpreterException("Treatment has multiple instructions");
93 + }
94 +
95 + Instruction instruction = treatment.allInstructions().get(0);
96 +
97 + switch (instruction.type()) {
98 + case OUTPUT:
99 + OutputInstruction outInstruction = (OutputInstruction) instruction;
100 + PortNumber port = outInstruction.port();
101 + if (!port.isLogical()) {
102 + return buildEgressAction(port, configuration);
103 + } else if (port.equals(CONTROLLER)) {
104 + return actionWithName(SEND_TO_CPU);
105 + } else {
106 + throw new Bmv2InterpreterException("Egress on logical port not supported: " + port);
107 + }
108 + case NOACTION:
109 + return actionWithName(DROP);
110 + default:
111 + throw new Bmv2InterpreterException("Instruction type not supported: " + instruction.type().name());
112 + }
113 + }
114 +
115 + private static Bmv2Action buildEgressAction(PortNumber port, Bmv2Configuration configuration)
116 + throws Bmv2InterpreterException {
117 +
118 + int portBitWidth = configuration.action(SET_EGRESS_PORT).runtimeData(PORT).bitWidth();
119 +
120 + try {
121 + ImmutableByteSequence portBs = fitByteSequence(ImmutableByteSequence.copyFrom(port.toLong()), portBitWidth);
122 + return Bmv2Action.builder()
123 + .withName(SET_EGRESS_PORT)
124 + .addParameter(portBs)
125 + .build();
126 + } catch (Bmv2TranslatorUtils.ByteSequenceFitException e) {
127 + throw new Bmv2InterpreterException(e.getMessage());
128 + }
129 + }
130 +
131 + private static Bmv2Action actionWithName(String name) {
132 + return Bmv2Action.builder().withName(name).build();
133 + }
134 +}
1 +/*
2 + * Copyright 2016-present Open Networking Laboratory
3 + *
4 + * Licensed under the Apache License, Version 2.0 (the "License");
5 + * you may not use this file except in compliance with the License.
6 + * You may obtain a copy of the License at
7 + *
8 + * http://www.apache.org/licenses/LICENSE-2.0
9 + *
10 + * Unless required by applicable law or agreed to in writing, software
11 + * distributed under the License is distributed on an "AS IS" BASIS,
12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 + * See the License for the specific language governing permissions and
14 + * limitations under the License.
15 + */
16 +
17 +/**
18 + * BMv2 demo app for the WCMP configuration.
19 + */
20 +package org.onosproject.bmv2.demo.app.wcmp;
...\ No newline at end of file ...\ No newline at end of file
1 +/Users/carmelo/workspace/onos-p4-dev/p4src/build/wcmp.json
...\ No newline at end of file ...\ No newline at end of file
...@@ -71,6 +71,7 @@ ...@@ -71,6 +71,7 @@
71 <module>graphitemetrics</module> 71 <module>graphitemetrics</module>
72 <module>xosclient</module> 72 <module>xosclient</module>
73 <module>scalablegateway</module> 73 <module>scalablegateway</module>
74 + <module>bmv2-demo</module>
74 </modules> 75 </modules>
75 76
76 77
......