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