blob: fccbab5de203e6dd638a27fea180f56d2763e723 [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.net.DeviceId;
25import org.onosproject.net.Host;
26import org.onosproject.net.Path;
27import org.onosproject.net.Port;
28import org.onosproject.net.PortNumber;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020029import org.onosproject.net.flow.DefaultTrafficSelector;
30import org.onosproject.net.flow.DefaultTrafficTreatment;
31import org.onosproject.net.flow.FlowRule;
wu5f6c5b82017-08-04 16:45:19 +080032import org.onosproject.net.flow.TrafficSelector;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020033import org.onosproject.net.flow.TrafficTreatment;
wu5f6c5b82017-08-04 16:45:19 +080034import org.onosproject.net.flow.criteria.Criterion;
35import org.onosproject.net.flow.criteria.PiCriterion;
36import org.onosproject.net.pi.runtime.PiAction;
37import org.onosproject.net.pi.runtime.PiActionId;
38import org.onosproject.net.pi.runtime.PiActionParam;
39import org.onosproject.net.pi.runtime.PiActionParamId;
40import org.onosproject.net.pi.runtime.PiHeaderFieldId;
41import org.onosproject.net.pi.runtime.PiTableAction;
wu5f6c5b82017-08-04 16:45:19 +080042import org.onosproject.net.topology.DefaultTopologyVertex;
43import org.onosproject.net.topology.Topology;
44import org.onosproject.net.topology.TopologyGraph;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020045import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
wu5f6c5b82017-08-04 16:45:19 +080046
wu5f6c5b82017-08-04 16:45:19 +080047import java.util.Collection;
48import java.util.Iterator;
49import java.util.List;
50import java.util.Map;
51import java.util.Set;
52import java.util.stream.Collectors;
53
Carmelo Cascone3929cc82017-09-06 13:34:25 +020054import static java.lang.String.format;
wu5f6c5b82017-08-04 16:45:19 +080055import static java.util.stream.Collectors.toSet;
56import static org.onlab.packet.EthType.EtherType.IPV4;
wu5f6c5b82017-08-04 16:45:19 +080057import static org.onosproject.pi.demo.app.ecmp.EcmpInterpreter.*;
58
59
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.pi-ecmp-fabric";
wu5f6c5b82017-08-04 16:45:19 +080067
68 private static final Map<DeviceId, Map<Set<PortNumber>, Short>> DEVICE_GROUP_ID_MAP = Maps.newHashMap();
69
70 public EcmpFabricApp() {
Carmelo Cascone6e854042017-09-11 21:37:53 +020071 super(APP_NAME, EcmpPipeconfFactory.getAll());
wu5f6c5b82017-08-04 16:45:19 +080072 }
73
74 @Override
75 public boolean initDevice(DeviceId deviceId) {
76 // Nothing to do.
77 return true;
78 }
79
80 @Override
81 public List<FlowRule> generateLeafRules(DeviceId leaf, Host srcHost, Collection<Host> dstHosts,
82 Collection<DeviceId> availableSpines, Topology topo)
83 throws FlowRuleGeneratorException {
84
85 // Get ports which connect this leaf switch to hosts.
86 Set<PortNumber> hostPorts = deviceService.getPorts(leaf)
87 .stream()
88 .filter(port -> !isFabricPort(port, topo))
89 .map(Port::number)
90 .collect(Collectors.toSet());
91
92 // Get ports which connect this leaf to the given available spines.
93 TopologyGraph graph = topologyService.getGraph(topo);
94 Set<PortNumber> fabricPorts = graph.getEdgesFrom(new DefaultTopologyVertex(leaf))
95 .stream()
96 .filter(e -> availableSpines.contains(e.dst().deviceId()))
97 .map(e -> e.link().src().port())
98 .collect(Collectors.toSet());
99
100 if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
101 log.error("Leaf switch has invalid port configuration: hostPorts={}, fabricPorts={}",
102 hostPorts.size(), fabricPorts.size());
103 throw new FlowRuleGeneratorException();
104 }
105 PortNumber hostPort = hostPorts.iterator().next();
106
107 List<FlowRule> rules = Lists.newArrayList();
108
109 TrafficTreatment treatment;
110 if (fabricPorts.size() > 1) {
111 // Do ECMP.
112 Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(leaf, fabricPorts);
113 rules.addAll(result.getRight());
114 treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
115 } else {
116 // Output on port.
117 PortNumber outPort = fabricPorts.iterator().next();
118 treatment = DefaultTrafficTreatment.builder().setOutput(outPort).build();
119 }
120
121 // From srHost to dstHosts.
122 for (Host dstHost : dstHosts) {
123 FlowRule rule = flowRuleBuilder(leaf, EcmpInterpreter.TABLE0)
124 .withSelector(
125 DefaultTrafficSelector.builder()
126 .matchInPort(hostPort)
127 .matchEthType(IPV4.ethType().toShort())
128 .matchEthSrc(srcHost.mac())
129 .matchEthDst(dstHost.mac())
130 .build())
131 .withTreatment(treatment)
132 .build();
133 rules.add(rule);
134 }
135
136 // From fabric ports to this leaf host.
137 for (PortNumber port : fabricPorts) {
138 FlowRule rule = flowRuleBuilder(leaf, EcmpInterpreter.TABLE0)
139 .withSelector(
140 DefaultTrafficSelector.builder()
141 .matchInPort(port)
142 .matchEthType(IPV4.ethType().toShort())
143 .matchEthDst(srcHost.mac())
144 .build())
145 .withTreatment(
146 DefaultTrafficTreatment.builder()
147 .setOutput(hostPort)
148 .build())
149 .build();
150 rules.add(rule);
151 }
152
153 return rules;
154 }
155
156 @Override
157 public List<FlowRule> generateSpineRules(DeviceId deviceId, Collection<Host> dstHosts, Topology topo)
158 throws FlowRuleGeneratorException {
159
160 List<FlowRule> rules = Lists.newArrayList();
161
162 // for each host
163 for (Host dstHost : dstHosts) {
164
165 Set<Path> paths = topologyService.getPaths(topo, deviceId, dstHost.location().deviceId());
166
167 if (paths.size() == 0) {
168 log.warn("Can't find any path between spine {} and host {}", deviceId, dstHost);
169 throw new FlowRuleGeneratorException();
170 }
171
172 TrafficTreatment treatment;
173
174 if (paths.size() == 1) {
175 // Only one path, do output on port.
176 PortNumber port = paths.iterator().next().src().port();
177 treatment = DefaultTrafficTreatment.builder().setOutput(port).build();
178 } else {
179 // Multiple paths, do ECMP.
180 Set<PortNumber> portNumbers = paths.stream().map(p -> p.src().port()).collect(toSet());
181 Pair<PiTableAction, List<FlowRule>> result = provisionEcmpPiTableAction(deviceId, portNumbers);
182 rules.addAll(result.getRight());
183 treatment = DefaultTrafficTreatment.builder().piTableAction(result.getLeft()).build();
184 }
185
186 FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.TABLE0)
187 .withSelector(
188 DefaultTrafficSelector.builder()
189 .matchEthType(IPV4.ethType().toShort())
190 .matchEthDst(dstHost.mac())
191 .build())
192 .withTreatment(treatment)
193 .build();
194
195 rules.add(rule);
196 }
197
198 return rules;
199 }
200
201 private Pair<PiTableAction, List<FlowRule>> provisionEcmpPiTableAction(DeviceId deviceId,
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200202 Set<PortNumber> fabricPorts)
wu5f6c5b82017-08-04 16:45:19 +0800203 throws FlowRuleGeneratorException {
204
205 // Install ECMP group table entries that map from hash values to actual fabric ports...
206 int groupId = groupIdOf(deviceId, fabricPorts);
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200207 if (fabricPorts.size() != HASHED_LINKS) {
208 throw new FlowRuleGeneratorException(format(
209 "Invalid number of fabric ports for %s, expected %d but found %d",
210 deviceId, HASHED_LINKS, fabricPorts.size()));
211 }
wu5f6c5b82017-08-04 16:45:19 +0800212 Iterator<PortNumber> portIterator = fabricPorts.iterator();
213 List<FlowRule> rules = Lists.newArrayList();
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200214 for (short i = 0; i < HASHED_LINKS; i++) {
wu5f6c5b82017-08-04 16:45:19 +0800215 FlowRule rule = flowRuleBuilder(deviceId, EcmpInterpreter.ECMP_GROUP_TABLE)
216 .withSelector(
217 buildEcmpTrafficSelector(groupId, i))
218 .withTreatment(
219 DefaultTrafficTreatment.builder()
220 .setOutput(portIterator.next())
221 .build())
222 .build();
223 rules.add(rule);
224 }
225
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200226 PiTableAction piTableAction = buildEcmpPiTableAction(groupId);
wu5f6c5b82017-08-04 16:45:19 +0800227
228 return Pair.of(piTableAction, rules);
229 }
230
Carmelo Cascone3929cc82017-09-06 13:34:25 +0200231 private PiTableAction buildEcmpPiTableAction(int groupId) {
wu5f6c5b82017-08-04 16:45:19 +0800232
233 return PiAction.builder()
234 .withId(PiActionId.of(ECMP_GROUP_ACTION_NAME))
235 .withParameter(new PiActionParam(PiActionParamId.of(GROUP_ID),
236 ImmutableByteSequence.copyFrom(groupId)))
wu5f6c5b82017-08-04 16:45:19 +0800237 .build();
238 }
239
240 private TrafficSelector buildEcmpTrafficSelector(int groupId, int selector) {
241 Criterion ecmpCriterion = PiCriterion.builder()
242 .matchExact(PiHeaderFieldId.of(ECMP_METADATA_HEADER_NAME, GROUP_ID), groupId)
243 .matchExact(PiHeaderFieldId.of(ECMP_METADATA_HEADER_NAME, SELECTOR), selector)
244 .build();
245
246 return DefaultTrafficSelector.builder()
247 .matchPi((PiCriterion) ecmpCriterion)
248 .build();
249 }
250
251 public int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
252 DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
253 // Counts the number of unique portNumber sets for each deviceId.
254 // Each distinct set of portNumbers will have a unique ID.
255 return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
256 (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
257 }
258}