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