blob: 2f95ce8ad41ce99a3427ceec1883d9ef4c8105a6 [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;
7import org.onlab.osgi.ServiceDirectory;
8import org.onlab.packet.MplsLabel;
9import org.onlab.packet.VlanId;
10import org.onosproject.core.ApplicationId;
11import org.onosproject.core.DefaultGroupId;
12import org.onosproject.net.DeviceId;
13import org.onosproject.net.PortNumber;
14import org.onosproject.net.behaviour.NextGroup;
15import org.onosproject.net.behaviour.PipelinerContext;
16import org.onosproject.net.flow.DefaultTrafficTreatment;
17import org.onosproject.net.flow.TrafficSelector;
18import org.onosproject.net.flow.TrafficTreatment;
19import org.onosproject.net.flow.criteria.Criterion;
20import org.onosproject.net.flow.criteria.VlanIdCriterion;
21import org.onosproject.net.flow.instructions.Instruction;
22import org.onosproject.net.flow.instructions.Instructions;
23import org.onosproject.net.flow.instructions.L2ModificationInstruction;
24import org.onosproject.net.flowobjective.FlowObjectiveStore;
25import org.onosproject.net.flowobjective.NextObjective;
26import org.onosproject.net.flowobjective.ObjectiveError;
27import org.onosproject.net.group.DefaultGroupBucket;
28import org.onosproject.net.group.DefaultGroupDescription;
29import org.onosproject.net.group.DefaultGroupKey;
30import org.onosproject.net.group.Group;
31import org.onosproject.net.group.GroupBucket;
32import org.onosproject.net.group.GroupBuckets;
33import org.onosproject.net.group.GroupDescription;
34import org.onosproject.net.group.GroupEvent;
35import org.onosproject.net.group.GroupKey;
36import org.onosproject.net.group.GroupListener;
37import org.onosproject.net.group.GroupService;
38import org.slf4j.Logger;
39
40import java.util.ArrayDeque;
41import java.util.ArrayList;
42import java.util.Collection;
43import java.util.Collections;
44import java.util.Deque;
45import java.util.List;
46import java.util.Map;
47import java.util.Set;
48import java.util.concurrent.ConcurrentHashMap;
49import java.util.concurrent.CopyOnWriteArrayList;
50import java.util.concurrent.Executors;
51import java.util.concurrent.ScheduledExecutorService;
52import java.util.concurrent.TimeUnit;
53import java.util.concurrent.atomic.AtomicInteger;
54import java.util.stream.Collectors;
55
56import static org.onlab.util.Tools.groupedThreads;
57import static org.slf4j.LoggerFactory.getLogger;
58
59/**
60 * Group handler for OFDPA2 pipeline.
61 */
62public class OFDPA2GroupHandler {
63 /*
64 * OFDPA requires group-id's to have a certain form.
65 * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid>
66 * L3 Unicast Groups have <4bits-2><28bits-index>
67 * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index>
68 * L3 ECMP Groups have <4bits-7><28bits-index>
69 * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index>
70 * L3 VPN Groups have <4bits-9><4bits-2><24bits-index>
71 */
72 private static final int L2INTERFACEMASK = 0x0;
73 private static final int L3UNICASTMASK = 0x20000000;
74 private static final int MPLSINTERFACEMASK = 0x90000000;
75 private static final int L3ECMPMASK = 0x70000000;
76 private static final int L2FLOODMASK = 0x40000000;
77 private static final int L3VPNMASK = 0x92000000;
78
79 private final Logger log = getLogger(getClass());
80 private ServiceDirectory serviceDirectory;
81 protected GroupService groupService;
82
83 private DeviceId deviceId;
84 private FlowObjectiveStore flowObjectiveStore;
85 private Cache<GroupKey, List<OfdpaNextGroup>> pendingNextObjectives;
86 private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups;
87 private ScheduledExecutorService groupChecker =
88 Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", "ofdpa2-%d"));
89
90 // index number for group creation
91 private AtomicInteger l3vpnindex = new AtomicInteger(0);
92
93 // local stores for port-vlan mapping
94 protected Map<PortNumber, VlanId> port2Vlan = new ConcurrentHashMap<>();
95 protected Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<>();
96
97 // local store for pending bucketAdds - by design there can only be one
98 // pending bucket for a group
99 protected ConcurrentHashMap<Integer, NextObjective> pendingBuckets = new ConcurrentHashMap<>();
100
101 protected void init(DeviceId deviceId, PipelinerContext context) {
102 this.deviceId = deviceId;
103 this.flowObjectiveStore = context.store();
104 this.serviceDirectory = context.directory();
105 this.groupService = serviceDirectory.get(GroupService.class);
106
107 pendingNextObjectives = CacheBuilder.newBuilder()
108 .expireAfterWrite(20, TimeUnit.SECONDS)
109 .removalListener((
110 RemovalNotification<GroupKey, List<OfdpaNextGroup>> notification) -> {
111 if (notification.getCause() == RemovalCause.EXPIRED) {
112 notification.getValue().forEach(ofdpaNextGrp ->
113 OFDPA2Pipeline.fail(ofdpaNextGrp.nextObj,
114 ObjectiveError.GROUPINSTALLATIONFAILED));
115
116 }
117 }).build();
118 pendingGroups = new ConcurrentHashMap<>();
119 groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS);
120
121 groupService.addListener(new InnerGroupListener());
122 }
123
124 protected void addGroup(NextObjective nextObjective) {
125 switch (nextObjective.type()) {
126 case SIMPLE:
127 Collection<TrafficTreatment> treatments = nextObjective.next();
128 if (treatments.size() != 1) {
129 log.error("Next Objectives of type Simple should only have a "
130 + "single Traffic Treatment. Next Objective Id:{}",
131 nextObjective.id());
132 OFDPA2Pipeline.fail(nextObjective, ObjectiveError.BADPARAMS);
133 return;
134 }
135 processSimpleNextObjective(nextObjective);
136 break;
137 case BROADCAST:
138 processBroadcastNextObjective(nextObjective);
139 break;
140 case HASHED:
141 processHashedNextObjective(nextObjective);
142 break;
143 case FAILOVER:
144 OFDPA2Pipeline.fail(nextObjective, ObjectiveError.UNSUPPORTED);
145 log.warn("Unsupported next objective type {}", nextObjective.type());
146 break;
147 default:
148 OFDPA2Pipeline.fail(nextObjective, ObjectiveError.UNKNOWN);
149 log.warn("Unknown next objective type {}", nextObjective.type());
150 }
151 }
152
153 /**
154 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
155 * a chain of groups. The simple Next Objective passed
156 * in by the application has to be broken up into a group chain
157 * comprising of an L3 Unicast Group that points to an L2 Interface
158 * Group which in-turn points to an output port. In some cases, the simple
159 * next Objective can just be an L2 interface without the need for chaining.
160 *
161 * @param nextObj the nextObjective of type SIMPLE
162 */
163 private void processSimpleNextObjective(NextObjective nextObj) {
164 TrafficTreatment treatment = nextObj.next().iterator().next();
165 // determine if plain L2 or L3->L2
166 boolean plainL2 = true;
167 for (Instruction ins : treatment.allInstructions()) {
168 if (ins.type() == Instruction.Type.L2MODIFICATION) {
169 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
170 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_DST ||
171 l2ins.subtype() == L2ModificationInstruction.L2SubType.ETH_SRC) {
172 plainL2 = false;
173 break;
174 }
175 }
176 }
177
178 if (plainL2) {
179 createL2InterfaceGroup(nextObj);
180 return;
181 }
182
183 // break up simple next objective to GroupChain objects
184 GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(),
185 nextObj.appId(), false,
186 nextObj.meta());
187 if (groupInfo == null) {
188 log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId);
189 return;
190 }
191 // create object for local and distributed storage
192 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
193 gkeyChain.addFirst(groupInfo.innerGrpDesc.appCookie());
194 gkeyChain.addFirst(groupInfo.outerGrpDesc.appCookie());
195 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
196 Collections.singletonList(gkeyChain),
197 nextObj);
198
199 // store l3groupkey with the ofdpaNextGroup for the nextObjective that depends on it
200 updatePendingNextObjective(groupInfo.outerGrpDesc.appCookie(), ofdpaGrp);
201
202 // now we are ready to send the l2 groupDescription (inner), as all the stores
203 // that will get async replies have been updated. By waiting to update
204 // the stores, we prevent nasty race conditions.
205 groupService.addGroup(groupInfo.innerGrpDesc);
206 }
207
208 private void updatePendingNextObjective(GroupKey key, OfdpaNextGroup value) {
209 List<OfdpaNextGroup> nextList = new CopyOnWriteArrayList<OfdpaNextGroup>();
210 nextList.add(value);
211 List<OfdpaNextGroup> ret = pendingNextObjectives.asMap()
212 .putIfAbsent(key, nextList);
213 if (ret != null) {
214 ret.add(value);
215 }
216 }
217
218 private void updatePendingGroups(GroupKey gkey, GroupChainElem gce) {
219 Set<GroupChainElem> gceSet = Collections.newSetFromMap(
220 new ConcurrentHashMap<GroupChainElem, Boolean>());
221 gceSet.add(gce);
222 Set<GroupChainElem> retval = pendingGroups.putIfAbsent(gkey, gceSet);
223 if (retval != null) {
224 retval.add(gce);
225 }
226 }
227
228 /**
229 * Creates a simple L2 Interface Group.
230 *
231 * @param nextObj the next Objective
232 */
233 private void createL2InterfaceGroup(NextObjective nextObj) {
234 // only allowed actions are vlan pop and outport
235 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
236 PortNumber portNum = null;
237 for (Instruction ins : nextObj.next().iterator().next().allInstructions()) {
238 if (ins.type() == Instruction.Type.L2MODIFICATION) {
239 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
240 switch (l2ins.subtype()) {
241 case VLAN_POP:
242 ttb.add(l2ins);
243 break;
244 default:
245 break;
246 }
247 } else if (ins.type() == Instruction.Type.OUTPUT) {
248 portNum = ((Instructions.OutputInstruction) ins).port();
249 ttb.add(ins);
250 } else {
251 log.warn("Driver does not handle this type of TrafficTreatment"
252 + " instruction in simple nextObjectives: {}", ins.type());
253 }
254 }
255 //use the vlanid associated with the port
256 VlanId vlanid = port2Vlan.get(portNum);
257
258 if (vlanid == null && nextObj.meta() != null) {
259 // use metadata vlan info if available
260 Criterion vidCriterion = nextObj.meta().getCriterion(Criterion.Type.VLAN_VID);
261 if (vidCriterion != null) {
262 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
263 }
264 }
265
266 if (vlanid == null) {
267 log.error("Driver cannot process an L2/L3 group chain without "
268 + "egress vlan information for dev: {} port:{}",
269 deviceId, portNum);
270 return;
271 }
272
273 // assemble information for ofdpa l2interface group
274 Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum.toLong();
275 // a globally unique groupkey that is different for ports in the same devices
276 // but different for the same portnumber on different devices. Also different
277 // for the various group-types created out of the same next objective.
278 int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong());
279 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
280
281 // create group description for the l2interfacegroup
282 GroupBucket l2interfaceGroupBucket =
283 DefaultGroupBucket.createIndirectGroupBucket(ttb.build());
284 GroupDescription l2groupDescription =
285 new DefaultGroupDescription(
286 deviceId,
287 GroupDescription.Type.INDIRECT,
288 new GroupBuckets(Collections.singletonList(
289 l2interfaceGroupBucket)),
290 l2groupkey,
291 l2groupId,
292 nextObj.appId());
293 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
294 deviceId, Integer.toHexString(l2groupId),
295 l2groupkey, nextObj.id());
296
297 // create object for local and distributed storage
298 Deque<GroupKey> singleKey = new ArrayDeque<>();
299 singleKey.addFirst(l2groupkey);
300 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(
301 Collections.singletonList(singleKey),
302 nextObj);
303
304 // store l2groupkey for the nextObjective that depends on it
305 updatePendingNextObjective(l2groupkey, ofdpaGrp);
306 // send the group description to the group service
307 groupService.addGroup(l2groupDescription);
308 }
309
310 /**
311 * Creates one of two possible group-chains from the treatment
312 * passed in. Depending on the MPLS boolean, this method either creates
313 * an L3Unicast Group --> L2Interface Group, if mpls is false;
314 * or MPLSInterface Group --> L2Interface Group, if mpls is true;
315 * The returned 'inner' group description is always the L2 Interface group.
316 *
317 * @param treatment that needs to be broken up to create the group chain
318 * @param nextId of the next objective that needs this group chain
319 * @param appId of the application that sent this next objective
320 * @param mpls determines if L3Unicast or MPLSInterface group is created
321 * @param meta metadata passed in by the application as part of the nextObjective
322 * @return GroupInfo containing the GroupDescription of the
323 * L2Interface group(inner) and the GroupDescription of the (outer)
324 * L3Unicast/MPLSInterface group. May return null if there is an
325 * error in processing the chain
326 */
327 private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId,
328 ApplicationId appId, boolean mpls,
329 TrafficSelector meta) {
330 // for the l2interface group, get vlan and port info
331 // for the outer group, get the src/dst mac, and vlan info
332 TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder();
333 TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder();
334 VlanId vlanid = null;
335 long portNum = 0;
336 boolean setVlan = false, popVlan = false;
337 for (Instruction ins : treatment.allInstructions()) {
338 if (ins.type() == Instruction.Type.L2MODIFICATION) {
339 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
340 switch (l2ins.subtype()) {
341 case ETH_DST:
342 outerTtb.setEthDst(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
343 break;
344 case ETH_SRC:
345 outerTtb.setEthSrc(((L2ModificationInstruction.ModEtherInstruction) l2ins).mac());
346 break;
347 case VLAN_ID:
348 vlanid = ((L2ModificationInstruction.ModVlanIdInstruction) l2ins).vlanId();
349 outerTtb.setVlanId(vlanid);
350 setVlan = true;
351 break;
352 case VLAN_POP:
353 innerTtb.popVlan();
354 popVlan = true;
355 break;
356 case DEC_MPLS_TTL:
357 case MPLS_LABEL:
358 case MPLS_POP:
359 case MPLS_PUSH:
360 case VLAN_PCP:
361 case VLAN_PUSH:
362 default:
363 break;
364 }
365 } else if (ins.type() == Instruction.Type.OUTPUT) {
366 portNum = ((Instructions.OutputInstruction) ins).port().toLong();
367 innerTtb.add(ins);
368 } else {
369 log.warn("Driver does not handle this type of TrafficTreatment"
370 + " instruction in nextObjectives: {}", ins.type());
371 }
372 }
373
374 if (vlanid == null && meta != null) {
375 // use metadata if available
376 Criterion vidCriterion = meta.getCriterion(Criterion.Type.VLAN_VID);
377 if (vidCriterion != null) {
378 vlanid = ((VlanIdCriterion) vidCriterion).vlanId();
379 }
380 // if vlan is not set, use the vlan in metadata for outerTtb
381 if (vlanid != null && !setVlan) {
382 outerTtb.setVlanId(vlanid);
383 }
384 }
385
386 if (vlanid == null) {
387 log.error("Driver cannot process an L2/L3 group chain without "
388 + "egress vlan information for dev: {} port:{}",
389 deviceId, portNum);
390 return null;
391 }
392
393 if (!setVlan && !popVlan) {
394 // untagged outgoing port
395 TrafficTreatment.Builder temp = DefaultTrafficTreatment.builder();
396 temp.popVlan();
397 innerTtb.build().allInstructions().forEach(i -> temp.add(i));
398 innerTtb = temp;
399 }
400
401 // assemble information for ofdpa l2interface group
402 Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum;
403 // a globally unique groupkey that is different for ports in the same devices
404 // but different for the same portnumber on different devices. Also different
405 // for the various group-types created out of the same next objective.
406 int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum);
407 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
408
409 // assemble information for outer group
410 GroupDescription outerGrpDesc = null;
411 if (mpls) {
412 // outer group is MPLSInteface
413 Integer mplsgroupId = MPLSINTERFACEMASK | (int) portNum;
414 // using mplsinterfacemask in groupkey to differentiate from l2interface
415 int mplsgk = MPLSINTERFACEMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum));
416 final GroupKey mplsgroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(mplsgk));
417 outerTtb.group(new DefaultGroupId(l2groupId));
418 // create the mpls-interface group description to wait for the
419 // l2 interface group to be processed
420 GroupBucket mplsinterfaceGroupBucket =
421 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
422 outerGrpDesc = new DefaultGroupDescription(
423 deviceId,
424 GroupDescription.Type.INDIRECT,
425 new GroupBuckets(Collections.singletonList(
426 mplsinterfaceGroupBucket)),
427 mplsgroupkey,
428 mplsgroupId,
429 appId);
430 log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}",
431 deviceId, Integer.toHexString(mplsgroupId),
432 mplsgroupkey, nextId);
433 } else {
434 // outer group is L3Unicast
435 Integer l3groupId = L3UNICASTMASK | (int) portNum;
436 int l3gk = L3UNICASTMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum));
437 final GroupKey l3groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3gk));
438 outerTtb.group(new DefaultGroupId(l2groupId));
439 // create the l3unicast group description to wait for the
440 // l2 interface group to be processed
441 GroupBucket l3unicastGroupBucket =
442 DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build());
443 outerGrpDesc = new DefaultGroupDescription(
444 deviceId,
445 GroupDescription.Type.INDIRECT,
446 new GroupBuckets(Collections.singletonList(
447 l3unicastGroupBucket)),
448 l3groupkey,
449 l3groupId,
450 appId);
451 log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}",
452 deviceId, Integer.toHexString(l3groupId),
453 l3groupkey, nextId);
454 }
455
456 // store l2groupkey with the groupChainElem for the outer-group that depends on it
457 GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1, false);
458 updatePendingGroups(l2groupkey, gce);
459
460 // create group description for the inner l2interfacegroup
461 GroupBucket l2interfaceGroupBucket =
462 DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build());
463 GroupDescription l2groupDescription =
464 new DefaultGroupDescription(
465 deviceId,
466 GroupDescription.Type.INDIRECT,
467 new GroupBuckets(Collections.singletonList(
468 l2interfaceGroupBucket)),
469 l2groupkey,
470 l2groupId,
471 appId);
472 log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}",
473 deviceId, Integer.toHexString(l2groupId),
474 l2groupkey, nextId);
475 return new GroupInfo(l2groupDescription, outerGrpDesc);
476
477 }
478
479 /**
480 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
481 * a chain of groups. The broadcast Next Objective passed in by the application
482 * has to be broken up into a group chain comprising of an
483 * L2 Flood group whose buckets point to L2 Interface groups.
484 *
485 * @param nextObj the nextObjective of type BROADCAST
486 */
487 private void processBroadcastNextObjective(NextObjective nextObj) {
488 // break up broadcast next objective to multiple groups
489 Collection<TrafficTreatment> buckets = nextObj.next();
490
491 // Read VLAN information from the metadata
492 TrafficSelector metadata = nextObj.meta();
493 Criterion criterion = metadata.getCriterion(Criterion.Type.VLAN_VID);
494 if (criterion == null) {
495 log.warn("Required VLAN ID info in nextObj metadata but not found. Aborting");
496 return;
497 }
498 VlanId vlanId = ((VlanIdCriterion) criterion).vlanId();
499
500 // each treatment is converted to an L2 interface group
501 List<GroupDescription> l2interfaceGroupDescs = new ArrayList<>();
502 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
503 for (TrafficTreatment treatment : buckets) {
504 TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder();
505 PortNumber portNum = null;
506 // ensure that the only allowed treatments are pop-vlan and output
507 for (Instruction ins : treatment.allInstructions()) {
508 if (ins.type() == Instruction.Type.L2MODIFICATION) {
509 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
510 switch (l2ins.subtype()) {
511 case VLAN_POP:
512 newTreatment.add(l2ins);
513 break;
514 default:
515 log.debug("action {} not permitted for broadcast nextObj",
516 l2ins.subtype());
517 break;
518 }
519 } else if (ins.type() == Instruction.Type.OUTPUT) {
520 portNum = ((Instructions.OutputInstruction) ins).port();
521 newTreatment.add(ins);
522 } else {
523 log.debug("TrafficTreatment of type {} not permitted in "
524 + " broadcast nextObjective", ins.type());
525 }
526 }
527
528 // Ensure that all ports of this broadcast nextObj are in the same vlan
529 // XXX maybe HA issue here?
530 VlanId expectedVlanId = port2Vlan.putIfAbsent(portNum, vlanId);
531 if (expectedVlanId != null && !vlanId.equals(expectedVlanId)) {
532 log.error("Driver requires all ports in a broadcast nextObj "
533 + "to be in the same vlan. Different vlans found "
534 + "{} and {}. Aborting group creation", vlanId, expectedVlanId);
535 return;
536 }
537
538
539 // assemble info for l2 interface group
540 int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong());
541 final GroupKey l2groupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2gk));
542 Integer l2groupId = L2INTERFACEMASK | (vlanId.toShort() << 16) |
543 (int) portNum.toLong();
544 GroupBucket l2interfaceGroupBucket =
545 DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build());
546 GroupDescription l2interfaceGroupDescription =
547 new DefaultGroupDescription(
548 deviceId,
549 GroupDescription.Type.INDIRECT,
550 new GroupBuckets(Collections.singletonList(
551 l2interfaceGroupBucket)),
552 l2groupkey,
553 l2groupId,
554 nextObj.appId());
555 log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}",
556 deviceId, Integer.toHexString(l2groupId),
557 l2groupkey, nextObj.id());
558
559 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
560 gkeyChain.addFirst(l2groupkey);
561
562 // store the info needed to create this group
563 l2interfaceGroupDescs.add(l2interfaceGroupDescription);
564 allGroupKeys.add(gkeyChain);
565 }
566
567 // assemble info for l2 flood group
568 Integer l2floodgroupId = L2FLOODMASK | (vlanId.toShort() << 16) | nextObj.id();
569 int l2floodgk = L2FLOODMASK | nextObj.id() << 12;
570 final GroupKey l2floodgroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l2floodgk));
571 // collection of group buckets pointing to all the l2 interface groups
572 List<GroupBucket> l2floodBuckets = new ArrayList<>();
573 for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
574 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
575 ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId()));
576 GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build());
577 l2floodBuckets.add(abucket);
578 }
579 // create the l2flood group-description to wait for all the
580 // l2interface groups to be processed
581 GroupDescription l2floodGroupDescription =
582 new DefaultGroupDescription(
583 deviceId,
584 GroupDescription.Type.ALL,
585 new GroupBuckets(l2floodBuckets),
586 l2floodgroupkey,
587 l2floodgroupId,
588 nextObj.appId());
589 GroupChainElem gce = new GroupChainElem(l2floodGroupDescription,
590 l2interfaceGroupDescs.size(),
591 false);
592 log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}",
593 deviceId, Integer.toHexString(l2floodgroupId),
594 l2floodgroupkey, nextObj.id());
595
596 // create objects for local and distributed storage
597 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l2floodgroupkey));
598 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
599
600 // store l2floodgroupkey with the ofdpaGroupChain for the nextObjective
601 // that depends on it
602 updatePendingNextObjective(l2floodgroupkey, ofdpaGrp);
603
604 for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) {
605 // store all l2groupkeys with the groupChainElem for the l2floodgroup
606 // that depends on it
607 updatePendingGroups(l2intGrpDesc.appCookie(), gce);
608 // send groups for all l2 interface groups
609 groupService.addGroup(l2intGrpDesc);
610 }
611 }
612
613
614
615 /**
616 * As per the OFDPA 2.0 TTP, packets are sent out of ports by using
617 * a chain of groups. The hashed Next Objective passed in by the application
618 * has to be broken up into a group chain comprising of an
619 * L3 ECMP group as the top level group. Buckets of this group can point
620 * to a variety of groups in a group chain, depending on the whether
621 * MPLS labels are being pushed or not.
622 * <p>
623 * NOTE: We do not create MPLS ECMP groups as they are unimplemented in
624 * OF-DPA 2.0 (even though it is in the spec). Therefore we do not
625 * check the nextObjective meta to see what is matching before being
626 * sent to this nextObjective.
627 *
628 * @param nextObj the nextObjective of type HASHED
629 */
630 private void processHashedNextObjective(NextObjective nextObj) {
631 // storage for all group keys in the chain of groups created
632 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
633 List<GroupInfo> unsentGroups = new ArrayList<>();
634 createHashBucketChains(nextObj, allGroupKeys, unsentGroups);
635
636 // now we can create the outermost L3 ECMP group
637 List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>();
638 for (GroupInfo gi : unsentGroups) {
639 // create ECMP bucket to point to the outer group
640 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
641 ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
642 GroupBucket sbucket = DefaultGroupBucket
643 .createSelectGroupBucket(ttb.build());
644 l3ecmpGroupBuckets.add(sbucket);
645 }
646 int l3ecmpGroupId = L3ECMPMASK | nextObj.id() << 12;
647 GroupKey l3ecmpGroupKey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3ecmpGroupId));
648 GroupDescription l3ecmpGroupDesc =
649 new DefaultGroupDescription(
650 deviceId,
651 GroupDescription.Type.SELECT,
652 new GroupBuckets(l3ecmpGroupBuckets),
653 l3ecmpGroupKey,
654 l3ecmpGroupId,
655 nextObj.appId());
656 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc,
657 l3ecmpGroupBuckets.size(),
658 false);
659
660 // create objects for local and distributed storage
661 allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey));
662 OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj);
663
664 // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective
665 // that depends on it
666 updatePendingNextObjective(l3ecmpGroupKey, ofdpaGrp);
667
668 log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
669 deviceId, Integer.toHexString(l3ecmpGroupId),
670 l3ecmpGroupKey, nextObj.id());
671 // finally we are ready to send the innermost groups
672 for (GroupInfo gi : unsentGroups) {
673 log.debug("Sending innermost group {} in group chain on device {} ",
674 Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
675 updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
676 groupService.addGroup(gi.innerGrpDesc);
677 }
678
679 }
680
681 /**
682 * Creates group chains for all buckets in a hashed group, and stores the
683 * GroupInfos and GroupKeys for all the groups in the lists passed in, which
684 * should be empty.
685 * <p>
686 * Does not create the top level ECMP group. Does not actually send the
687 * groups to the groupService.
688 *
689 * @param nextObj the Next Objective with buckets that need to be converted
690 * to group chains
691 * @param allGroupKeys a list to store groupKey for each bucket-group-chain
692 * @param unsentGroups a list to store GroupInfo for each bucket-group-chain
693 */
694 private void createHashBucketChains(NextObjective nextObj,
695 List<Deque<GroupKey>> allGroupKeys,
696 List<GroupInfo> unsentGroups) {
697 // break up hashed next objective to multiple groups
698 Collection<TrafficTreatment> buckets = nextObj.next();
699
700 for (TrafficTreatment bucket : buckets) {
701 //figure out how many labels are pushed in each bucket
702 int labelsPushed = 0;
703 MplsLabel innermostLabel = null;
704 for (Instruction ins : bucket.allInstructions()) {
705 if (ins.type() == Instruction.Type.L2MODIFICATION) {
706 L2ModificationInstruction l2ins = (L2ModificationInstruction) ins;
707 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_PUSH) {
708 labelsPushed++;
709 }
710 if (l2ins.subtype() == L2ModificationInstruction.L2SubType.MPLS_LABEL) {
711 if (innermostLabel == null) {
712 innermostLabel = ((L2ModificationInstruction.ModMplsLabelInstruction) l2ins).mplsLabel();
713 }
714 }
715 }
716 }
717
718 Deque<GroupKey> gkeyChain = new ArrayDeque<>();
719 // XXX we only deal with 0 and 1 label push right now
720 if (labelsPushed == 0) {
721 GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
722 nextObj.appId(), false,
723 nextObj.meta());
724 if (nolabelGroupInfo == null) {
725 log.error("Could not process nextObj={} in dev:{}",
726 nextObj.id(), deviceId);
727 return;
728 }
729 gkeyChain.addFirst(nolabelGroupInfo.innerGrpDesc.appCookie());
730 gkeyChain.addFirst(nolabelGroupInfo.outerGrpDesc.appCookie());
731
732 // we can't send the inner group description yet, as we have to
733 // create the dependent ECMP group first. So we store..
734 unsentGroups.add(nolabelGroupInfo);
735
736 } else if (labelsPushed == 1) {
737 GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(),
738 nextObj.appId(), true,
739 nextObj.meta());
740 if (onelabelGroupInfo == null) {
741 log.error("Could not process nextObj={} in dev:{}",
742 nextObj.id(), deviceId);
743 return;
744 }
745 // we need to add another group to this chain - the L3VPN group
746 TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder();
747 l3vpnTtb.pushMpls()
748 .setMpls(innermostLabel)
749 .setMplsBos(true)
750 .copyTtlOut()
751 .group(new DefaultGroupId(
752 onelabelGroupInfo.outerGrpDesc.givenGroupId()));
753 GroupBucket l3vpnGrpBkt =
754 DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build());
755 int l3vpngroupId = L3VPNMASK | l3vpnindex.incrementAndGet();
756 int l3vpngk = L3VPNMASK | nextObj.id() << 12 | l3vpnindex.get();
757 GroupKey l3vpngroupkey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3vpngk));
758 GroupDescription l3vpnGroupDesc =
759 new DefaultGroupDescription(
760 deviceId,
761 GroupDescription.Type.INDIRECT,
762 new GroupBuckets(Collections.singletonList(
763 l3vpnGrpBkt)),
764 l3vpngroupkey,
765 l3vpngroupId,
766 nextObj.appId());
767 GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1, false);
768 updatePendingGroups(onelabelGroupInfo.outerGrpDesc.appCookie(), l3vpnGce);
769
770 gkeyChain.addFirst(onelabelGroupInfo.innerGrpDesc.appCookie());
771 gkeyChain.addFirst(onelabelGroupInfo.outerGrpDesc.appCookie());
772 gkeyChain.addFirst(l3vpngroupkey);
773
774 //now we can replace the outerGrpDesc with the one we just created
775 onelabelGroupInfo.outerGrpDesc = l3vpnGroupDesc;
776
777 // we can't send the innermost group yet, as we have to create
778 // the dependent ECMP group first. So we store ...
779 unsentGroups.add(onelabelGroupInfo);
780
781 log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}",
782 deviceId, Integer.toHexString(l3vpngroupId),
783 l3vpngroupkey, nextObj.id());
784
785 } else {
786 log.warn("Driver currently does not handle more than 1 MPLS "
787 + "labels. Not processing nextObjective {}", nextObj.id());
788 return;
789 }
790
791 // all groups in this chain
792 allGroupKeys.add(gkeyChain);
793 }
794 }
795
796 /**
797 * Adds a bucket to the top level group of a group-chain, and creates the chain.
798 *
799 * @param nextObjective the next group to add a bucket to
800 * @param next the representation of the existing group-chain for this next objective
801 */
802 protected void addBucketToGroup(NextObjective nextObjective, NextGroup next) {
803 if (nextObjective.type() != NextObjective.Type.HASHED) {
804 log.warn("AddBuckets not applied to nextType:{} in dev:{} for next:{}",
805 nextObjective.type(), deviceId, nextObjective.id());
806 return;
807 }
808 if (nextObjective.next().size() > 1) {
809 log.warn("Only one bucket can be added at a time");
810 return;
811 }
812 // storage for all group keys in the chain of groups created
813 List<Deque<GroupKey>> allGroupKeys = new ArrayList<>();
814 List<GroupInfo> unsentGroups = new ArrayList<>();
815 createHashBucketChains(nextObjective, allGroupKeys, unsentGroups);
816
817 // now we can create the outermost L3 ECMP group bucket to add
818 GroupInfo gi = unsentGroups.get(0); // only one bucket, so only one group-chain
819 TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder();
820 ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId()));
821 GroupBucket sbucket = DefaultGroupBucket.createSelectGroupBucket(ttb.build());
822
823 // recreate the original L3 ECMP group id and description
824 int l3ecmpGroupId = L3ECMPMASK | nextObjective.id() << 12;
825 GroupKey l3ecmpGroupKey = new DefaultGroupKey(OFDPA2Pipeline.appKryo.serialize(l3ecmpGroupId));
826
827 // Although GroupDescriptions are not necessary for adding buckets to
828 // existing groups, we use one in the GroupChainElem. When the latter is
829 // processed, the info will be extracted for the bucketAdd call to groupService
830 GroupDescription l3ecmpGroupDesc =
831 new DefaultGroupDescription(
832 deviceId,
833 GroupDescription.Type.SELECT,
834 new GroupBuckets(Collections.singletonList(sbucket)),
835 l3ecmpGroupKey,
836 l3ecmpGroupId,
837 nextObjective.appId());
838 GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, 1, true);
839
840 // update original NextGroup with new bucket-chain
841 // don't need to update pendingNextObjectives -- group already exists
842 Deque<GroupKey> newBucketChain = allGroupKeys.get(0);
843 newBucketChain.addFirst(l3ecmpGroupKey);
844 List<Deque<GroupKey>> allOriginalKeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
845 allOriginalKeys.add(newBucketChain);
846 flowObjectiveStore.putNextGroup(nextObjective.id(),
847 new OfdpaNextGroup(allOriginalKeys, nextObjective));
848
849 log.debug("Adding to L3ECMP: device:{} gid:{} gkey:{} nextId:{}",
850 deviceId, Integer.toHexString(l3ecmpGroupId),
851 l3ecmpGroupKey, nextObjective.id());
852 // send the innermost group
853 log.debug("Sending innermost group {} in group chain on device {} ",
854 Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId);
855 updatePendingGroups(gi.outerGrpDesc.appCookie(), l3ecmpGce);
856 groupService.addGroup(gi.innerGrpDesc);
857
858 }
859
860 /**
861 * Removes the bucket in the top level group of a possible group-chain. Does
862 * not remove the groups in a group-chain pointed to by this bucket, as they
863 * may be in use (referenced by other groups) elsewhere.
864 *
865 * @param nextObjective the next group to remove a bucket from
866 * @param next the representation of the existing group-chain for this next objective
867 */
868 protected void removeBucketFromGroup(NextObjective nextObjective, NextGroup next) {
869 if (nextObjective.type() != NextObjective.Type.HASHED) {
870 log.warn("RemoveBuckets not applied to nextType:{} in dev:{} for next:{}",
871 nextObjective.type(), deviceId, nextObjective.id());
872 return;
873 }
874 Collection<TrafficTreatment> treatments = nextObjective.next();
875 TrafficTreatment treatment = treatments.iterator().next();
876 // find the bucket to remove by noting the outport, and figuring out the
877 // top-level group in the group-chain that indirectly references the port
878 PortNumber outport = null;
879 for (Instruction ins : treatment.allInstructions()) {
880 if (ins instanceof Instructions.OutputInstruction) {
881 outport = ((Instructions.OutputInstruction) ins).port();
882 break;
883 }
884 }
885 if (outport == null) {
886 log.error("next objective {} has no outport", nextObjective.id());
887 return;
888 }
889
890 List<Deque<GroupKey>> allgkeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
891 Deque<GroupKey> foundChain = null;
892 int index = 0;
893 for (Deque<GroupKey> gkeys : allgkeys) {
894 GroupKey groupWithPort = gkeys.peekLast();
895 Group group = groupService.getGroup(deviceId, groupWithPort);
896 if (group == null) {
897 log.warn("Inconsistent group chain");
898 continue;
899 }
900 // last group in group chain should have a single bucket pointing to port
901 List<Instruction> lastIns = group.buckets().buckets().iterator()
902 .next().treatment().allInstructions();
903 for (Instruction i : lastIns) {
904 if (i instanceof Instructions.OutputInstruction) {
905 PortNumber lastport = ((Instructions.OutputInstruction) i).port();
906 if (lastport.equals(outport)) {
907 foundChain = gkeys;
908 break;
909 }
910 }
911 }
912 if (foundChain != null) {
913 break;
914 }
915 index++;
916 }
917 if (foundChain != null) {
918 //first groupkey is the one we want to modify
919 GroupKey modGroupKey = foundChain.peekFirst();
920 Group modGroup = groupService.getGroup(deviceId, modGroupKey);
921 //second groupkey is the one we wish to remove the reference to
922 GroupKey pointedGroupKey = null;
923 int i = 0;
924 for (GroupKey gk : foundChain) {
925 if (i++ == 1) {
926 pointedGroupKey = gk;
927 break;
928 }
929 }
930 Group pointedGroup = groupService.getGroup(deviceId, pointedGroupKey);
931 GroupBucket bucket = DefaultGroupBucket.createSelectGroupBucket(
932 DefaultTrafficTreatment.builder()
933 .group(pointedGroup.id())
934 .build());
935 GroupBuckets removeBuckets = new GroupBuckets(Collections
936 .singletonList(bucket));
937 log.debug("Removing buckets from group id {} for next id {} in device {}",
938 modGroup.id(), nextObjective.id(), deviceId);
939 groupService.removeBucketsFromGroup(deviceId, modGroupKey,
940 removeBuckets, modGroupKey,
941 nextObjective.appId());
942 //update store
943 allgkeys.remove(index);
944 flowObjectiveStore.putNextGroup(nextObjective.id(),
945 new OfdpaNextGroup(allgkeys, nextObjective));
946 } else {
947 log.warn("Could not find appropriate group-chain for removing bucket"
948 + " for next id {} in dev:{}", nextObjective.id(), deviceId);
949 }
950 }
951
952 /**
953 * Removes all groups in multiple possible group-chains that represent the next
954 * objective.
955 *
956 * @param nextObjective the next objective to remove
957 * @param next the NextGroup that represents the existing group-chain for
958 * this next objective
959 */
960 protected void removeGroup(NextObjective nextObjective, NextGroup next) {
961 List<Deque<GroupKey>> allgkeys = OFDPA2Pipeline.appKryo.deserialize(next.data());
962 allgkeys.forEach(groupChain -> groupChain.forEach(groupKey ->
963 groupService.removeGroup(deviceId, groupKey, nextObjective.appId())));
964 flowObjectiveStore.removeNextGroup(nextObjective.id());
965 }
966
967 /**
968 * Processes next element of a group chain. Assumption is that if this
969 * group points to another group, the latter has already been created
970 * and this driver has received notification for it. A second assumption is
971 * that if there is another group waiting for this group then the appropriate
972 * stores already have the information to act upon the notification for the
973 * creation of this group.
974 * <p>
975 * The processing of the GroupChainElement depends on the number of groups
976 * this element is waiting on. For all group types other than SIMPLE, a
977 * GroupChainElement could be waiting on multiple groups.
978 *
979 * @param gce the group chain element to be processed next
980 */
981 private void processGroupChain(GroupChainElem gce) {
982 int waitOnGroups = gce.decrementAndGetGroupsWaitedOn();
983 if (waitOnGroups != 0) {
984 log.debug("GCE: {} not ready to be processed", gce);
985 return;
986 }
987 log.debug("GCE: {} ready to be processed", gce);
988 if (gce.addBucketToGroup) {
989 groupService.addBucketsToGroup(gce.groupDescription.deviceId(),
990 gce.groupDescription.appCookie(),
991 gce.groupDescription.buckets(),
992 gce.groupDescription.appCookie(),
993 gce.groupDescription.appId());
994 } else {
995 groupService.addGroup(gce.groupDescription);
996 }
997 }
998
999 private class GroupChecker implements Runnable {
1000 @Override
1001 public void run() {
1002 Set<GroupKey> keys = pendingGroups.keySet().stream()
1003 .filter(key -> groupService.getGroup(deviceId, key) != null)
1004 .collect(Collectors.toSet());
1005 Set<GroupKey> otherkeys = pendingNextObjectives.asMap().keySet().stream()
1006 .filter(otherkey -> groupService.getGroup(deviceId, otherkey) != null)
1007 .collect(Collectors.toSet());
1008 keys.addAll(otherkeys);
1009
1010 keys.stream().forEach(key ->
1011 processPendingGroupsOrNextObjectives(key, false));
1012 }
1013 }
1014
1015 private void processPendingGroupsOrNextObjectives(GroupKey key, boolean added) {
1016 //first check for group chain
1017 Set<GroupChainElem> gceSet = pendingGroups.remove(key);
1018 if (gceSet != null) {
1019 for (GroupChainElem gce : gceSet) {
1020 log.info("Group service {} group key {} in device {}. "
1021 + "Processing next group in group chain with group id {}",
1022 (added) ? "ADDED" : "processed",
1023 key, deviceId,
1024 Integer.toHexString(gce.groupDescription.givenGroupId()));
1025 processGroupChain(gce);
1026 }
1027 } else {
1028 // otherwise chain complete - check for waiting nextObjectives
1029 List<OfdpaNextGroup> nextGrpList = pendingNextObjectives.getIfPresent(key);
1030 if (nextGrpList != null) {
1031 pendingNextObjectives.invalidate(key);
1032 nextGrpList.forEach(nextGrp -> {
1033 log.info("Group service {} group key {} in device:{}. "
1034 + "Done implementing next objective: {} <<-->> gid:{}",
1035 (added) ? "ADDED" : "processed",
1036 key, deviceId, nextGrp.nextObjective().id(),
1037 Integer.toHexString(groupService.getGroup(deviceId, key)
1038 .givenGroupId()));
1039 OFDPA2Pipeline.pass(nextGrp.nextObjective());
1040 flowObjectiveStore.putNextGroup(nextGrp.nextObjective().id(), nextGrp);
1041 // check if addBuckets waiting for this completion
1042 NextObjective pendBkt = pendingBuckets
1043 .remove(nextGrp.nextObjective().id());
1044 if (pendBkt != null) {
1045 addBucketToGroup(pendBkt, nextGrp);
1046 }
1047 });
1048 }
1049 }
1050 }
1051
1052 private class InnerGroupListener implements GroupListener {
1053 @Override
1054 public void event(GroupEvent event) {
1055 log.trace("received group event of type {}", event.type());
1056 if (event.type() == GroupEvent.Type.GROUP_ADDED) {
1057 GroupKey key = event.subject().appCookie();
1058 processPendingGroupsOrNextObjectives(key, true);
1059 }
1060 }
1061 }
1062
1063 /**
1064 * Utility class for moving group information around.
1065 */
1066 private class GroupInfo {
1067 private GroupDescription innerGrpDesc;
1068 private GroupDescription outerGrpDesc;
1069
1070 GroupInfo(GroupDescription innerGrpDesc, GroupDescription outerGrpDesc) {
1071 this.innerGrpDesc = innerGrpDesc;
1072 this.outerGrpDesc = outerGrpDesc;
1073 }
1074 }
1075
1076 /**
1077 * Represents an entire group-chain that implements a Next-Objective from
1078 * the application. The objective is represented as a list of deques, where
1079 * each deque is a separate chain of groups.
1080 * <p>
1081 * For example, an ECMP group with 3 buckets, where each bucket points to
1082 * a group chain of L3 Unicast and L2 interface groups will look like this:
1083 * <ul>
1084 * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1085 * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1086 * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last)
1087 * </ul>
1088 * where the first element of each deque is the same, representing the
1089 * top level ECMP group, while every other element represents a unique groupKey.
1090 * <p>
1091 * Also includes information about the next objective that
1092 * resulted in this group-chain.
1093 *
1094 */
1095 protected class OfdpaNextGroup implements NextGroup {
1096 private final NextObjective nextObj;
1097 private final List<Deque<GroupKey>> gkeys;
1098
1099 public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) {
1100 this.gkeys = gkeys;
1101 this.nextObj = nextObj;
1102 }
1103
1104 @SuppressWarnings("unused")
1105 public List<Deque<GroupKey>> groupKey() {
1106 return gkeys;
1107 }
1108
1109 public NextObjective nextObjective() {
1110 return nextObj;
1111 }
1112
1113 @Override
1114 public byte[] data() {
1115 return OFDPA2Pipeline.appKryo.serialize(gkeys);
1116 }
1117 }
1118
1119 /**
1120 * Represents a group element that is part of a chain of groups.
1121 * Stores enough information to create a Group Description to add the group
1122 * to the switch by requesting the Group Service. Objects instantiating this
1123 * class are meant to be temporary and live as long as it is needed to wait for
1124 * preceding groups in the group chain to be created.
1125 */
1126 private class GroupChainElem {
1127 private GroupDescription groupDescription;
1128 private AtomicInteger waitOnGroups;
1129 private boolean addBucketToGroup;
1130
1131 GroupChainElem(GroupDescription groupDescription, int waitOnGroups,
1132 boolean addBucketToGroup) {
1133 this.groupDescription = groupDescription;
1134 this.waitOnGroups = new AtomicInteger(waitOnGroups);
1135 this.addBucketToGroup = addBucketToGroup;
1136 }
1137
1138 /**
1139 * This methods atomically decrements the counter for the number of
1140 * groups this GroupChainElement is waiting on, for notifications from
1141 * the Group Service. When this method returns a value of 0, this
1142 * GroupChainElement is ready to be processed.
1143 *
1144 * @return integer indication of the number of notifications being waited on
1145 */
1146 int decrementAndGetGroupsWaitedOn() {
1147 return waitOnGroups.decrementAndGet();
1148 }
1149
1150 @Override
1151 public String toString() {
1152 return (Integer.toHexString(groupDescription.givenGroupId()) +
1153 " groupKey: " + groupDescription.appCookie() +
1154 " waiting-on-groups: " + waitOnGroups.get() +
1155 " addBucketToGroup: " + addBucketToGroup +
1156 " device: " + deviceId);
1157 }
1158 }
1159}