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