blob: bb15afc846b13b59bde6e5b3e6d267eca5673254 [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;
Carmelo Cascone5167f322017-11-21 21:58:50 -080021import com.google.common.collect.Sets;
22import org.apache.commons.lang3.tuple.ImmutablePair;
wu5f6c5b82017-08-04 16:45:19 +080023import org.apache.commons.lang3.tuple.Pair;
24import org.apache.felix.scr.annotations.Component;
Carmelo Cascone5167f322017-11-21 21:58:50 -080025import org.apache.felix.scr.annotations.Deactivate;
26import org.onlab.packet.IpAddress;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020027import org.onosproject.net.DeviceId;
28import org.onosproject.net.Host;
29import org.onosproject.net.Path;
30import org.onosproject.net.Port;
31import org.onosproject.net.PortNumber;
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.criteria.PiCriterion;
Carmelo Cascone5167f322017-11-21 21:58:50 -080036import org.onosproject.net.group.DefaultGroupBucket;
37import org.onosproject.net.group.DefaultGroupDescription;
38import org.onosproject.net.group.GroupBucket;
39import org.onosproject.net.group.GroupBuckets;
40import org.onosproject.net.group.GroupDescription;
41import org.onosproject.net.group.GroupKey;
wu5f6c5b82017-08-04 16:45:19 +080042import org.onosproject.net.pi.runtime.PiAction;
Carmelo Cascone5167f322017-11-21 21:58:50 -080043import org.onosproject.net.pi.runtime.PiActionGroupId;
wu5f6c5b82017-08-04 16:45:19 +080044import org.onosproject.net.pi.runtime.PiActionParam;
Carmelo Cascone5167f322017-11-21 21:58:50 -080045import org.onosproject.net.pi.runtime.PiGroupKey;
wu5f6c5b82017-08-04 16:45:19 +080046import org.onosproject.net.pi.runtime.PiTableAction;
wu5f6c5b82017-08-04 16:45:19 +080047import org.onosproject.net.topology.DefaultTopologyVertex;
48import org.onosproject.net.topology.Topology;
49import org.onosproject.net.topology.TopologyGraph;
Carmelo Casconea62ac3d2017-08-30 03:19:00 +020050import org.onosproject.pi.demo.app.common.AbstractUpgradableFabricApp;
Carmelo Casconeca94bcf2017-10-27 14:16:59 -070051import org.onosproject.pipelines.basic.PipeconfLoader;
wu5f6c5b82017-08-04 16:45:19 +080052
wu5f6c5b82017-08-04 16:45:19 +080053import java.util.List;
54import java.util.Map;
55import java.util.Set;
56import java.util.stream.Collectors;
57
Carmelo Casconeca94bcf2017-10-27 14:16:59 -070058import static java.util.Collections.singleton;
wu5f6c5b82017-08-04 16:45:19 +080059import static java.util.stream.Collectors.toSet;
Carmelo Cascone5167f322017-11-21 21:58:50 -080060import static org.onlab.util.ImmutableByteSequence.copyFrom;
61import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRF_WCMP_SELECTOR_ID;
Carmelo Casconeca94bcf2017-10-27 14:16:59 -070062import static org.onosproject.pipelines.basic.BasicConstants.ACT_PRM_NEXT_HOP_ID;
63import static org.onosproject.pipelines.basic.BasicConstants.ACT_SET_NEXT_HOP_ID;
64import static org.onosproject.pipelines.basic.BasicConstants.HDR_NEXT_HOP_ID;
Carmelo Casconeca94bcf2017-10-27 14:16:59 -070065import static org.onosproject.pipelines.basic.BasicConstants.TBL_TABLE0_ID;
Carmelo Cascone5167f322017-11-21 21:58:50 -080066import static org.onosproject.pipelines.basic.BasicConstants.TBL_WCMP_TABLE_ID;
wu5f6c5b82017-08-04 16:45:19 +080067
68/**
Carmelo Cascone5167f322017-11-21 21:58:50 -080069 * Implementation of an upgradable fabric app for the Basic pipeconf (basic.p4)
70 * with ECMP support.
wu5f6c5b82017-08-04 16:45:19 +080071 */
72@Component(immediate = true)
73public class EcmpFabricApp extends AbstractUpgradableFabricApp {
74
Carmelo Casconeca94bcf2017-10-27 14:16:59 -070075 private static final String APP_NAME = "org.onosproject.pi-ecmp";
wu5f6c5b82017-08-04 16:45:19 +080076
Carmelo Cascone5167f322017-11-21 21:58:50 -080077 private static final Map<DeviceId, Map<Set<PortNumber>, Short>>
78 DEVICE_GROUP_ID_MAP = Maps.newHashMap();
79
80 private final Set<Pair<DeviceId, GroupKey>> groupKeys = Sets.newHashSet();
wu5f6c5b82017-08-04 16:45:19 +080081
82 public EcmpFabricApp() {
Carmelo Cascone5167f322017-11-21 21:58:50 -080083 super(APP_NAME, singleton(PipeconfLoader.BASIC_PIPECONF));
84 }
85
86 @Deactivate
87 public void deactivate() {
88 groupKeys.forEach(pair -> groupService.removeGroup(
89 pair.getLeft(), pair.getRight(), appId));
90 super.deactivate();
wu5f6c5b82017-08-04 16:45:19 +080091 }
92
93 @Override
94 public boolean initDevice(DeviceId deviceId) {
95 // Nothing to do.
96 return true;
97 }
98
99 @Override
Carmelo Cascone5167f322017-11-21 21:58:50 -0800100 public List<FlowRule> generateLeafRules(DeviceId leaf, Host localHost,
101 Set<Host> remoteHosts,
102 Set<DeviceId> availableSpines,
103 Topology topo)
wu5f6c5b82017-08-04 16:45:19 +0800104 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)
Carmelo Cascone5167f322017-11-21 21:58:50 -0800111 .collect(toSet());
wu5f6c5b82017-08-04 16:45:19 +0800112
113 // Get ports which connect this leaf to the given available spines.
114 TopologyGraph graph = topologyService.getGraph(topo);
Carmelo Cascone5167f322017-11-21 21:58:50 -0800115 Set<PortNumber> fabricPorts = graph
116 .getEdgesFrom(new DefaultTopologyVertex(leaf))
wu5f6c5b82017-08-04 16:45:19 +0800117 .stream()
118 .filter(e -> availableSpines.contains(e.dst().deviceId()))
119 .map(e -> e.link().src().port())
Carmelo Cascone5167f322017-11-21 21:58:50 -0800120 .collect(toSet());
wu5f6c5b82017-08-04 16:45:19 +0800121
122 if (hostPorts.size() != 1 || fabricPorts.size() == 0) {
Carmelo Cascone5167f322017-11-21 21:58:50 -0800123 log.error("Leaf has invalid port configuration: hostPorts={}, fabricPorts={}",
wu5f6c5b82017-08-04 16:45:19 +0800124 hostPorts.size(), fabricPorts.size());
125 throw new FlowRuleGeneratorException();
126 }
127 PortNumber hostPort = hostPorts.iterator().next();
128
129 List<FlowRule> rules = Lists.newArrayList();
130
Carmelo Cascone5167f322017-11-21 21:58:50 -0800131 // From local host to remote ones.
132 for (Host remoteHost : remoteHosts) {
133 int groupId = provisionGroup(leaf, fabricPorts);
wu5f6c5b82017-08-04 16:45:19 +0800134
Carmelo Cascone5167f322017-11-21 21:58:50 -0800135 rules.add(groupFlowRule(leaf, groupId));
136
137 PiTableAction piTableAction = PiAction.builder()
138 .withId(ACT_SET_NEXT_HOP_ID)
139 .withParameter(new PiActionParam(
140 ACT_PRM_NEXT_HOP_ID,
141 copyFrom(groupId)))
wu5f6c5b82017-08-04 16:45:19 +0800142 .build();
Carmelo Cascone5167f322017-11-21 21:58:50 -0800143
144 for (IpAddress ipAddr : remoteHost.ipAddresses()) {
145 FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
146 .withSelector(
147 DefaultTrafficSelector.builder()
148 .matchIPDst(ipAddr.toIpPrefix())
149 .build())
150 .withTreatment(
151 DefaultTrafficTreatment.builder()
152 .piTableAction(piTableAction)
153 .build())
154 .build();
155 rules.add(rule);
156 }
wu5f6c5b82017-08-04 16:45:19 +0800157 }
158
Carmelo Cascone5167f322017-11-21 21:58:50 -0800159 // From remote hosts to the local one
160 for (IpAddress dstIpAddr : localHost.ipAddresses()) {
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700161 FlowRule rule = flowRuleBuilder(leaf, TBL_TABLE0_ID)
wu5f6c5b82017-08-04 16:45:19 +0800162 .withSelector(
163 DefaultTrafficSelector.builder()
Carmelo Cascone5167f322017-11-21 21:58:50 -0800164 .matchIPDst(dstIpAddr.toIpPrefix())
wu5f6c5b82017-08-04 16:45:19 +0800165 .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
Carmelo Cascone5167f322017-11-21 21:58:50 -0800178 public List<FlowRule> generateSpineRules(DeviceId spine, Set<Host> hosts,
179 Topology topo)
wu5f6c5b82017-08-04 16:45:19 +0800180 throws FlowRuleGeneratorException {
181
182 List<FlowRule> rules = Lists.newArrayList();
183
Carmelo Cascone5167f322017-11-21 21:58:50 -0800184 // For each host pair (src -> dst)
185 for (Host dstHost : hosts) {
wu5f6c5b82017-08-04 16:45:19 +0800186
Carmelo Cascone5167f322017-11-21 21:58:50 -0800187 Set<Path> paths = topologyService.getPaths(
188 topo, spine, dstHost.location().deviceId());
wu5f6c5b82017-08-04 16:45:19 +0800189
190 if (paths.size() == 0) {
Carmelo Cascone5167f322017-11-21 21:58:50 -0800191 log.warn("No path between spine {} and host {}",
192 spine, dstHost);
wu5f6c5b82017-08-04 16:45:19 +0800193 throw new FlowRuleGeneratorException();
194 }
195
Carmelo Cascone5167f322017-11-21 21:58:50 -0800196 Set<PortNumber> ports = paths.stream()
197 .map(p -> p.src().port())
198 .collect(toSet());
wu5f6c5b82017-08-04 16:45:19 +0800199
Carmelo Cascone5167f322017-11-21 21:58:50 -0800200 int groupId = provisionGroup(spine, ports);
wu5f6c5b82017-08-04 16:45:19 +0800201
Carmelo Cascone5167f322017-11-21 21:58:50 -0800202 rules.add(groupFlowRule(spine, groupId));
203
204 PiTableAction piTableAction = PiAction.builder()
205 .withId(ACT_SET_NEXT_HOP_ID)
206 .withParameter(new PiActionParam(ACT_PRM_NEXT_HOP_ID,
207 copyFrom(groupId)))
wu5f6c5b82017-08-04 16:45:19 +0800208 .build();
209
Carmelo Cascone5167f322017-11-21 21:58:50 -0800210 for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
211 FlowRule rule = flowRuleBuilder(spine, TBL_TABLE0_ID)
212 .withSelector(DefaultTrafficSelector.builder()
213 .matchIPDst(dstIpAddr.toIpPrefix())
214 .build())
215 .withTreatment(DefaultTrafficTreatment.builder()
216 .piTableAction(piTableAction)
217 .build())
218 .build();
219 rules.add(rule);
220 }
wu5f6c5b82017-08-04 16:45:19 +0800221 }
222
223 return rules;
224 }
225
Carmelo Cascone5167f322017-11-21 21:58:50 -0800226 /**
227 * Provisions an ECMP group for the given device and set of ports, returns
228 * the group ID.
229 */
230 private int provisionGroup(DeviceId deviceId, Set<PortNumber> ports)
wu5f6c5b82017-08-04 16:45:19 +0800231 throws FlowRuleGeneratorException {
232
Carmelo Cascone5167f322017-11-21 21:58:50 -0800233 int groupId = groupIdOf(deviceId, ports);
wu5f6c5b82017-08-04 16:45:19 +0800234
Carmelo Cascone5167f322017-11-21 21:58:50 -0800235 // Group buckets
236 List<GroupBucket> bucketList = ports.stream()
237 .map(port -> DefaultTrafficTreatment.builder()
238 .setOutput(port)
239 .build())
240 .map(DefaultGroupBucket::createSelectGroupBucket)
241 .collect(Collectors.toList());
wu5f6c5b82017-08-04 16:45:19 +0800242
Carmelo Cascone5167f322017-11-21 21:58:50 -0800243 // Group cookie (with action profile ID)
244 PiGroupKey groupKey = new PiGroupKey(TBL_WCMP_TABLE_ID,
245 ACT_PRF_WCMP_SELECTOR_ID,
246 groupId);
247
248 log.info("Adding group {} to {}...", groupId, deviceId);
249 groupService.addGroup(
250 new DefaultGroupDescription(deviceId,
251 GroupDescription.Type.SELECT,
252 new GroupBuckets(bucketList),
253 groupKey,
254 groupId,
255 appId));
256
257 groupKeys.add(ImmutablePair.of(deviceId, groupKey));
258
259 return groupId;
wu5f6c5b82017-08-04 16:45:19 +0800260 }
261
Carmelo Cascone5167f322017-11-21 21:58:50 -0800262 private FlowRule groupFlowRule(DeviceId deviceId, int groupId)
263 throws FlowRuleGeneratorException {
264 return flowRuleBuilder(deviceId, TBL_WCMP_TABLE_ID)
265 .withSelector(
266 DefaultTrafficSelector.builder()
267 .matchPi(
268 PiCriterion.builder()
269 .matchExact(HDR_NEXT_HOP_ID,
270 groupId)
271 .build())
272 .build())
273 .withTreatment(
274 DefaultTrafficTreatment.builder()
275 .piTableAction(PiActionGroupId.of(groupId))
276 .build())
wu5f6c5b82017-08-04 16:45:19 +0800277 .build();
278 }
279
Carmelo Casconeca94bcf2017-10-27 14:16:59 -0700280 private int groupIdOf(DeviceId deviceId, Set<PortNumber> ports) {
wu5f6c5b82017-08-04 16:45:19 +0800281 DEVICE_GROUP_ID_MAP.putIfAbsent(deviceId, Maps.newHashMap());
282 // Counts the number of unique portNumber sets for each deviceId.
283 // Each distinct set of portNumbers will have a unique ID.
284 return DEVICE_GROUP_ID_MAP.get(deviceId).computeIfAbsent(ports, (pp) ->
285 (short) (DEVICE_GROUP_ID_MAP.get(deviceId).size() + 1));
286 }
Carmelo Cascone5167f322017-11-21 21:58:50 -0800287}