blob: 30a9c32a5bf5daf709db7694dee2e483214247ca [file] [log] [blame]
Charles Chan188ebf52015-12-23 00:15:11 -08001package org.onosproject.driver.pipeline;
2
3import com.google.common.cache.Cache;
4import com.google.common.cache.CacheBuilder;
5import com.google.common.cache.RemovalCause;
6import com.google.common.cache.RemovalNotification;
Charles Chan5b9df8d2016-03-28 22:21:40 -07007import com.google.common.collect.ImmutableList;
8import com.google.common.collect.Lists;
Charles Chan188ebf52015-12-23 00:15:11 -08009import org.onlab.osgi.ServiceDirectory;
Charles Chan5b9df8d2016-03-28 22:21:40 -070010import org.onlab.packet.IpPrefix;
Charles Chan5270ed02016-01-30 23:22:37 -080011import org.onlab.packet.MacAddress;
Charles Chan188ebf52015-12-23 00:15:11 -080012import org.onlab.packet.MplsLabel;
13import org.onlab.packet.VlanId;
14import org.onosproject.core.ApplicationId;
15import org.onosproject.core.DefaultGroupId;
16import org.onosproject.net.DeviceId;
17import org.onosproject.net.PortNumber;
18import org.onosproject.net.behaviour.NextGroup;
19import org.onosproject.net.behaviour.PipelinerContext;
20import org.onosproject.net.flow.DefaultTrafficTreatment;
21import org.onosproject.net.flow.TrafficSelector;
22import org.onosproject.net.flow.TrafficTreatment;
23import org.onosproject.net.flow.criteria.Criterion;
24import org.onosproject.net.flow.criteria.VlanIdCriterion;
25import org.onosproject.net.flow.instructions.Instruction;
26import org.onosproject.net.flow.instructions.Instructions;
27import org.onosproject.net.flow.instructions.L2ModificationInstruction;
28import org.onosproject.net.flowobjective.FlowObjectiveStore;
29import org.onosproject.net.flowobjective.NextObjective;
30import org.onosproject.net.flowobjective.ObjectiveError;
31import org.onosproject.net.group.DefaultGroupBucket;
32import org.onosproject.net.group.DefaultGroupDescription;
33import org.onosproject.net.group.DefaultGroupKey;
34import org.onosproject.net.group.Group;
35import org.onosproject.net.group.GroupBucket;
36import org.onosproject.net.group.GroupBuckets;
37import org.onosproject.net.group.GroupDescription;
38import org.onosproject.net.group.GroupEvent;
39import org.onosproject.net.group.GroupKey;
40import org.onosproject.net.group.GroupListener;
41import org.onosproject.net.group.GroupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080042import org.onosproject.store.service.AtomicCounter;
43import org.onosproject.store.service.StorageService;
Charles Chan188ebf52015-12-23 00:15:11 -080044import org.slf4j.Logger;
45
46import java.util.ArrayDeque;
47import java.util.ArrayList;
48import java.util.Collection;
49import java.util.Collections;
50import java.util.Deque;
51import java.util.List;
52import java.util.Map;
Charles Chand0fd5dc2016-02-16 23:14:49 -080053import java.util.Objects;
Charles Chan188ebf52015-12-23 00:15:11 -080054import java.util.Set;
55import java.util.concurrent.ConcurrentHashMap;
56import java.util.concurrent.CopyOnWriteArrayList;
57import java.util.concurrent.Executors;
58import java.util.concurrent.ScheduledExecutorService;
59import java.util.concurrent.TimeUnit;
60import java.util.concurrent.atomic.AtomicInteger;
61import java.util.stream.Collectors;
62
63import static org.onlab.util.Tools.groupedThreads;
64import static org.slf4j.LoggerFactory.getLogger;
65
66/**
67 * Group handler for OFDPA2 pipeline.
68 */
Charles Chan361154b2016-03-24 10:23:39 -070069public class Ofdpa2GroupHandler {
Charles Chan188ebf52015-12-23 00:15:11 -080070 /*
71 * OFDPA requires group-id's to have a certain form.
72 * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
73 * L3 Unicast Groups have <4bits-2><28bits-index>
74 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
75 * L3 ECMP Groups have <4bits-7><28bits-index>
76 * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
77 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
78 */
Charles Chane849c192016-01-11 18:28:54 -080079 private static final int L2_INTERFACE_TYPE = 0x00000000;
Charles Chan5b9df8d2016-03-28 22:21:40 -070080 private static final int L3_INTERFACE_TYPE = 0x50000000;
Charles Chane849c192016-01-11 18:28:54 -080081 private static final int L3_UNICAST_TYPE = 0x20000000;
Charles Chan5b9df8d2016-03-28 22:21:40 -070082 private static final int L3_MULTICAST_TYPE = 0x60000000;
Charles Chane849c192016-01-11 18:28:54 -080083 private static final int MPLS_INTERFACE_TYPE = 0x90000000;
84 private static final int MPLS_L3VPN_SUBTYPE = 0x92000000;
85 private static final int L3_ECMP_TYPE = 0x70000000;
86 private static final int L2_FLOOD_TYPE = 0x40000000;
87
88 private static final int TYPE_MASK = 0x0fffffff;
89 private static final int SUBTYPE_MASK = 0x00ffffff;
Charles Chan5b9df8d2016-03-28 22:21:40 -070090 private static final int TYPE_VLAN_MASK = 0x0000ffff;
Charles Chane849c192016-01-11 18:28:54 -080091
92 private static final int PORT_LOWER_BITS_MASK = 0x3f;
93 private static final long PORT_HIGHER_BITS_MASK = ~PORT_LOWER_BITS_MASK;
Charles Chan188ebf52015-12-23 00:15:11 -080094
95 private final Logger log = getLogger(getClass());
96 private ServiceDirectory serviceDirectory;
97 protected GroupService groupService;
Saurav Das8be4e3a2016-03-11 17:19:07 -080098 protected StorageService storageService;
Charles Chan188ebf52015-12-23 00:15:11 -080099
100 private DeviceId deviceId;
101 private FlowObjectiveStore flowObjectiveStore;
102 private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
103 private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
104 private ScheduledExecutorService groupChecker =
105 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
106
107 // index number for group creation
Saurav Das8be4e3a2016-03-11 17:19:07 -0800108 private AtomicCounter nextIndex;
Charles Chan188ebf52015-12-23 00:15:11 -0800109
110 // local stores for port-vlan mapping
111 protected Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<>();
112 protected Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<>();
113
114 // local store for pending bucketAdds - by design there can only be one
115 // pending bucket for a group
116 protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
117
118 protected void init(DeviceId deviceId, PipelinerContext context) {
119 this.deviceId = deviceId;
120 this.flowObjectiveStore = context.store();
121 this.serviceDirectory = context.directory();
122 this.groupService = serviceDirectory.get(GroupService.class);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800123 this.storageService = serviceDirectory.get(StorageService.class);
124 this.nextIndex = storageService.atomicCounterBuilder()
125 .withName("group-id-index-counter")
126 .build()
127 .asAtomicCounter();
Charles Chan188ebf52015-12-23 00:15:11 -0800128
129 pendingNextObjectives = CacheBuilder.newBuilder()
130 .expireAfterWrite(20, TimeUnit.SECONDS)
131 .removalListener((
132 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
133 if (notification.getCause() == RemovalCause.EXPIRED) {
134 notification.getValue().forEach(ofdpaNextGrp ->
Charles Chan361154b2016-03-24 10:23:39 -0700135 Ofdpa2Pipeline.fail(ofdpaNextGrp.nextObj,
Charles Chan188ebf52015-12-23 00:15:11 -0800136 ObjectiveError.GROUPINSTALLATIONFAILED));
137
138 }
139 }).build();
140 pendingGroups = new ConcurrentHashMap<>();
141 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
142
143 groupService.addListener(new InnerGroupListener());
144 }
145
Saurav Das8be4e3a2016-03-11 17:19:07 -0800146 //////////////////////////////////////
147 // Group Creation
148 //////////////////////////////////////
149
Charles Chan188ebf52015-12-23 00:15:11 -0800150 protected void addGroup(NextObjective nextObjective) {
151 switch (nextObjective.type()) {
152 case SIMPLE:
153 Collection<TrafficTreatment> treatments = nextObjective.next();
154 if (treatments.size() != 1) {
155 log.error("Next Objectives of type Simple should only have a "
156 + "single Traffic Treatment. Next Objective Id:{}",
157 nextObjective.id());
Charles Chan361154b2016-03-24 10:23:39 -0700158 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800159 return;
160 }
161 processSimpleNextObjective(nextObjective);
162 break;
163 case BROADCAST:
164 processBroadcastNextObjective(nextObjective);
165 break;
166 case HASHED:
167 processHashedNextObjective(nextObjective);
168 break;
169 case FAILOVER:
Charles Chan361154b2016-03-24 10:23:39 -0700170 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
Charles Chan188ebf52015-12-23 00:15:11 -0800171 log.warn("Unsupported next objective type {}", nextObjective.type());
172 break;
173 default:
Charles Chan361154b2016-03-24 10:23:39 -0700174 Ofdpa2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
Charles Chan188ebf52015-12-23 00:15:11 -0800175 log.warn("Unknown next objective type {}", nextObjective.type());
176 }
177 }
178
179 /**
180 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
181 * a chain of groups. The simple Next Objective passed
182 * in by the application has to be broken up into a group chain
183 * comprising of an L3 Unicast Group that points to an L2 Interface
184 * Group which in-turn points to an output port. In some cases, the simple
185 * next Objective can just be an L2 interface without the need for chaining.
186 *
187 * @param nextObj the nextObjective of type SIMPLE
188 */
189 private void processSimpleNextObjective(NextObjective nextObj) {
190 TrafficTreatment treatment = nextObj.next().iterator().next();
191 // determine if plain L2 or L3->L2
192 boolean plainL2 = true;
193 for (Instruction ins : treatment.allInstructions()) {
194 if (ins.type() == Instruction.Type.L2MODIFICATION) {
195 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
196 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
197 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
198 plainL2 = false;
199 break;
200 }
201 }
202 }
203
204 if (plainL2) {
205 createL2InterfaceGroup(nextObj);
206 return;
207 }
208
209 // break up simple next objective to GroupChain objects
210 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
Saurav Das8be4e3a2016-03-11 17:19:07 -0800211 nextObj.appId(), false,
212 nextObj.meta());
Charles Chan188ebf52015-12-23 00:15:11 -0800213 if (groupInfo == null) {
214 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
215 return;
216 }
217 // create object for local and distributed storage
218 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700219 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
220 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800221 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
222 Collections.singletonList(gkeyChain),
223 nextObj);
224
225 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
Charles Chan5b9df8d2016-03-28 22:21:40 -0700226 updatePendingNextObjective(groupInfo.nextGroupDesc.appCookie(), ofdpaGrp);
Charles Chan188ebf52015-12-23 00:15:11 -0800227
228 // now we are ready to send the l2 groupDescription (inner), as all the stores
229 // that will get async replies have been updated. By waiting to update
230 // the stores, we prevent nasty race conditions.
Charles Chan5b9df8d2016-03-28 22:21:40 -0700231 groupService.addGroup(groupInfo.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800232 }
233
Charles Chan188ebf52015-12-23 00:15:11 -0800234 /**
235 * Creates a simple L2 Interface Group.
236 *
237 * @param nextObj the next Objective
238 */
239 private void createL2InterfaceGroup(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700240 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
241 if (assignedVlan == null) {
242 log.warn("VLAN ID required by simple next obj is missing. Abort.");
243 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800244 return;
245 }
246
Charles Chan5b9df8d2016-03-28 22:21:40 -0700247 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
Charles Chan188ebf52015-12-23 00:15:11 -0800248
Charles Chan5b9df8d2016-03-28 22:21:40 -0700249 // There is only one L2 interface group in this case
250 GroupDescription l2InterfaceGroupDesc = groupInfos.get(0).innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800251
Charles Chan5b9df8d2016-03-28 22:21:40 -0700252 // Put all dependency information into allGroupKeys
253 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
254 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
255 gkeyChain.addFirst(l2InterfaceGroupDesc.appCookie());
256 allGroupKeys.add(gkeyChain);
Charles Chan188ebf52015-12-23 00:15:11 -0800257
Charles Chan5b9df8d2016-03-28 22:21:40 -0700258 // Point the next objective to this group
259 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
260 updatePendingNextObjective(l2InterfaceGroupDesc.appCookie(), ofdpaGrp);
261
262 // Start installing the inner-most group
263 groupService.addGroup(l2InterfaceGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800264 }
265
266 /**
267 * Creates one of two possible group-chains from the treatment
268 * passed in. Depending on the MPLS boolean, this method either creates
269 * an L3Unicast Group --> L2Interface Group, if mpls is false;
270 * or MPLSInterface Group --> L2Interface Group, if mpls is true;
271 * The returned 'inner' group description is always the L2 Interface group.
272 *
273 * @param treatment that needs to be broken up to create the group chain
274 * @param nextId of the next objective that needs this group chain
275 * @param appId of the application that sent this next objective
276 * @param mpls determines if L3Unicast or MPLSInterface group is created
277 * @param meta metadata passed in by the application as part of the nextObjective
278 * @return GroupInfo containing the GroupDescription of the
279 * L2Interface group(inner) and the GroupDescription of the (outer)
280 * L3Unicast/MPLSInterface group. May return null if there is an
281 * error in processing the chain
282 */
283 private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800284 ApplicationId appId, boolean mpls,
285 TrafficSelector meta) {
Charles Chan188ebf52015-12-23 00:15:11 -0800286 // for the l2interface group, get vlan and port info
287 // for the outer group, get the src/dst mac, and vlan info
288 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
289 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
290 VlanId vlanid = null;
291 long portNum = 0;
292 boolean setVlan = false, popVlan = false;
Charles Chand0fd5dc2016-02-16 23:14:49 -0800293 MacAddress srcMac = MacAddress.ZERO;
Charles Chan5270ed02016-01-30 23:22:37 -0800294 MacAddress dstMac = MacAddress.ZERO;
Charles Chan188ebf52015-12-23 00:15:11 -0800295 for (Instruction ins : treatment.allInstructions()) {
296 if (ins.type() == Instruction.Type.L2MODIFICATION) {
297 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
298 switch (l2ins.subtype()) {
299 case ETH_DST:
Charles Chan5270ed02016-01-30 23:22:37 -0800300 dstMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
301 outerTtb.setEthDst(dstMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800302 break;
303 case ETH_SRC:
Charles Chand0fd5dc2016-02-16 23:14:49 -0800304 srcMac = ((L2ModificationInstruction.ModEtherInstruction) l2ins).mac();
305 outerTtb.setEthSrc(srcMac);
Charles Chan188ebf52015-12-23 00:15:11 -0800306 break;
307 case VLAN_ID:
308 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
309 outerTtb.setVlanId(vlanid);
310 setVlan = true;
311 break;
312 case VLAN_POP:
313 innerTtb.popVlan();
314 popVlan = true;
315 break;
316 case DEC_MPLS_TTL:
317 case MPLS_LABEL:
318 case MPLS_POP:
319 case MPLS_PUSH:
320 case VLAN_PCP:
321 case VLAN_PUSH:
322 default:
323 break;
324 }
325 } else if (ins.type() == Instruction.Type.OUTPUT) {
326 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
327 innerTtb.add(ins);
328 } else {
329 log.warn("Driver does not handle this type of TrafficTreatment"
330 + " instruction in nextObjectives: {}", ins.type());
331 }
332 }
333
334 if (vlanid == null && meta != null) {
335 // use metadata if available
336 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
337 if (vidCriterion != null) {
338 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
339 }
340 // if vlan is not set, use the vlan in metadata for outerTtb
341 if (vlanid != null && !setVlan) {
342 outerTtb.setVlanId(vlanid);
343 }
344 }
345
346 if (vlanid == null) {
347 log.error("Driver cannot process an L2/L3 group chain without "
348 + "egress vlan information for dev: {} port:{}",
349 deviceId, portNum);
350 return null;
351 }
352
353 if (!setVlan && !popVlan) {
354 // untagged outgoing port
355 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
356 temp.popVlan();
357 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
358 innerTtb = temp;
359 }
360
361 // assemble information for ofdpa l2interface group
Charles Chane849c192016-01-11 18:28:54 -0800362 int l2groupId = L2_INTERFACE_TYPE | (vlanid.toShort() << 16) | (int) portNum;
Saurav Das8be4e3a2016-03-11 17:19:07 -0800363 // a globally unique groupkey that is different for ports in the same device,
Charles Chan188ebf52015-12-23 00:15:11 -0800364 // but different for the same portnumber on different devices. Also different
365 // for the various group-types created out of the same next objective.
Charles Chane849c192016-01-11 18:28:54 -0800366 int l2gk = l2InterfaceGroupKey(deviceId, vlanid, portNum);
Charles Chan361154b2016-03-24 10:23:39 -0700367 final GroupKey l2groupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
Charles Chan188ebf52015-12-23 00:15:11 -0800368
369 // assemble information for outer group
370 GroupDescription outerGrpDesc = null;
371 if (mpls) {
372 // outer group is MPLSInteface
Saurav Das8be4e3a2016-03-11 17:19:07 -0800373 int mplsInterfaceIndex = getNextAvailableIndex();
374 int mplsgroupId = MPLS_INTERFACE_TYPE | (SUBTYPE_MASK & mplsInterfaceIndex);
375 final GroupKey mplsgroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700376 Ofdpa2Pipeline.appKryo.serialize(mplsInterfaceIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800377 outerTtb.group(new DefaultGroupId(l2groupId));
378 // create the mpls-interface group description to wait for the
379 // l2 interface group to be processed
380 GroupBucket mplsinterfaceGroupBucket =
381 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
382 outerGrpDesc = new DefaultGroupDescription(
383 deviceId,
384 GroupDescription.Type.INDIRECT,
385 new GroupBuckets(Collections.singletonList(
386 mplsinterfaceGroupBucket)),
387 mplsgroupkey,
388 mplsgroupId,
389 appId);
390 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
391 deviceId, Integer.toHexString(mplsgroupId),
392 mplsgroupkey, nextId);
393 } else {
394 // outer group is L3Unicast
Saurav Das8be4e3a2016-03-11 17:19:07 -0800395 int l3unicastIndex = getNextAvailableIndex();
396 int l3groupId = L3_UNICAST_TYPE | (TYPE_MASK & l3unicastIndex);
397 final GroupKey l3groupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700398 Ofdpa2Pipeline.appKryo.serialize(l3unicastIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800399 outerTtb.group(new DefaultGroupId(l2groupId));
400 // create the l3unicast group description to wait for the
401 // l2 interface group to be processed
402 GroupBucket l3unicastGroupBucket =
403 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
404 outerGrpDesc = new DefaultGroupDescription(
405 deviceId,
406 GroupDescription.Type.INDIRECT,
407 new GroupBuckets(Collections.singletonList(
408 l3unicastGroupBucket)),
409 l3groupkey,
410 l3groupId,
411 appId);
412 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
413 deviceId, Integer.toHexString(l3groupId),
414 l3groupkey, nextId);
415 }
416
417 // store l2groupkey with the groupChainElem for the outer-group that depends on it
418 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
419 updatePendingGroups(l2groupkey, gce);
420
421 // create group description for the inner l2interfacegroup
Charles Chan5b9df8d2016-03-28 22:21:40 -0700422 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800423 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
424 GroupDescription l2groupDescription =
425 new DefaultGroupDescription(
426 deviceId,
427 GroupDescription.Type.INDIRECT,
428 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700429 l2InterfaceGroupBucket)),
Charles Chan188ebf52015-12-23 00:15:11 -0800430 l2groupkey,
431 l2groupId,
432 appId);
433 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
434 deviceId, Integer.toHexString(l2groupId),
435 l2groupkey, nextId);
436 return new GroupInfo(l2groupDescription, outerGrpDesc);
437
438 }
439
440 /**
441 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
442 * a chain of groups. The broadcast Next Objective passed in by the application
443 * has to be broken up into a group chain comprising of an
444 * L2 Flood group whose buckets point to L2 Interface groups.
445 *
446 * @param nextObj the nextObjective of type BROADCAST
447 */
448 private void processBroadcastNextObjective(NextObjective nextObj) {
Charles Chan5b9df8d2016-03-28 22:21:40 -0700449 VlanId assignedVlan = Ofdpa2Pipeline.readVlanFromSelector(nextObj.meta());
450 if (assignedVlan == null) {
451 log.warn("VLAN ID required by broadcast next obj is missing. Abort.");
452 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
Charles Chan188ebf52015-12-23 00:15:11 -0800453 return;
454 }
Charles Chan188ebf52015-12-23 00:15:11 -0800455
Charles Chan5b9df8d2016-03-28 22:21:40 -0700456 List<GroupInfo> groupInfos = prepareL2InterfaceGroup(nextObj, assignedVlan);
457
458 IpPrefix ipDst = Ofdpa2Pipeline.readIpDstFromSelector(nextObj.meta());
459 if (ipDst != null) {
460 if (ipDst.isMulticast()) {
461 createL3MulticastGroup(nextObj, assignedVlan, groupInfos);
462 } else {
463 log.warn("Broadcast NextObj with non-multicast IP address {}", nextObj);
464 Ofdpa2Pipeline.fail(nextObj, ObjectiveError.BADPARAMS);
465 return;
466 }
467 } else {
468 createL2FloodGroup(nextObj, assignedVlan, groupInfos);
469 }
470 }
471
472 private List<GroupInfo> prepareL2InterfaceGroup(NextObjective nextObj, VlanId assignedVlan) {
473 ImmutableList.Builder<GroupInfo> groupInfoBuilder = ImmutableList.builder();
474
475 // break up broadcast next objective to multiple groups
476 Collection<TrafficTreatment> buckets = nextObj.next();
477
Charles Chan188ebf52015-12-23 00:15:11 -0800478 // each treatment is converted to an L2 interface group
Charles Chan188ebf52015-12-23 00:15:11 -0800479 for (TrafficTreatment treatment : buckets) {
480 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
481 PortNumber portNum = null;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700482 VlanId egressVlan = null;
Charles Chan188ebf52015-12-23 00:15:11 -0800483 // ensure that the only allowed treatments are pop-vlan and output
484 for (Instruction ins : treatment.allInstructions()) {
485 if (ins.type() == Instruction.Type.L2MODIFICATION) {
486 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
487 switch (l2ins.subtype()) {
488 case VLAN_POP:
489 newTreatment.add(l2ins);
490 break;
Charles Chan5b9df8d2016-03-28 22:21:40 -0700491 case VLAN_ID:
492 egressVlan = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
493 break;
Charles Chan188ebf52015-12-23 00:15:11 -0800494 default:
495 log.debug("action {} not permitted for broadcast nextObj",
496 l2ins.subtype());
497 break;
498 }
499 } else if (ins.type() == Instruction.Type.OUTPUT) {
500 portNum = ((Instructions.OutputInstruction) ins).port();
501 newTreatment.add(ins);
502 } else {
Charles Chane849c192016-01-11 18:28:54 -0800503 log.debug("TrafficTreatment of type {} not permitted in " +
504 " broadcast nextObjective", ins.type());
Charles Chan188ebf52015-12-23 00:15:11 -0800505 }
506 }
507
Charles Chan188ebf52015-12-23 00:15:11 -0800508 // assemble info for l2 interface group
Charles Chan5b9df8d2016-03-28 22:21:40 -0700509 VlanId l2InterfaceGroupVlan =
510 (egressVlan != null && !assignedVlan.equals(egressVlan)) ?
511 egressVlan : assignedVlan;
512 int l2gk = l2InterfaceGroupKey(deviceId, l2InterfaceGroupVlan, portNum.toLong());
513 final GroupKey l2InterfaceGroupKey =
514 new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2gk));
515 int l2InterfaceGroupId = L2_INTERFACE_TYPE | (l2InterfaceGroupVlan.toShort() << 16) |
Charles Chan188ebf52015-12-23 00:15:11 -0800516 (int) portNum.toLong();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700517 GroupBucket l2InterfaceGroupBucket =
Charles Chan188ebf52015-12-23 00:15:11 -0800518 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
Charles Chan5b9df8d2016-03-28 22:21:40 -0700519 GroupDescription l2InterfaceGroupDescription =
Charles Chan188ebf52015-12-23 00:15:11 -0800520 new DefaultGroupDescription(
521 deviceId,
522 GroupDescription.Type.INDIRECT,
523 new GroupBuckets(Collections.singletonList(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700524 l2InterfaceGroupBucket)),
525 l2InterfaceGroupKey,
526 l2InterfaceGroupId,
Charles Chan188ebf52015-12-23 00:15:11 -0800527 nextObj.appId());
528 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700529 deviceId, Integer.toHexString(l2InterfaceGroupId),
530 l2InterfaceGroupKey, nextObj.id());
Charles Chan188ebf52015-12-23 00:15:11 -0800531
Charles Chan5b9df8d2016-03-28 22:21:40 -0700532 groupInfoBuilder.add(new GroupInfo(l2InterfaceGroupDescription,
533 l2InterfaceGroupDescription));
Charles Chan188ebf52015-12-23 00:15:11 -0800534 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700535 return groupInfoBuilder.build();
536 }
Charles Chan188ebf52015-12-23 00:15:11 -0800537
Charles Chan5b9df8d2016-03-28 22:21:40 -0700538 private void createL2FloodGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
Charles Chan188ebf52015-12-23 00:15:11 -0800539 // assemble info for l2 flood group
Saurav Das0fd79d92016-03-07 10:58:36 -0800540 // since there can be only one flood group for a vlan, its index is always the same - 0
541 Integer l2floodgroupId = L2_FLOOD_TYPE | (vlanId.toShort() << 16);
Saurav Das8be4e3a2016-03-11 17:19:07 -0800542 int l2floodgk = getNextAvailableIndex();
Charles Chan361154b2016-03-24 10:23:39 -0700543 final GroupKey l2floodgroupkey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l2floodgk));
Charles Chan5b9df8d2016-03-28 22:21:40 -0700544
Charles Chan188ebf52015-12-23 00:15:11 -0800545 // collection of group buckets pointing to all the l2 interface groups
Charles Chan5b9df8d2016-03-28 22:21:40 -0700546 List<GroupBucket> l2floodBuckets = Lists.newArrayList();
547 groupInfos.forEach(groupInfo -> {
548 GroupDescription l2intGrpDesc = groupInfo.nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800549 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
550 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
551 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
552 l2floodBuckets.add(abucket);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700553 });
Charles Chan188ebf52015-12-23 00:15:11 -0800554 // create the l2flood group-description to wait for all the
555 // l2interface groups to be processed
556 GroupDescription l2floodGroupDescription =
557 new DefaultGroupDescription(
558 deviceId,
559 GroupDescription.Type.ALL,
560 new GroupBuckets(l2floodBuckets),
561 l2floodgroupkey,
562 l2floodgroupId,
563 nextObj.appId());
Charles Chan188ebf52015-12-23 00:15:11 -0800564 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
565 deviceId, Integer.toHexString(l2floodgroupId),
566 l2floodgroupkey, nextObj.id());
567
Charles Chan5b9df8d2016-03-28 22:21:40 -0700568 // Put all dependency information into allGroupKeys
569 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
570 groupInfos.forEach(groupInfo -> {
571 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
572 // In this case we should have L2 interface group only
573 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
574 gkeyChain.addFirst(l2floodgroupkey);
575 allGroupKeys.add(gkeyChain);
576 });
Charles Chan188ebf52015-12-23 00:15:11 -0800577
Charles Chan5b9df8d2016-03-28 22:21:40 -0700578 // Point the next objective to this group
579 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
Charles Chan188ebf52015-12-23 00:15:11 -0800580 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
581
Charles Chan5b9df8d2016-03-28 22:21:40 -0700582 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
583 groupInfos.size(), false);
584 groupInfos.forEach(groupInfo -> {
585 // Point this group to the next group
586 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), gce);
587 // Start installing the inner-most group
588 groupService.addGroup(groupInfo.innerMostGroupDesc);
589 });
590 }
591
592 private void createL3MulticastGroup(NextObjective nextObj, VlanId vlanId, List<GroupInfo> groupInfos) {
593 List<GroupBucket> l3McastBuckets = new ArrayList<>();
594 groupInfos.forEach(groupInfo -> {
595 // Points to L3 interface group if there is one.
596 // Otherwise points to L2 interface group directly.
597 GroupDescription nextGroupDesc = (groupInfo.nextGroupDesc != null) ?
598 groupInfo.nextGroupDesc : groupInfo.innerMostGroupDesc;
599 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
600 ttb.group(new DefaultGroupId(nextGroupDesc.givenGroupId()));
601 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
602 l3McastBuckets.add(abucket);
603 });
604
605 int l3MulticastIndex = getNextAvailableIndex();
606 int l3MulticastGroupId = L3_MULTICAST_TYPE | vlanId.toShort() << 16 | (TYPE_VLAN_MASK & l3MulticastIndex);
607 final GroupKey l3MulticastGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3MulticastIndex));
608
609 GroupDescription l3MulticastGroupDesc = new DefaultGroupDescription(deviceId,
610 GroupDescription.Type.ALL,
611 new GroupBuckets(l3McastBuckets),
612 l3MulticastGroupKey,
613 l3MulticastGroupId,
614 nextObj.appId());
615
616 // Put all dependency information into allGroupKeys
617 List<Deque<GroupKey>> allGroupKeys = Lists.newArrayList();
618 groupInfos.forEach(groupInfo -> {
619 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
620 gkeyChain.addFirst(groupInfo.innerMostGroupDesc.appCookie());
621 // Add L3 interface group to the chain if there is one.
622 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
623 gkeyChain.addFirst(groupInfo.nextGroupDesc.appCookie());
624 }
625 gkeyChain.addFirst(l3MulticastGroupKey);
626 allGroupKeys.add(gkeyChain);
627 });
628
629 // Point the next objective to this group
630 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
631 updatePendingNextObjective(l3MulticastGroupKey, ofdpaGrp);
632
633 GroupChainElem outerGce = new GroupChainElem(l3MulticastGroupDesc,
634 groupInfos.size(), false);
635 groupInfos.forEach(groupInfo -> {
636 // Point this group (L3 multicast) to the next group
637 updatePendingGroups(groupInfo.nextGroupDesc.appCookie(), outerGce);
638
639 // Point next group to inner-most group, if any
640 if (!groupInfo.nextGroupDesc.equals(groupInfo.innerMostGroupDesc)) {
641 GroupChainElem innerGce = new GroupChainElem(groupInfo.nextGroupDesc,
642 1, false);
643 updatePendingGroups(groupInfo.innerMostGroupDesc.appCookie(), innerGce);
644 }
645
646 // Start installing the inner-most group
647 groupService.addGroup(groupInfo.innerMostGroupDesc);
648 });
Charles Chan188ebf52015-12-23 00:15:11 -0800649 }
650
Charles Chan188ebf52015-12-23 00:15:11 -0800651 /**
652 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
653 * a chain of groups. The hashed Next Objective passed in by the application
654 * has to be broken up into a group chain comprising of an
655 * L3 ECMP group as the top level group. Buckets of this group can point
656 * to a variety of groups in a group chain, depending on the whether
657 * MPLS labels are being pushed or not.
658 * <p>
659 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
660 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
661 * check the nextObjective meta to see what is matching before being
662 * sent to this nextObjective.
663 *
664 * @param nextObj the nextObjective of type HASHED
665 */
666 private void processHashedNextObjective(NextObjective nextObj) {
667 // storage for all group keys in the chain of groups created
668 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
669 List<GroupInfo> unsentGroups = new ArrayList<>();
670 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
671
672 // now we can create the outermost L3 ECMP group
673 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
674 for (GroupInfo gi : unsentGroups) {
675 // create ECMP bucket to point to the outer group
676 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700677 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800678 GroupBucket sbucket = DefaultGroupBucket
679 .createSelectGroupBucket(ttb.build());
680 l3ecmpGroupBuckets.add(sbucket);
681 }
Saurav Das8be4e3a2016-03-11 17:19:07 -0800682 int l3ecmpIndex = getNextAvailableIndex();
683 int l3ecmpGroupId = L3_ECMP_TYPE | (TYPE_MASK & l3ecmpIndex);
684 GroupKey l3ecmpGroupKey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700685 Ofdpa2Pipeline.appKryo.serialize(l3ecmpIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800686 GroupDescription l3ecmpGroupDesc =
687 new DefaultGroupDescription(
688 deviceId,
689 GroupDescription.Type.SELECT,
690 new GroupBuckets(l3ecmpGroupBuckets),
691 l3ecmpGroupKey,
692 l3ecmpGroupId,
693 nextObj.appId());
694 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
695 l3ecmpGroupBuckets.size(),
696 false);
697
698 // create objects for local and distributed storage
699 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
700 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
701
702 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
703 // that depends on it
704 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
705
706 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
707 deviceId, Integer.toHexString(l3ecmpGroupId),
708 l3ecmpGroupKey, nextObj.id());
709 // finally we are ready to send the innermost groups
710 for (GroupInfo gi : unsentGroups) {
711 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700712 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
713 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
714 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800715 }
716
717 }
718
719 /**
720 * Creates group chains for all buckets in a hashed group, and stores the
721 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
722 * should be empty.
723 * <p>
724 * Does not create the top level ECMP group. Does not actually send the
725 * groups to the groupService.
726 *
727 * @param nextObj the Next Objective with buckets that need to be converted
728 * to group chains
729 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
730 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
731 */
732 private void createHashBucketChains(NextObjective nextObj,
Saurav Das8be4e3a2016-03-11 17:19:07 -0800733 List<Deque<GroupKey>> allGroupKeys,
734 List<GroupInfo> unsentGroups) {
Charles Chan188ebf52015-12-23 00:15:11 -0800735 // break up hashed next objective to multiple groups
736 Collection<TrafficTreatment> buckets = nextObj.next();
737
738 for (TrafficTreatment bucket : buckets) {
739 //figure out how many labels are pushed in each bucket
740 int labelsPushed = 0;
741 MplsLabel innermostLabel = null;
742 for (Instruction ins : bucket.allInstructions()) {
743 if (ins.type() == Instruction.Type.L2MODIFICATION) {
744 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
745 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
746 labelsPushed++;
747 }
748 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
749 if (innermostLabel == null) {
Ray Milkey125572b2016-02-22 16:48:17 -0800750 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).label();
Charles Chan188ebf52015-12-23 00:15:11 -0800751 }
752 }
753 }
754 }
755
756 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
757 // XXX we only deal with 0 and 1 label push right now
758 if (labelsPushed == 0) {
759 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
760 nextObj.appId(), false,
761 nextObj.meta());
762 if (nolabelGroupInfo == null) {
763 log.error("Could not process nextObj={} in dev:{}",
764 nextObj.id(), deviceId);
765 return;
766 }
Charles Chan5b9df8d2016-03-28 22:21:40 -0700767 gkeyChain.addFirst(nolabelGroupInfo.innerMostGroupDesc.appCookie());
768 gkeyChain.addFirst(nolabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800769
770 // we can't send the inner group description yet, as we have to
771 // create the dependent ECMP group first. So we store..
772 unsentGroups.add(nolabelGroupInfo);
773
774 } else if (labelsPushed == 1) {
775 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
776 nextObj.appId(), true,
777 nextObj.meta());
778 if (onelabelGroupInfo == null) {
779 log.error("Could not process nextObj={} in dev:{}",
780 nextObj.id(), deviceId);
781 return;
782 }
783 // we need to add another group to this chain - the L3VPN group
784 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
785 l3vpnTtb.pushMpls()
786 .setMpls(innermostLabel)
787 .setMplsBos(true)
788 .copyTtlOut()
789 .group(new DefaultGroupId(
Charles Chan5b9df8d2016-03-28 22:21:40 -0700790 onelabelGroupInfo.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800791 GroupBucket l3vpnGrpBkt =
792 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
Saurav Das8be4e3a2016-03-11 17:19:07 -0800793 int l3vpnIndex = getNextAvailableIndex();
794 int l3vpngroupId = MPLS_L3VPN_SUBTYPE | (SUBTYPE_MASK & l3vpnIndex);
795 GroupKey l3vpngroupkey = new DefaultGroupKey(
Charles Chan361154b2016-03-24 10:23:39 -0700796 Ofdpa2Pipeline.appKryo.serialize(l3vpnIndex));
Charles Chan188ebf52015-12-23 00:15:11 -0800797 GroupDescription l3vpnGroupDesc =
798 new DefaultGroupDescription(
799 deviceId,
800 GroupDescription.Type.INDIRECT,
801 new GroupBuckets(Collections.singletonList(
802 l3vpnGrpBkt)),
803 l3vpngroupkey,
804 l3vpngroupId,
805 nextObj.appId());
806 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
Charles Chan5b9df8d2016-03-28 22:21:40 -0700807 updatePendingGroups(onelabelGroupInfo.nextGroupDesc.appCookie(), l3vpnGce);
Charles Chan188ebf52015-12-23 00:15:11 -0800808
Charles Chan5b9df8d2016-03-28 22:21:40 -0700809 gkeyChain.addFirst(onelabelGroupInfo.innerMostGroupDesc.appCookie());
810 gkeyChain.addFirst(onelabelGroupInfo.nextGroupDesc.appCookie());
Charles Chan188ebf52015-12-23 00:15:11 -0800811 gkeyChain.addFirst(l3vpngroupkey);
812
813 //now we can replace the outerGrpDesc with the one we just created
Charles Chan5b9df8d2016-03-28 22:21:40 -0700814 onelabelGroupInfo.nextGroupDesc = l3vpnGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -0800815
816 // we can't send the innermost group yet, as we have to create
817 // the dependent ECMP group first. So we store ...
818 unsentGroups.add(onelabelGroupInfo);
819
820 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
821 deviceId, Integer.toHexString(l3vpngroupId),
822 l3vpngroupkey, nextObj.id());
823
824 } else {
825 log.warn("Driver currently does not handle more than 1 MPLS "
826 + "labels. Not processing nextObjective {}", nextObj.id());
827 return;
828 }
829
830 // all groups in this chain
831 allGroupKeys.add(gkeyChain);
832 }
833 }
834
Saurav Das8be4e3a2016-03-11 17:19:07 -0800835 //////////////////////////////////////
836 // Group Editing
837 //////////////////////////////////////
838
Charles Chan188ebf52015-12-23 00:15:11 -0800839 /**
840 * Adds a bucket to the top level group of a group-chain, and creates the chain.
841 *
842 * @param nextObjective the next group to add a bucket to
843 * @param next the representation of the existing group-chain for this next objective
844 */
845 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
846 if (nextObjective.type() != NextObjective.Type.HASHED) {
847 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
848 nextObjective.type(), deviceId, nextObjective.id());
849 return;
850 }
851 if (nextObjective.next().size() > 1) {
852 log.warn("Only one bucket can be added at a time");
853 return;
854 }
855 // storage for all group keys in the chain of groups created
856 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
857 List<GroupInfo> unsentGroups = new ArrayList<>();
858 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
859
860 // now we can create the outermost L3 ECMP group bucket to add
861 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
862 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
Charles Chan5b9df8d2016-03-28 22:21:40 -0700863 ttb.group(new DefaultGroupId(gi.nextGroupDesc.givenGroupId()));
Charles Chan188ebf52015-12-23 00:15:11 -0800864 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
865
866 // recreate the original L3 ECMP group id and description
Charles Chane849c192016-01-11 18:28:54 -0800867 int l3ecmpGroupId = L3_ECMP_TYPE | nextObjective.id() << 12;
Charles Chan361154b2016-03-24 10:23:39 -0700868 GroupKey l3ecmpGroupKey = new DefaultGroupKey(Ofdpa2Pipeline.appKryo.serialize(l3ecmpGroupId));
Charles Chan188ebf52015-12-23 00:15:11 -0800869
870 // Although GroupDescriptions are not necessary for adding buckets to
871 // existing groups, we use one in the GroupChainElem. When the latter is
872 // processed, the info will be extracted for the bucketAdd call to groupService
873 GroupDescription l3ecmpGroupDesc =
874 new DefaultGroupDescription(
875 deviceId,
876 GroupDescription.Type.SELECT,
877 new GroupBuckets(Collections.singletonList(sbucket)),
878 l3ecmpGroupKey,
879 l3ecmpGroupId,
880 nextObjective.appId());
881 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
882
883 // update original NextGroup with new bucket-chain
884 // don't need to update pendingNextObjectives -- group already exists
885 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
886 newBucketChain.addFirst(l3ecmpGroupKey);
Charles Chan361154b2016-03-24 10:23:39 -0700887 List<Deque<GroupKey>> allOriginalKeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800888 allOriginalKeys.add(newBucketChain);
889 flowObjectiveStore.putNextGroup(nextObjective.id(),
890 new OfdpaNextGroup(allOriginalKeys, nextObjective));
891
892 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
893 deviceId, Integer.toHexString(l3ecmpGroupId),
894 l3ecmpGroupKey, nextObjective.id());
895 // send the innermost group
896 log.debug("Sending innermost group {} in group chain on device {} ",
Charles Chan5b9df8d2016-03-28 22:21:40 -0700897 Integer.toHexString(gi.innerMostGroupDesc.givenGroupId()), deviceId);
898 updatePendingGroups(gi.nextGroupDesc.appCookie(), l3ecmpGce);
899 groupService.addGroup(gi.innerMostGroupDesc);
Charles Chan188ebf52015-12-23 00:15:11 -0800900
901 }
902
903 /**
904 * Removes the bucket in the top level group of a possible group-chain. Does
905 * not remove the groups in a group-chain pointed to by this bucket, as they
906 * may be in use (referenced by other groups) elsewhere.
907 *
908 * @param nextObjective the next group to remove a bucket from
909 * @param next the representation of the existing group-chain for this next objective
910 */
911 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
912 if (nextObjective.type() != NextObjective.Type.HASHED) {
913 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
914 nextObjective.type(), deviceId, nextObjective.id());
915 return;
916 }
917 Collection<TrafficTreatment> treatments = nextObjective.next();
918 TrafficTreatment treatment = treatments.iterator().next();
919 // find the bucket to remove by noting the outport, and figuring out the
920 // top-level group in the group-chain that indirectly references the port
921 PortNumber outport = null;
922 for (Instruction ins : treatment.allInstructions()) {
923 if (ins instanceof Instructions.OutputInstruction) {
924 outport = ((Instructions.OutputInstruction) ins).port();
925 break;
926 }
927 }
928 if (outport == null) {
929 log.error("next objective {} has no outport", nextObjective.id());
930 return;
931 }
932
Charles Chan361154b2016-03-24 10:23:39 -0700933 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -0800934 Deque<GroupKey> foundChain = null;
935 int index = 0;
936 for (Deque<GroupKey> gkeys : allgkeys) {
937 GroupKey groupWithPort = gkeys.peekLast();
938 Group group = groupService.getGroup(deviceId, groupWithPort);
939 if (group == null) {
940 log.warn("Inconsistent group chain");
941 continue;
942 }
943 // last group in group chain should have a single bucket pointing to port
944 List<Instruction> lastIns = group.buckets().buckets().iterator()
945 .next().treatment().allInstructions();
946 for (Instruction i : lastIns) {
947 if (i instanceof Instructions.OutputInstruction) {
948 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
949 if (lastport.equals(outport)) {
950 foundChain = gkeys;
951 break;
952 }
953 }
954 }
955 if (foundChain != null) {
956 break;
957 }
958 index++;
959 }
960 if (foundChain != null) {
961 //first groupkey is the one we want to modify
962 GroupKey modGroupKey = foundChain.peekFirst();
963 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
964 //second groupkey is the one we wish to remove the reference to
965 GroupKey pointedGroupKey = null;
966 int i = 0;
967 for (GroupKey gk : foundChain) {
968 if (i++ == 1) {
969 pointedGroupKey = gk;
970 break;
971 }
972 }
973 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
974 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
975 DefaultTrafficTreatment.builder()
976 .group(pointedGroup.id())
977 .build());
978 GroupBuckets removeBuckets = new GroupBuckets(Collections
979 .singletonList(bucket));
980 log.debug("Removing buckets from group id {} for next id {} in device {}",
981 modGroup.id(), nextObjective.id(), deviceId);
982 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
983 removeBuckets, modGroupKey,
984 nextObjective.appId());
985 //update store
986 allgkeys.remove(index);
987 flowObjectiveStore.putNextGroup(nextObjective.id(),
988 new OfdpaNextGroup(allgkeys, nextObjective));
989 } else {
990 log.warn("Could not find appropriate group-chain for removing bucket"
991 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
992 }
993 }
994
995 /**
996 * Removes all groups in multiple possible group-chains that represent the next
997 * objective.
998 *
999 * @param nextObjective the next objective to remove
1000 * @param next the NextGroup that represents the existing group-chain for
1001 * this next objective
1002 */
1003 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
Charles Chan361154b2016-03-24 10:23:39 -07001004 List<Deque<GroupKey>> allgkeys = Ofdpa2Pipeline.appKryo.deserialize(next.data());
Charles Chan188ebf52015-12-23 00:15:11 -08001005 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
1006 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
1007 flowObjectiveStore.removeNextGroup(nextObjective.id());
1008 }
1009
Saurav Das8be4e3a2016-03-11 17:19:07 -08001010 //////////////////////////////////////
1011 // Helper Methods and Classes
1012 //////////////////////////////////////
1013
1014 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
1015 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
1016 nextList.add(value);
1017 List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
1018 .putIfAbsent(key, nextList);
1019 if (ret != null) {
1020 ret.add(value);
1021 }
1022 }
1023
1024 private void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
1025 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
1026 new ConcurrentHashMap<GroupChainElem, Boolean>());
1027 gceSet.add(gce);
1028 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
1029 if (retval != null) {
1030 retval.add(gce);
1031 }
1032 }
1033
Charles Chan188ebf52015-12-23 00:15:11 -08001034 /**
1035 * Processes next element of a group chain. Assumption is that if this
1036 * group points to another group, the latter has already been created
1037 * and this driver has received notification for it. A second assumption is
1038 * that if there is another group waiting for this group then the appropriate
1039 * stores already have the information to act upon the notification for the
1040 * creation of this group.
1041 * <p>
1042 * The processing of the GroupChainElement depends on the number of groups
1043 * this element is waiting on. For all group types other than SIMPLE, a
1044 * GroupChainElement could be waiting on multiple groups.
1045 *
1046 * @param gce the group chain element to be processed next
1047 */
1048 private void processGroupChain(GroupChainElem gce) {
1049 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
1050 if (waitOnGroups != 0) {
1051 log.debug("GCE: {} not ready to be processed", gce);
1052 return;
1053 }
1054 log.debug("GCE: {} ready to be processed", gce);
1055 if (gce.addBucketToGroup) {
1056 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
1057 gce.groupDescription.appCookie(),
1058 gce.groupDescription.buckets(),
1059 gce.groupDescription.appCookie(),
1060 gce.groupDescription.appId());
1061 } else {
1062 groupService.addGroup(gce.groupDescription);
1063 }
1064 }
1065
1066 private class GroupChecker implements Runnable {
1067 @Override
1068 public void run() {
1069 Set<GroupKey> keys = pendingGroups.keySet().stream()
1070 .filter(key -> groupService.getGroup(deviceId, key) != null)
1071 .collect(Collectors.toSet());
1072 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
1073 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1074 .collect(Collectors.toSet());
1075 keys.addAll(otherkeys);
1076
1077 keys.stream().forEach(key ->
1078 processPendingGroupsOrNextObjectives(key, false));
1079 }
1080 }
1081
Saurav Das8be4e3a2016-03-11 17:19:07 -08001082 private class InnerGroupListener implements GroupListener {
1083 @Override
1084 public void event(GroupEvent event) {
1085 log.trace("received group event of type {}", event.type());
1086 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1087 GroupKey key = event.subject().appCookie();
1088 processPendingGroupsOrNextObjectives(key, true);
1089 }
1090 }
1091 }
1092
Charles Chan188ebf52015-12-23 00:15:11 -08001093 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1094 //first check for group chain
1095 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1096 if (gceSet != null) {
1097 for (GroupChainElem gce : gceSet) {
1098 log.info("Group service {} group key {} in device {}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001099 + "Processing next group in group chain with group id 0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001100 (added) ? "ADDED" : "processed",
1101 key, deviceId,
1102 Integer.toHexString(gce.groupDescription.givenGroupId()));
1103 processGroupChain(gce);
1104 }
1105 } else {
1106 // otherwise chain complete - check for waiting nextObjectives
1107 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1108 if (nextGrpList != null) {
1109 pendingNextObjectives.invalidate(key);
1110 nextGrpList.forEach(nextGrp -> {
1111 log.info("Group service {} group key {} in device:{}. "
Saurav Das0fd79d92016-03-07 10:58:36 -08001112 + "Done implementing next objective: {} <<-->> gid:0x{}",
Charles Chan188ebf52015-12-23 00:15:11 -08001113 (added) ? "ADDED" : "processed",
1114 key, deviceId, nextGrp.nextObjective().id(),
1115 Integer.toHexString(groupService.getGroup(deviceId, key)
1116 .givenGroupId()));
Charles Chan361154b2016-03-24 10:23:39 -07001117 Ofdpa2Pipeline.pass(nextGrp.nextObjective());
Charles Chan188ebf52015-12-23 00:15:11 -08001118 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1119 // check if addBuckets waiting for this completion
1120 NextObjective pendBkt = pendingBuckets
1121 .remove(nextGrp.nextObjective().id());
1122 if (pendBkt != null) {
1123 addBucketToGroup(pendBkt, nextGrp);
1124 }
1125 });
1126 }
1127 }
1128 }
1129
Saurav Das8be4e3a2016-03-11 17:19:07 -08001130 private int getNextAvailableIndex() {
1131 return (int) nextIndex.incrementAndGet();
1132 }
1133
Charles Chane849c192016-01-11 18:28:54 -08001134 /**
1135 * Returns a hash as the L2 Interface Group Key.
1136 *
1137 * Keep the lower 6-bit for port since port number usually smaller than 64.
1138 * Hash other information into remaining 28 bits.
1139 *
1140 * @param deviceId Device ID
1141 * @param vlanId VLAN ID
1142 * @param portNumber Port number
1143 * @return L2 interface group key
1144 */
1145 private int l2InterfaceGroupKey(
1146 DeviceId deviceId, VlanId vlanId, long portNumber) {
1147 int portLowerBits = (int) portNumber & PORT_LOWER_BITS_MASK;
1148 long portHigherBits = portNumber & PORT_HIGHER_BITS_MASK;
Charles Chand0fd5dc2016-02-16 23:14:49 -08001149 int hash = Objects.hash(deviceId, vlanId, portHigherBits);
Charles Chane849c192016-01-11 18:28:54 -08001150 return L2_INTERFACE_TYPE | (TYPE_MASK & hash << 6) | portLowerBits;
1151 }
1152
Charles Chan188ebf52015-12-23 00:15:11 -08001153 /**
1154 * Utility class for moving group information around.
1155 */
1156 private class GroupInfo {
Charles Chan5b9df8d2016-03-28 22:21:40 -07001157 /**
1158 * Description of the inner-most group of the group chain.
1159 * It is always an L2 interface group.
1160 */
1161 private GroupDescription innerMostGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001162
Charles Chan5b9df8d2016-03-28 22:21:40 -07001163 /**
1164 * Description of the next group in the group chain.
1165 * It can be L2 interface, L3 interface, L3 unicast, L3 VPN group.
1166 * It is possible that nextGroup is the same as the innerMostGroup.
1167 */
1168 private GroupDescription nextGroupDesc;
1169
1170 GroupInfo(GroupDescription innerMostGroupDesc, GroupDescription nextGroupDesc) {
1171 this.innerMostGroupDesc = innerMostGroupDesc;
1172 this.nextGroupDesc = nextGroupDesc;
Charles Chan188ebf52015-12-23 00:15:11 -08001173 }
1174 }
1175
1176 /**
1177 * Represents an entire group-chain that implements a Next-Objective from
1178 * the application. The objective is represented as a list of deques, where
1179 * each deque is a separate chain of groups.
1180 * <p>
1181 * For example, an ECMP group with 3 buckets, where each bucket points to
1182 * a group chain of L3 Unicast and L2 interface groups will look like this:
1183 * <ul>
1184 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1185 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1186 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1187 * </ul>
1188 * where the first element of each deque is the same, representing the
1189 * top level ECMP group, while every other element represents a unique groupKey.
1190 * <p>
1191 * Also includes information about the next objective that
1192 * resulted in this group-chain.
1193 *
1194 */
1195 protected class OfdpaNextGroup implements NextGroup {
1196 private final NextObjective nextObj;
1197 private final List<Deque<GroupKey>> gkeys;
1198
1199 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1200 this.gkeys = gkeys;
1201 this.nextObj = nextObj;
1202 }
1203
Charles Chan188ebf52015-12-23 00:15:11 -08001204 public List<Deque<GroupKey>> groupKey() {
1205 return gkeys;
1206 }
1207
1208 public NextObjective nextObjective() {
1209 return nextObj;
1210 }
1211
1212 @Override
1213 public byte[] data() {
Charles Chan361154b2016-03-24 10:23:39 -07001214 return Ofdpa2Pipeline.appKryo.serialize(gkeys);
Charles Chan188ebf52015-12-23 00:15:11 -08001215 }
1216 }
1217
1218 /**
1219 * Represents a group element that is part of a chain of groups.
1220 * Stores enough information to create a Group Description to add the group
1221 * to the switch by requesting the Group Service. Objects instantiating this
1222 * class are meant to be temporary and live as long as it is needed to wait for
1223 * preceding groups in the group chain to be created.
1224 */
1225 private class GroupChainElem {
1226 private GroupDescription groupDescription;
1227 private AtomicInteger waitOnGroups;
1228 private boolean addBucketToGroup;
1229
1230 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1231 boolean addBucketToGroup) {
1232 this.groupDescription = groupDescription;
1233 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1234 this.addBucketToGroup = addBucketToGroup;
1235 }
1236
1237 /**
1238 * This methods atomically decrements the counter for the number of
1239 * groups this GroupChainElement is waiting on, for notifications from
1240 * the Group Service. When this method returns a value of 0, this
1241 * GroupChainElement is ready to be processed.
1242 *
1243 * @return integer indication of the number of notifications being waited on
1244 */
1245 int decrementAndGetGroupsWaitedOn() {
1246 return waitOnGroups.decrementAndGet();
1247 }
1248
1249 @Override
1250 public String toString() {
1251 return (Integer.toHexString(groupDescription.givenGroupId()) +
1252 " groupKey: " + groupDescription.appCookie() +
1253 " waiting-on-groups: " + waitOnGroups.get() +
1254 " addBucketToGroup: " + addBucketToGroup +
1255 " device: " + deviceId);
1256 }
1257 }
1258}