blob: ab42d2fae342a7403327b8bfaae5e243d55bb21e [file] [log] [blame]
Carmelo Casconeefc0a922016-06-14 14:32:33 -07001/*
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
17package org.onosproject.bmv2.demo.app.ecmp;
18
19import com.eclipsesource.json.Json;
20import com.eclipsesource.json.JsonObject;
21import com.google.common.collect.Lists;
Carmelo Cascone0ec92f12016-06-17 14:41:40 -070022import com.google.common.collect.Maps;
Carmelo Casconeefc0a922016-06-14 14:32:33 -070023import org.apache.commons.lang3.tuple.Pair;
24import org.apache.felix.scr.annotations.Component;
25import org.onosproject.bmv2.api.context.Bmv2Configuration;
26import org.onosproject.bmv2.api.context.Bmv2DefaultConfiguration;
27import org.onosproject.bmv2.api.context.Bmv2DeviceContext;
Carmelo Cascone0ec92f12016-06-17 14:41:40 -070028import org.onosproject.bmv2.api.runtime.Bmv2ExtensionSelector;
29import org.onosproject.bmv2.api.runtime.Bmv2ExtensionTreatment;
Carmelo Casconeefc0a922016-06-14 14:32:33 -070030import org.onosproject.bmv2.demo.app.common.AbstractUpgradableFabricApp;
31import org.onosproject.net.DeviceId;
32import org.onosproject.net.Host;
33import org.onosproject.net.Path;
34import org.onosproject.net.Port;
35import org.onosproject.net.PortNumber;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.FlowRule;
39import org.onosproject.net.flow.TrafficTreatment;
40import org.onosproject.net.flow.criteria.ExtensionSelector;
41import org.onosproject.net.flow.instructions.ExtensionTreatment;
42import org.onosproject.net.topology.DefaultTopologyVertex;
43import org.onosproject.net.topology.Topology;
44import org.onosproject.net.topology.TopologyGraph;
45
46import java.io.BufferedReader;
47import java.io.IOException;
48import java.io.InputStreamReader;
49import java.util.Collection;
50import java.util.Iterator;
51import java.util.List;
Carmelo Cascone0ec92f12016-06-17 14:41:40 -070052import java.util.Map;
Carmelo Casconeefc0a922016-06-14 14:32:33 -070053import java.util.Set;
54import java.util.stream.Collectors;
55
56import static java.util.stream.Collectors.toSet;
57import static org.onlab.packet.EthType.EtherType.IPV4;
Carmelo Cascone0ec92f12016-06-17 14:41:40 -070058import static org.onosproject.bmv2.demo.app.ecmp.EcmpInterpreter.*;
Carmelo Casconeefc0a922016-06-14 14:32:33 -070059
60/**
61 * Implementation of an upgradable fabric app for the ECMP configuration.
62 */
63@Component(immediate = true)
64public class EcmpFabricApp extends AbstractUpgradableFabricApp {
65
66 private static final String APP_NAME = "org.onosproject.bmv2-ecmp-fabric";
67 private static final String MODEL_NAME = "ECMP";
68 private static final String JSON_CONFIG_PATH = "/ecmp.json";
69
70 private static final Bmv2Configuration ECMP_CONFIGURATION = loadConfiguration();
71 private static final EcmpInterpreter ECMP_INTERPRETER = new EcmpInterpreter();
72 protected static final Bmv2DeviceContext ECMP_CONTEXT = new Bmv2DeviceContext(ECMP_CONFIGURATION, ECMP_INTERPRETER);
73
Carmelo Cascone0ec92f12016-06-17 14:41:40 -070074 private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
75
Carmelo Casconeefc0a922016-06-14 14:32:33 -070076 public EcmpFabricApp() {
77 super(APP_NAME, MODEL_NAME, ECMP_CONTEXT);
78 }
79
80 @Override
81 public boolean initDevice(DeviceId deviceId) {
82 // Nothing to do.
83 return true;
84 }
85
86 @Override
87 public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
88 Collection<DeviceId> availableSpines, Topology topo)
89 throws FlowRuleGeneratorException {
90
91 // Get ports which connect this leaf switch to hosts.
92 Set<PortNumber> hostPorts = deviceService.getPorts(leaf)
93 .stream()
94 .filter(port -> !isFabricPort(port, topo))
95 .map(Port::number)
96 .collect(Collectors.toSet());
97
98 // Get ports which connect this leaf to the given available spines.
99 TopologyGraph graph = topologyService.getGraph(topo);
100 Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
101 .stream()
102 .filter(e -> availableSpines.contains(e.dst().deviceId()))
103 .map(e -> e.link().src().port())
104 .collect(Collectors.toSet());
105
106 if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
107 log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
108 hostPorts.size(), fabricPorts.size());
109 throw new FlowRuleGeneratorException();
110 }
111 PortNumber hostPort = hostPorts.iterator().next();
112
113 List<FlowRule> rules = Lists.newArrayList();
114
115 TrafficTreatment treatment;
116 if (fabricPorts.size() > 1) {
117 // Do ECMP.
118 Pair<ExtensionTreatment, List<FlowRule>> result = provisionEcmpTreatment(leaf, fabricPorts);
119 rules.addAll(result.getRight());
120 ExtensionTreatment extTreatment = result.getLeft();
121 treatment = DefaultTrafficTreatment.builder().extension(extTreatment, leaf).build();
122 } else {
123 // Output on port.
124 PortNumber outPort = fabricPorts.iterator().next();
125 treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
126 }
127
128 // From srHost to dstHosts.
129 for (Host dstHost : dstHosts) {
130 FlowRule rule = flowRuleBuilder(leaf, TABLE0)
131 .withSelector(
132 DefaultTrafficSelector.builder()
133 .matchInPort(hostPort)
134 .matchEthType(IPV4.ethType().toShort())
135 .matchEthSrc(srcHost.mac())
136 .matchEthDst(dstHost.mac())
137 .build())
138 .withTreatment(treatment)
139 .build();
140 rules.add(rule);
141 }
142
143 // From fabric ports to this leaf host.
144 for (PortNumber port : fabricPorts) {
145 FlowRule rule = flowRuleBuilder(leaf, TABLE0)
146 .withSelector(
147 DefaultTrafficSelector.builder()
148 .matchInPort(port)
149 .matchEthType(IPV4.ethType().toShort())
150 .matchEthDst(srcHost.mac())
151 .build())
152 .withTreatment(
153 DefaultTrafficTreatment.builder()
154 .setOutput(hostPort)
155 .build())
156 .build();
157 rules.add(rule);
158 }
159
160 return rules;
161 }
162
163 @Override
164 public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
165 throws FlowRuleGeneratorException {
166
167 List<FlowRule> rules = Lists.newArrayList();
168
169 // for each host
170 for (Host dstHost : dstHosts) {
171
172 Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
173
174 if (paths.size() == 0) {
175 log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
176 throw new FlowRuleGeneratorException();
177 }
178
179 TrafficTreatment treatment;
180
181 if (paths.size() == 1) {
182 // Only one path, do output on port.
183 PortNumber port = paths.iterator().next().src().port();
184 treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
185 } else {
186 // Multiple paths, do ECMP.
187 Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
188 Pair<ExtensionTreatment, List<FlowRule>> result = provisionEcmpTreatment(deviceId, portNumbers);
189 rules.addAll(result.getRight());
190 treatment = DefaultTrafficTreatment.builder().extension(result.getLeft(), deviceId).build();
191 }
192
193 FlowRule rule = flowRuleBuilder(deviceId, TABLE0)
194 .withSelector(
195 DefaultTrafficSelector.builder()
196 .matchEthType(IPV4.ethType().toShort())
197 .matchEthDst(dstHost.mac())
198 .build())
199 .withTreatment(treatment)
200 .build();
201
202 rules.add(rule);
203 }
204
205 return rules;
206 }
207
208 private Pair<ExtensionTreatment, List<FlowRule>> provisionEcmpTreatment(DeviceId deviceId,
209 Set<PortNumber> fabricPorts)
210 throws FlowRuleGeneratorException {
211
212 // Install ECMP group table entries that map from hash values to actual fabric ports...
213 int groupId = groupIdOf(deviceId, fabricPorts);
214 int groupSize = fabricPorts.size();
215 Iterator<PortNumber> portIterator = fabricPorts.iterator();
216 List<FlowRule> rules = Lists.newArrayList();
217 for (short i = 0; i < groupSize; i++) {
Carmelo Cascone0ec92f12016-06-17 14:41:40 -0700218 ExtensionSelector extSelector = buildEcmpSelector(groupId, i);
Carmelo Casconeefc0a922016-06-14 14:32:33 -0700219 FlowRule rule = flowRuleBuilder(deviceId, ECMP_GROUP_TABLE)
220 .withSelector(
221 DefaultTrafficSelector.builder()
222 .extension(extSelector, deviceId)
223 .build())
224 .withTreatment(
225 DefaultTrafficTreatment.builder()
226 .setOutput(portIterator.next())
227 .build())
228 .build();
229 rules.add(rule);
230 }
231
Carmelo Cascone0ec92f12016-06-17 14:41:40 -0700232 ExtensionTreatment extTreatment = buildEcmpTreatment(groupId, groupSize);
Carmelo Casconeefc0a922016-06-14 14:32:33 -0700233
234 return Pair.of(extTreatment, rules);
235 }
236
Carmelo Cascone0ec92f12016-06-17 14:41:40 -0700237 private Bmv2ExtensionTreatment buildEcmpTreatment(int groupId, int groupSize) {
238 return Bmv2ExtensionTreatment.builder()
239 .forConfiguration(ECMP_CONTEXT.configuration())
240 .setActionName(ECMP_GROUP)
241 .addParameter(GROUP_ID, groupId)
242 .addParameter(GROUP_SIZE, groupSize)
243 .build();
244 }
245
246 private Bmv2ExtensionSelector buildEcmpSelector(int groupId, int selector) {
247 return Bmv2ExtensionSelector.builder()
248 .forConfiguration(ECMP_CONTEXT.configuration())
249 .matchExact(ECMP_METADATA, GROUP_ID, groupId)
250 .matchExact(ECMP_METADATA, SELECTOR, selector)
251 .build();
252 }
253
254
255 public int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
256 DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
257 // Counts the number of unique portNumber sets for each deviceId.
258 // Each distinct set of portNumbers will have a unique ID.
259 return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
260 (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
261 }
262
Carmelo Casconeefc0a922016-06-14 14:32:33 -0700263 private static Bmv2Configuration loadConfiguration() {
264 try {
265 JsonObject json = Json.parse(new BufferedReader(new InputStreamReader(
266 EcmpFabricApp.class.getResourceAsStream(JSON_CONFIG_PATH)))).asObject();
267 return Bmv2DefaultConfiguration.parse(json);
268 } catch (IOException e) {
269 throw new RuntimeException("Unable to load configuration", e);
270 }
271 }
272}