blob: 1b6d48b5d3c57267221e7f1bf64027ba3b562d21 [file] [log] [blame]
wu5f6c5b82017-08-04 16:45:19 +08001/*
2 * Copyright 2017-present Open Networking Foundation
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.pi.demo.app.ecmp;
18
19import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import org.apache.commons.lang3.tuple.Pair;
22import org.apache.felix.scr.annotations.Component;
23import org.onlab.util.ImmutableByteSequence;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020024import org.onosproject.bmv2.model.Bmv2PipelineModelParser;
Carmelo Cascone9f760a12017-08-24 13:14:53 +020025import org.onosproject.driver.pipeline.DefaultSingleTablePipeline;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020026import org.onosproject.net.DeviceId;
27import org.onosproject.net.Host;
28import org.onosproject.net.Path;
29import org.onosproject.net.Port;
30import org.onosproject.net.PortNumber;
Carmelo Cascone9f760a12017-08-24 13:14:53 +020031import org.onosproject.net.behaviour.Pipeliner;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020032import org.onosproject.net.flow.DefaultTrafficSelector;
33import org.onosproject.net.flow.DefaultTrafficTreatment;
34import org.onosproject.net.flow.FlowRule;
wu5f6c5b82017-08-04 16:45:19 +080035import org.onosproject.net.flow.TrafficSelector;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020036import org.onosproject.net.flow.TrafficTreatment;
wu5f6c5b82017-08-04 16:45:19 +080037import org.onosproject.net.flow.criteria.Criterion;
38import org.onosproject.net.flow.criteria.PiCriterion;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020039import org.onosproject.net.pi.model.DefaultPiPipeconf;
40import org.onosproject.net.pi.model.PiPipeconf;
41import org.onosproject.net.pi.model.PiPipeconfId;
42import org.onosproject.net.pi.model.PiPipelineInterpreter;
wu5f6c5b82017-08-04 16:45:19 +080043import org.onosproject.net.pi.runtime.PiAction;
44import org.onosproject.net.pi.runtime.PiActionId;
45import org.onosproject.net.pi.runtime.PiActionParam;
46import org.onosproject.net.pi.runtime.PiActionParamId;
47import org.onosproject.net.pi.runtime.PiHeaderFieldId;
48import org.onosproject.net.pi.runtime.PiTableAction;
wu5f6c5b82017-08-04 16:45:19 +080049import org.onosproject.net.topology.DefaultTopologyVertex;
50import org.onosproject.net.topology.Topology;
51import org.onosproject.net.topology.TopologyGraph;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020052import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
wu5f6c5b82017-08-04 16:45:19 +080053
54import java.net.URL;
55import java.util.Collection;
56import java.util.Iterator;
57import java.util.List;
58import java.util.Map;
59import java.util.Set;
60import java.util.stream.Collectors;
61
62import static java.util.stream.Collectors.toSet;
63import static org.onlab.packet.EthType.EtherType.IPV4;
64import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
65import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
66import static org.onosproject.pi.demo.app.ecmp.EcmpInterpreter.*;
67
68
69/**
70 * Implementation of an upgradable fabric app for the ECMP configuration.
71 */
72@Component(immediate = true)
73public class EcmpFabricApp extends AbstractUpgradableFabricApp {
74
75 private static final String APP_NAME = "org.onosproject.pi-ecmp-fabric";
Carmelo Cascone1fb27d32017-08-25 20:40:20 +020076 private static final String PIPECONF_ID = "pi-demo-ecmp";
Carmelo Cascone9f760a12017-08-24 13:14:53 +020077 private static final URL P4INFO_URL = EcmpFabricApp.class.getResource("/ecmp.p4info");
78 private static final URL JSON_URL = EcmpFabricApp.class.getResource("/ecmp.json");
wu5f6c5b82017-08-04 16:45:19 +080079
80 private static final PiPipeconf ECMP_PIPECONF = DefaultPiPipeconf.builder()
81 .withId(new PiPipeconfId(PIPECONF_ID))
82 .withPipelineModel(Bmv2PipelineModelParser.parse(JSON_URL))
83 .addBehaviour(PiPipelineInterpreter.class, EcmpInterpreter.class)
Carmelo Cascone9f760a12017-08-24 13:14:53 +020084 .addBehaviour(Pipeliner.class, DefaultSingleTablePipeline.class)
wu5f6c5b82017-08-04 16:45:19 +080085 .addExtension(P4_INFO_TEXT, P4INFO_URL)
86 .addExtension(BMV2_JSON, JSON_URL)
87 .build();
88
89 private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
90
91 public EcmpFabricApp() {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +020092 super(APP_NAME, ECMP_PIPECONF);
wu5f6c5b82017-08-04 16:45:19 +080093 }
94
95 @Override
96 public boolean initDevice(DeviceId deviceId) {
97 // Nothing to do.
98 return true;
99 }
100
101 @Override
102 public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
103 Collection<DeviceId> availableSpines, Topology topo)
104 throws FlowRuleGeneratorException {
105
106 // Get ports which connect this leaf switch to hosts.
107 Set<PortNumber> hostPorts = deviceService.getPorts(leaf)
108 .stream()
109 .filter(port -> !isFabricPort(port, topo))
110 .map(Port::number)
111 .collect(Collectors.toSet());
112
113 // Get ports which connect this leaf to the given available spines.
114 TopologyGraph graph = topologyService.getGraph(topo);
115 Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
116 .stream()
117 .filter(e -> availableSpines.contains(e.dst().deviceId()))
118 .map(e -> e.link().src().port())
119 .collect(Collectors.toSet());
120
121 if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
122 log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
123 hostPorts.size(), fabricPorts.size());
124 throw new FlowRuleGeneratorException();
125 }
126 PortNumber hostPort = hostPorts.iterator().next();
127
128 List<FlowRule> rules = Lists.newArrayList();
129
130 TrafficTreatment treatment;
131 if (fabricPorts.size() > 1) {
132 // Do ECMP.
133 Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(leaf, fabricPorts);
134 rules.addAll(result.getRight());
135 treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
136 } else {
137 // Output on port.
138 PortNumber outPort = fabricPorts.iterator().next();
139 treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
140 }
141
142 // From srHost to dstHosts.
143 for (Host dstHost : dstHosts) {
144 FlowRule rule = flowRuleBuilder(leaf, EcmpInterpreter.TABLE0)
145 .withSelector(
146 DefaultTrafficSelector.builder()
147 .matchInPort(hostPort)
148 .matchEthType(IPV4.ethType().toShort())
149 .matchEthSrc(srcHost.mac())
150 .matchEthDst(dstHost.mac())
151 .build())
152 .withTreatment(treatment)
153 .build();
154 rules.add(rule);
155 }
156
157 // From fabric ports to this leaf host.
158 for (PortNumber port : fabricPorts) {
159 FlowRule rule = flowRuleBuilder(leaf, EcmpInterpreter.TABLE0)
160 .withSelector(
161 DefaultTrafficSelector.builder()
162 .matchInPort(port)
163 .matchEthType(IPV4.ethType().toShort())
164 .matchEthDst(srcHost.mac())
165 .build())
166 .withTreatment(
167 DefaultTrafficTreatment.builder()
168 .setOutput(hostPort)
169 .build())
170 .build();
171 rules.add(rule);
172 }
173
174 return rules;
175 }
176
177 @Override
178 public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
179 throws FlowRuleGeneratorException {
180
181 List<FlowRule> rules = Lists.newArrayList();
182
183 // for each host
184 for (Host dstHost : dstHosts) {
185
186 Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
187
188 if (paths.size() == 0) {
189 log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
190 throw new FlowRuleGeneratorException();
191 }
192
193 TrafficTreatment treatment;
194
195 if (paths.size() == 1) {
196 // Only one path, do output on port.
197 PortNumber port = paths.iterator().next().src().port();
198 treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
199 } else {
200 // Multiple paths, do ECMP.
201 Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
202 Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(deviceId, portNumbers);
203 rules.addAll(result.getRight());
204 treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
205 }
206
207 FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.TABLE0)
208 .withSelector(
209 DefaultTrafficSelector.builder()
210 .matchEthType(IPV4.ethType().toShort())
211 .matchEthDst(dstHost.mac())
212 .build())
213 .withTreatment(treatment)
214 .build();
215
216 rules.add(rule);
217 }
218
219 return rules;
220 }
221
222 private Pair<PiTableAction, List<FlowRule>> provisionEcmpPiTableAction(DeviceId deviceId,
223 Set<PortNumber> fabricPorts)
224 throws FlowRuleGeneratorException {
225
226 // Install ECMP group table entries that map from hash values to actual fabric ports...
227 int groupId = groupIdOf(deviceId, fabricPorts);
228 int groupSize = fabricPorts.size();
229 Iterator<PortNumber> portIterator = fabricPorts.iterator();
230 List<FlowRule> rules = Lists.newArrayList();
231 for (short i = 0; i < groupSize; i++) {
232 FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.ECMP_GROUP_TABLE)
233 .withSelector(
234 buildEcmpTrafficSelector(groupId, i))
235 .withTreatment(
236 DefaultTrafficTreatment.builder()
237 .setOutput(portIterator.next())
238 .build())
239 .build();
240 rules.add(rule);
241 }
242
243 PiTableAction piTableAction = buildEcmpPiTableAction(groupId, groupSize);
244
245 return Pair.of(piTableAction, rules);
246 }
247
248 private PiTableAction buildEcmpPiTableAction(int groupId, int groupSize) {
249
250 return PiAction.builder()
251 .withId(PiActionId.of(ECMP_GROUP_ACTION_NAME))
252 .withParameter(new PiActionParam(PiActionParamId.of(GROUP_ID),
253 ImmutableByteSequence.copyFrom(groupId)))
254 .withParameter(new PiActionParam(PiActionParamId.of(GROUP_SIZE),
255 ImmutableByteSequence.copyFrom(groupSize)))
256 .build();
257 }
258
259 private TrafficSelector buildEcmpTrafficSelector(int groupId, int selector) {
260 Criterion ecmpCriterion = PiCriterion.builder()
261 .matchExact(PiHeaderFieldId.of(ECMP_METADATA_HEADER_NAME, GROUP_ID), groupId)
262 .matchExact(PiHeaderFieldId.of(ECMP_METADATA_HEADER_NAME, SELECTOR), selector)
263 .build();
264
265 return DefaultTrafficSelector.builder()
266 .matchPi((PiCriterion) ecmpCriterion)
267 .build();
268 }
269
270 public int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
271 DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
272 // Counts the number of unique portNumber sets for each deviceId.
273 // Each distinct set of portNumbers will have a unique ID.
274 return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
275 (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
276 }
277}