blob: b66c16042d3e6feb32b4ded5073a467ef730f6a4 [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
Carmelo Cascone3929cc82017-09-06 13:34:25 +020062import static java.lang.String.format;
wu5f6c5b82017-08-04 16:45:19 +080063import static java.util.stream.Collectors.toSet;
64import static org.onlab.packet.EthType.EtherType.IPV4;
65import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.BMV2_JSON;
66import static org.onosproject.net.pi.model.PiPipeconf.ExtensionType.P4_INFO_TEXT;
67import static org.onosproject.pi.demo.app.ecmp.EcmpInterpreter.*;
68
69
70/**
71 * Implementation of an upgradable fabric app for the ECMP configuration.
72 */
73@Component(immediate = true)
74public class EcmpFabricApp extends AbstractUpgradableFabricApp {
75
76 private static final String APP_NAME = "org.onosproject.pi-ecmp-fabric";
Carmelo Cascone1fb27d32017-08-25 20:40:20 +020077 private static final String PIPECONF_ID = "pi-demo-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() {
Carmelo Cascone1fb27d32017-08-25 20:40:20 +020093 super(APP_NAME, ECMP_PIPECONF);
wu5f6c5b82017-08-04 16:45:19 +080094 }
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,
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200224 Set<PortNumber> fabricPorts)
wu5f6c5b82017-08-04 16:45:19 +0800225 throws FlowRuleGeneratorException {
226
227 // Install ECMP group table entries that map from hash values to actual fabric ports...
228 int groupId = groupIdOf(deviceId, fabricPorts);
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200229 if (fabricPorts.size() != HASHED_LINKS) {
230 throw new FlowRuleGeneratorException(format(
231 "Invalid number of fabric ports for %s, expected %d but found %d",
232 deviceId, HASHED_LINKS, fabricPorts.size()));
233 }
wu5f6c5b82017-08-04 16:45:19 +0800234 Iterator<PortNumber> portIterator = fabricPorts.iterator();
235 List<FlowRule> rules = Lists.newArrayList();
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200236 for (short i = 0; i < HASHED_LINKS; i++) {
wu5f6c5b82017-08-04 16:45:19 +0800237 FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.ECMP_GROUP_TABLE)
238 .withSelector(
239 buildEcmpTrafficSelector(groupId, i))
240 .withTreatment(
241 DefaultTrafficTreatment.builder()
242 .setOutput(portIterator.next())
243 .build())
244 .build();
245 rules.add(rule);
246 }
247
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200248 PiTableAction piTableAction = buildEcmpPiTableAction(groupId);
wu5f6c5b82017-08-04 16:45:19 +0800249
250 return Pair.of(piTableAction, rules);
251 }
252
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200253 private PiTableAction buildEcmpPiTableAction(int groupId) {
wu5f6c5b82017-08-04 16:45:19 +0800254
255 return PiAction.builder()
256 .withId(PiActionId.of(ECMP_GROUP_ACTION_NAME))
257 .withParameter(new PiActionParam(PiActionParamId.of(GROUP_ID),
258 ImmutableByteSequence.copyFrom(groupId)))
wu5f6c5b82017-08-04 16:45:19 +0800259 .build();
260 }
261
262 private TrafficSelector buildEcmpTrafficSelector(int groupId, int selector) {
263 Criterion ecmpCriterion = PiCriterion.builder()
264 .matchExact(PiHeaderFieldId.of(ECMP_METADATA_HEADER_NAME, GROUP_ID), groupId)
265 .matchExact(PiHeaderFieldId.of(ECMP_METADATA_HEADER_NAME, SELECTOR), selector)
266 .build();
267
268 return DefaultTrafficSelector.builder()
269 .matchPi((PiCriterion) ecmpCriterion)
270 .build();
271 }
272
273 public int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
274 DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
275 // Counts the number of unique portNumber sets for each deviceId.
276 // Each distinct set of portNumbers will have a unique ID.
277 return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
278 (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
279 }
280}